1 /*
2  * The GNU Octave dicom package is Copyright Andy Buckle 2010
3  * Contact: blondandy using the sf.net system,
4  * <https://sourceforge.net/sendmessage.php?touser=1760416>
5  *
6  * The GNU Octave dicom package is free software: you can redistribute
7  * it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * The GNU Octave dicom packag is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the
13  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  * PURPOSE.  See the GNU General Public License for more details.
15  *
16  * Please see the file, "COPYING" for further details of GNU General
17  * Public License version 3.
18  *
19  */
20 
21 #include "octave/oct.h"
22 #include "octave/ov-struct.h"
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #include "gdcmDictEntry.h"
29 #include "gdcmImage.h"
30 #include "gdcmImageWriter.h"
31 #include "gdcmFileDerivation.h"
32 #include "gdcmUIDGenerator.h"
33 #include "gdcmPhotometricInterpretation.h"
34 
35 #include "gdcmAttribute.h"
36 
37 #include "dicomdict.h"
38 
39 #define DICOM_ERR -1
40 #define DICOM_OK 0
41 
42 #define OCT_FN_NAME dicomwrite
43 #define QUOTED_(x) #x
44 #define QUOTED(x) QUOTED_(x)
45 
46 // TODO: allow user to specify colourmap
47 // TODO: allow user to specify metadata
48 // TODO: return status, like the help output says
49 
50 // TODO all fns here should throw exceptions, not use this "std::string & err" arg
51 
52 void struct2metadata (gdcm::ImageWriter *w, gdcm::File *file, const octave_value  & ov, bool trial, int sequenceDepth) ;
53 void structarray2sequence (gdcm::SequenceOfItems & sq, octave_map * om, bool trial, int sequenceDepth);
54 void value2element (gdcm::DataElement * de, const octave_value * ov, gdcm::Tag * tag, const std::string & keyword, bool trial, bool * handled, int sequenceDepth);
55 void octaveVal2dicomImage (gdcm::ImageWriter *w, octave_value *pixval) ;
56 void genMinimalMetaData (gdcm::ImageWriter *w, gdcm::File *file);
57 
58 DEFUN_DLD (dicomwrite, args, nargout,
59     "-*- texinfo -*- \n\
60 @deftypefn {Loadable Function} {} dicomwrite(@var{im}, @var{filename})\n\
61 @deftypefnx {Loadable Function} {} dicomwrite(@var{im}, @var{filename}, @var{info})\n\
62 \n\
63 Write a DICOM format file to @var{filename}.\n\
64 \n\
65 @var{im} is image data or empty matrix, [], if only metadata save is required\n\
66 @var{filename} is filename to write dicom to. if [], then function runs in verbose trial mode.\n\
67 @var{info} struct, like that produced by dicominfo\n\
68 \n\
69 @seealso{dicomread, dicominfo}\n\
70 @end deftypefn \n\
71 ")
72 {
73   octave_value_list retval;  // create object to store return values
74   if (2 > args.length())
75     {
76       error (QUOTED(OCT_FN_NAME)": should have at least 2 arguments");
77       return retval;
78     }
79   if (! args(1).is_string ())
80     {
81       error (QUOTED(OCT_FN_NAME)": second argument should be a string filename");
82       return retval;
83     }
84 
85   charMatrix ch = args(1).char_matrix_value ();
86   bool trial = false;
87   if (0 == ch.numel())
88     {
89       trial = true; // more output if null string supplied for filename;
90     }
91   else if (ch.rows() != 1)
92     {
93       error (QUOTED(OCT_FN_NAME)": second arg should be a filename");
94       return retval;
95     }
96   std::string filename = ch.row_as_string(0);
97 
98   //bool fn_failed_verbose=trial; //TODO other way of setting this. is it even necessary?
99   bool writing_image = (0 != args(0).numel()) ;
100 
101   // prepare writer
102   gdcm::ImageWriter w;
103   if (!trial) w.SetFileName (filename.c_str());
104 
105   gdcm::SmartPointer<gdcm::File> file = new gdcm::File; // should delete be used later?
106 
107   if (3 > args.length())
108     {
109       // no metadata supplied, need to make some
110       try
111         {
112           genMinimalMetaData(&w, file);
113         }
114       catch (std::exception&)
115         {
116           return retval;
117         }
118     }
119   else
120     {
121       // 3rd arg should be struct to turn into metadata
122       try
123         {
124           struct2metadata (&w, file, args(2), trial, 0 /* depth of indent for SQ nesting */);
125         }
126       catch (std::exception&)
127         {
128           return retval;
129         }
130     }
131 
132   if (writing_image)
133     {
134       octave_value pixval = args(0);
135       try
136         {
137           octaveVal2dicomImage(&w, &pixval);
138         }
139       catch (std::exception&)
140         {
141           return retval;
142         }
143      }
144 
145 #if 0 /* debug */
146   //NOTE: debug code currently crashes octave - do not use without rework
147   gdcm::File fc=w.GetFile();
148   gdcm::DataSet dsc=fc.GetDataSet();
149   const gdcm::DataElement & sopclass = dsc.GetDataElement( gdcm::Tag(0x0008, 0x0016) ); // SOPClassUID
150   const gdcm::ByteValue *bv = sopclass.GetByteValue();
151   char * bufc=(char *) malloc((bv->GetLength()+1)*sizeof(char));
152   memcpy(bufc, bv->GetPointer(), bv->GetLength());
153   bufc[bv->GetLength()]='\0';
154   octave_stdout << bufc << '\n' ;
155   free(bufc);
156 #endif
157 
158   if (!trial)
159     {
160       // if not writing image, use superclass
161       if (writing_image ? !w.ImageWriter::Write() : !w.Writer::Write())
162         {
163           error (QUOTED(OCT_FN_NAME)": unable to save");
164           return retval;
165         }
166     }
167 
168   return retval;
169 }
170 
struct2metadata(gdcm::ImageWriter * w,gdcm::File * file,const octave_value & ov,bool trial,int sequenceDepth)171 void struct2metadata (gdcm::ImageWriter *w, gdcm::File *file, const octave_value  & ov, bool trial, int sequenceDepth)
172 {
173   if (!ov.OV_ISMAP())
174     {
175       error (QUOTED(OCT_FN_NAME)": 3rd arg should be struct holding metadata. it is %s",ov.type_name().c_str());
176       throw std::exception ();
177     }
178   gdcm::DataSet ds;
179   gdcm::FileMetaInformation hds;
180   octave_map om=ov.map_value();
181   uint32_t skipped = 0;
182   for (octave_map::iterator it = om.begin(); it != om.end(); it++)
183     {
184       std::string keyword(om.key(it));
185       Cell cell = om.contents(it);
186       if (!dicom_is_present(keyword))
187         {
188           if ( 0==keyword.compare("FileModDate") || 0==keyword.compare("Filename"))
189             {
190               if (trial)
191                 octave_stdout << "from dicominfo, ignoring:" << keyword << ":[" << cell(0).string_value() << "]\n";
192               continue; //dicominfo adds these non-DICOM bits
193             }
194           skipped++;
195           // warning (QUOTED(OCT_FN_NAME)": skipping \"%s\". not in dictionary\n",keyword.c_str());
196           continue;
197         }
198       // PixelData is here becuase it handled by other fn. the others seem to cause bugs
199       if (0 == keyword.compare("PixelData") || 0 == keyword.compare("FileMetaInformationVersion"))
200         {
201           if (trial)
202             octave_stdout << "handled separately:" << keyword << std::endl;
203           continue;
204         }
205       gdcm::DataElement de;
206       gdcm::Tag tag;
207       bool handled;
208       try
209         {
210           value2element(&de, &cell(0), &tag, keyword, trial, &handled, sequenceDepth);
211         }
212       catch (std::exception&)
213         {
214           return;
215         }
216       if (handled)
217         {
218           if (tag.GetGroup() == (uint32_t)0x0002 )
219             {
220               // group 0x2 : header
221               hds.Insert(de);
222               // TODO move this somehow: if (trial) octave_stdout << ':' << "(header)";
223             }
224           else
225             {
226               // everything else is metadata
227               ds.Insert(de);
228             }
229         }
230     }
231   if (skipped > 0)
232     {
233       // TODO: this does not count elements nested in SQs
234       warning (QUOTED(OCT_FN_NAME)": skipped %u keyword-value pairs. not in dictionary\n",skipped);
235     }
236   // TODO are these set functions taking a copy? if they take a reference to objects that are about to go out of scope, we have a problem
237   file->SetDataSet ((gdcm::DataSet &)ds);
238   file->SetHeader (hds);
239   w->SetFile (*file);
240   return;
241 }
242 
structarray2sequence(gdcm::SequenceOfItems & sq,octave_map * om,bool trial,int sequenceDepth)243 void structarray2sequence(gdcm::SequenceOfItems & sq, octave_map * om, bool trial, int sequenceDepth)
244 {
245   for (octave_map::iterator it = om->begin(); it != om->end(); it++)
246     {
247       gdcm::Item item;
248       // item.SetVLToUndefined(); //TODO: does VL need to be set for items that contain datasets?
249       gdcm::DataSet &nds = item.GetNestedDataSet();
250       std::string itemname(om->key(it));
251       // TODO: test itemname is something like Item_n.
252       Cell cell = om->contents(it);
253       octave_map subom = cell(0).map_value();
254       // octave_stdout << itemname <<std::endl;
255       for (octave_map::iterator subit = subom.begin(); subit != subom.end(); subit++)
256         {
257           std::string subkeyword(subom.key(subit));
258           gdcm::DataElement de;
259           gdcm::Tag tag;
260           bool handled;
261           try
262             {
263               value2element(&de, &(subom.contents(subit)(0)), &tag, subkeyword, trial, &handled, sequenceDepth);
264             }
265           catch (std::exception&)
266             {
267               return;
268             }
269           if (!handled)
270             {
271               warning (QUOTED(OCT_FN_NAME)": element in sequence not handled %s", subkeyword.c_str());
272               continue;
273             }
274           nds.Insert(de); //insert element into dataset
275         }
276       sq.AddItem(item); // add dataset to sequence
277     }
278   return;
279 }
280 
value2element(gdcm::DataElement * de,const octave_value * ov,gdcm::Tag * tag,const std::string & keyword,bool trial,bool * handled,int sequenceDepth)281 void value2element (gdcm::DataElement * de, const octave_value * ov, gdcm::Tag * tag, const std::string & keyword, bool trial, bool * handled, int sequenceDepth)
282 {
283   gdcm::DictEntry entry;
284   if (!dicom_is_present(keyword))
285     {
286       if (trial)
287         {
288           octave_stdout << std::setw(2*sequenceDepth) << "" << std::setw(0);
289           octave_stdout << keyword << ": not in dictionary" << std::endl;
290         }
291       *handled = false;
292       return;
293     }
294   lookup_dicom_tag (*tag, keyword);
295   lookup_dicom_entry (entry, *tag);
296   gdcm::VL len((uint32_t)ov->byte_size());
297   //gdcm::DataElement de(*tag, len, entry.GetVR());
298   de->SetTag (*tag);
299   de->SetVL (len);
300   de->SetVR (entry.GetVR());
301   *handled = true;
302   if (trial)
303     {
304       octave_stdout << std::setw(2*sequenceDepth) << "" << std::setw(0);
305       octave_stdout << *tag << ':' << entry.GetVR() << ':' << keyword << ':';
306     }
307   if (entry.GetVR() & VRSTRING)
308     {
309       // TODO: check even padding requirement
310       // TODO some dicom string types are stored as numbers
311       if (!ov->is_string())
312         {
313           warning (QUOTED(OCT_FN_NAME)": dicomdict gives string VR for %s, octave value is %s", keyword.c_str(), ov->class_name().c_str());
314         }
315       if (trial)
316         octave_stdout << '[' << ov->string_value() << ']' << std::endl ;
317       de->SetByteValue ((const char *)ov->string_value().c_str(),len);
318     }
319   else if (entry.GetVR() & gdcm::VR::US)
320     {
321       // unsigned short
322       if (!ov->is_scalar_type())
323         {
324           // dicominfo turns US into double, this turns it back to uint16
325           warning (QUOTED(OCT_FN_NAME)": dicomdict gives VR of US for %s, octave value is %s", keyword.c_str(), ov->class_name().c_str());
326         }
327       if (trial)
328         octave_stdout << '[' << ov->uint16_scalar_value() << ']' << std::endl ;
329       de->SetByteValue ((const char *)ov->uint16_array_value().fortran_vec(), gdcm::VL((uint32_t)2));
330     }
331   else if ( entry.GetVR() & gdcm::VR::OB)
332     {
333       // other byte
334       uint8NDArray obv = ov->uint8_array_value();
335       //if (!ov->is_scalar_type())
336       //  {
337       //    // dicominfo seems to return string
338       //    warning (QUOTED(OCT_FN_NAME)": dicomdict gives VR of OB for %s, octave value is %s", keyword.c_str(), ov->class_name().c_str());
339       //  }
340       if (trial)
341         {
342           // TODO surely there is a better way to do this with c++ output stream
343           octave_uint8 * obv_p=obv.fortran_vec();
344           char buf[8];
345           octave_stdout  << '[' ;
346           for (octave_idx_type i = 0; i < (octave_idx_type)len; i++)
347             {
348               snprintf (buf, 7, "%02X ", (const uint8_t)obv_p[i]);
349               octave_stdout  << buf << " " ;
350             }
351           octave_stdout  << ']' << std::endl ;
352         }
353       de->SetByteValue((const char *)obv.fortran_vec(), gdcm::VL((uint32_t)obv.byte_size()));
354     }
355   else if (entry.GetVR() & gdcm::VR::DS)
356     {
357       // double string. sep by '\\'
358       if (!ov->is_double_type())
359         {
360           warning (QUOTED(OCT_FN_NAME)": dicomdict gives VR of DS for %s, expecting double, octave value is %s", keyword.c_str(), ov->class_name().c_str());
361         }
362       std::stringstream ss;
363       Matrix mat=ov->matrix_value() ; //to matrix of doubles
364       double * mat_p=mat.fortran_vec();
365       for (int i = 0; i < mat.numel() ;i++)
366         ss << mat_p[i] << '\\' ;
367       std::string s = ss.str();
368       s = s.substr(0,s.length()-1); // strip last '\\'
369       if (s.length()%2==1)
370         s = s +" "; // ensure even number of chars
371       if (trial)
372         octave_stdout << '[' << s << ']' << std::endl ;
373       de->SetByteValue ( (const char *)s.c_str(), gdcm::VL((uint32_t)s.length()) );
374     }
375   else if ( entry.GetVR() & gdcm::VR::IS)
376     {
377       // integer string. only single values, I think
378       if (!ov->is_scalar_type())
379         {
380         warning(QUOTED(OCT_FN_NAME)": dicomdict gives VR of IS for %s, octave value is %s", keyword.c_str(), ov->class_name().c_str());
381         }
382       int32_t val=ov->int32_scalar_value() ;
383       char buf[16];
384       snprintf (buf, 14, "%i", val);
385       int len = strlen(buf);
386       // ensure even number of chars
387       if (len%2 == 1)
388         {
389           buf[len]=' ';
390           buf[len+1]='\0';
391         }
392       if (trial)
393         octave_stdout << '[' << buf << ']' << std::endl;
394       de->SetByteValue ( buf, gdcm::VL((uint32_t)strlen(buf)) );
395     }
396   else if ( entry.GetVR() & gdcm::VR::SQ)
397     {
398       // sequence
399       if (!ov->OV_ISMAP())
400         {
401           warning (QUOTED(OCT_FN_NAME)": dicomdict gives VR of SQ for %s, octave value is %s", keyword.c_str(), ov->class_name().c_str());
402         }
403       octave_stdout << std::endl;
404       //int nObj = ov->numel();
405       octave_map subom = ov->map_value();
406       gdcm::SmartPointer<gdcm::SequenceOfItems> sq = new gdcm::SequenceOfItems();
407       try
408         {
409           structarray2sequence(*sq, &subom, trial, ++sequenceDepth) ;
410         }
411       catch (std::exception&)
412         {
413           return;
414         }
415       sequenceDepth--;
416     }
417   else
418     {
419       if (trial)
420         octave_stdout << " ### not handled ### " << std::endl;
421       *handled = false;
422     }
423 }
424 
425 // TODO set HighBit etc using octave class. or cast pixel data using metadata, or just give error if they don't agree
octaveVal2dicomImage(gdcm::ImageWriter * w,octave_value * pixval)426 void octaveVal2dicomImage(gdcm::ImageWriter *w, octave_value *pixval)
427 {
428   if (pixval->ndims() != 2)
429     {
430       error (QUOTED(OCT_FN_NAME)": image has %i dimensions. not implemented. ", (int)pixval->ndims());
431       throw std::exception ();
432     }
433 
434   // get current properties we may have gotten from the input
435   gdcm::DataSet ds = w->GetFile().GetDataSet();
436 
437   // make image
438   gdcm::SmartPointer<gdcm::Image> im = new gdcm::Image;
439 
440   im->SetNumberOfDimensions (2);
441   im->SetDimension (0, pixval->dims()(0));
442   im->SetDimension (1, pixval->dims()(1));
443 
444   gdcm::Attribute<0x0028,0x0004> pi_at;
445   if (ds.FindDataElement (pi_at.GetTag()))
446     {
447       pi_at.SetFromDataElement (ds.GetDataElement (pi_at.GetTag()));
448 
449       gdcm::PhotometricInterpretation::PIType type = gdcm::PhotometricInterpretation::GetPIType(pi_at.GetValue());
450       if (type != gdcm::PhotometricInterpretation().GetType())
451         im->SetPhotometricInterpretation( type );
452       else
453         im->SetPhotometricInterpretation( gdcm::PhotometricInterpretation::MONOCHROME1 );
454     }
455   else
456     {
457       im->SetPhotometricInterpretation( gdcm::PhotometricInterpretation::MONOCHROME1 );
458     }
459 
460   // prepare to make data from mat available
461   char * matbuf = 0; // to be set to point to octave matrix
462 
463   if (pixval->is_uint8_type())
464     {
465       uint8NDArray data = pixval->uint8_array_value().transpose();
466       uint8_t * buf = new uint8_t[data.numel ()];
467       memcpy (buf, data.fortran_vec(), data.numel()*sizeof(uint8_t));
468       matbuf = (char *)buf;
469       im->GetPixelFormat().SetScalarType(gdcm::PixelFormat::UINT8);
470     }
471   else if (pixval->is_uint16_type())
472     {
473       uint16NDArray data = pixval->uint16_array_value().transpose();
474       uint16_t * buf = new uint16_t[data.numel ()];
475       memcpy (buf, data.fortran_vec(), data.numel()*sizeof(uint16_t));
476       matbuf = (char *)buf;
477       im->GetPixelFormat().SetScalarType(gdcm::PixelFormat::UINT16);
478     }
479   else if (pixval->is_uint32_type())
480     {
481       uint32NDArray data = pixval->uint32_array_value().transpose();
482       uint32_t * buf = new uint32_t[data.numel ()];
483       memcpy (buf, data.fortran_vec(), data.numel()*sizeof(uint32_t));
484       matbuf = (char *)buf;
485       im->GetPixelFormat().SetScalarType(gdcm::PixelFormat::UINT32);
486     }
487   else if (pixval->is_int8_type())
488     {
489       int8NDArray data = pixval->int8_array_value().transpose();
490       int8_t * buf = new int8_t[data.numel ()];
491       memcpy (buf, data.fortran_vec(), data.numel()*sizeof(int8_t));
492       matbuf = (char *)buf;
493       im->GetPixelFormat().SetScalarType(gdcm::PixelFormat::INT8);
494     }
495   else if (pixval->is_int16_type())
496     {
497       int16NDArray data = pixval->int16_array_value().transpose();
498       int16_t * buf = new int16_t[data.numel ()];
499       memcpy (buf, data.fortran_vec(), data.numel()*sizeof(int16_t));
500       matbuf = (char *)buf;
501       im->GetPixelFormat().SetScalarType(gdcm::PixelFormat::INT16);
502     }
503   else if (pixval->is_int32_type())
504     {
505       int32NDArray data = pixval->int32_array_value().transpose();
506       int32_t * buf = new int32_t[data.numel ()];
507       memcpy (buf, data.fortran_vec(), data.numel()*sizeof(int32_t));
508       matbuf = (char *)buf;
509       im->GetPixelFormat().SetScalarType(gdcm::PixelFormat::INT32);
510     }
511   else
512     { // TODO: gdcm::PixelFormat has float types too.
513       error(QUOTED(OCT_FN_NAME)": cannot write this type");
514       throw std::exception();
515     }
516 
517   unsigned long buflen = im->GetBufferLength();
518   if (buflen != pixval->byte_size())
519     {
520       delete [] matbuf;
521       error (QUOTED(OCT_FN_NAME)": prepared DICOM buffer size(%lu) does not match Octave array buffer size(%i).",buflen, (int)pixval->byte_size());
522       throw std::exception();
523     }
524 
525   gdcm::DataElement pixeldata (gdcm::Tag(0x7fe0,0x0010));
526   pixeldata.SetByteValue (matbuf, buflen);
527 
528   im->SetDataElement (pixeldata);
529 
530   delete [] matbuf;
531 
532   w->SetImage (*im);
533 
534   // set any image values we already have in our data attributes
535   gdcm::Attribute<0x0028,0x0030> sp_at;
536   if (ds.FindDataElement(sp_at.GetTag()))
537     {
538       sp_at.SetFromDataElement( ds.GetDataElement(sp_at.GetTag()) );
539       im->SetSpacing(0, sp_at.GetValue(0));
540       im->SetSpacing(1, sp_at.GetValue(1));
541     }
542   gdcm::Attribute<0x0020,0x0037> dir_at;
543   if (ds.FindDataElement(dir_at.GetTag()))
544     {
545       dir_at.SetFromDataElement( ds.GetDataElement(dir_at.GetTag()) );
546       im->SetDirectionCosines(0, (double)dir_at.GetValue(0));
547       im->SetDirectionCosines(1, (double)dir_at.GetValue(1));
548       im->SetDirectionCosines(2, (double)dir_at.GetValue(2));
549       im->SetDirectionCosines(3, (double)dir_at.GetValue(3));
550       im->SetDirectionCosines(4, (double)dir_at.GetValue(4));
551       im->SetDirectionCosines(5, (double)dir_at.GetValue(5));
552     }
553   gdcm::Attribute<0x0020,0x0032> or_at;
554   if (ds.FindDataElement(or_at.GetTag()))
555     {
556       or_at.SetFromDataElement( ds.GetDataElement(or_at.GetTag()) );
557       im->SetOrigin(0, or_at.GetValue(0));
558       im->SetOrigin(1, or_at.GetValue(1));
559       im->SetOrigin(2, or_at.GetValue(2));
560     }
561   gdcm::Attribute<0x0028,0x1053> sl_at;
562   if (ds.FindDataElement(sl_at.GetTag()))
563     {
564       sl_at.SetFromDataElement( ds.GetDataElement(sl_at.GetTag()) );
565       im->SetSlope(sl_at.GetValue());
566     }
567   gdcm::Attribute<0x0028,0x1052> int_at;
568   if (ds.FindDataElement(int_at.GetTag()))
569     {
570       int_at.SetFromDataElement( ds.GetDataElement(int_at.GetTag()) );
571       im->SetIntercept(int_at.GetValue());
572     }
573 
574   return;
575 }
576 
genMinimalMetaData(gdcm::ImageWriter * w,gdcm::File * file)577 void genMinimalMetaData(gdcm::ImageWriter *w, gdcm::File *file)
578 {
579   gdcm::UIDGenerator uid; // helper for uid generation
580 
581   // Step 2: DERIVED object
582   gdcm::FileDerivation fd; //TODO: copied this: don't understand it
583   // For the pupose of this execise we will pretend that this image is referencing
584   // two source image (we need to generate fake UID for that).
585   const char ReferencedSOPClassUID[] = "1.2.840.10008.5.1.4.1.1.7"; // Secondary Capture
586   fd.AddReference( ReferencedSOPClassUID, uid.Generate() );
587   fd.AddReference( ReferencedSOPClassUID, uid.Generate() );
588 
589   // Again for the purpose of the exercise we will pretend that the image is a
590   // multiplanar reformat (MPR):
591   // CID 7202 Source Image Purposes of Reference
592   // {"DCM",121322,"Source image for image processing operation"},
593   fd.SetPurposeOfReferenceCodeSequenceCodeValue( 121322 );
594   // CID 7203 Image Derivation
595   // { "DCM",113072,"Multiplanar reformatting" },
596   fd.SetDerivationCodeSequenceCodeValue( 113072 );
597   fd.SetFile( *file );
598   // If all Code Value are ok the filter will execute properly
599   if (! fd.Derive())
600     {
601       error(QUOTED(OCT_FN_NAME)": file derivation failed");
602       throw std::exception();
603     }
604 
605   w->SetFile (fd.GetFile ());
606 
607   return;
608 }
609 /*
610 %!shared testfile1, testfile2
611 %! testfile1 = tempname ();
612 %! testfile2 = tempname ();
613 
614 %!test
615 %! wdata = uint8 (10*rand (10,10));
616 %! dicomwrite (wdata, testfile1);
617 %! rdata = dicomread (testfile1);
618 %! assert(wdata, rdata);
619 
620 %!fail ("dicominfo", "dicominfo: one arg required: dicom filename");
621 %!fail ("dicominfo ([])");
622 %!fail ("dicominfo ([],1)");
623 
624 %!test
625 %! wdata = uint8 (10*rand (10,10));
626 %! dicomwrite (wdata, testfile1);
627 %! info = dicominfo (testfile1);
628 %! dicomwrite (wdata, testfile2, info);
629 
630 %!test
631 %! wdata = uint8 (10*rand (10,10));
632 %! s.PatientName = "fred";
633 %! s.PatientID = "1";
634 %! dicomwrite (wdata, testfile2, s);
635 %! p = dicominfo (testfile2);
636 %! assert (p.PatientName, "fred");
637 %! assert (p.PatientID, "1");
638 
639 %!test
640 %! # test we have control of image property information
641 %! wdata = uint8 (10*rand (10,10));
642 %! dicomwrite (wdata, testfile2);
643 %! p = dicominfo (testfile2);
644 %! assert (p.PhotometricInterpretation, "MONOCHROME1 ");
645 %! s.PhotometricInterpretation = "MONOCHROME2";
646 %! dicomwrite (wdata, testfile2, s);
647 %! p = dicominfo (testfile2);
648 %! assert (p.PhotometricInterpretation, "MONOCHROME2 ");
649 
650 %!test
651 %! if exist (testfile1, 'file')
652 %!   delete (testfile1);
653 %! endif
654 %! if exist (testfile2, 'file')
655 %!   delete (testfile2);
656 %! endif
657 */
658