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 #include "itksys/SystemTools.hxx"
20 #include "itkIPLCommonImageIO.h"
21 #include "itkByteSwapper.h"
22 #include "itkSpatialOrientationAdapter.h"
23 #include "itkDirectory.h"
24 #include "itkMetaDataObject.h"
25 #include <iostream>
26 #include <fstream>
27 #include <cstring>
28 #include <climits>
29 #include <cstdlib>
30 #include <cstdio>
31 #include <ctime>
32 #include <cassert>
33 #include <vector>
34 
35 //From uiig library "The University of Iowa Imaging Group-UIIG"
36 
37 namespace itk
38 {
39 // Default constructor
IPLCommonImageIO()40 IPLCommonImageIO::IPLCommonImageIO()
41 {
42   m_SystemByteOrder =
43     ByteSwapper< int >::SystemIsBigEndian() ?
44     ImageIOBase::BigEndian :
45     ImageIOBase::LittleEndian;
46   m_ImageHeader = nullptr;
47   m_FilenameList = new IPLFileNameList;
48   this->SetComponentType(ImageIOBase::SHORT);
49 }
50 
~IPLCommonImageIO()51 IPLCommonImageIO::~IPLCommonImageIO()
52 {
53   delete m_ImageHeader;
54   delete m_FilenameList;
55 }
56 
PrintSelf(std::ostream & os,Indent indent) const57 void IPLCommonImageIO::PrintSelf(std::ostream & os, Indent indent) const
58 {
59   Superclass::PrintSelf(os, indent);
60 }
61 
CanWriteFile(const char *)62 bool IPLCommonImageIO::CanWriteFile(const char *)
63 {
64   return false;
65 }
66 
GetComponentSize() const67 unsigned int IPLCommonImageIO::GetComponentSize() const
68 {
69   return sizeof( short int );
70 }
71 
Read(void * buffer)72 void IPLCommonImageIO::Read(void *buffer)
73 {
74   auto * img_buffer = (short int *)buffer;
75   auto it = m_FilenameList->begin();
76   auto itend = m_FilenameList->end();
77 
78   for (; it != itend; it++ )
79     {
80     std::string   curfilename = ( *it )->GetImageFileName();
81     std::ifstream f;
82     this->OpenFileForReading( f, curfilename );
83 
84     f.seekg ( ( *it )->GetSliceOffset(), std::ios::beg );
85     if ( !this->ReadBufferAsBinary( f, img_buffer, m_FilenameList->GetXDim() * m_FilenameList->GetYDim()
86                                     * sizeof( short int ) ) )
87       {
88       f.close();
89       RAISE_EXCEPTION();
90       }
91     f.close();
92     // ByteSwapper::SwapRangeFromSystemToBigEndian is set up based on
93     // the FILE endian-ness, not as the name would lead you to believe.
94     // So, on LittleEndian systems, SwapFromSystemToBigEndian will swap.
95     // On BigEndian systems, SwapFromSystemToBigEndian will do nothing.
96     itk::ByteSwapper< short int >::SwapRangeFromSystemToBigEndian( img_buffer,
97                                                                    m_FilenameList->GetXDim() * m_FilenameList->GetYDim() );
98     img_buffer += m_FilenameList->GetXDim() * m_FilenameList->GetYDim();
99     }
100 }
101 
ReadHeader(const char *)102 GEImageHeader * IPLCommonImageIO::ReadHeader(const char *)
103 {
104   //
105   // must be redefined in a child class
106   //
107   return nullptr;
108 }
109 
CanReadFile(const char *)110 bool IPLCommonImageIO::CanReadFile(const char *)
111 {
112   //
113   // must be redefined in child class or you'll never read anything ;-)
114   //
115   return false;
116 }
117 
ReadImageInformation()118 void IPLCommonImageIO::ReadImageInformation()
119 {
120   std::string FileNameToRead = this->GetFileName();
121   //
122   // GE images are stored in separate files per slice.
123   //char imagePath[IOCommon::ITK_MAXPATHLEN+1];
124   //TODO -- use std::string instead of C strings
125   char        imageMask[IOCommon::ITK_MAXPATHLEN + 1];
126   char        imagePath[IOCommon::ITK_MAXPATHLEN + 1];
127   std::string _imagePath =
128     itksys::SystemTools::CollapseFullPath( FileNameToRead.c_str() );
129 
130   FileNameToRead = _imagePath;
131 
132   this->m_ImageHeader = this->ReadHeader( FileNameToRead.c_str() );
133   //
134   // if anything fails in the header read, just let
135   // exceptions propagate up.
136 
137   bool isCT = false;
138   std::string modality = m_ImageHeader->modality;
139   if( modality == "CT" )
140     {
141     isCT = true;
142     }
143 
144   AddElementToList(m_ImageHeader->filename,
145                    m_ImageHeader->sliceLocation,
146                    m_ImageHeader->offset,
147                    m_ImageHeader->imageXsize,
148                    m_ImageHeader->imageYsize,
149                    m_ImageHeader->imageXres,
150                    m_ImageHeader->imageYres,
151                    m_ImageHeader->seriesNumber,
152                    (isCT)?m_ImageHeader->examNumber:
153                    m_ImageHeader->echoNumber);  // If CT use examNumber, otherwise use echoNumber.
154 
155   // Add header info to metadictionary
156 
157   itk::MetaDataDictionary & thisDic = this->GetMetaDataDictionary();
158   std::string               classname( this->GetNameOfClass() );
159   itk::EncapsulateMetaData< std::string >(thisDic, ITK_InputFilterName, classname);
160   itk::EncapsulateMetaData< std::string >( thisDic, ITK_OnDiskStorageTypeName, std::string("SHORT") );
161   itk::EncapsulateMetaData< short int >(thisDic, ITK_OnDiskBitPerPixel, (short int)16);
162 
163   //
164   // has to be set before setting dir cosines,
165   // otherwise the vector doesn't get allocated
166   this->SetNumberOfDimensions(3);
167 
168   itk::EncapsulateMetaData< std::string >( thisDic, ITK_PatientID, std::string(m_ImageHeader->patientId) );
169   itk::EncapsulateMetaData< std::string >( thisDic, ITK_ExperimentDate, std::string(m_ImageHeader->date) );
170 
171   if ( _imagePath.empty() )
172     {
173     RAISE_EXCEPTION();
174     }
175   strncpy( imagePath, _imagePath.c_str(), sizeof( imagePath ) );
176   imagePath[IOCommon::ITK_MAXPATHLEN] = '\0';
177   strncpy( imageMask, imagePath, sizeof( imageMask ) );
178   imageMask[IOCommon::ITK_MAXPATHLEN] = '\0';
179 
180   char *lastslash = strrchr(imagePath, '/');
181   if ( lastslash == nullptr )
182     {
183     strcpy(imagePath, ".");
184     }
185   else
186     {
187     *lastslash = '\0';
188     }
189   itk::Directory::Pointer Dir = itk::Directory::New();
190   if ( Dir->Load(imagePath) == 0 )
191     {
192     RAISE_EXCEPTION();
193     }
194   std::vector< std::string >::size_type i;
195   std::vector< std::string >::size_type numfiles;
196 
197   GEImageHeader *curImageHeader;
198 
199   for ( i = 0, numfiles = Dir->GetNumberOfFiles(); i < numfiles; i++ )
200     {
201     const char *curFname =  Dir->GetFile(static_cast<unsigned int>( i ) );
202 
203     if ( curFname == nullptr )
204       {
205       break;
206       }
207      const std::string fullPath=std::string(imagePath) + "/" + curFname;
208      if ( FileNameToRead == fullPath ) //strcmp(curFname,FileNameToRead) ==
209                                            // 0)
210       {
211       continue;
212       }
213     try
214       {
215       curImageHeader = this->ReadHeader(fullPath.c_str());
216       }
217     catch ( itk::ExceptionObject & )
218       {
219       // ReadGE4XHeader throws an exception on any error.
220       // So if, for example we run into a subdirectory, it would
221       // throw an exception, and we'd just want to skip it.
222       continue;
223       }
224     if( (((isCT)?curImageHeader->examNumber:curImageHeader->echoNumber)
225         == m_FilenameList->GetKey2()) &&
226         (curImageHeader->seriesNumber == m_FilenameList->GetKey1()))
227       {
228       AddElementToList(curImageHeader->filename,
229                        curImageHeader->sliceLocation,
230                        curImageHeader->offset,
231                        curImageHeader->imageXsize,
232                        curImageHeader->imageYsize,
233                        curImageHeader->imageXres,
234                        curImageHeader->imageYres,
235                        curImageHeader->seriesNumber,
236                        (isCT)?curImageHeader->examNumber:
237                        curImageHeader->echoNumber);  // If CT use examNumber, otherwise use echoNumber.
238       }
239     delete curImageHeader;
240     }
241 
242   //sort image list
243   m_FilenameList->sortImageList();
244 
245   //
246   //
247   // set the image properties
248   this->SetDimensions(0, m_ImageHeader->imageXsize);
249   this->SetDimensions(1, m_ImageHeader->imageYsize);
250   this->SetDimensions( 2, static_cast<unsigned int>( m_FilenameList->NumFiles() ) );
251   this->SetSpacing(0, m_ImageHeader->imageXres);
252   this->SetSpacing(1, m_ImageHeader->imageYres);
253   this->SetSpacing(2, m_ImageHeader->sliceThickness + m_ImageHeader->sliceGap);
254 
255   //
256   // set direction cosines
257   using OrientAdapterType = SpatialOrientationAdapter;
258   SpatialOrientationAdapter::DirectionType dir =
259     OrientAdapterType().ToDirectionCosines(m_ImageHeader->coordinateOrientation);
260   std::vector< double > dirx(3, 0), diry(3, 0), dirz(3, 0);
261   dirx[0] = dir[0][0];
262   dirx[1] = dir[1][0];
263   dirx[2] = dir[2][0];
264   diry[0] = dir[0][1];
265   diry[1] = dir[1][1];
266   diry[2] = dir[2][1];
267   dirz[0] = dir[0][2];
268   dirz[1] = dir[1][2];
269   dirz[2] = dir[2][2];
270 
271   this->SetDirection(0, dirx);
272   this->SetDirection(1, diry);
273   this->SetDirection(2, dirz);
274 
275   this->ModifyImageInformation();
276 }
277 
SortImageListByNameAscend()278 void IPLCommonImageIO::SortImageListByNameAscend()
279 {
280   m_FilenameList->SetSortOrder(IPLFileNameList::SortByNameAscend);
281 }
282 
SortImageListByNameDescend()283 void IPLCommonImageIO::SortImageListByNameDescend()
284 {
285   m_FilenameList->SetSortOrder(IPLFileNameList::SortByNameDescend);
286 }
287 
288 /**
289  *
290  */
291 void
292 IPLCommonImageIO
WriteImageInformation()293 ::WriteImageInformation()
294 {
295   RAISE_EXCEPTION();
296 }
297 
298 /**
299  *
300  */
301 void
302 IPLCommonImageIO
Write(const void *)303 ::Write(const void *)
304 {
305   RAISE_EXCEPTION();
306 }
307 
308 int
309 IPLCommonImageIO
GetStringAt(std::ifstream & f,std::streamoff Offset,char * buf,size_t amount,bool throw_exception)310 ::GetStringAt(std::ifstream & f,
311               std::streamoff Offset,
312               char *buf,
313               size_t amount, bool throw_exception)
314 {
315   f.seekg(Offset, std::ios::beg);
316   if ( f.fail() )
317     {
318     if ( throw_exception )
319       {
320       RAISE_EXCEPTION();
321       }
322     else
323       {
324       return -1;
325       }
326     }
327   if ( !this->ReadBufferAsBinary(f, (void *)buf, amount) )
328     {
329     if ( throw_exception )
330       {
331       RAISE_EXCEPTION();
332       }
333     else
334       {
335       return -1;
336       }
337     }
338   return 0;
339 }
340 
341 int IPLCommonImageIO
GetIntAt(std::ifstream & f,std::streamoff Offset,int * ip,bool throw_exception)342 ::GetIntAt(std::ifstream & f, std::streamoff Offset, int *ip,
343            bool throw_exception)
344 {
345   int tmp;
346 
347   if ( this->GetStringAt(f, Offset, (char *)&tmp, sizeof( int ),
348                          throw_exception) == 0 )
349     {
350     *ip = this->hdr2Int( (char *)&tmp );
351     }
352   else
353     {
354     *ip = 0;
355     }
356   return 0;
357 }
358 
359 int IPLCommonImageIO
GetShortAt(std::ifstream & f,std::streamoff Offset,short * ip,bool throw_exception)360 ::GetShortAt(std::ifstream & f, std::streamoff Offset, short *ip,
361              bool throw_exception)
362 {
363   short tmp;
364 
365   if ( this->GetStringAt(f, Offset, (char *)&tmp, sizeof( short ),
366                          throw_exception) == 0 )
367     {
368     *ip = this->hdr2Short( (char *)&tmp );
369     }
370   else
371     {
372     *ip = 0;
373     }
374   return 0;
375 }
376 
377 int IPLCommonImageIO
GetFloatAt(std::ifstream & f,std::streamoff Offset,float * ip,bool throw_exception)378 ::GetFloatAt(std::ifstream & f, std::streamoff Offset, float *ip,
379              bool throw_exception)
380 {
381   float tmp;
382 
383   if ( this->GetStringAt(f, Offset, (char *)&tmp, sizeof( float ),
384                          throw_exception) == 0 )
385     {
386     *ip = this->hdr2Float( (char *)&tmp );
387     }
388   else
389     {
390     *ip = 0.0f;
391     }
392   return 0;
393 }
394 
395 int IPLCommonImageIO
GetDoubleAt(std::ifstream & f,std::streamoff Offset,double * ip,bool throw_exception)396 ::GetDoubleAt(std::ifstream & f, std::streamoff Offset, double *ip,
397               bool throw_exception)
398 {
399   double tmp;
400 
401   if ( this->GetStringAt(f, Offset, (char *)&tmp, sizeof( double ),
402                          throw_exception) == 0 )
403     {
404     *ip = this->hdr2Double( (char *)&tmp );
405     }
406   else
407     {
408     *ip = 0.0;
409     }
410   return 0;
411 }
412 
hdr2Short(char * hdr)413 short IPLCommonImageIO::hdr2Short(char *hdr)
414 {
415   short shortValue;
416 
417   memcpy ( &shortValue, hdr, sizeof( short ) );
418   ByteSwapper< short int >::SwapFromSystemToBigEndian (&shortValue);
419   return ( shortValue );
420 }
421 
422 int IPLCommonImageIO
hdr2Int(char * hdr)423 ::hdr2Int(char *hdr)
424 {
425   int intValue;
426 
427   memcpy ( &intValue, hdr, sizeof( int ) );
428   ByteSwapper< int >::SwapFromSystemToBigEndian (&intValue);
429   return ( intValue );
430 }
431 
432 float IPLCommonImageIO
hdr2Float(char * hdr)433 ::hdr2Float(char *hdr)
434 {
435   float floatValue;
436 
437   memcpy (&floatValue, hdr, 4);
438   ByteSwapper< float >::SwapFromSystemToBigEndian (&floatValue);
439 
440   return ( floatValue );
441 }
442 
443 double IPLCommonImageIO
hdr2Double(char * hdr)444 ::hdr2Double(char *hdr)
445 {
446   double doubleValue;
447 
448   memcpy ( &doubleValue, hdr, sizeof( double ) );
449   ByteSwapper< double >::SwapFromSystemToBigEndian (&doubleValue);
450 
451   return ( doubleValue );
452 }
453 
454 int IPLCommonImageIO
AddElementToList(char const * const filename,const float sliceLocation,const int offset,const int XDim,const int YDim,const float XRes,const float YRes,const int Key1,const int Key2)455 ::AddElementToList(char const *const filename,
456                    const float sliceLocation,
457                    const int offset,
458                    const int XDim,
459                    const int YDim,
460                    const float XRes,
461                    const float YRes,
462                    const int Key1,
463                    const int Key2)
464 {
465   if ( m_FilenameList->NumFiles() == 0 )
466     {
467     m_FilenameList->SetXDim(XDim);
468     m_FilenameList->SetYDim(YDim);
469     m_FilenameList->SetXRes(XRes);
470     m_FilenameList->SetYRes(YRes);
471     m_FilenameList->SetKey1(Key1);
472     m_FilenameList->SetKey2(Key2);
473     }
474   else if ( XDim != m_FilenameList->GetXDim() || YDim != m_FilenameList->GetYDim() )
475     {
476     return 0;
477     }
478   else if( itk::Math::NotAlmostEquals( XRes, m_FilenameList->GetXRes() ) || itk::Math::NotAlmostEquals( YRes, m_FilenameList->GetYRes() )  )
479     {
480     return 0;
481     }
482   else if ( m_FilenameList->GetKey1() != Key1 ||  m_FilenameList->GetKey2() != Key2 )
483     {
484     return 1;  //It is OK for keys to not match,  Just don't add.
485     }
486   m_FilenameList->AddElementToList(filename, sliceLocation,
487                                    offset, XDim, YDim, XRes, YRes, 0, Key1, Key2);
488   return 1;
489 }
490 
491 void IPLCommonImageIO
sortImageListAscend()492 ::sortImageListAscend()
493 {
494   m_FilenameList->sortImageListAscend();
495 }
496 
497 void IPLCommonImageIO
sortImageListDescend()498 ::sortImageListDescend()
499 {
500   m_FilenameList->sortImageListDescend();
501 }
502 
503 int IPLCommonImageIO
statTimeToAscii(void * clock,char * timeString,int len)504 ::statTimeToAscii(void *clock, char *timeString,int len)
505 {
506 
507   auto tclock = (time_t)*( (int *)clock );
508   const char * const asciiTime = ctime (&tclock);
509 
510   strncpy (timeString, asciiTime, len);
511   timeString[len-1] = '\0';
512   char *newline;
513   if((newline = strrchr(timeString,'\n')) != nullptr ||
514      (newline = strrchr(timeString,'\r')))
515     {
516     *newline = '\0';
517     }
518   return 1;
519 }
520 } // end namespace itk
521