/*========================================================================= Program: GDCM (Grassroots DICOM). A DICOM library Copyright (c) 2006-2011 Mathieu Malaterre All rights reserved. See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ /* * Simple C# example * * Shows multiple steps: * Steps 1. * Create a fake (dummy) DICOM file, with size 512 x 512 x 2 We use a small * image to be able to create the volume in memory Of course you can use any * existing DICOM instead * * Step 2. * Hack the DICOM file to pretend the number of frames is 1000 (instead of 2) * At this point in time this makes the DICOM file invalid (truncated). But the * next step will fix this. * * Step 3. * Use C# to create a binary data which will represent our source object for * image. * * Step 4. * We use gdcm.FileStreamer to merge the template DICOM file from Step 2, with * the binary data from Step 3. We decide to read a scanline at a time, but * this can be read with any number of bytes. AppendToDataElement() will always * do the proper computation. * * Step 5. * We compress this gigantic file, into [JPEG Lossless, Non-Hierarchical, * First-Order Prediction (Process 14 [Selection Value 1])] * * Usage: * $ mono bin/FileChangeTS.exe small.dcm big.dcm raw.data merge.dcm jpeg.dcm */ using System; using System.IO; using gdcm; public class FileChangeTS { public static byte[] StrToByteArray(string str) { System.Text.ASCIIEncoding encoding=new System.Text.ASCIIEncoding(); return encoding.GetBytes(str); } // Create a 256 x 256 Secondary Capture Image Storage static private void CreateSmallDICOM(string fileName) { using( var writer = new gdcm.PixmapWriter() ) { gdcm.Pixmap img = writer.GetImage(); img.SetNumberOfDimensions( 3 ); img.SetDimension(0, 512 ); img.SetDimension(1, 512 ); img.SetDimension(2, 2 ); // fake a 3d volume PhotometricInterpretation pi = new PhotometricInterpretation( PhotometricInterpretation.PIType.MONOCHROME2 ); img.SetPhotometricInterpretation( pi ); gdcm.DataElement pixeldata = new gdcm.DataElement( new gdcm.Tag(0x7fe0,0x0010) ); byte[] buffer = new byte[ 512 * 512 * 2 ]; pixeldata.SetByteValue( buffer, new gdcm.VL((uint)buffer.Length) ); img.SetDataElement( pixeldata ); gdcm.File file = writer.GetFile(); gdcm.DataSet ds = file.GetDataSet(); gdcm.DataElement ms = new gdcm.DataElement(new gdcm.Tag(0x0008,0x0016)); string mediastorage = "1.2.840.10008.5.1.4.1.1.7.2"; // Multi-frame Grayscale Byte Secondary Capture Image Storage byte[] val = StrToByteArray(mediastorage); ms.SetByteValue( val, new gdcm.VL( (uint)val.Length) ); ds.Insert( ms ); writer.SetFileName( fileName ); writer.Write(); } } static private void CreateBigDICOM(string fileName, string outfilename) { using( var ano = new gdcm.FileAnonymizer() ) { // The following is somewhat dangerous, do not try at home: string nframes = "1000"; ano.Replace( new gdcm.Tag(0x0028,0x0008), nframes ); ano.SetInputFileName(fileName); ano.SetOutputFileName(outfilename); ano.Write(); // at this point the DICOM is invalid ! } } static private void CreateDummyFile(string fileName, long length) { using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) { // Looks like C# always init to 0 (fallocate ?) // For the purpose of the test we could add some random noise fileStream.SetLength(length); } } static private void ReadBytesIntoArray( byte[] array, FileStream source ) { int numBytesToRead = array.Length; int numBytesRead = 0; while (numBytesToRead > 0) { // According to spec: Read() may return anything from 0 to numBytesToRead. int n = source.Read(array, numBytesRead, numBytesToRead); // Break when the end of the file is reached. if (n == 0) break; numBytesRead += n; numBytesToRead -= n; } } static private void AssembleDICOMAndRaw(string dicomfn, string rawdata, string outfn) { using ( var fs = new gdcm.FileStreamer() ) { fs.SetTemplateFileName(dicomfn); fs.SetOutputFileName(outfn); gdcm.Tag pixeldata = new gdcm.Tag(0x7fe0, 0x0010); // FileStreamer support automatic checking of pixel data length // based on DICOM attributes, only if we say so: fs.CheckDataElement( pixeldata ); // Declare we are working on Pixel Data attribute: fs.StartDataElement( pixeldata ); using (FileStream rawSource = new FileStream(rawdata, FileMode.Open, FileAccess.Read)) { byte[] bytes = new byte[512]; // Only read one scanline at a time // We could have been reading more at once, if this is more efficient, // AppendToDataElement will do the logic in all cases. for( int i = 0; i < 512 * 1000; ++i ) { // Read the source file into a byte array. ReadBytesIntoArray( bytes, rawSource ); fs.AppendToDataElement( pixeldata, bytes, (uint)bytes.Length ); } } if( !fs.StopDataElement( pixeldata ) ) { // Most likely an issue with Pixel Data Length computation: throw new Exception("StopDataElement failed"); } } } static private void CompressIntoJPEG(string rawdicom, string jpegdicom) { using( var sfcts = FileChangeTransferSyntax.New() ) { // Need to retrieve the actual C++ reference, to pass to // SimpleSubjectWatcher: FileChangeTransferSyntax fcts = sfcts.__ref__(); SimpleSubjectWatcher watcher = new SimpleSubjectWatcher(fcts, "FileChangeTransferSyntax"); gdcm.TransferSyntax ts = new TransferSyntax( TransferSyntax.TSType.JPEGLosslessProcess14_1 ); fcts.SetTransferSyntax( ts ); fcts.SetInputFileName( rawdicom ); fcts.SetOutputFileName( jpegdicom ); fcts.Change(); } } public static int Main(string[] args) { string filename = args[0]; string outfilename = args[1]; string rawfilename = args[2]; string mergefn = args[3]; string jpegfn = args[4]; CreateSmallDICOM(filename); CreateBigDICOM(filename, outfilename); CreateDummyFile(rawfilename, 512 * 512 * 1000 ); AssembleDICOMAndRaw(outfilename, rawfilename, mergefn); CompressIntoJPEG(mergefn, jpegfn); return 0; } }