1 /*=========================================================================
2  *
3  *  Copyright Insight Software Consortium
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *         http://www.apache.org/licenses/LICENSE-2.0.txt
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *=========================================================================*/
18 #include "itkBruker2dseqImageIO.h"
19 #include "itkMacro.h"
20 #include "itkIOCommon.h"
21 #include "itkByteSwapper.h"
22 #include "itksys/SystemTools.hxx"
23 #include "itkMetaDataObject.h"
24 
25 namespace itk
26 {
27 
28 #define BRUKER_LITTLE_ENDIAN  "littleEndian"
29 #define BRUKER_BIG_ENDIAN     "bigEndian"
30 #define BRUKER_SIGNED_CHAR    "_8BIT_SGN_INT"
31 #define BRUKER_UNSIGNED_CHAR  "_8BIT_UNSGN_INT"
32 #define BRUKER_SIGNED_SHORT   "_16BIT_SGN_INT"
33 #define BRUKER_SIGNED_INT     "_32BIT_SGN_INT"
34 #define BRUKER_FLOAT          "_32BIT_FLOAT"
35 
36 namespace
37 {
38 using SizeType = ImageIOBase::SizeType;
39 
40 // Internal function to throw an exception if a needed parameter does not exist
41 template<typename T>
GetParameter(const itk::MetaDataDictionary & dict,const std::string & name)42 T GetParameter(const itk::MetaDataDictionary &dict, const std::string &name)
43 {
44   T value;
45   if (!ExposeMetaData(dict, name, value))
46     {
47     itkGenericExceptionMacro("Could not read parameter: " << name);
48     }
49   return value;
50 }
51 
52 // Internal function to rescale pixel according to slope & intercept
53 template<typename T>
54 void
Rescale(T * buffer,const std::vector<double> & slopes,const std::vector<double> & offsets,const SizeType frameSize,const SizeType frameCount)55 Rescale(T *buffer, const std::vector<double> &slopes, const std::vector<double> &offsets,
56   const SizeType frameSize, const SizeType frameCount)
57 {
58   SizeType i = 0;
59   for (SizeType f = 0; f < frameCount; ++f)
60     {
61     for (SizeType v = 0; v < frameSize; ++v, ++i)
62       {
63       double tmp = static_cast<double>(buffer[i]) * slopes[f] + offsets[f];
64       buffer[i] = static_cast<T>(tmp);
65       }
66     }
67 }
68 
69 // Internal function to swap slices and volumes
70 template< typename T >
71 void
SwapSlicesAndVolumes(T * buffer,const SizeType sizeX,const SizeType sizeY,const SizeType sizeZ,const SizeType sizeToSwap,const SizeType sizeNoSwap)72 SwapSlicesAndVolumes(T *buffer,
73                      const SizeType sizeX, const SizeType sizeY, const SizeType sizeZ,
74                      const SizeType sizeToSwap, const SizeType sizeNoSwap)
75 {
76   const SizeType szSlice = sizeX*sizeY;
77   std::vector<T> tempBuffer(szSlice*sizeZ*sizeToSwap*sizeNoSwap);
78   T *toPixel = &(tempBuffer[0]);
79   T *fromNoSwapVol = buffer;
80   for (SizeType n = 0; n < sizeNoSwap; ++n)
81     {
82     T *fromSwapVol = fromNoSwapVol;
83     for (SizeType v = 0; v < sizeToSwap; ++v)
84       {
85       T *fromSlice = fromSwapVol;
86       for (SizeType z = 0; z < sizeZ; ++z)
87         {
88         T *fromPixel = fromSlice;
89         for (SizeType p = 0; p < szSlice; ++p)
90           {
91           *toPixel = *fromPixel;
92           toPixel++;
93           fromPixel++;
94           }
95         fromSlice += sizeToSwap * szSlice;
96         }
97       fromSwapVol += szSlice;
98       }
99     fromNoSwapVol += szSlice*sizeZ*sizeToSwap;
100     }
101 
102   // Now copy back to buffer
103   toPixel = buffer;
104   for (auto it = tempBuffer.begin(); it != tempBuffer.end(); ++it, ++toPixel)
105     {
106     *toPixel = *it;
107     }
108 }
109 
110 // Internal function to reverse slice order
111 template< typename T >
112 void
ReverseSliceOrder(T * buffer,const SizeType sizeX,const SizeType sizeY,const SizeType sz,const SizeType sizeToSwap)113 ReverseSliceOrder(T *buffer,
114                   const SizeType sizeX, const SizeType sizeY, const SizeType sz,
115                   const SizeType sizeToSwap)
116 {
117   const SizeType ss = sizeX*sizeY;
118   T *fromVol = buffer;
119   T temp;
120   for (SizeType v = 0; v < sizeToSwap; ++v)
121     {
122     T *fromSlice = fromVol;
123     T *toSlice = fromVol + (ss*(sz-1));
124     for (SizeType z = 0; z < sz/2; ++z)
125       {
126       T *fromPixel = fromSlice;
127       T *toPixel = toSlice;
128       for (SizeType p = 0; p < ss; ++p)
129         {
130         temp = *toPixel;
131         *toPixel = *fromPixel;
132         *fromPixel = temp;
133         toPixel++;
134         fromPixel++;
135         }
136       fromSlice += ss;
137       toSlice -= ss;
138       }
139     fromVol += ss*sz;
140     }
141 }
142 
143 // Internal function to copy and cast at the same time
144 template< typename PixelType >
145 void
CastCopy(float * to,void * from,size_t pixelCount)146 CastCopy(float *to, void *from, size_t pixelCount)
147 {
148   auto * tempFrom = static_cast< PixelType * >( from );
149   for ( unsigned i = 0; i < pixelCount; ++i )
150     {
151     to[i] = static_cast< float >( tempFrom[i] );
152     }
153 }
154 
155 // Internal function to read a JCAMPDX parameter file
ReadJCAMPDX(const std::string & filename,MetaDataDictionary & dict)156 void ReadJCAMPDX(const std::string &filename, MetaDataDictionary &dict)
157 {
158   std::ifstream paramsStream(filename.c_str());
159 
160   std::string line;
161   // First five lines are 'comments' starting with ##
162   std::getline(paramsStream, line);
163   std::getline(paramsStream, line);
164   std::getline(paramsStream, line);
165   std::getline(paramsStream, line);
166   std::getline(paramsStream, line);
167 
168   // Then three lines starting with $$
169   std::getline(paramsStream, line);
170   std::getline(paramsStream, line);
171   std::getline(paramsStream, line);
172 
173   // Now process parameters starting with ##$
174   while(std::getline(paramsStream, line))
175     {
176     // Check start of line
177     if (line.substr(0, 2) == "$$")
178       {
179       // Comment line
180       continue;
181       }
182     else if (line.substr(0, 5) == "##END")
183       {
184       // There should be one comment line after this line in the file
185       continue;
186       }
187     else if (line.substr(0, 3) != "##$")
188       {
189       itkGenericExceptionMacro("Failed to parse Bruker JCAMPDX: " + line);
190       }
191 
192     std::string::size_type epos = line.find('=', 3);
193     if (epos == std::string::npos)
194       {
195       itkGenericExceptionMacro("Invalid Bruker JCAMPDX parameter line (Missing =): " << line);
196       }
197 
198     std::string parname = line.substr(3, epos - 3);
199     std::string par = line.substr(epos + 1);
200     if (par[0] == '(')
201       {
202       // Array value
203       // The array sizes appear to be entirely meaningless. 65 means a string, except when it doesn't and actually gives the length of the string
204       // Skip to next line, process lines until we hit a comment or new parameter
205       // Then process all lines together and look for strings or numbers
206       par.clear();
207       std::string lines;
208       while (paramsStream.peek() != '#' && paramsStream.peek() != '$')
209         {
210         std::getline(paramsStream, line);
211         lines.append(line);
212         }
213 
214       std::string::size_type leftBracket = lines.find('(');
215       if (leftBracket == std::string::npos)
216         {
217         // Now check for array of strings marked with <>
218         std::string::size_type left = lines.find('<');
219         if (left != std::string::npos)
220           {
221           std::vector<std::string> stringArray;
222           while(left != std::string::npos)
223             {
224             std::string::size_type right = lines.find('>', left + 1);
225             stringArray.push_back(lines.substr(left + 1, right - (left + 1)));
226             left = lines.find('<', right + 1);
227             }
228           EncapsulateMetaData(dict, parname, stringArray);
229           }
230         else
231           {
232           // An array of numbers
233           std::stringstream lineStream(lines);
234           double doubleValue;
235           std::vector<double> doubleArray;
236           while (lineStream >> doubleValue)
237             {
238             doubleArray.push_back(doubleValue);
239             if (lineStream.peek() == ',')
240               {
241               // Ignore commas
242               lineStream.ignore();
243               }
244             }
245           EncapsulateMetaData(dict, parname, doubleArray);
246           }
247         }
248       else
249         {
250         // An array of arrays
251         std::string::size_type rightBracket = lines.find(')', leftBracket);
252 
253         if (lines.find('<') != std::string::npos)
254           {
255           // Array of array of strings (and maybe doubles, but let's keep it sane)
256           std::vector<std::vector<std::string> > stringArrayArray;
257           while (leftBracket != std::string::npos)
258             {
259             std::string::size_type stringStart = leftBracket + 1;
260             std::string::size_type stringEnd = lines.find(',', stringStart);
261             std::vector<std::string> stringArray;
262             while (stringStart < rightBracket)
263               {
264               stringArray.push_back(lines.substr(stringStart, stringEnd - stringStart));
265               stringStart = stringEnd + 2; // Eat comma + space character
266               stringEnd = lines.find(',', stringStart + 1);
267               if (stringEnd > rightBracket)
268                 {
269                 stringEnd = rightBracket;
270                 }
271               }
272             stringArrayArray.push_back(stringArray);
273             leftBracket = lines.find('(', rightBracket);
274             rightBracket = lines.find(')', leftBracket);
275             }
276           EncapsulateMetaData(dict, parname, stringArrayArray);
277           }
278         else
279           {
280           // Array of array of numbers
281           std::vector<std::vector<double> > doubleArrayArray;
282           while (leftBracket != std::string::npos)
283             {
284             std::istringstream arrayStream(lines.substr(leftBracket, rightBracket - leftBracket));
285             std::vector<double> doubleArray;
286             double doubleValue;
287             while (arrayStream >> doubleValue)
288               {
289               doubleArray.push_back(doubleValue);
290               if (arrayStream && (arrayStream.peek() == ','))
291                 {
292                 // Ignore commas. Some arrays have them, others don't
293                 arrayStream.ignore();
294                 }
295               }
296             doubleArrayArray.push_back(doubleArray);
297             leftBracket = lines.find('(', rightBracket);
298             rightBracket = lines.find(')', leftBracket);
299             }
300           EncapsulateMetaData(dict, parname, doubleArrayArray);
301           }
302         }
303       }
304     else
305       {
306       // A single value
307       std::istringstream streamPar(par);
308       double value;
309       streamPar >> value;
310       if (streamPar.fail())
311         {
312         // Didn't read a valid number, so it's a string
313         EncapsulateMetaData(dict, parname, par);
314         }
315       else
316         {
317         EncapsulateMetaData(dict, parname, value);
318         }
319       }
320     }
321 }
322 }
323 
Bruker2dseqImageIO()324 Bruker2dseqImageIO::Bruker2dseqImageIO()
325 
326 {
327   // By default, only have 3 dimensions
328   this->SetNumberOfDimensions(3);
329   this->m_PixelType         = SCALAR;
330   this->m_ComponentType     = CHAR;
331   this->SetNumberOfComponents(1);
332 
333   // Set m_MachineByteOrder to the ByteOrder of the machine
334   // Start out with file byte order == system byte order
335   // this will be changed if we're reading a file to whatever
336   // the file actually contains.
337   if ( ByteSwapper< int >::SystemIsBigEndian() )
338     {
339     this->m_MachineByteOrder = this->m_ByteOrder = BigEndian;
340     }
341   else
342     {
343     this->m_MachineByteOrder = this->m_ByteOrder = LittleEndian;
344     }
345 }
346 
347 Bruker2dseqImageIO::~Bruker2dseqImageIO() = default;
348 
SwapBytesIfNecessary(void * buff,SizeValueType components)349 void Bruker2dseqImageIO::SwapBytesIfNecessary(void *buff, SizeValueType components)
350 {
351   if (m_ByteOrder == LittleEndian)
352     {
353 #define BYTE_SWAP( T ) ByteSwapper< T >::SwapRangeFromSystemToLittleEndian(( T *)buff, components)
354     switch (this->m_OnDiskComponentType)
355       {
356       case CHAR:
357         BYTE_SWAP( char );
358         break;
359       case UCHAR:
360         BYTE_SWAP( unsigned char );
361         break;
362       case SHORT:
363         BYTE_SWAP( short );
364         break;
365       case USHORT:
366         BYTE_SWAP( unsigned short );
367         break;
368       case INT:
369         BYTE_SWAP( int );
370         break;
371       case UINT:
372         BYTE_SWAP( unsigned int );
373         break;
374       case LONG:
375         BYTE_SWAP( long );
376         break;
377       case ULONG:
378         BYTE_SWAP( unsigned long );
379         break;
380       case FLOAT:
381         BYTE_SWAP( float );
382         break;
383       case DOUBLE:
384         BYTE_SWAP( double );
385         break;
386       default:
387         itkExceptionMacro("Component Type Unknown");
388       }
389 #undef BYTE_SWAP
390     }
391   else
392     {
393 #define BYTE_SWAP( T ) ByteSwapper< T >::SwapRangeFromSystemToBigEndian(( T *)buff, components)
394     switch (this->m_OnDiskComponentType)
395       {
396       case CHAR:
397         BYTE_SWAP( char );
398         break;
399       case UCHAR:
400         BYTE_SWAP( unsigned char );
401         break;
402       case SHORT:
403         BYTE_SWAP( short );
404         break;
405       case USHORT:
406         BYTE_SWAP( unsigned short );
407         break;
408       case INT:
409         BYTE_SWAP( int );
410         break;
411       case UINT:
412         BYTE_SWAP( unsigned int );
413         break;
414       case LONG:
415         BYTE_SWAP( long ); break;
416       case ULONG:
417         BYTE_SWAP( unsigned long );
418         break;
419       case FLOAT:
420         BYTE_SWAP( float );
421         break;
422       case DOUBLE
423         : BYTE_SWAP( double );
424         break;
425       default:
426         itkExceptionMacro("Component Type Unknown");
427       }
428 #undef BYTE_SWAP
429     }
430 }
431 
Read(void * buffer)432 void Bruker2dseqImageIO::Read(void *buffer)
433 {
434   const auto numberOfComponents = this->GetImageSizeInComponents();
435 
436   std::string path2Dseq = itksys::SystemTools::CollapseFullPath(this->m_FileName);
437   itksys::SystemTools::ConvertToUnixSlashes(path2Dseq);
438   std::ifstream stream2Dseq;
439   this->OpenFileForReading(stream2Dseq, path2Dseq);
440 
441   if (m_ComponentType != m_OnDiskComponentType)
442     {
443     SizeType numberOfBytesOnDisk = numberOfComponents;
444     switch ( m_OnDiskComponentType )
445       {
446       case UCHAR:
447         numberOfBytesOnDisk *= sizeof( unsigned char );
448         break;
449       case CHAR:
450         numberOfBytesOnDisk *= sizeof( char );
451         break;
452       case USHORT:
453         numberOfBytesOnDisk *= sizeof( unsigned short );
454         break;
455       case SHORT:
456         numberOfBytesOnDisk *= sizeof( short );
457         break;
458       case UINT:
459         numberOfBytesOnDisk *= sizeof( unsigned int );
460         break;
461       case INT:
462         numberOfBytesOnDisk *= sizeof( int );
463         break;
464       case ULONG:
465         numberOfBytesOnDisk *= sizeof( unsigned long );
466         break;
467       case LONG:
468         numberOfBytesOnDisk *= sizeof( long );
469         break;
470       case FLOAT:
471         numberOfBytesOnDisk *= sizeof( float );
472         break;
473       case DOUBLE:
474         numberOfBytesOnDisk *= sizeof( double );
475         break;
476       case UNKNOWNCOMPONENTTYPE:
477       default:
478         itkExceptionMacro ("Unknown component type: " << m_ComponentType);
479       }
480 
481     std::vector<char> dataFromDisk(numberOfBytesOnDisk);
482     char * dataFromDiskBuffer = &(dataFromDisk[0]);
483     stream2Dseq.read(dataFromDiskBuffer, numberOfBytesOnDisk);
484     if (stream2Dseq.fail())
485       {
486       itkExceptionMacro("Failed to read file: " << path2Dseq);
487       }
488 
489     this->SwapBytesIfNecessary(dataFromDiskBuffer, numberOfComponents);
490 
491     auto * floatBuffer = static_cast<float *>(buffer);
492     switch (m_OnDiskComponentType)
493       {
494       case CHAR:
495         CastCopy<char>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
496         break;
497       case UCHAR:
498         CastCopy<unsigned char>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
499         break;
500       case SHORT:
501         CastCopy<short>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
502         break;
503       case USHORT:
504         CastCopy<unsigned short>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
505         break;
506       case INT:
507         CastCopy<int>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
508         break;
509       case UINT:
510         CastCopy<unsigned int>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
511         break;
512       case LONG:
513         CastCopy<long>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
514         break;
515       case ULONG:
516         CastCopy<unsigned long>(floatBuffer, dataFromDiskBuffer, numberOfComponents);
517         break;
518       case FLOAT:
519         itkExceptionMacro("FLOAT pixels do not need Casting to float");
520       case DOUBLE:
521         itkExceptionMacro("DOUBLE pixels do not need Casting to float");
522       case UNKNOWNCOMPONENTTYPE:
523       default:
524         itkExceptionMacro("Bad OnDiskComponentType UNKNOWNCOMPONENTTYPE");
525       }
526     }
527   else
528     {
529       const auto numberOfBytesOnDisk = this->GetImageSizeInBytes();
530       auto * charBuffer = static_cast<char *>(buffer);
531       stream2Dseq.read(charBuffer, numberOfBytesOnDisk);
532       if (stream2Dseq.fail())
533         {
534         itkExceptionMacro("Failed to read file: " << path2Dseq);
535         }
536       this->SwapBytesIfNecessary(charBuffer, numberOfComponents);
537     }
538 
539   MetaDataDictionary &dict = this->GetMetaDataDictionary();
540   const std::vector<double> slopes = GetParameter<std::vector<double> >(dict, "VisuCoreDataSlope");
541   const std::vector<double> offsets = GetParameter<std::vector<double> >(dict, "VisuCoreDataOffs");
542   const SizeType frameCount = static_cast<SizeType>(GetParameter<double>(dict, "VisuCoreFrameCount"));
543   const SizeType frameDim = static_cast<SizeType>(GetParameter<double>(dict, "VisuCoreDim"));
544   SizeType frameSize = this->GetDimensions(0) * this->GetDimensions(1);
545 
546   if (frameDim == 3)
547     {
548     frameSize *= this->GetDimensions(2);
549     }
550 
551   switch ( this->m_ComponentType )
552     {
553     case CHAR:
554       ITK_FALLTHROUGH;
555     case UCHAR:
556       ITK_FALLTHROUGH;
557     case SHORT:
558       ITK_FALLTHROUGH;
559     case USHORT:
560       ITK_FALLTHROUGH;
561     case INT:
562       ITK_FALLTHROUGH;
563     case UINT:
564       ITK_FALLTHROUGH;
565     case LONG:
566       ITK_FALLTHROUGH;
567     case ULONG:
568       itkExceptionMacro("Must have float pixels to rescale");
569     case FLOAT:
570       Rescale(static_cast<float *>(buffer), slopes, offsets, frameSize, frameCount);
571       break;
572     case DOUBLE:
573       Rescale(static_cast<double *>(buffer), slopes, offsets, frameSize, frameCount);
574       break;
575     default:
576       itkExceptionMacro("Datatype not supported: " << this->GetComponentTypeAsString(this->m_ComponentType));
577     }
578 
579   //
580   // 2D Multi-echo or calculated maps (e.g. DTI) may be stored echo/image first, then slice
581   // Look at the Order Description field to check if they need re-ordering
582   //
583   if (frameDim == 2 && dict.HasKey("VisuFGOrderDesc") )
584     {
585     size_t sizeToSwap = 1;
586     for (auto & i : GetParameter<std::vector<std::vector<std::string> > >(dict, "VisuFGOrderDesc") )
587       {
588       // Anything before the SLICE order needs to be re-ordered
589       if (i[1] == "<FG_SLICE>")
590         {
591         break;
592         }
593       else
594         {
595         sizeToSwap *= std::stoi(i[0].c_str());
596         }
597       }
598     if (sizeToSwap > 1)
599       {
600       const SizeValueType x = this->GetDimensions(0);
601       const SizeValueType y = this->GetDimensions(1);
602       const SizeValueType z = this->GetDimensions(2);
603       const SizeValueType noswap = this->GetDimensions(3) / sizeToSwap;
604       switch ( this->m_ComponentType )
605         {
606         case CHAR:
607           SwapSlicesAndVolumes(static_cast<char *>(buffer), x, y, z, sizeToSwap, noswap);
608           break;
609         case UCHAR:
610           SwapSlicesAndVolumes(static_cast<unsigned char *>(buffer), x, y, z, sizeToSwap, noswap);
611           break;
612         case SHORT:
613           SwapSlicesAndVolumes(static_cast<short *>(buffer), x, y, z, sizeToSwap, noswap);
614           break;
615         case USHORT:
616           SwapSlicesAndVolumes(static_cast<unsigned short *>(buffer), x, y, z, sizeToSwap, noswap);
617           break;
618         case INT:
619           SwapSlicesAndVolumes(static_cast<int *>(buffer), x, y, z, sizeToSwap, noswap);
620           break;
621         case UINT:
622           SwapSlicesAndVolumes(static_cast<unsigned int *>(buffer), x, y, z, sizeToSwap, noswap);
623           break;
624         case LONG:
625           SwapSlicesAndVolumes(static_cast<long *>(buffer), x, y, z, sizeToSwap, noswap);
626           break;
627         case ULONG:
628           SwapSlicesAndVolumes(static_cast<unsigned long *>(buffer), x, y, z, sizeToSwap, noswap);
629           break;
630         case FLOAT:
631           SwapSlicesAndVolumes(static_cast<float *>(buffer), x, y, z, sizeToSwap, noswap);
632           break;
633         case DOUBLE:
634           SwapSlicesAndVolumes(static_cast<double *>(buffer), x, y, z, sizeToSwap, noswap);
635           break;
636         default:
637           itkExceptionMacro("Datatype not supported: " << this->GetComponentTypeAsString(this->m_ComponentType));
638         }
639       }
640     }
641 
642   if (dict.HasKey("VisuCoreDiskSliceOrder") && (GetParameter<std::string>(dict, "VisuCoreDiskSliceOrder") == "disk_reverse_slice_order"))
643     {
644     const SizeValueType x = this->GetDimensions(0);
645     const SizeValueType y = this->GetDimensions(1);
646     const SizeValueType z = this->GetDimensions(2);
647     const SizeValueType v = (this->GetNumberOfDimensions() > 3) ? this->GetDimensions(3) : 1;
648     switch ( this->m_ComponentType )
649       {
650       case CHAR:
651         ReverseSliceOrder(static_cast<char *>(buffer), x, y, z, v);
652         break;
653       case UCHAR:
654         ReverseSliceOrder(static_cast<unsigned char *>(buffer), x, y, z, v);
655         break;
656       case SHORT:
657         ReverseSliceOrder(static_cast<short *>(buffer), x, y, z, v);
658         break;
659       case USHORT:
660         ReverseSliceOrder(static_cast<unsigned short *>(buffer), x, y, z, v);
661         break;
662       case INT:
663         ReverseSliceOrder(static_cast<int *>(buffer), x, y, z, v);
664         break;
665       case UINT:
666         ReverseSliceOrder(static_cast<unsigned int *>(buffer), x, y, z, v);
667         break;
668       case LONG:
669         ReverseSliceOrder(static_cast<long *>(buffer), x, y, z, v);
670         break;
671       case ULONG:
672         ReverseSliceOrder(static_cast<unsigned long *>(buffer), x, y, z, v);
673         break;
674       case FLOAT:
675         ReverseSliceOrder(static_cast<float *>(buffer), x, y, z, v);
676         break;
677       case DOUBLE:
678         ReverseSliceOrder(static_cast<double *>(buffer), x, y, z, v);
679         break;
680       default:
681         itkExceptionMacro("Datatype not supported: " << this->GetComponentTypeAsString(this->m_ComponentType));
682       }
683     }
684 }
685 
CanReadFile(const char * FileNameToRead)686 bool Bruker2dseqImageIO::CanReadFile(const char *FileNameToRead)
687 {
688   std::string file2Dseq = itksys::SystemTools::CollapseFullPath(FileNameToRead);
689   itksys::SystemTools::ConvertToUnixSlashes(file2Dseq);
690   std::string fileVisu = itksys::SystemTools::GetFilenamePath(file2Dseq) + "/visu_pars";
691 
692   if (!itksys::SystemTools::FileExists(file2Dseq))
693     {
694     return false;
695     }
696   if (!itksys::SystemTools::FileExists(fileVisu))
697     {
698     return false;
699     }
700   return true;
701 }
702 
ReadImageInformation()703 void Bruker2dseqImageIO::ReadImageInformation()
704 {
705   // Get the meta dictionary for this object.
706   MetaDataDictionary &dict = this->GetMetaDataDictionary();
707   EncapsulateMetaData< std::string >(dict, ITK_InputFilterName, this->GetNameOfClass());
708 
709   std::string path2Dseq = itksys::SystemTools::CollapseFullPath(this->m_FileName);
710   itksys::SystemTools::ConvertToUnixSlashes(path2Dseq);
711   std::string pathVisu = itksys::SystemTools::GetFilenamePath(path2Dseq) + "/visu_pars";
712   ReadJCAMPDX(pathVisu, dict);
713 
714   // If the method file exists, read it in case user wants the meta-data
715   // However, visu_pars contains everything needed to read so make this optional
716   std::string methodFilename = itksys::SystemTools::GetFilenamePath(path2Dseq) + "/../../method";
717   if (itksys::SystemTools::FileExists(methodFilename))
718     {
719     ReadJCAMPDX(methodFilename, dict);
720     }
721 
722   const std::string wordType = GetParameter<std::string>(dict, "VisuCoreWordType");
723   if (wordType == BRUKER_SIGNED_CHAR)
724     {
725     this->m_ComponentType = CHAR;
726     this->m_PixelType = SCALAR;
727     }
728   else if (wordType == BRUKER_UNSIGNED_CHAR)
729     {
730     this->m_ComponentType = UCHAR;
731     this->m_PixelType = SCALAR;
732     }
733   else if (wordType == BRUKER_SIGNED_SHORT)
734     {
735     this->m_ComponentType = SHORT;
736     this->m_PixelType = SCALAR;
737     }
738   else if (wordType == BRUKER_SIGNED_INT)
739     {
740     this->m_ComponentType = INT;
741     this->m_PixelType = SCALAR;
742     }
743   else if (wordType == BRUKER_FLOAT)
744     {
745     this->m_ComponentType = FLOAT;
746     this->m_PixelType = SCALAR;
747     }
748   else
749     {
750     itkExceptionMacro("VisuCoreWordType parameter is invalid: " << wordType);
751     }
752 
753   // Similar to NIFTI - promote to at least float for rescaling
754   this->m_OnDiskComponentType = this->m_ComponentType;
755   if ( this->m_ComponentType == CHAR
756     || this->m_ComponentType == UCHAR
757     || this->m_ComponentType == SHORT
758     || this->m_ComponentType == USHORT
759     || this->m_ComponentType == INT
760     || this->m_ComponentType == UINT
761     || this->m_ComponentType == LONG
762     || this->m_ComponentType == ULONG )
763       {
764       this->m_ComponentType = FLOAT;
765       }
766 
767   const std::string byteOrder = GetParameter<std::string>(dict, "VisuCoreByteOrder");
768   if (byteOrder == BRUKER_LITTLE_ENDIAN)
769     {
770     this->m_ByteOrder = LittleEndian;
771     }
772   else if (byteOrder == BRUKER_BIG_ENDIAN)
773     {
774     this->m_ByteOrder = BigEndian;
775     }
776   else
777     {
778     itkExceptionMacro("VisuCoreByteOrder parameter is invalid: " << byteOrder);
779     }
780 
781   const SizeType brukerDim = static_cast<SizeType>(GetParameter<double>(dict, "VisuCoreDim"));
782   const SizeType frames = static_cast<SizeType>(GetParameter<double>(dict, "VisuCoreFrameCount"));
783   const std::vector<double> size = GetParameter<std::vector<double> >(dict, "VisuCoreSize");
784   const std::vector<double> FoV = GetParameter<std::vector<double> >(dict, "VisuCoreExtent");
785 
786   if (brukerDim == 1)
787     {
788     // Spectroscopy Data. Should probably ignore this, but we've got this far
789     // so attempt to convert
790     //
791     this->SetNumberOfDimensions(1);
792     this->SetDimensions(0, size[0]);
793     this->SetSpacing(0, FoV[0] / size[0]);
794     this->SetOrigin(0, 0);
795     }
796   else
797     {
798     const std::vector<double> position = GetParameter<std::vector<double> >(dict, "VisuCorePosition");
799     // Bruker 'origin' is corner of slice/volume. Needs shifting by half-voxel to be ITK origin
800     // But for 2D images, the slice position is correct (center of slice)
801     vnl_vector<double> halfStep(3);
802     halfStep[0] = FoV[0] / (2*size[0]);
803     halfStep[1] = FoV[1] / (2*size[1]);
804     SizeType sizeZ = 1;
805     SizeType sizeT = 1;
806     double spacingZ = 1;
807     double reverseZ = 1;
808     if (brukerDim == 2)
809       {
810       // The obvious way to get number of slices is sum of SlicePacksSlices - but single-slice images do not store this!
811       // The easiest way is divide the length of Position by 3 (3 co-ordinates per slice position)
812       sizeZ = position.size() / 3;
813       if (sizeZ == 1)
814         { // Special case for single-slice, because that doesn't store SliceDist
815         spacingZ = GetParameter<std::vector<double> >(dict, "VisuCoreFrameThickness")[0];
816         }
817       else
818         {
819         // FrameThickness does not include slice gap
820         // You would think that we could use the SliceDist field for multi-slice, but ParaVision
821         // has a bug that sometimes sets SliceDist to 0
822         // So - calculate this manually from the SlicePosition field
823         vnl_vector<double> slice1(&position[0], 3);
824         vnl_vector<double> slice2(&position[3], 3);
825         vnl_vector<double> diff = slice2 - slice1;
826         spacingZ = diff.magnitude();
827         }
828       if (dict.HasKey("VisuFGOrderDesc"))
829         {
830         // Find the FG_CYCLE field
831         sizeT = 1;
832         for (auto & i : GetParameter<std::vector<std::vector<std::string> > >(dict, "VisuFGOrderDesc") )
833           {
834           // Anything dimension that isn't a slice needs to be collapsed into the 4th dimension
835           if (i[1] != "<FG_SLICE>")
836             {
837             sizeT *= std::stoi(i[0].c_str());
838             }
839           }
840         }
841       else
842         {
843         itkGenericExceptionMacro("Could not find order description field");
844         }
845       halfStep[2] = 0; // Slice position will be correct
846 
847       if (sizeZ > 1)
848         { // There appears to be a bug in 2dseq orientations for Coronal 2D slices.
849           // This code checks if we have coronal slices and reverses the Z-direction
850           // further down, which makes the images appear correct in FSL View.
851           // The acquisition orientation is not stored in visu_pars so work out if
852           // this is coronal by checking if the Y component of the slice positions is
853           // changing.
854         vnl_vector<double> corner1(&position[0], 3);
855         vnl_vector<double> corner2(&position[3], 3);
856         vnl_vector<double> diff = corner2 - corner1;
857         if (diff[1] != 0)
858           {
859           reverseZ = -1;
860           }
861         }
862       }
863     else
864       {
865       sizeZ = size[2];
866       spacingZ = FoV[2] / sizeZ;
867       sizeT = frames; // Each volume is a 'frame'
868       halfStep[2] = FoV[2] / (2*size[2]);
869       }
870 
871     if (sizeT > 1)
872       {
873       this->SetNumberOfDimensions(4);
874       this->SetDimensions(3, sizeT);
875       if (dict.HasKey("VisuAcqRepetitionTime"))
876         {
877         const double TR = GetParameter<std::vector<double> >(dict, "VisuAcqRepetitionTime")[0];
878         this->SetSpacing(3, TR / 1e3); // TR is in milliseconds, convert to seconds
879         }
880       else
881         {
882         // Map images from Bruker X-Tip don't have a TR
883         this->SetSpacing(3, 1);
884         }
885       this->SetOrigin(3, 0);
886       }
887 
888     // It is possible for every slice to have a different orientation,
889     // but ITK doesn't support this so concatenate all slices as if they
890     // had the same orientation
891     const std::vector<double> orient = GetParameter<std::vector<double> >(dict, "VisuCoreOrientation");
892 
893     // The Bruker orient field is scanner-to-image. ITK is image-to-scanner.
894     // However, ITK stores column-wise, Bruker row-wise. So the below is
895     // equivalent to a matrix transpose, which because these are direction
896     // matrices with determinant +/-1, is equivalent to an inverse. So this
897     // gives the correct orientations.
898     vnl_matrix<double> dirMatrix(&orient[0], 3, 3);
899     this->SetDirection(0, dirMatrix.get_row(0));
900     this->SetDirection(1, dirMatrix.get_row(1));
901     // See note above for apparent bug in 2D coronal acquisitions
902     this->SetDirection(2, reverseZ * dirMatrix.get_row(2));
903 
904     // Now work out the correct ITK origin including the half-voxel offset
905     vnl_vector<double> corner(&position[0], 3);
906     vnl_vector<double> origin = corner + dirMatrix * halfStep;
907 
908     this->SetOrigin(0, origin[0]);
909     this->SetOrigin(1, origin[1]);
910     this->SetOrigin(2, origin[2]);
911 
912     // Finally set matrix size and voxel spacing
913     this->SetDimensions(0, size[0]);
914     this->SetDimensions(1, size[1]);
915     this->SetDimensions(2, sizeZ);
916 
917     this->SetSpacing(0, FoV[0] / size[0]);
918     this->SetSpacing(1, FoV[1] / size[1]);
919     this->SetSpacing(2, spacingZ);
920     }
921 }
922 
PrintSelf(std::ostream & os,Indent indent) const923 void Bruker2dseqImageIO::PrintSelf(std::ostream & os, Indent indent) const
924 {
925   Superclass::PrintSelf(os, indent);
926 
927   os << indent << "OnDiskComponentType"
928     << static_cast< NumericTraits< ImageIOBase::IOComponentType >::PrintType >( m_OnDiskComponentType )
929     << std::endl;
930   os << indent << "MachineByteOrder"
931     << static_cast< NumericTraits< ImageIOBase::ByteOrder >::PrintType >( m_MachineByteOrder )
932     << std::endl;
933 }
934 } // end namespace itk
935