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