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 /**
19 * \author Don C. Bigler
20 * The Pennsylvania State University 2005
21 *
22 * This implementation was contributed as a paper to the Insight Journal
23 * http://insight-journal.org/midas/handle.php?handle=1926/1381
24 *
25 */
26
27 #include "itkPhilipsRECImageIO.h"
28 #include "itkPhilipsPAR.h"
29 #include "itkIOCommon.h"
30 #include "itkByteSwapper.h"
31 #include "itkMetaDataObject.h"
32 #include "itkSpatialOrientationAdapter.h"
33 #include "itksys/SystemTools.hxx"
34 #include "itk_zlib.h"
35 #include <fstream>
36
37 namespace itk
38 {
39 const char *const PAR_Version = "PAR_Version";
40 const char *const PAR_SliceOrientation = "PAR_SliceOrientation";
41 const char *const PAR_ExaminationName = "PAR_ExaminationName";
42 const char *const PAR_ProtocolName = "PAR_ProtocolName";
43 const char *const PAR_SeriesType = "PAR_SeriesType";
44 const char *const PAR_AcquisitionNr = "PAR_AcquisitionNr";
45 const char *const PAR_ReconstructionNr = "PAR_ReconstructionNr";
46 const char *const PAR_ScanDuration = "PAR_ScanDuration";
47 const char *const PAR_MaxNumberOfCardiacPhases = "PAR_MaxNumberOfCardiacPhases";
48 const char *const PAR_TriggerTimes = "PAR_TriggerTimes";
49 const char *const PAR_MaxNumberOfEchoes = "PAR_MaxNumberOfEchoes";
50 const char *const PAR_EchoTimes = "PAR_EchoTimes";
51 const char *const PAR_MaxNumberOfDynamics = "PAR_MaxNumberOfDynamics";
52 const char *const PAR_MaxNumberOfMixes = "PAR_MaxNumberOfMixes";
53 const char *const PAR_PatientPosition = "PAR_PatientPosition";
54 const char *const PAR_PreparationDirection = "PAR_PreparationDirection";
55 const char *const PAR_Technique = "PAR_Technique";
56 const char *const PAR_ScanMode = "PAR_ScanMode";
57 const char *const PAR_NumberOfAverages = "PAR_NumberOfAverages";
58 const char *const PAR_ScanResolution = "PAR_ScanResolution";
59 const char *const PAR_RepetitionTimes = "PAR_RepetitionTimes";
60 const char *const PAR_ScanPercentage = "PAR_ScanPercentage";
61 const char *const PAR_FOV = "PAR_FOV";
62 const char *const PAR_WaterFatShiftPixels = "PAR_WaterFatShiftPixels";
63 const char *const PAR_AngulationMidSlice = "PAR_AngulationMidSlice";
64 const char *const PAR_OffCentreMidSlice = "PAR_OffCentreMidSlice";
65 const char *const PAR_FlowCompensation = "PAR_FlowCompensation";
66 const char *const PAR_Presaturation = "PAR_Presaturation";
67 const char *const PAR_CardiacFrequency = "PAR_CardiacFrequency";
68 const char *const PAR_MinRRInterval = "PAR_MinRRInterval";
69 const char *const PAR_MaxRRInterval = "PAR_MaxRRInterval";
70 const char *const PAR_PhaseEncodingVelocity = "PAR_PhaseEncodingVelocity";
71 const char *const PAR_MTC = "PAR_MTC";
72 const char *const PAR_SPIR = "PAR_SPIR";
73 const char *const PAR_EPIFactor = "PAR_EPIFactor";
74 const char *const PAR_TurboFactor = "PAR_TurboFactor";
75 const char *const PAR_DynamicScan = "PAR_DynamicScan";
76 const char *const PAR_Diffusion = "PAR_Diffusion";
77 const char *const PAR_DiffusionEchoTime = "PAR_DiffusionEchoTime";
78 const char *const PAR_MaxNumberOfDiffusionValues =
79 "PAR_MaxNumberOfDiffusionValues";
80 const char *const PAR_GradientBValues = "PAR_GradientBValues";
81 const char *const PAR_MaxNumberOfGradientOrients =
82 "PAR_MaxNumberOfGradientOrients";
83 const char *const PAR_GradientDirectionValues = "PAR_GradientDirectionValues";
84 const char *const PAR_InversionDelay = "PAR_InversionDelay";
85 const char *const PAR_NumberOfImageTypes = "PAR_NumberOfImageTypes";
86 const char *const PAR_ImageTypes = "PAR_ImageTypes";
87 const char *const PAR_NumberOfScanningSequences =
88 "PAR_NumberOfScanningSequences";
89 const char *const PAR_ScanningSequences = "PAR_ScanningSequences";
90 const char *const PAR_ScanningSequenceImageTypeRescaleValues =
91 "PAR_ScanningSequenceImageTypeRescaleValues";
92 const char *const PAR_NumberOfASLLabelTypes = "PAR_NumberOfASLLabelTypes";
93 const char *const PAR_ASLLabelTypes = "PAR_ASLLabelTypes";
94
95 static std::string
GetExtension(const std::string & filename)96 GetExtension(const std::string & filename)
97 {
98 std::string fileExt( itksys::SystemTools::GetFilenameLastExtension(filename) );
99
100 // If the last extension is .gz, then need to pull off 2 extensions.
101 //.gz is the only valid compression extension.
102 if ( fileExt == std::string(".gz") )
103 {
104 fileExt = itksys::SystemTools::GetFilenameLastExtension(
105 itksys::SystemTools::GetFilenameWithoutLastExtension(filename) );
106 fileExt += ".gz";
107 }
108 // Check that a valid extension was found
109 // Will check for either all caps or all lower-case.
110 // By default the Philips Pride Workstation outputs
111 // the filenames as all caps, but a user may change the
112 // filenames to lowercase. This will allow one or the
113 // other. Mixed caps/lower-case will always (with the
114 // exception of the lower-case gz on the end which is
115 // always assumed to be lower-case) fail on an OS with
116 // a case sensitive file system.
117 if ( fileExt != ".REC.gz"
118 && fileExt != ".REC"
119 && fileExt != ".PAR"
120 && fileExt != ".rec.gz"
121 && fileExt != ".rec"
122 && fileExt != ".par" )
123 {
124 return ( "" );
125 }
126
127 return ( fileExt );
128 }
129
130 static std::string
GetRootName(const std::string & filename)131 GetRootName(const std::string & filename)
132 {
133 const std::string fileExt = GetExtension(filename);
134
135 // Create a base filename
136 // i.e Image.PAR --> Image
137 if ( fileExt.length() > 0
138 && filename.length() > fileExt.length() )
139 {
140 const std::string::size_type it = filename.find_last_of(fileExt);
141 std::string baseName( filename, 0, it - ( fileExt.length() - 1 ) );
142 return ( baseName );
143 }
144 //Default to return same as input when the extension is nothing.
145 return ( filename );
146 }
147
148 static std::string
GetHeaderFileName(const std::string & filename)149 GetHeaderFileName(const std::string & filename)
150 {
151 std::string ImageFileName(filename);
152 const std::string fileExt = GetExtension(filename);
153
154 // Accommodate either all caps or all lower-case filenames.
155 if ( ( fileExt == ".REC" ) || ( fileExt == ".REC.gz" ) )
156 {
157 ImageFileName = GetRootName(filename);
158 ImageFileName += ".PAR";
159 }
160 else if ( ( fileExt == ".rec" ) || ( fileExt == ".rec.gz" ) )
161 {
162 ImageFileName = GetRootName(filename);
163 ImageFileName += ".par";
164 }
165 return ( ImageFileName );
166 }
167
168 //Returns the base image filename.
GetImageFileName(const std::string & filename)169 static std::string GetImageFileName(const std::string & filename)
170 {
171 std::string ImageFileName(filename);
172 const std::string fileExt = GetExtension(filename);
173
174 // Default to uncompressed .REC if .PAR is given as file name.
175 if ( fileExt == ".PAR" )
176 {
177 ImageFileName = GetRootName(filename);
178 ImageFileName += ".REC";
179 }
180 else if ( fileExt == ".par" )
181 {
182 ImageFileName = GetRootName(filename);
183 ImageFileName += ".rec";
184 }
185 return ( ImageFileName );
186 }
187
188 //----------------------------------------------------------------------------
189 // This generates the correct offset to the desired image type and
190 // scanning sequence (randomly ordered in the REC).
PhilipsRECImageIOGetImageTypeOffset(int imageType,int scanSequence,int volumeIndex,int slice,int numSlices,struct par_parameter parParam,PhilipsPAR::PARSliceIndexImageTypeVector sliceImageTypesIndex,PhilipsPAR::PARSliceIndexScanSequenceVector sliceScanSequenceIndex)191 int PhilipsRECImageIOGetImageTypeOffset(int imageType, int scanSequence,
192 int volumeIndex, int slice, int numSlices, struct par_parameter parParam,
193 PhilipsPAR::PARSliceIndexImageTypeVector sliceImageTypesIndex,
194 PhilipsPAR::PARSliceIndexScanSequenceVector sliceScanSequenceIndex)
195 {
196 int index = volumeIndex * parParam.num_slice_repetitions * numSlices
197 + slice * parParam.num_slice_repetitions;
198 int i;
199
200 for ( i = 0; i < parParam.num_slice_repetitions; i++ )
201 {
202 if ( ( sliceImageTypesIndex[index + i].second == imageType )
203 && ( sliceScanSequenceIndex[index + i].second == scanSequence ) )
204 {
205 break;
206 }
207 }
208 return i;
209 }
210
211 //----------------------------------------------------------------------------
212 // This creates the desired slice order index (slice or image block).
PhilipsRECImageIOSetupSliceIndex(PhilipsRECImageIO::SliceIndexType * indexMatrix,int sortBlock,struct par_parameter parParam,PhilipsPAR::PARImageTypeScanSequenceVector imageTypesScanSequenceIndex,PhilipsPAR::PARSliceIndexImageTypeVector sliceImageTypesIndex,PhilipsPAR::PARSliceIndexScanSequenceVector sliceScanSequenceIndex)213 void PhilipsRECImageIOSetupSliceIndex(
214 PhilipsRECImageIO::SliceIndexType *indexMatrix, int sortBlock,
215 struct par_parameter parParam,
216 PhilipsPAR::PARImageTypeScanSequenceVector imageTypesScanSequenceIndex,
217 PhilipsPAR::PARSliceIndexImageTypeVector sliceImageTypesIndex,
218 PhilipsPAR::PARSliceIndexScanSequenceVector sliceScanSequenceIndex)
219 {
220 int index = 0;
221 int actualSlices = parParam.slice;
222 int remainingVolumes = parParam.image_blocks / parParam.num_slice_repetitions;
223
224 if ( indexMatrix->size() !=
225 (PhilipsRECImageIO::SliceIndexType::size_type)parParam.dim[2] )
226 {
227 std::ostringstream message;
228 message << "indexMatrix->size(): "
229 << indexMatrix->size()
230 << " != parParam.dim[2]: "
231 << parParam.dim[2];
232 ExceptionObject exception(__FILE__, __LINE__,
233 message.str(),
234 ITK_LOCATION);
235 throw exception;
236 }
237 if ( parParam.dim[2] != ( parParam.slice * parParam.image_blocks ) )
238 {
239 std::ostringstream message;
240 message << "parParam.dim[2]: "
241 << parParam.dim[2]
242 << " != (parParam.slice*parParam.image_blocks): "
243 << parParam.slice * parParam.image_blocks;
244 ExceptionObject exception(__FILE__, __LINE__,
245 message.str(),
246 ITK_LOCATION);
247 throw exception;
248 }
249 if ( !parParam.slicessorted && (imageTypesScanSequenceIndex.size() !=
250 (PhilipsRECImageIO::SliceIndexType::size_type)parParam.num_slice_repetitions) )
251 {
252 std::ostringstream message;
253 message << "imageTypesScanSequenceIndex.size(): "
254 << imageTypesScanSequenceIndex.size()
255 << " != parParam.num_slice_repetitions "
256 << parParam.num_slice_repetitions;
257 ExceptionObject exception(__FILE__, __LINE__,
258 message.str(),
259 ITK_LOCATION);
260 throw exception;
261 }
262
263 // Different index depending on the desired slice sort and the REC
264 // slice order.
265 if ( ( sortBlock && parParam.slicessorted )
266 || ( !sortBlock && !parParam.slicessorted ) )
267 {
268 // No sorting necessary for these cases.
269 for ( int i = 0; i < parParam.dim[2]; i++ )
270 {
271 ( *indexMatrix )[i] = i;
272 }
273 }
274 // This case is the real problematic one.
275 else if ( sortBlock && !parParam.slicessorted
276 && ( parParam.num_slice_repetitions > 1 ) )
277 {
278 // Ok, need to figure out where all of the images are located
279 // using sliceImageTypesIndex and sliceScanSequenceIndex.
280 for ( int i = 0; i < parParam.num_slice_repetitions; i++ )
281 {
282 for ( int j = 0; j < remainingVolumes; j++ )
283 {
284 for ( int k = 0; k < actualSlices; k++ )
285 {
286 ( *indexMatrix )[index] = j * parParam.num_slice_repetitions * actualSlices
287 + k * parParam.num_slice_repetitions
288 + PhilipsRECImageIOGetImageTypeOffset(
289 imageTypesScanSequenceIndex[i].first,
290 imageTypesScanSequenceIndex[i].second, j, k, actualSlices, parParam,
291 sliceImageTypesIndex, sliceScanSequenceIndex);
292 index++;
293 }
294 }
295 }
296 }
297 else
298 {
299 // Unsort image block or sort by image block.
300 for ( int i = 0; i < parParam.image_blocks; i++ )
301 {
302 for ( int j = 0; j < actualSlices; j++ )
303 {
304 ( *indexMatrix )[index] = j * parParam.image_blocks + i;
305 index++;
306 }
307 }
308 }
309 }
310
311 void
SwapBytesIfNecessary(void * buffer,SizeValueType numberOfPixels)312 PhilipsRECImageIO::SwapBytesIfNecessary(void *buffer,
313 SizeValueType numberOfPixels)
314 {
315 if ( m_ByteOrder == LittleEndian )
316 {
317 switch ( this->m_ComponentType )
318 {
319 case CHAR:
320 ByteSwapper< char >::SwapRangeFromSystemToLittleEndian
321 ( (char *)buffer, numberOfPixels );
322 break;
323 case UCHAR:
324 ByteSwapper< unsigned char >::SwapRangeFromSystemToLittleEndian
325 ( (unsigned char *)buffer, numberOfPixels );
326 break;
327 case SHORT:
328 ByteSwapper< short >::SwapRangeFromSystemToLittleEndian
329 ( (short *)buffer, numberOfPixels );
330 break;
331 case USHORT:
332 ByteSwapper< unsigned short >::SwapRangeFromSystemToLittleEndian
333 ( (unsigned short *)buffer, numberOfPixels );
334 break;
335 case INT:
336 ByteSwapper< int >::SwapRangeFromSystemToLittleEndian
337 ( (int *)buffer, numberOfPixels );
338 break;
339 case UINT:
340 ByteSwapper< unsigned int >::SwapRangeFromSystemToLittleEndian
341 ( (unsigned int *)buffer, numberOfPixels );
342 break;
343 case LONG:
344 ByteSwapper< long >::SwapRangeFromSystemToLittleEndian
345 ( (long *)buffer, numberOfPixels );
346 break;
347 case ULONG:
348 ByteSwapper< unsigned long >::SwapRangeFromSystemToLittleEndian
349 ( (unsigned long *)buffer, numberOfPixels );
350 break;
351 case FLOAT:
352 ByteSwapper< float >::SwapRangeFromSystemToLittleEndian
353 ( (float *)buffer, numberOfPixels );
354 break;
355 case DOUBLE:
356 ByteSwapper< double >::SwapRangeFromSystemToLittleEndian
357 ( (double *)buffer, numberOfPixels );
358 break;
359 default:
360 ExceptionObject exception(__FILE__, __LINE__,
361 "Component Type Unknown",
362 ITK_LOCATION);
363 throw exception;
364 }
365 }
366 else
367 {
368 switch ( this->m_ComponentType )
369 {
370 case CHAR:
371 ByteSwapper< char >::SwapRangeFromSystemToBigEndian
372 ( (char *)buffer, numberOfPixels );
373 break;
374 case UCHAR:
375 ByteSwapper< unsigned char >::SwapRangeFromSystemToBigEndian
376 ( (unsigned char *)buffer, numberOfPixels );
377 break;
378 case SHORT:
379 ByteSwapper< short >::SwapRangeFromSystemToBigEndian
380 ( (short *)buffer, numberOfPixels );
381 break;
382 case USHORT:
383 ByteSwapper< unsigned short >::SwapRangeFromSystemToBigEndian
384 ( (unsigned short *)buffer, numberOfPixels );
385 break;
386 case INT:
387 ByteSwapper< int >::SwapRangeFromSystemToBigEndian
388 ( (int *)buffer, numberOfPixels );
389 break;
390 case UINT:
391 ByteSwapper< unsigned int >::SwapRangeFromSystemToBigEndian
392 ( (unsigned int *)buffer, numberOfPixels );
393 break;
394 case LONG:
395 ByteSwapper< long >::SwapRangeFromSystemToBigEndian
396 ( (long *)buffer, numberOfPixels );
397 break;
398 case ULONG:
399 ByteSwapper< unsigned long >::SwapRangeFromSystemToBigEndian
400 ( (unsigned long *)buffer, numberOfPixels );
401 break;
402 case FLOAT:
403 ByteSwapper< float >::SwapRangeFromSystemToBigEndian
404 ( (float *)buffer, numberOfPixels );
405 break;
406 case DOUBLE:
407 ByteSwapper< double >::SwapRangeFromSystemToBigEndian
408 ( (double *)buffer, numberOfPixels );
409 break;
410 default:
411 ExceptionObject exception(__FILE__, __LINE__,
412 "Component Type Unknown",
413 ITK_LOCATION);
414 throw exception;
415 }
416 }
417 }
418
PhilipsRECImageIO()419 PhilipsRECImageIO::PhilipsRECImageIO()
420 {
421 //by default, have 4 dimensions
422 this->SetNumberOfDimensions(4);
423 this->m_PixelType = SCALAR;
424 this->m_ComponentType = CHAR;
425
426 // Set m_MachineByteOrder to the ByteOrder of the machine
427 // Start out with file byte order == system byte order
428 // this will be changed if we're reading a file to whatever
429 // the file actually contains.
430 if ( ByteSwapper< int >::SystemIsBigEndian() )
431 {
432 this->m_MachineByteOrder = this->m_ByteOrder = BigEndian;
433 }
434 else
435 {
436 this->m_MachineByteOrder = this->m_ByteOrder = LittleEndian;
437 }
438 this->m_SliceIndex = new SliceIndexType();
439 }
440
~PhilipsRECImageIO()441 PhilipsRECImageIO::~PhilipsRECImageIO()
442 {
443 delete this->m_SliceIndex;
444 }
445
PrintSelf(std::ostream & os,Indent indent) const446 void PhilipsRECImageIO::PrintSelf(std::ostream & os, Indent indent) const
447 {
448 Superclass::PrintSelf(os, indent);
449 }
450
451 PhilipsRECImageIO::IndexValueType
GetSliceIndex(IndexValueType index) const452 PhilipsRECImageIO::GetSliceIndex(IndexValueType index) const
453 {
454 IndexValueType maximumSliceNumber =
455 Math::CastWithRangeCheck< IndexValueType, size_t >( this->m_SliceIndex->size() ) - 1;
456
457 if ( ( index < 0 ) || ( index > maximumSliceNumber ) )
458 {
459 return -1;
460 }
461 return ( *this->m_SliceIndex )[index];
462 }
463
Read(void * buffer)464 void PhilipsRECImageIO::Read(void *buffer)
465 {
466 unsigned int dim;
467 const unsigned int dimensions = this->GetNumberOfDimensions();
468 unsigned int numberOfPixels = 1;
469
470 for ( dim = 0; dim < dimensions; dim++ )
471 {
472 numberOfPixels *= this->m_Dimensions[dim];
473 }
474
475 auto *const p = static_cast< char * >( buffer );
476 //8 cases to handle
477 //1: given .PAR and image is .REC
478 //2: given .PAR and image is .REC.gz
479 //3: given .REC
480 //4: given .REC.gz
481 //5: given .par and image is .rec
482 //6: given .par and image is .rec.gz
483 //7: given .rec
484 //8: given .rec.gz
485
486 /* Returns proper name for cases 1,2,3,4,5,6 */
487 std::string ImageFileName = GetImageFileName(this->m_FileName);
488 //NOTE: gzFile operations act just like FILE * operations when the files
489 // are not in gzip fromat.
490 // This greatly simplifies the following code, and gzFile types are used
491 // everywhere.
492 // In addition, it has the added benefit of reading gzip compressed image
493 // files that do not have a .gz ending.
494 gzFile file_p = gzopen(ImageFileName.c_str(), "rb");
495 if ( file_p == nullptr )
496 {
497 ImageFileName += ".gz";
498 file_p = gzopen(ImageFileName.c_str(), "rb");
499 if ( file_p == nullptr )
500 {
501 std::ostringstream message;
502 message << "Philips REC Data File can not be opened. "
503 << "The following files were attempted:" << std::endl
504 << GetImageFileName(this->m_FileName) << std::endl
505 << ImageFileName;
506 ExceptionObject exception(__FILE__, __LINE__,
507 message.str(),
508 ITK_LOCATION);
509 throw exception;
510 }
511 }
512
513 // read image a slice at a time (sorted).
514 SizeType numberOfSlices = this->m_Dimensions[2];
515 if ( dimensions > 3 )
516 {
517 numberOfSlices *= this->m_Dimensions[3];
518 }
519
520 SizeType imageSliceSizeInBytes = this->GetImageSizeInBytes() / numberOfSlices;
521
522 for ( IndexValueType slice = 0; slice < numberOfSlices; slice++ )
523 {
524 IndexValueType realIndex = this->GetSliceIndex( (int)slice );
525 if ( realIndex < 0 )
526 {
527 std::ostringstream message;
528 message << "Philips REC Data File can not be read. "
529 << "The following files were attempted:" << std::endl
530 << GetImageFileName(this->m_FileName) << std::endl
531 << ImageFileName;
532 ExceptionObject exception(__FILE__, __LINE__,
533 message.str(),
534 ITK_LOCATION);
535 throw exception;
536 }
537 const auto offset = Math::CastWithRangeCheck< z_off_t, SizeType >(realIndex * imageSliceSizeInBytes);
538 gzseek(file_p, offset, SEEK_SET);
539 gzread( file_p, p + ( slice * imageSliceSizeInBytes ),
540 Math::CastWithRangeCheck< unsigned int, SizeType >(imageSliceSizeInBytes) );
541 }
542 gzclose(file_p);
543 SwapBytesIfNecessary(buffer, numberOfPixels);
544 }
545
CanReadFile(const char * FileNameToRead)546 bool PhilipsRECImageIO::CanReadFile(const char *FileNameToRead)
547 {
548 std::string filename(FileNameToRead);
549
550 // we check that the correct extension is given by the user
551 std::string filenameext = GetExtension(filename);
552
553 if ( filenameext != std::string(".PAR")
554 && filenameext != std::string(".REC")
555 && filenameext != std::string(".REC.gz")
556 && filenameext != std::string(".par")
557 && filenameext != std::string(".rec")
558 && filenameext != std::string(".rec.gz") )
559 {
560 return false;
561 }
562
563 const std::string HeaderFileName = GetHeaderFileName(filename);
564
565 // Try to read the par file.
566 struct par_parameter par;
567 // Zero out par_parameter.
568 memset( &par, 0, sizeof( struct par_parameter ) );
569
570 PhilipsPAR::Pointer philipsPAR = PhilipsPAR::New();
571 try
572 {
573 philipsPAR->ReadPAR(HeaderFileName, &par);
574 // Check to see if there were any problems reading
575 // the par file.
576 if ( par.problemreading )
577 {
578 return false;
579 }
580 }
581 catch ( ExceptionObject & )
582 {
583 return false;
584 }
585
586 return true;
587 }
588
ReadImageInformation()589 void PhilipsRECImageIO::ReadImageInformation()
590 {
591 const std::string HeaderFileName = GetHeaderFileName(this->m_FileName);
592 struct par_parameter par;
593
594 // Zero out par_parameter.
595 memset( &par, 0, sizeof( struct par_parameter ) );
596
597 // Read PAR file.
598 PhilipsPAR::Pointer philipsPAR = PhilipsPAR::New();
599 try
600 {
601 philipsPAR->ReadPAR(HeaderFileName, &par);
602 }
603 catch ( itk::ExceptionObject & )
604 {
605 throw;
606 }
607 if ( par.problemreading )
608 {
609 ExceptionObject exception(__FILE__, __LINE__,
610 "Problem reading PAR file",
611 ITK_LOCATION);
612 throw exception;
613 }
614
615 // Get all the diffusion info, rescale, etc.
616 GradientBvalueContainerType::Pointer diffusionBvalueVector =
617 GradientBvalueContainerType::New();
618 GradientDirectionContainerType::Pointer diffusionGradientOrientationVector =
619 GradientDirectionContainerType::New();
620 if ( !philipsPAR->GetDiffusionGradientOrientationAndBValues(HeaderFileName,
621 diffusionGradientOrientationVector, diffusionBvalueVector) )
622 {
623 ExceptionObject exception(__FILE__, __LINE__,
624 "Problem reading diffusion gradients and b values from PAR file",
625 ITK_LOCATION);
626 throw exception;
627 }
628
629 // Get ASL label types.
630 LabelTypesASLContainerType::Pointer labelTypesASLVector =
631 LabelTypesASLContainerType::New();
632 if ( !philipsPAR->GetLabelTypesASL(HeaderFileName, labelTypesASLVector) )
633 {
634 ExceptionObject exception(__FILE__, __LINE__,
635 "Problem reading ASL label types from PAR file",
636 ITK_LOCATION);
637 throw exception;
638 }
639
640 // Get rescale values associated with each scanning sequence.
641 ScanningSequenceImageTypeRescaleValuesContainerType::Pointer
642 scanningSequenceImageTypeRescaleVector =
643 ScanningSequenceImageTypeRescaleValuesContainerType::New();
644 scanningSequenceImageTypeRescaleVector->clear();
645 // Must match number of scanning sequences.
646 scanningSequenceImageTypeRescaleVector->resize(par.num_scanning_sequences);
647 for ( int scanIndex = 0; scanIndex < par.num_scanning_sequences; scanIndex++ )
648 {
649 ImageTypeRescaleValuesContainerType::Pointer imageTypeRescaleValuesVector =
650 ImageTypeRescaleValuesContainerType::New();
651 if ( !philipsPAR->GetRECRescaleValues(HeaderFileName, imageTypeRescaleValuesVector,
652 par.scanning_sequences[scanIndex]) )
653 {
654 ExceptionObject exception(__FILE__, __LINE__,
655 "Problem reading recale values for each scanning sequence from PAR file",
656 ITK_LOCATION);
657 throw exception;
658 }
659 ( *scanningSequenceImageTypeRescaleVector )[scanIndex] =
660 imageTypeRescaleValuesVector;
661 }
662
663 // Setup the slice index matrix.
664 this->m_SliceIndex->clear();
665 this->m_SliceIndex->resize(par.dim[2]);
666 PhilipsPAR::PARSliceIndexImageTypeVector sliceImageTypesIndexes =
667 philipsPAR->GetRECSliceIndexImageTypes(HeaderFileName);
668 PhilipsPAR::PARSliceIndexScanSequenceVector sliceScanSequencesIndexes =
669 philipsPAR->GetRECSliceIndexScanningSequence(HeaderFileName);
670 PhilipsPAR::PARImageTypeScanSequenceVector imageTypesScanSequencesIndexes =
671 philipsPAR->GetImageTypesScanningSequence(HeaderFileName);
672 PhilipsRECImageIOSetupSliceIndex(this->m_SliceIndex, 1, par,
673 imageTypesScanSequencesIndexes, sliceImageTypesIndexes,
674 sliceScanSequencesIndexes);
675
676 // As far as I know all Philips REC files are littleEndian.
677 this->m_ByteOrder = LittleEndian;
678
679 // Set dimensions.
680 unsigned int numberOfDimensions = 4;
681 // In reality PAR/REC files can have more dimensions
682 // but it is very difficult to sort out all of the
683 // possibilities. The reader will sort the images
684 // by block and the different types of images
685 // stored in the blocks may be determined using the
686 // MetaDataDictionary.
687 // NOTE: ImageFileReader checks the number of dimensions to see if they
688 // are larger than the template dimensions. If so, it sets the direction
689 // cosines to the default value. In my view this is a bug, but in order
690 // to get around this problem I need to set the number of dimensions to 3
691 // if the number of image blocks is less than 2.
692 if ( par.image_blocks < 2 )
693 {
694 numberOfDimensions = 3;
695 }
696
697 this->SetNumberOfDimensions(numberOfDimensions);
698
699 // As far as I know, Philips REC files are only
700 // 8-bit or 16-bit signed integers.
701 switch ( par.bit )
702 {
703 case 8:
704 m_ComponentType = CHAR;
705 m_PixelType = SCALAR;
706 break;
707 case 16:
708 m_ComponentType = SHORT;
709 m_PixelType = SCALAR;
710 break;
711 default:
712 std::ostringstream message;
713 message << "Unknown data type. par.bit must be 8 or 16. "
714 << "par.bit is "
715 << par.bit;
716 ExceptionObject exception(__FILE__, __LINE__,
717 message.str(),
718 ITK_LOCATION);
719 throw exception;
720 }
721 //
722 // set up the dimension stuff
723 this->SetDimensions(0, par.dim[0]);
724 this->SetDimensions(1, par.dim[1]);
725 this->SetDimensions(2, par.slice);
726 this->SetSpacing(0, par.vox[0]);
727 this->SetSpacing(1, par.vox[1]);
728 this->SetSpacing(2, par.vox[2]);
729 if ( numberOfDimensions > 3 )
730 {
731 this->SetDimensions(3,par.image_blocks);
732 // Just 1 for the fourth dimension.
733 this->SetSpacing(3,1.0f);
734 }
735
736 //
737 // figure out re-orientation required if not in Coronal
738 this->ComputeStrides();
739
740 //Get Dictionary Information
741 //Important hk fields.
742 MetaDataDictionary & thisDic = this->GetMetaDataDictionary();
743 // Necessary to clear dict if ImageIO object is re-used
744 thisDic.Clear();
745 std::string classname( this->GetNameOfClass() );
746 EncapsulateMetaData< std::string >(thisDic, ITK_InputFilterName, classname);
747
748 EncapsulateMetaData< std::string >( thisDic, ITK_ImageFileBaseName,
749 GetRootName(this->m_FileName) );
750
751 //Important dime fields
752 EncapsulateMetaData< std::string >( thisDic, ITK_VoxelUnits, std::string("mm") );
753 EncapsulateMetaData< short int >(thisDic, ITK_OnDiskBitPerPixel, par.bit);
754 EncapsulateMetaData< int >(thisDic, ITK_NumberOfDimensions, numberOfDimensions);
755
756 switch ( par.bit )
757 {
758 case 8:
759 EncapsulateMetaData< std::string >( thisDic, ITK_OnDiskStorageTypeName,
760 std::string( typeid( char ).name() ) );
761 break;
762 case 16:
763 EncapsulateMetaData< std::string >( thisDic, ITK_OnDiskStorageTypeName,
764 std::string( typeid( short ).name() ) );
765 break;
766 default:
767 break;
768 }
769
770 //Important hist fields
771 EncapsulateMetaData< std::string >( thisDic, ITK_FileNotes,
772 std::string(par.series_type) );
773
774 using AffineMatrix = Matrix< double, 4, 4 >;
775 AffineMatrix spacing;
776 spacing.SetIdentity();
777
778 SpatialOrientation::ValidCoordinateOrientationFlags coord_orient;
779
780 switch ( par.sliceorient )
781 {
782 case PAR_SLICE_ORIENTATION_TRANSVERSAL:
783 // Transverse - the REC data appears to be stored as right-left,
784 // anterior-posterior, and inferior-superior.
785 // Verified using a marker on right side of brain.
786 coord_orient = SpatialOrientation::ITK_COORDINATE_ORIENTATION_RAI;
787 spacing[0][0] = par.vox[0];
788 spacing[1][1] = par.vox[1];
789 spacing[2][2] = par.vox[2];
790 break;
791 case PAR_SLICE_ORIENTATION_SAGITTAL:
792 // Sagittal - the REC data appears to be stored as anterior-posterior,
793 // superior-inferior, and right-left.
794 // Verified using marker on right side of brain.
795 coord_orient = SpatialOrientation::ITK_COORDINATE_ORIENTATION_ASL;
796 spacing[0][0] = par.vox[2];
797 spacing[1][1] = par.vox[0];
798 spacing[2][2] = par.vox[1];
799 break;
800 case PAR_SLICE_ORIENTATION_CORONAL:
801 // Coronal - the REC data appears to be stored as right-left,
802 // superior-inferior, and anterior-posterior.
803 // Verified using marker on right side of brain.
804 // fall thru
805 default:
806 coord_orient = SpatialOrientation::ITK_COORDINATE_ORIENTATION_RSA;
807 spacing[0][0] = par.vox[0];
808 spacing[1][1] = par.vox[2];
809 spacing[2][2] = par.vox[1];
810 }
811
812 using OrientAdapterType = SpatialOrientationAdapter;
813 SpatialOrientationAdapter::DirectionType dir =
814 OrientAdapterType().ToDirectionCosines(coord_orient);
815
816 AffineMatrix direction;
817 direction.SetIdentity();
818 int rows, columns;
819 for(rows=0; rows<3; rows++)
820 {
821 for(columns=0; columns<3; columns++)
822 {
823 direction[columns][rows] = dir[columns][rows];
824 }
825 }
826 //#define DEBUG_ORIENTATION
827 #ifdef DEBUG_ORIENTATION
828 std::cout << "Direction cosines = " << direction << std::endl;
829 std::cout << "Spacing = " << spacing << std::endl;
830 #endif
831 // Create right/left rotation matrix (about x axis).
832 AffineMatrix r1;
833 r1.SetIdentity();
834 r1[1][1] = std::cos(par.angRL*Math::pi/180.0);
835 r1[2][1] = -std::sin(par.angRL*Math::pi/180.0);
836 r1[1][2] = std::sin(par.angRL*Math::pi/180.0);
837 r1[2][2] = std::cos(par.angRL*Math::pi/180.0);
838 // Create anterior/posterior rotation matrix (about y axis).
839 AffineMatrix r2;
840 r2.SetIdentity();
841 r2[0][0] = std::cos(par.angAP*Math::pi/180.0);
842 r2[2][0] = std::sin(par.angAP*Math::pi/180.0);
843 r2[0][2] = -std::sin(par.angAP*Math::pi/180.0);
844 r2[2][2] = std::cos(par.angAP*Math::pi/180.0);
845 // Create foot/head rotation matrix (about z axis).
846 AffineMatrix r3;
847 r3.SetIdentity();
848 r3[0][0] = std::cos(par.angFH*Math::pi/180.0);
849 r3[1][0] = -std::sin(par.angFH*Math::pi/180.0);
850 r3[0][1] = std::sin(par.angFH*Math::pi/180.0);
851 r3[1][1] = std::cos(par.angFH*Math::pi/180.0);
852 // Total rotation matrix.
853 AffineMatrix rtotal = r1*r2*r3;
854 #ifdef DEBUG_ORIENTATION
855 std::cout << "Right-Left rotation = " << r1 << std::endl;
856 std::cout << "Anterior-Posterior rotation = " << r2 << std::endl;
857 std::cout << "Foot-Head rotation = " << r3 << std::endl;
858 std::cout << "Total = " << rtotal << std::endl;
859 #endif
860
861 // Find and set origin
862 AffineMatrix final = rtotal*spacing*direction;
863 #ifdef DEBUG_ORIENTATION
864 std::cout << "Final transformation = " << final << std::endl;
865 #endif
866 using PointVector = Vector< double, 4 >;
867 PointVector center;
868 center[0] = (par.dim[0]-1)/2.0;
869 center[1] = (par.dim[0]-1)/2.0;
870 center[2] = (par.slice-1)/2.0;
871 center[3] = 1;
872 PointVector origin = final*center;
873 origin = -origin;
874 #ifdef DEBUG_ORIENTATION
875 std::cout << "Origin before offset = " << origin << std::endl;
876 #endif
877 PointVector offset;
878 offset[0] = par.offRL;
879 offset[1] = par.offAP;
880 offset[2] = par.offFH;
881 offset[3] = 1;
882 #ifdef DEBUG_ORIENTATION
883 std::cout << "Offset = " << offset << std::endl;
884 #endif
885 origin[0] = origin[0]+offset[0];
886 origin[1] = origin[1]+offset[1];
887 origin[2] = origin[2]+offset[2];
888 #ifdef DEBUG_ORIENTATION
889 std::cout << "Origin after offset = " << origin << std::endl;
890 #endif
891
892 this->SetOrigin(0, origin[0]);
893 this->SetOrigin(1, origin[1]);
894 this->SetOrigin(2, origin[2]);
895
896 // Find true direction cosines after taking rotations into account.
897 direction = rtotal*direction;
898 #ifdef DEBUG_ORIENTATION
899 std::cout << "Final direction cosines after rotation = " << direction << std::endl;
900 #endif
901
902 std::vector<double> dirx(numberOfDimensions,0),
903 diry(numberOfDimensions,0),dirz(numberOfDimensions,0),
904 dirBlock(numberOfDimensions,0);
905 dirBlock[numberOfDimensions-1] = 1;
906 dirx[0] = direction[0][0];
907 dirx[1] = direction[1][0];
908 dirx[2] = direction[2][0];
909 diry[0] = direction[0][1];
910 diry[1] = direction[1][1];
911 diry[2] = direction[2][1];
912 dirz[0] = direction[0][2];
913 dirz[1] = direction[1][2];
914 dirz[2] = direction[2][2];
915
916 this->SetDirection(0,dirx);
917 this->SetDirection(1,diry);
918 this->SetDirection(2,dirz);
919 if ( numberOfDimensions > 3 )
920 {
921 this->SetDirection(3,dirBlock);
922 }
923
924 EncapsulateMetaData< std::string >( thisDic, ITK_PatientID,
925 std::string(par.patient_name) );
926 EncapsulateMetaData< std::string >( thisDic, ITK_ExperimentDate,
927 std::string(par.exam_date) );
928 EncapsulateMetaData< std::string >( thisDic, ITK_ExperimentTime,
929 std::string(par.exam_time) );
930
931 // Encapsulate remaining PAR parameters
932 EncapsulateMetaData< int >(thisDic, PAR_SliceOrientation, par.sliceorient);
933 switch ( par.ResToolsVersion )
934 {
935 case RESEARCH_IMAGE_EXPORT_TOOL_V3:
936 EncapsulateMetaData< std::string >( thisDic, PAR_Version, std::string("V3") );
937 break;
938 case RESEARCH_IMAGE_EXPORT_TOOL_V4:
939 EncapsulateMetaData< std::string >( thisDic, PAR_Version, std::string("V4") );
940 break;
941 case RESEARCH_IMAGE_EXPORT_TOOL_V4_1:
942 EncapsulateMetaData< std::string >( thisDic, PAR_Version,
943 std::string("V4.1") );
944 break;
945 case RESEARCH_IMAGE_EXPORT_TOOL_V4_2:
946 EncapsulateMetaData< std::string >( thisDic, PAR_Version,
947 std::string("V4.2") );
948 break;
949 }
950
951 EncapsulateMetaData< std::string >( thisDic, PAR_ExaminationName,
952 std::string(par.exam_name) );
953 EncapsulateMetaData< std::string >( thisDic, PAR_ProtocolName,
954 std::string(par.protocol_name) );
955 EncapsulateMetaData< std::string >( thisDic, PAR_SeriesType,
956 std::string(par.series_type) );
957 EncapsulateMetaData< int >(thisDic, PAR_AcquisitionNr, par.scno);
958 EncapsulateMetaData< int >(thisDic, PAR_ReconstructionNr, par.recno);
959 EncapsulateMetaData< int >(thisDic, PAR_ScanDuration, par.scan_duration);
960 EncapsulateMetaData< int >(thisDic, PAR_MaxNumberOfCardiacPhases,
961 par.cardiac_phases);
962 TriggerTimesContainerType::Pointer triggerTimes =
963 TriggerTimesContainerType::New();
964 triggerTimes->resize(par.cardiac_phases);
965
966 for ( unsigned int ttime_index = 0; ttime_index < (unsigned int)par.cardiac_phases;
967 ttime_index++ )
968 {
969 triggerTimes->SetElement(ttime_index, (double)par.trigger_times[ttime_index]);
970 }
971
972 EncapsulateMetaData< TriggerTimesContainerType::Pointer >(thisDic,
973 PAR_TriggerTimes, triggerTimes);
974 EncapsulateMetaData< int >(thisDic, PAR_MaxNumberOfEchoes, par.echoes);
975 EchoTimesContainerType::Pointer echoTimes = EchoTimesContainerType::New();
976 echoTimes->resize(par.echoes);
977
978 for ( unsigned int echo_index = 0; echo_index < (unsigned int)par.echoes;
979 echo_index++ )
980 {
981 echoTimes->SetElement(echo_index, (double)par.echo_times[echo_index]);
982 }
983
984 EncapsulateMetaData< EchoTimesContainerType::Pointer >(thisDic, PAR_EchoTimes,
985 echoTimes);
986 EncapsulateMetaData< int >(thisDic, PAR_MaxNumberOfDynamics, par.dyn);
987 EncapsulateMetaData< int >(thisDic, PAR_MaxNumberOfMixes, par.mixes);
988 EncapsulateMetaData< std::string >( thisDic, PAR_PatientPosition,
989 std::string(par.patient_position) );
990 EncapsulateMetaData< std::string >( thisDic, PAR_PreparationDirection,
991 std::string(par.prep_direction) );
992 EncapsulateMetaData< std::string >( thisDic, PAR_Technique,
993 std::string(par.technique) );
994 EncapsulateMetaData< std::string >( thisDic, PAR_ScanMode,
995 std::string(par.scan_mode) );
996 EncapsulateMetaData< int >(thisDic, PAR_NumberOfAverages, par.num_averages);
997 EncapsulateMetaData< ScanResolutionType >( thisDic, PAR_ScanResolution,
998 ScanResolutionType(par.scan_resolution) );
999 RepetitionTimesContainerType::Pointer repTimes =
1000 RepetitionTimesContainerType::New();
1001 repTimes->resize(par.mixes); // This has only been verified using a
1002 // Look-Locker sequence and may not be valid.
1003
1004 for ( unsigned int rep_index = 0; rep_index < (unsigned int)par.mixes; rep_index++ )
1005 {
1006 repTimes->SetElement(rep_index, (double)par.repetition_time[rep_index]);
1007 }
1008
1009 EncapsulateMetaData< RepetitionTimesContainerType::Pointer >(thisDic,
1010 PAR_RepetitionTimes, repTimes);
1011 EncapsulateMetaData< int >(thisDic, PAR_ScanPercentage, par.scan_percent);
1012 EncapsulateMetaData< FOVType >( thisDic, PAR_FOV, FOVType(par.fov) );
1013 EncapsulateMetaData< float >(thisDic, PAR_WaterFatShiftPixels,
1014 par.water_fat_shift);
1015 AngulationMidSliceType tempAngulation;
1016 tempAngulation[0] = (double)par.angAP;
1017 tempAngulation[1] = (double)par.angFH;
1018 tempAngulation[2] = (double)par.angRL;
1019 EncapsulateMetaData< AngulationMidSliceType >(thisDic, PAR_AngulationMidSlice,
1020 tempAngulation);
1021 OffCentreMidSliceType tempOffcentre;
1022 tempOffcentre[0] = (double)par.offAP;
1023 tempOffcentre[1] = (double)par.offFH;
1024 tempOffcentre[2] = (double)par.offRL;
1025 EncapsulateMetaData< OffCentreMidSliceType >(thisDic, PAR_OffCentreMidSlice,
1026 tempOffcentre);
1027 EncapsulateMetaData< int >(thisDic, PAR_FlowCompensation, par.flow_comp);
1028 EncapsulateMetaData< int >(thisDic, PAR_Presaturation, par.presaturation);
1029 EncapsulateMetaData< int >(thisDic, PAR_CardiacFrequency, par.cardiac_freq);
1030 EncapsulateMetaData< int >(thisDic, PAR_MinRRInterval, par.min_rr_int);
1031 EncapsulateMetaData< int >(thisDic, PAR_MaxRRInterval, par.max_rr_int);
1032 EncapsulateMetaData< PhaseEncodingVelocityType >( thisDic,
1033 PAR_PhaseEncodingVelocity,
1034 PhaseEncodingVelocityType(par.phase_encode_vel) );
1035 EncapsulateMetaData< int >(thisDic, PAR_MTC, par.mtc);
1036 EncapsulateMetaData< int >(thisDic, PAR_SPIR, par.spir);
1037 EncapsulateMetaData< int >(thisDic, PAR_EPIFactor, par.epi);
1038 EncapsulateMetaData< int >(thisDic, PAR_TurboFactor, par.turbo);
1039 EncapsulateMetaData< int >(thisDic, PAR_DynamicScan, par.dynamic_scan);
1040 EncapsulateMetaData< int >(thisDic, PAR_Diffusion, par.diffusion);
1041 EncapsulateMetaData< float >(thisDic, PAR_DiffusionEchoTime, par.diff_echo);
1042 EncapsulateMetaData< int >(thisDic, PAR_MaxNumberOfDiffusionValues,
1043 par.max_num_diff_vals);
1044 EncapsulateMetaData< GradientBvalueContainerType::Pointer >(thisDic,
1045 PAR_GradientBValues, diffusionBvalueVector);
1046 EncapsulateMetaData< int >(thisDic, PAR_MaxNumberOfGradientOrients,
1047 par.max_num_grad_orient);
1048 EncapsulateMetaData< GradientDirectionContainerType::Pointer >(thisDic,
1049 PAR_GradientDirectionValues,
1050 diffusionGradientOrientationVector);
1051 EncapsulateMetaData< float >(thisDic, PAR_InversionDelay, par.inversion_delay);
1052 EncapsulateMetaData< int >(thisDic, PAR_NumberOfImageTypes, par.num_image_types);
1053 EncapsulateMetaData< ImageTypesType >( thisDic, PAR_ImageTypes,
1054 ImageTypesType(par.image_types) );
1055 EncapsulateMetaData< int >(thisDic, PAR_NumberOfScanningSequences,
1056 par.num_scanning_sequences);
1057 EncapsulateMetaData< ScanningSequencesType >( thisDic, PAR_ScanningSequences,
1058 ScanningSequencesType(par.scanning_sequences) );
1059 using ScanningSequenceImageTypeRescaleValuesContainerTypePtr = ScanningSequenceImageTypeRescaleValuesContainerType::Pointer;
1060 EncapsulateMetaData< ScanningSequenceImageTypeRescaleValuesContainerTypePtr >(
1061 thisDic, PAR_ScanningSequenceImageTypeRescaleValues,
1062 scanningSequenceImageTypeRescaleVector);
1063 EncapsulateMetaData< int >(thisDic, PAR_NumberOfASLLabelTypes,
1064 par.num_label_types);
1065 EncapsulateMetaData< LabelTypesASLContainerType::Pointer >(thisDic,
1066 PAR_ASLLabelTypes, labelTypesASLVector);
1067 }
1068 } // end namespace itk
1069