1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkMNITagPointReader.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 /*=========================================================================
16 
17 Copyright (c) 2006 Atamai, Inc.
18 
19 Use, modification and redistribution of the software, in source or
20 binary forms, are permitted provided that the following terms and
21 conditions are met:
22 
23 1) Redistribution of the source code, in verbatim or modified
24    form, must retain the above copyright notice, this license,
25    the following disclaimer, and any notices that refer to this
26    license and/or the following disclaimer.
27 
28 2) Redistribution in binary form must include the above copyright
29    notice, a copy of this license and the following disclaimer
30    in the documentation or with other materials provided with the
31    distribution.
32 
33 3) Modified copies of the source code must be clearly marked as such,
34    and must not be misrepresented as verbatim copies of the source code.
35 
36 THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS"
37 WITHOUT EXPRESSED OR IMPLIED WARRANTY INCLUDING, BUT NOT LIMITED TO,
38 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
39 PURPOSE.  IN NO EVENT SHALL ANY COPYRIGHT HOLDER OR OTHER PARTY WHO MAY
40 MODIFY AND/OR REDISTRIBUTE THE SOFTWARE UNDER THE TERMS OF THIS LICENSE
41 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES
42 (INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA OR DATA BECOMING INACCURATE
43 OR LOSS OF PROFIT OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF
44 THE USE OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF THE
45 POSSIBILITY OF SUCH DAMAGES.
46 
47 =========================================================================*/
48 
49 #include "vtkMNITagPointReader.h"
50 
51 #include "vtkObjectFactory.h"
52 
53 #include "vtkInformation.h"
54 #include "vtkInformationVector.h"
55 #include "vtkStreamingDemandDrivenPipeline.h"
56 
57 #include "vtkCellArray.h"
58 #include "vtkDoubleArray.h"
59 #include "vtkIntArray.h"
60 #include "vtkPointData.h"
61 #include "vtkPoints.h"
62 #include "vtkPolyData.h"
63 #include "vtkStringArray.h"
64 
65 #include <cctype>
66 
67 #include <string>
68 #include <vector>
69 #include <vtksys/FStream.hxx>
70 #include <vtksys/SystemTools.hxx>
71 
72 //------------------------------------------------------------------------------
73 vtkStandardNewMacro(vtkMNITagPointReader);
74 
75 //------------------------------------------------------------------------------
vtkMNITagPointReader()76 vtkMNITagPointReader::vtkMNITagPointReader()
77 {
78   this->FileName = nullptr;
79   this->NumberOfVolumes = 1;
80   this->LineNumber = 0;
81   this->Comments = nullptr;
82 
83   this->SetNumberOfInputPorts(0);
84   this->SetNumberOfOutputPorts(2);
85 }
86 
87 //------------------------------------------------------------------------------
~vtkMNITagPointReader()88 vtkMNITagPointReader::~vtkMNITagPointReader()
89 {
90   delete[] this->FileName;
91   delete[] this->Comments;
92 }
93 
94 //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)95 void vtkMNITagPointReader::PrintSelf(ostream& os, vtkIndent indent)
96 {
97   this->Superclass::PrintSelf(os, indent);
98 
99   os << indent << "FileName: " << (this->FileName ? this->FileName : "none") << "\n";
100   os << indent << "NumberOfVolumes: " << this->NumberOfVolumes << "\n";
101   os << indent << "Comments: " << (this->Comments ? this->Comments : "none") << "\n";
102 }
103 
104 //------------------------------------------------------------------------------
CanReadFile(const char * fname)105 int vtkMNITagPointReader::CanReadFile(const char* fname)
106 {
107   // First make sure the file exists.  This prevents an empty file
108   // from being created on older compilers.
109   vtksys::SystemTools::Stat_t fs;
110   if (vtksys::SystemTools::Stat(fname, &fs) != 0)
111   {
112     return 0;
113   }
114 
115   // Try to read the first line of the file.
116   int status = 0;
117 
118   vtksys::ifstream infile(fname);
119 
120   if (infile.good())
121   {
122     status = 1;
123     char linetext[256];
124     infile.getline(linetext, 256);
125     if (strncmp(linetext, "MNI Tag Point File", 18) != 0)
126     {
127       status = 0;
128     }
129 
130     infile.close();
131   }
132 
133   return status;
134 }
135 
136 //------------------------------------------------------------------------------
137 // Internal function to read in a line up to 256 characters and then
138 // skip to the next line in the file.
ReadLine(istream & infile,std::string & linetext,std::string::iterator & pos)139 int vtkMNITagPointReader::ReadLine(
140   istream& infile, std::string& linetext, std::string::iterator& pos)
141 {
142   this->LineNumber++;
143 
144   std::getline(infile, linetext);
145   pos = linetext.begin();
146 
147   if (infile.fail())
148   {
149     if (infile.eof())
150     {
151       return 0;
152     }
153   }
154 
155   return 1;
156 }
157 
158 //------------------------------------------------------------------------------
159 // Skip all blank lines or comment lines and return the first useful line
ReadLineAfterComments(istream & infile,std::string & linetext,std::string::iterator & pos)160 int vtkMNITagPointReader::ReadLineAfterComments(
161   istream& infile, std::string& linetext, std::string::iterator& pos)
162 {
163   // Skip over any comment lines or blank lines.
164   // Comment lines start with '%'
165   std::string comments;
166   do
167   {
168     this->ReadLine(infile, linetext, pos);
169     while (pos != linetext.end() && isspace(*pos))
170     {
171       ++pos;
172     }
173     if (linetext.length() != 0 && linetext[0] == '%')
174     {
175       if (comments.length() > 0)
176       {
177         comments.push_back('\n');
178       }
179       if (linetext.length())
180       {
181         comments.append(linetext);
182       }
183     }
184     else if (linetext.length() != 0 && pos != linetext.end())
185     {
186       delete[] this->Comments;
187       this->Comments = new char[comments.length() + 1];
188       strncpy(this->Comments, comments.c_str(), comments.length());
189       this->Comments[comments.length()] = '\0';
190 
191       return 1;
192     }
193   } while (infile.good());
194 
195   return 0;
196 }
197 
198 //------------------------------------------------------------------------------
199 // Skip all whitespace, reading additional lines if necessary if nl != 0
SkipWhitespace(istream & infile,std::string & linetext,std::string::iterator & pos,int nl)200 int vtkMNITagPointReader::SkipWhitespace(
201   istream& infile, std::string& linetext, std::string::iterator& pos, int nl)
202 {
203   while (infile.good())
204   {
205     // Skip leading whitespace
206     while (pos != linetext.end() && isspace(*pos))
207     {
208       ++pos;
209     }
210 
211     if (pos != linetext.end())
212     {
213       return 1;
214     }
215 
216     if (nl == 0)
217     {
218       break;
219     }
220 
221     this->ReadLine(infile, linetext, pos);
222   }
223 
224   return 0;
225 }
226 
227 //------------------------------------------------------------------------------
228 // Read the left hand side of a statement, including the equals sign
229 // and any whitespace following the equals.
ParseLeftHandSide(istream & infile,std::string & linetext,std::string::iterator & pos,std::string & identifier)230 int vtkMNITagPointReader::ParseLeftHandSide(
231   istream& infile, std::string& linetext, std::string::iterator& pos, std::string& identifier)
232 {
233   identifier.clear();
234 
235   // Read alphanumeric plus underscore
236   if (pos != linetext.end() && !isdigit(*pos))
237   {
238     while (pos != linetext.end() && (isalnum(*pos) || *pos == '_'))
239     {
240       identifier.push_back(*pos);
241       ++pos;
242     }
243   }
244 
245   // Check for equals
246   this->SkipWhitespace(infile, linetext, pos, 1);
247   if (pos == linetext.end() || *pos != '=')
248   {
249     return 0;
250   }
251 
252   // Eat the equals
253   ++pos;
254 
255   // Skip ahead to the value part of the statement
256   this->SkipWhitespace(infile, linetext, pos, 1);
257 
258   return 1;
259 }
260 
261 //------------------------------------------------------------------------------
262 // Read a string value.  The terminating semicolon will be read, but
263 // won't be included in the output string.  Neither will any
264 // whitespace occurring before the semicolon. The string may not be
265 // split across multiple lines.
ParseStringValue(istream & infile,std::string & linetext,std::string::iterator & pos,std::string & data)266 int vtkMNITagPointReader::ParseStringValue(
267   istream& infile, std::string& linetext, std::string::iterator& pos, std::string& data)
268 {
269   this->SkipWhitespace(infile, linetext, pos, 0);
270 
271   if (pos != linetext.end() && *pos == '\"')
272   {
273     // eat the opening quote
274     ++pos;
275 
276     // read the string
277     while (pos != linetext.end() && *pos != '\"')
278     {
279       char c = *pos;
280       ++pos;
281       if (c == '\\')
282       {
283         if (pos != linetext.end())
284         {
285           c = '\0';
286           static char ctrltable[] = { '\a', 'a', '\b', 'b', '\f', 'f', '\n', 'n', '\r', 'r', '\t',
287             't', '\v', 'v', '\\', '\\', '\"', '\"', '\0', '\0' };
288 
289           if (*pos >= 0 && *pos <= 9)
290           {
291             for (int j = 0; j < 3 && pos != linetext.end() && *pos >= 0 && *pos <= 9; ++j, ++pos)
292             {
293               c = ((c << 3) | (*pos - '0'));
294             }
295           }
296           else if (*pos == 'x')
297           {
298             ++pos;
299             for (int j = 0; j < 2 && pos != linetext.end() && isalnum(*pos); ++j, ++pos)
300             {
301               char x = tolower(*pos);
302               if (x >= '0' && x <= '9')
303               {
304                 c = ((c << 4) | (x - '0'));
305               }
306               else if (x >= 'a' && x <= 'f')
307               {
308                 c = ((c << 4) | (x - 'a' + 10));
309               }
310             }
311           }
312           else
313           {
314             for (int ci = 0; ctrltable[ci] != '\0'; ci += 2)
315             {
316               if (*pos == ctrltable[ci + 1])
317               {
318                 c = ctrltable[ci];
319                 break;
320               }
321             }
322             if (c == '\0')
323             {
324               c = *pos;
325             }
326             ++pos;
327           }
328         }
329       }
330 
331       data.push_back(c);
332     }
333   }
334 
335   if (pos == linetext.end())
336   {
337     vtkErrorMacro("Syntax error " << this->FileName << ":" << this->LineNumber);
338     return 0;
339   }
340 
341   // eat the trailing quote
342   ++pos;
343 
344   return 1;
345 }
346 
347 //------------------------------------------------------------------------------
348 // Read an int value
ParseIntValues(istream & infile,std::string & linetext,std::string::iterator & pos,int * values,int n)349 int vtkMNITagPointReader::ParseIntValues(
350   istream& infile, std::string& linetext, std::string::iterator& pos, int* values, int n)
351 {
352   this->SkipWhitespace(infile, linetext, pos, 0);
353 
354   int i = 0;
355   while (pos != linetext.end() && *pos != ';' && i < n)
356   {
357     const char* cp = linetext.c_str() + (pos - linetext.begin());
358     char* ep = nullptr;
359     long val = strtol(cp, &ep, 10);
360     if (ep == cp)
361     {
362       vtkErrorMacro("Syntax error " << this->FileName << ":" << this->LineNumber);
363       return 0;
364     }
365     pos += (ep - cp);
366     values[i++] = static_cast<int>(val);
367     this->SkipWhitespace(infile, linetext, pos, 0);
368   }
369 
370   if (i != n)
371   {
372     vtkErrorMacro("Not enough values: " << this->FileName << ":" << this->LineNumber);
373     return 0;
374   }
375 
376   return 1;
377 }
378 
379 //------------------------------------------------------------------------------
380 // Read floating-point values into a point triplet.
ParseFloatValues(istream & infile,std::string & linetext,std::string::iterator & pos,double * values,int n)381 int vtkMNITagPointReader::ParseFloatValues(
382   istream& infile, std::string& linetext, std::string::iterator& pos, double* values, int n)
383 {
384   this->SkipWhitespace(infile, linetext, pos, 0);
385 
386   int i = 0;
387   while (pos != linetext.end() && *pos != ';' && i < n)
388   {
389     const char* cp = linetext.c_str() + (pos - linetext.begin());
390     char* ep = nullptr;
391     double val = strtod(cp, &ep);
392     if (ep == cp)
393     {
394       vtkErrorMacro("Syntax error " << this->FileName << ":" << this->LineNumber);
395       return 0;
396     }
397     pos += (ep - cp);
398     values[i++] = val;
399     this->SkipWhitespace(infile, linetext, pos, 0);
400   }
401 
402   if (i != n)
403   {
404     vtkErrorMacro("Not enough values: " << this->FileName << ":" << this->LineNumber);
405     return 0;
406   }
407 
408   return 1;
409 }
410 
411 //------------------------------------------------------------------------------
ReadFile(vtkPolyData * output1,vtkPolyData * output2)412 int vtkMNITagPointReader::ReadFile(vtkPolyData* output1, vtkPolyData* output2)
413 {
414   // Check that the file name has been set.
415   if (!this->FileName)
416   {
417     vtkErrorMacro("ReadFile: No file name has been set");
418     return 0;
419   }
420 
421   // Make sure that the file exists.
422   vtksys::SystemTools::Stat_t fs;
423   if (vtksys::SystemTools::Stat(this->FileName, &fs) != 0)
424   {
425     vtkErrorMacro("ReadFile: Can't open file " << this->FileName);
426     return 0;
427   }
428 
429   // Make sure that the file is readable.
430   vtksys::ifstream infile(this->FileName);
431   std::string linetext;
432   std::string::iterator pos = linetext.begin();
433 
434   if (infile.fail())
435   {
436     vtkErrorMacro("ReadFile: Can't read the file " << this->FileName);
437     return 0;
438   }
439 
440   // Read the first line
441   this->LineNumber = 0;
442   this->ReadLine(infile, linetext, pos);
443   if (strncmp(linetext.c_str(), "MNI Tag Point File", 18) != 0)
444   {
445     vtkErrorMacro("ReadFile: File is not a MNI tag file: " << this->FileName);
446     infile.close();
447     return 0;
448   }
449 
450   // Read the number of volumes
451   this->ReadLine(infile, linetext, pos);
452   this->SkipWhitespace(infile, linetext, pos, 1);
453   int numVolumes = 1;
454   std::string identifier;
455   if (!this->ParseLeftHandSide(infile, linetext, pos, identifier) ||
456     strcmp(identifier.c_str(), "Volumes") != 0 ||
457     !this->ParseIntValues(infile, linetext, pos, &numVolumes, 1) ||
458     (numVolumes != 1 && numVolumes != 2) || !this->SkipWhitespace(infile, linetext, pos, 0) ||
459     *pos != ';')
460   {
461     vtkErrorMacro("ReadFile: Line must be Volumes = 1; or Volumes = 2; " << this->FileName << ":"
462                                                                          << this->LineNumber);
463     infile.close();
464     return 0;
465   }
466 
467   this->NumberOfVolumes = numVolumes;
468 
469   // Read the comments
470   this->ReadLineAfterComments(infile, linetext, pos);
471 
472   // Rad the tag points
473   if (!this->ParseLeftHandSide(infile, linetext, pos, identifier) ||
474     strcmp(identifier.c_str(), "Points") != 0)
475   {
476     vtkErrorMacro("ReadFile: Cannot find Points in file; " << this->FileName);
477     infile.close();
478     linetext.clear();
479     return 0;
480   }
481 
482   vtkPoints* points[2];
483   points[0] = vtkPoints::New();
484   points[1] = vtkPoints::New();
485   vtkCellArray* verts = vtkCellArray::New();
486   vtkStringArray* labels = vtkStringArray::New();
487   vtkDoubleArray* weights = vtkDoubleArray::New();
488   vtkIntArray* structureIds = vtkIntArray::New();
489   vtkIntArray* patientIds = vtkIntArray::New();
490 
491   int errorOccurred = 0;
492   this->SkipWhitespace(infile, linetext, pos, 1);
493   for (vtkIdType count = 0; infile.good() && *pos != ';'; count++)
494   {
495     for (int i = 0; i < numVolumes; i++)
496     {
497       double point[3];
498       if (!this->ParseFloatValues(infile, linetext, pos, point, 3))
499       {
500         errorOccurred = 1;
501         break;
502       }
503       points[i]->InsertNextPoint(point);
504       verts->InsertNextCell(1);
505       verts->InsertCellPoint(count);
506     }
507     if (errorOccurred)
508     {
509       break;
510     }
511 
512     this->SkipWhitespace(infile, linetext, pos, 0);
513     if (pos != linetext.end() && *pos != '\"' && *pos != ';')
514     {
515       double weight;
516       int structureId;
517       int patientId;
518       if (!this->ParseFloatValues(infile, linetext, pos, &weight, 1) ||
519         !this->ParseIntValues(infile, linetext, pos, &structureId, 1) ||
520         !this->ParseIntValues(infile, linetext, pos, &patientId, 1))
521       {
522         errorOccurred = 1;
523         break;
524       }
525       vtkIdType lastCount = weights->GetNumberOfTuples();
526       weights->InsertValue(count, weight);
527       structureIds->InsertValue(count, structureId);
528       patientIds->InsertValue(count, patientId);
529       for (vtkIdType j = lastCount; j < count; j++)
530       {
531         weights->SetValue(j, 0.0);
532         structureIds->SetValue(j, -1);
533         patientIds->SetValue(j, -1);
534       }
535     }
536 
537     this->SkipWhitespace(infile, linetext, pos, 0);
538     if (pos != linetext.end() && *pos == '\"')
539     {
540       vtkStdString stringval;
541       if (!this->ParseStringValue(infile, linetext, pos, stringval))
542       {
543         errorOccurred = 1;
544         break;
545       }
546       labels->InsertValue(count, stringval);
547     }
548 
549     this->SkipWhitespace(infile, linetext, pos, 1);
550   }
551 
552   // Close the file
553   infile.close();
554 
555   if (!errorOccurred)
556   {
557     output1->SetPoints(points[0]);
558     output2->SetPoints(points[1]);
559 
560     vtkPolyData* output[2];
561     output[0] = output1;
562     output[1] = output2;
563 
564     weights->SetName("Weights");
565     structureIds->SetName("StructureIds");
566     patientIds->SetName("PatientIds");
567     labels->SetName("LabelText");
568 
569     for (int k = 0; k < this->NumberOfVolumes; k++)
570     {
571       output[k]->SetVerts(verts);
572 
573       if (weights->GetNumberOfTuples())
574       {
575         output[k]->GetPointData()->AddArray(weights);
576       }
577       if (structureIds->GetNumberOfTuples())
578       {
579         output[k]->GetPointData()->AddArray(structureIds);
580       }
581       if (patientIds->GetNumberOfTuples())
582       {
583         output[k]->GetPointData()->AddArray(patientIds);
584       }
585       if (labels->GetNumberOfValues())
586       {
587         output[k]->GetPointData()->AddArray(labels);
588       }
589     }
590   }
591 
592   points[0]->Delete();
593   points[1]->Delete();
594 
595   verts->Delete();
596   weights->Delete();
597   structureIds->Delete();
598   patientIds->Delete();
599   labels->Delete();
600 
601   return 1;
602 }
603 
604 //------------------------------------------------------------------------------
GetNumberOfVolumes()605 int vtkMNITagPointReader::GetNumberOfVolumes()
606 {
607   this->Update();
608 
609   return this->NumberOfVolumes;
610 }
611 
612 //------------------------------------------------------------------------------
GetPoints(int port)613 vtkPoints* vtkMNITagPointReader::GetPoints(int port)
614 {
615   this->Update();
616 
617   if (port < 0 || port >= this->NumberOfVolumes)
618   {
619     return nullptr;
620   }
621 
622   vtkPolyData* output = static_cast<vtkPolyData*>(this->GetOutputDataObject(port));
623 
624   if (output)
625   {
626     return output->GetPoints();
627   }
628 
629   return nullptr;
630 }
631 
632 //------------------------------------------------------------------------------
GetLabelText()633 vtkStringArray* vtkMNITagPointReader::GetLabelText()
634 {
635   this->Update();
636 
637   vtkPolyData* output = static_cast<vtkPolyData*>(this->GetOutputDataObject(0));
638 
639   if (output)
640   {
641     return vtkArrayDownCast<vtkStringArray>(output->GetPointData()->GetAbstractArray("LabelText"));
642   }
643 
644   return nullptr;
645 }
646 
647 //------------------------------------------------------------------------------
GetWeights()648 vtkDoubleArray* vtkMNITagPointReader::GetWeights()
649 {
650   this->Update();
651 
652   vtkPolyData* output = static_cast<vtkPolyData*>(this->GetOutputDataObject(0));
653 
654   if (output)
655   {
656     return vtkArrayDownCast<vtkDoubleArray>(output->GetPointData()->GetArray("Weights"));
657   }
658 
659   return nullptr;
660 }
661 
662 //------------------------------------------------------------------------------
GetStructureIds()663 vtkIntArray* vtkMNITagPointReader::GetStructureIds()
664 {
665   this->Update();
666 
667   vtkPolyData* output = static_cast<vtkPolyData*>(this->GetOutputDataObject(0));
668 
669   if (output)
670   {
671     return vtkArrayDownCast<vtkIntArray>(output->GetPointData()->GetArray("StructureIds"));
672   }
673 
674   return nullptr;
675 }
676 
677 //------------------------------------------------------------------------------
GetPatientIds()678 vtkIntArray* vtkMNITagPointReader::GetPatientIds()
679 {
680   this->Update();
681 
682   vtkPolyData* output = static_cast<vtkPolyData*>(this->GetOutputDataObject(0));
683 
684   if (output)
685   {
686     return vtkArrayDownCast<vtkIntArray>(output->GetPointData()->GetArray("PatientIds"));
687   }
688 
689   return nullptr;
690 }
691 
692 //------------------------------------------------------------------------------
GetComments()693 const char* vtkMNITagPointReader::GetComments()
694 {
695   this->Update();
696 
697   return this->Comments;
698 }
699 
700 //------------------------------------------------------------------------------
RequestData(vtkInformation * vtkNotUsed (request),vtkInformationVector ** vtkNotUsed (inputVector),vtkInformationVector * outputVector)701 int vtkMNITagPointReader::RequestData(vtkInformation* vtkNotUsed(request),
702   vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
703 {
704   // get the info object
705   vtkInformation* outInfo1 = outputVector->GetInformationObject(0);
706   vtkInformation* outInfo2 = outputVector->GetInformationObject(1);
707 
708   // get the output
709   vtkPolyData* output1 = vtkPolyData::SafeDownCast(outInfo1->Get(vtkDataObject::DATA_OBJECT()));
710   vtkPolyData* output2 = vtkPolyData::SafeDownCast(outInfo2->Get(vtkDataObject::DATA_OBJECT()));
711 
712   // all of the data in the first piece.
713   if (outInfo1->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()) > 0 ||
714     outInfo2->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()) > 0)
715   {
716     return 0;
717   }
718 
719   // read the file
720   return this->ReadFile(output1, output2);
721 }
722