[Gdcm-hackers] gdcm-git:Grassroots DICOM branch master updated. f40d2cc12477b80e68e0ce177c67eb10ff3
Cross-platform DICOM implementation
Brought to you by:
malat
|
From: Mathieu M. <ma...@us...> - 2012-12-19 15:36:58
|
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Grassroots DICOM".
The branch, master has been updated
via f40d2cc12477b80e68e0ce177c67eb10ff34aab5 (commit)
from eeaeada156525bbec9541f640132d77bb255568c (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
https://sourceforge.net/p/gdcm/gdcm/ci/f40d2cc12477b80e68e0ce177c67eb10ff34aab5/
commit f40d2cc12477b80e68e0ce177c67eb10ff34aab5
Author: Mathieu Malaterre <mat...@gm...>
Date: Wed Dec 19 16:33:55 2012 +0100
Provide an implementation of a file-based anonymizer
The current gdcm::Anonymizer class suffer from the 'DOM' design selected in GDCM.
This means that a DataSet needs to be completly held in memory before empty/remove/replac'ing any of its attribute.
This does not work well in all cases, esp limited 32bits java VM. By providing a file based anonymizer
user is now able to realize limited functionalities such as empty/remove/replac'ing attribute directly on file.
diff --git a/Examples/Csharp/CMakeLists.txt b/Examples/Csharp/CMakeLists.txt
index 3e1b627..83ae92a 100644
--- a/Examples/Csharp/CMakeLists.txt
+++ b/Examples/Csharp/CMakeLists.txt
@@ -22,6 +22,7 @@ set(CSHARP_EXAMPLES
CompressLossyJPEG
SendFileSCU
MpegVideoInfo
+ FileAnonymize
)
if(BUILD_TESTING)
diff --git a/Examples/Csharp/FileAnonymize.cs b/Examples/Csharp/FileAnonymize.cs
new file mode 100644
index 0000000..526aed9
--- /dev/null
+++ b/Examples/Csharp/FileAnonymize.cs
@@ -0,0 +1,56 @@
+/*=========================================================================
+
+ 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
+ *
+ * Usage:
+ * $ mono bin/FileAnonymize.exe input.dcm output.dcm
+ */
+using System;
+using gdcm;
+
+public class FileAnonymize
+{
+ public static int Main(string[] args)
+ {
+ string filename = args[0];
+ string outfilename = args[1];
+
+ gdcm.FileAnonymizer fa = new gdcm.FileAnonymizer();
+ fa.SetInputFileName( filename );
+ fa.SetOutputFileName( outfilename );
+
+ // Empty Operations
+ // It will create elements, since those tags are non-registered public elements (2011):
+ fa.Empty( new Tag(0x0008,0x1313) );
+ fa.Empty( new Tag(0x0008,0x1317) );
+ // Remove Operations
+ // The following Tag are actually carefully chosen, since they refer to SQ:
+ fa.Remove( new Tag(0x0008,0x2112) );
+ fa.Remove( new Tag(0x0008,0x9215) );
+ // Replace Operations
+ // do not call replace operation on SQ attribute !
+ fa.Replace( new Tag(0x0018,0x5100), "MYVALUE " );
+ fa.Replace( new Tag(0x0008,0x1160), "MYOTHERVAL" );
+
+ if( !fa.Write() )
+ {
+ System.Console.WriteLine( "Could not write" );
+ return 1;
+ }
+
+ return 0;
+ }
+}
diff --git a/Examples/Cxx/CMakeLists.txt b/Examples/Cxx/CMakeLists.txt
index c9e28b8..4e38525 100644
--- a/Examples/Cxx/CMakeLists.txt
+++ b/Examples/Cxx/CMakeLists.txt
@@ -26,7 +26,6 @@ mark_as_advanced(QT_QMAKE_EXECUTABLE)
endif()
set(EXAMPLES_SRCS
- RemoveInPlace
DumpGEMSMovieGroup
DumpExamCard
ExtractIconFromFile
diff --git a/Examples/Cxx/RemoveInPlace.cxx b/Examples/Cxx/RemoveInPlace.cxx
deleted file mode 100644
index 73c096b..0000000
--- a/Examples/Cxx/RemoveInPlace.cxx
+++ /dev/null
@@ -1,127 +0,0 @@
-/*=========================================================================
-
- 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.
-
-=========================================================================*/
-#include "gdcmReader.h"
-#include "gdcmTag.h"
-#include "gdcmDataElement.h"
-
-#include <fstream>
-
-/*
- * A very simple example to anonymize in place.
- * Let's assume you cannot afford the memory consumption required
- * by gdcmanon --dumb, you could use this example to 'empty' some
- * tags.
- */
-int main(int argc, char *argv[])
-{
- if( argc < 3 )
- {
- std::cerr << argv[0] << " input.dcm output.dcm" << std::endl;
- return 1;
- }
- using namespace gdcm;
-
- // Step 1. Get the offset
- const char *filename = argv[1];
- const char *outfilename = argv[2];
-
- std::ifstream is( filename, std::ios::binary );
-
- gdcm::Tag t1(0x0018,0x0022);
- gdcm::Tag t2(0x0028,0x0002);
- //gdcm::Tag t(0x0025,0x100a);
- const gdcm::Tag &t = t2;
- std::set<gdcm::Tag> removeme;
- removeme.insert( t1 );
-
- gdcm::Reader reader;
- reader.SetStream( is );
- if( !reader.ReadSelectedTags( removeme ) )
- {
- // not DICOM ?
- return 1;
- }
- std::streampos pos0 = is.tellg();
-
- const File & f = reader.GetFile();
- const DataSet &ds = f.GetDataSet();
-
-#if 0
- if( ds.FindDataElement( t1 ) )
- {
- const DataElement &de = ds.GetDataElement( t1 );
- // skip value:
- if( de.GetVL().IsUndefined() )
- {
- // TODO
- assert( 0 );
- }
- else
- {
- is.seekg( de.GetVL(), std::ios::cur );
- }
- }
-#endif
- removeme.clear();
- removeme.insert( t2 );
-
- std::streampos pos1 = is.tellg();
-
- if( !reader.ReadSelectedTags( removeme ) )
- {
- // not DICOM ?
- return 1;
- }
- std::streampos pos = is.tellg();
- is.close();
-
- // Step 2. Copy & skip proper portion
- std::ofstream of( outfilename, std::ios::binary );
- std::ifstream is2( filename, std::ios::binary );
- if( ds.FindDataElement( t ) )
- {
- const DataElement &de = ds.GetDataElement( t );
- int vrlen = de.GetVR().GetLength();
-
- if( de.GetVL().IsUndefined() )
- {
- // TODO
- assert( 0 );
- }
- else
- {
- std::streampos end = pos;
- // seek back:
- end -= de.GetVL();
- end -= vrlen;
-
- // FIXME: most efficient way to copy chunk of file in c++ ?
- for( int i = 0; i < end; ++ i)
- {
- of.put( is2.get() );
- }
- for( int i = 0; i < vrlen; ++ i)
- {
- of.put( 0 );
- }
- is2.seekg( de.GetVL() + vrlen, std::ios::cur );
- }
- }
-
- of << is2.rdbuf();
- of.close();
- is2.close();
-
- return 0;
-}
diff --git a/Source/DataStructureAndEncodingDefinition/gdcmDataSet.txx b/Source/DataStructureAndEncodingDefinition/gdcmDataSet.txx
index f37b86d..d6fcf72 100644
--- a/Source/DataStructureAndEncodingDefinition/gdcmDataSet.txx
+++ b/Source/DataStructureAndEncodingDefinition/gdcmDataSet.txx
@@ -125,6 +125,16 @@ namespace gdcm
const Tag& tag = dataElem.GetTag();
if ( inputStream.fail() || maxTag < tag )
{
+ if( inputStream.good() )
+ {
+ const int l = dataElem.GetVR().GetLength();
+ inputStream.seekg( - 4 - 2 * l, std::ios::cur );
+ }
+ else
+ {
+ inputStream.clear();
+ inputStream.seekg( 0, std::ios::end );
+ }
// Failed to read the tag, or the read tag exceeds the maximum.
// As we assume ascending tag ordering, we can exit the loop.
break;
@@ -181,6 +191,16 @@ namespace gdcm
const Tag tag = dataElem.GetTag();
if ( inputStream.fail() || maxTag < tag )
{
+ if( inputStream.good() )
+ {
+ const int l = dataElem.GetVR().GetLength();
+ inputStream.seekg( - 4 - 2 * l, std::ios::cur );
+ }
+ else
+ {
+ inputStream.clear();
+ inputStream.seekg( 0, std::ios::end );
+ }
// Failed to read the tag, or the read tag exceeds the maximum.
// As we assume ascending tag ordering, we can exit the loop.
break;
diff --git a/Source/MediaStorageAndFileFormat/CMakeLists.txt b/Source/MediaStorageAndFileFormat/CMakeLists.txt
index de172b9..43eb0a6 100644
--- a/Source/MediaStorageAndFileFormat/CMakeLists.txt
+++ b/Source/MediaStorageAndFileFormat/CMakeLists.txt
@@ -2,6 +2,7 @@
# MSFF
set(MSFF_SRCS
gdcmAnonymizer.cxx
+ gdcmFileAnonymizer.cxx
gdcmIconImageFilter.cxx
gdcmIconImageGenerator.cxx
gdcmDICOMDIRGenerator.cxx
diff --git a/Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.cxx b/Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.cxx
new file mode 100644
index 0000000..d3be0cd
--- /dev/null
+++ b/Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.cxx
@@ -0,0 +1,518 @@
+/*=========================================================================
+
+ 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.
+
+=========================================================================*/
+#include "gdcmFileAnonymizer.h"
+
+#include "gdcmReader.h"
+
+#include <fstream>
+#include <set>
+#include <vector>
+#include <map>
+#include <algorithm> // sort
+
+namespace gdcm
+{
+
+enum Action
+{
+ EMPTY,
+ REMOVE,
+ REPLACE
+};
+
+struct PositionEmpty
+{
+ std::streampos BeginPos;
+ std::streampos EndPos;
+ Action action;
+ bool IsTagFound; // Required for EMPTY
+ DataElement DE;
+ bool operator() (const PositionEmpty & i, const PositionEmpty & j)
+ {
+ return (int)i.BeginPos < (int)j.BeginPos;
+ }
+};
+
+class FileAnonymizerInternals
+{
+public:
+ std::string InputFilename;
+ std::string OutputFilename;
+ std::set<Tag> EmptyTags;
+ std::set<Tag> RemoveTags;
+ std::map<Tag, std::string> ReplaceTags;
+ TransferSyntax TS;
+ std::vector<PositionEmpty> PositionEmptyArray;
+};
+
+FileAnonymizer::FileAnonymizer()
+{
+ Internals = new FileAnonymizerInternals;
+}
+
+FileAnonymizer::~FileAnonymizer()
+{
+ delete Internals;
+}
+
+void FileAnonymizer::Empty( Tag const &t )
+{
+ if( t.GetGroup() >= 0x0008 )
+ {
+ Internals->EmptyTags.insert( t );
+ }
+}
+
+void FileAnonymizer::Remove( Tag const &t )
+{
+ if( t.GetGroup() >= 0x0008 )
+ {
+ Internals->RemoveTags.insert( t );
+ }
+}
+
+void FileAnonymizer::Replace( Tag const &t, const char *value )
+{
+ if( t.GetGroup() >= 0x0008 )
+ {
+ Internals->ReplaceTags.insert(
+ std::make_pair<Tag,std::string>( t, value) );
+ }
+}
+
+void FileAnonymizer::Replace( Tag const &t, const char *value, VL const & vl )
+{
+ Internals->ReplaceTags.insert( std::make_pair<Tag,std::string>( t, std::string(value,vl) ) );
+}
+
+void FileAnonymizer::SetInputFileName(const char *filename_native)
+{
+ Internals->InputFilename = filename_native;
+}
+
+void FileAnonymizer::SetOutputFileName(const char *filename_native)
+{
+ Internals->OutputFilename = filename_native;
+}
+
+bool FileAnonymizer::ComputeReplaceTagPosition()
+{
+/*
+ Implementation details:
+ We should make sure that the user know what she is doing in case of SQ.
+ Let's assume a User call Replace( Tag(0x0008,0x2112), "FOOBAR" )
+ For quite a lot of DICOM implementation this Tag is required to be a SQ.
+ Therefore even if the Attribute is declared with VR:UN, some implementation
+ will try very hard to decode it as SQ...which obviously will fail
+ Instead do not support SQ at all here and document it should not be used for SQ
+ */
+ assert( !Internals->InputFilename.empty() );
+ const char *filename = Internals->InputFilename.c_str();
+ assert( filename );
+
+ std::map<Tag, std::string>::reverse_iterator rit = Internals->ReplaceTags.rbegin();
+ for ( ; rit != Internals->ReplaceTags.rend(); rit++ )
+ {
+ PositionEmpty pe;
+
+ std::set<Tag> removeme;
+ const Tag & t = rit->first;
+ const std::string & valuereplace = rit->second;
+ removeme.insert( t );
+
+ std::ifstream is( filename, std::ios::binary );
+ Reader reader;
+ reader.SetStream( is );
+ if( !reader.ReadSelectedTags( removeme ) )
+ {
+ return false;
+ }
+
+ pe.EndPos = pe.BeginPos = is.tellg();
+ pe.action = REPLACE;
+ pe.IsTagFound = false;
+ const File & f = reader.GetFile();
+ const DataSet &ds = f.GetDataSet();
+ const TransferSyntax &ts = f.GetHeader().GetDataSetTransferSyntax();
+ Internals->TS = ts;
+
+ pe.DE.SetTag( t );
+ if( ds.FindDataElement( t ) )
+ {
+ const DataElement &de = ds.GetDataElement( t );
+ pe.IsTagFound = true;
+ pe.DE.SetVL( de.GetVL() ); // Length is not used, unless to check undefined flag
+ pe.DE.SetVR( de.GetVR() );
+ assert( pe.DE.GetVL().IsUndefined() == de.GetVL().IsUndefined() );
+ assert( pe.DE.GetVR() == de.GetVR() );
+ assert( pe.DE.GetTag() == de.GetTag() );
+ if( de.GetVL().IsUndefined() )
+ {
+ // This is a SQ
+ gdcmErrorMacro( "Replacing a SQ is not supported. Use Remove() or Empty()" );
+ return false;
+ }
+ else
+ {
+ assert( !de.GetVL().IsUndefined() );
+ pe.BeginPos -= de.GetVL();
+ pe.BeginPos -= 2 * de.GetVR().GetLength(); // (VR+) VL
+ pe.BeginPos -= 4; // Tag
+ assert( pe.EndPos ==
+ (int)pe.BeginPos + (int)de.GetVL() + 2 * de.GetVR().GetLength() + 4 );
+ }
+ pe.DE.SetByteValue( valuereplace.c_str(), valuereplace.size() );
+ assert( pe.DE.GetVL() == valuereplace.size() );
+ }
+ else
+ {
+ // We need to insert an Empty Data Element !
+ //FIXME, for some public element we could do something nicer than VR:UN
+ pe.DE.SetVR( VR::UN );
+ pe.DE.SetByteValue( valuereplace.c_str(), valuereplace.size() );
+ assert( pe.DE.GetVL() == valuereplace.size() );
+ }
+
+ // We need to push_back outside of if() since Action:Empty
+ // on a missing tag, means insert it !
+ Internals->PositionEmptyArray.push_back( pe );
+
+ is.close();
+ }
+ return true;
+}
+
+bool FileAnonymizer::ComputeRemoveTagPosition()
+{
+ assert( !Internals->InputFilename.empty() );
+ const char *filename = Internals->InputFilename.c_str();
+ assert( filename );
+
+ std::set<Tag>::reverse_iterator rit = Internals->RemoveTags.rbegin();
+ for ( ; rit != Internals->RemoveTags.rend(); rit++ )
+ {
+ PositionEmpty pe;
+
+ std::set<Tag> removeme;
+ const Tag & t = *rit;
+ removeme.insert( t );
+
+ std::ifstream is( filename, std::ios::binary );
+ Reader reader;
+ reader.SetStream( is );
+ if( !reader.ReadSelectedTags( removeme ) )
+ {
+ return false;
+ }
+
+ pe.EndPos = pe.BeginPos = is.tellg();
+ pe.action = REMOVE;
+ pe.IsTagFound = false;
+ const File & f = reader.GetFile();
+ const DataSet &ds = f.GetDataSet();
+ const TransferSyntax &ts = f.GetHeader().GetDataSetTransferSyntax();
+ Internals->TS = ts;
+
+ pe.DE.SetTag( t );
+ if( ds.FindDataElement( t ) )
+ {
+ const DataElement &de = ds.GetDataElement( t );
+ pe.IsTagFound = true;
+ pe.DE.SetVL( de.GetVL() ); // Length is not used, unless to check undefined flag
+ pe.DE.SetVR( de.GetVR() );
+ assert( pe.DE.GetVL().IsUndefined() == de.GetVL().IsUndefined() );
+ assert( pe.DE.GetVR() == de.GetVR() );
+ assert( pe.DE.GetTag() == de.GetTag() );
+ if( de.GetVL().IsUndefined() )
+ {
+ // This is a SQ
+ VL vl;
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ vl = de.GetLength<ImplicitDataElement>();
+ }
+ else
+ {
+ vl = de.GetLength<ExplicitDataElement>();
+ }
+ assert( pe.BeginPos > vl );
+ pe.BeginPos -= vl;
+ }
+ else
+ {
+ assert( !de.GetVL().IsUndefined() );
+ pe.BeginPos -= de.GetVL();
+ pe.BeginPos -= 2 * de.GetVR().GetLength(); // (VR+) VL
+ pe.BeginPos -= 4; // Tag
+ assert( pe.EndPos ==
+ (int)pe.BeginPos + (int)de.GetVL() + 2 * de.GetVR().GetLength() + 4 );
+ }
+ Internals->PositionEmptyArray.push_back( pe );
+ }
+ else
+ {
+ // Yay no need to do anything !
+ }
+
+ is.close();
+ }
+
+ return true;
+}
+
+bool FileAnonymizer::ComputeEmptyTagPosition()
+{
+ // FIXME we sometime empty, attributes that are already empty...
+ assert( !Internals->InputFilename.empty() );
+ const char *filename = Internals->InputFilename.c_str();
+ assert( filename );
+
+ std::set<Tag>::reverse_iterator rit = Internals->EmptyTags.rbegin();
+ for ( ; rit != Internals->EmptyTags.rend(); rit++ )
+ {
+ PositionEmpty pe;
+
+ std::set<Tag> removeme;
+ const Tag & t = *rit;
+ removeme.insert( t );
+
+ std::ifstream is( filename, std::ios::binary );
+ Reader reader;
+ reader.SetStream( is );
+ if( !reader.ReadSelectedTags( removeme ) )
+ {
+ return false;
+ }
+
+ pe.EndPos = pe.BeginPos = is.tellg();
+ pe.action = EMPTY;
+ pe.IsTagFound = false;
+ const File & f = reader.GetFile();
+ const DataSet &ds = f.GetDataSet();
+ const TransferSyntax &ts = f.GetHeader().GetDataSetTransferSyntax();
+ Internals->TS = ts;
+
+ pe.DE.SetTag( t );
+ if( ds.FindDataElement( t ) )
+ {
+ const DataElement &de = ds.GetDataElement( t );
+ pe.IsTagFound = true;
+ pe.DE.SetVL( de.GetVL() ); // Length is not used, unless to check undefined flag
+ pe.DE.SetVR( de.GetVR() );
+ assert( pe.DE.GetVL().IsUndefined() == de.GetVL().IsUndefined() );
+ assert( pe.DE.GetVR() == de.GetVR() );
+ assert( pe.DE.GetTag() == de.GetTag() );
+ if( de.GetVL().IsUndefined() )
+ {
+ // This is a SQ
+ VL vl;
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ vl = de.GetLength<ImplicitDataElement>();
+ }
+ else
+ {
+ vl = de.GetLength<ExplicitDataElement>();
+ }
+ assert( pe.BeginPos > vl );
+ pe.BeginPos -= vl;
+ pe.BeginPos += 4; // Tag
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ pe.BeginPos += 0;
+ }
+ else
+ {
+ pe.BeginPos += de.GetVR().GetLength();
+ }
+ }
+ else
+ {
+ assert( !de.GetVL().IsUndefined() );
+ pe.BeginPos -= de.GetVL();
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ pe.BeginPos -= 4;
+ }
+ else
+ {
+ pe.BeginPos -= de.GetVR().GetLength();
+ assert( pe.EndPos ==
+ (int)pe.BeginPos + (int)de.GetVL() + de.GetVR().GetLength() );
+ }
+ }
+ }
+ else
+ {
+ // We need to insert an Empty Data Element !
+ //FIXME, for some public element we could do something nicer than VR:UN
+ pe.DE.SetVR( VR::UN );
+ pe.DE.SetVL( 0 );
+ }
+
+ // We need to push_back outside of if() since Action:Empty
+ // on a missing tag, means insert it !
+ Internals->PositionEmptyArray.push_back( pe );
+
+ is.close();
+ }
+
+ return true;
+}
+
+bool FileAnonymizer::Write()
+{
+ if( Internals->OutputFilename.empty() ) return false;
+ const char *outfilename = Internals->OutputFilename.c_str();
+ if( Internals->InputFilename.empty() ) return false;
+ const char *filename = Internals->InputFilename.c_str();
+
+ Internals->PositionEmptyArray.clear();
+
+ // Compute offsets
+ if( !ComputeRemoveTagPosition()
+ || !ComputeEmptyTagPosition()
+ || !ComputeReplaceTagPosition() )
+ {
+ return false;
+ }
+
+ // Make sure we will copy from lower offset to highest:
+ PositionEmpty pe_sort;
+ std::sort (Internals->PositionEmptyArray.begin(),
+ Internals->PositionEmptyArray.end(), pe_sort);
+
+ // Step 2. Copy & skip proper portion
+ std::ofstream of( outfilename, std::ios::binary );
+ std::ifstream is( filename, std::ios::binary );
+ std::vector<PositionEmpty>::const_iterator it =
+ Internals->PositionEmptyArray.begin();
+ std::streampos prev = 0;
+ const TransferSyntax &ts = Internals->TS;
+ for( ; it != Internals->PositionEmptyArray.end(); ++it )
+ {
+ const PositionEmpty & pe = *it;
+ Action action = pe.action;
+
+ if( pe.IsTagFound )
+ {
+ const DataElement & de = pe.DE;
+ int vrlen = de.GetVR().GetLength();
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ vrlen = 4;
+ }
+
+ std::streampos end = pe.BeginPos;
+
+ // FIXME: most efficient way to copy chunk of file in c++ ?
+ for( int i = prev; i < end; ++i)
+ {
+ of.put( is.get() );
+ }
+ if( action == EMPTY )
+ {
+ // Create a 0 Value Length (VR+Tag was copied in previous loop)
+ for( int i = 0; i < vrlen; ++i)
+ {
+ of.put( 0 );
+ }
+ }
+ else if( action == REPLACE )
+ {
+ if( ts.GetSwapCode() == SwapCode::BigEndian )
+ {
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ gdcmErrorMacro( "Cant write Virtual Big Endian" );
+ return 1;
+ }
+ else
+ {
+ pe.DE.Write<ExplicitDataElement,SwapperDoOp>( of );
+ }
+ }
+ else
+ {
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ pe.DE.Write<ImplicitDataElement,SwapperNoOp>( of );
+ }
+ else
+ {
+ pe.DE.Write<ExplicitDataElement,SwapperNoOp>( of );
+ }
+ }
+ }
+ // Skip the Value
+ assert( is.good() );
+ is.seekg( pe.EndPos );
+ assert( is.good() );
+ prev = is.tellg();
+ assert( prev == pe.EndPos );
+ }
+ else
+ {
+ std::streampos end = pe.BeginPos;
+
+ // FIXME: most efficient way to copy chunk of file in c++ ?
+ for( int i = prev; i < end; ++i)
+ {
+ of.put( is.get() );
+ }
+ if( ts.GetSwapCode() == SwapCode::BigEndian )
+ {
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ gdcmErrorMacro( "Cant write Virtual Big Endian" );
+ return 1;
+ }
+ else
+ {
+ pe.DE.Write<ExplicitDataElement,SwapperDoOp>( of );
+ }
+ }
+ else
+ {
+ if( ts.GetNegociatedType() == TransferSyntax::Implicit )
+ {
+ pe.DE.Write<ImplicitDataElement,SwapperNoOp>( of );
+ }
+ else
+ {
+ pe.DE.Write<ExplicitDataElement,SwapperNoOp>( of );
+ }
+ }
+ prev = is.tellg();
+ }
+ }
+
+ of << is.rdbuf();
+ of.close();
+ is.close();
+
+#if 0
+ Reader r;
+ r.SetFileName( outfilename );
+ if( !r.Read() )
+ {
+ gdcmErrorMacro( "Output file got corrupted, please report" );
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+} // end namespace gdcm
diff --git a/Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.h b/Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.h
new file mode 100644
index 0000000..cd9db52
--- /dev/null
+++ b/Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.h
@@ -0,0 +1,83 @@
+/*=========================================================================
+
+ 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.
+
+=========================================================================*/
+#ifndef GDCMFILEANONYMIZER_H
+#define GDCMFILEANONYMIZER_H
+
+#include "gdcmSubject.h"
+#include "gdcmEvent.h"
+#include "gdcmTag.h"
+#include "gdcmVL.h"
+
+namespace gdcm
+{
+class FileAnonymizerInternals;
+
+/**
+ * \brief FileAnonymizer
+ *
+ * This Anonymizer is a file-based Anonymizer. It requires a valid DICOM
+ * file and will use the Value Length to skip over any information.
+ *
+ * It will not load the data into memory and should consume much less
+ * memory than gdcm::Anonymizer
+ *
+ * caveats:
+ * This class will NOT work with unordered attributes in a DICOM File.
+ *
+ * This class does neither recompute nor update the Group Length element.
+ *
+ * This class currently does not update the File Meta Information header
+ */
+class GDCM_EXPORT FileAnonymizer : public Subject
+{
+public:
+ FileAnonymizer();
+ ~FileAnonymizer();
+
+ /// Make Tag t empty
+ /// Warning: does not handle SQ element
+ void Empty( Tag const &t );
+
+ /// remove a tag (even a SQ can be removed)
+ void Remove( Tag const &t );
+
+ /// Replace tag with another value, if tag is not found it will be created:
+ /// WARNING: this function can only execute if tag is a VRASCII
+ /// WARNING: Do not ever try to write a value in a SQ Data Element !
+ void Replace( Tag const &t, const char *value );
+
+ /// when the value contains \0, it is a good idea to specify the length. This function
+ /// is required when dealing with VRBINARY tag
+ void Replace( Tag const &t, const char *value, VL const & vl );
+
+ /// Set input filename
+ void SetInputFileName(const char *filename_native);
+
+ /// Set output filename
+ void SetOutputFileName(const char *filename_native);
+
+ /// Write the output file
+ bool Write();
+
+private:
+ bool ComputeEmptyTagPosition();
+ bool ComputeRemoveTagPosition();
+ bool ComputeReplaceTagPosition();
+ FileAnonymizerInternals *Internals;
+
+};
+
+} // end namespace gdcm
+
+#endif //GDCMFILEANONYMIZER_H
diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt b/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt
index a4b17d1..1dc159f 100644
--- a/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt
+++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/CMakeLists.txt
@@ -2,6 +2,9 @@
# MSFF
set(MSFF_TEST_SRCS
TestAnonymizer
+ TestFileAnonymizer1
+ TestFileAnonymizer2
+ TestFileAnonymizer3
TestIconImageFilter
TestIconImageGenerator
TestIconImageGenerator2
diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer1.cxx b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer1.cxx
new file mode 100644
index 0000000..ef676f4
--- /dev/null
+++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer1.cxx
@@ -0,0 +1,117 @@
+/*=========================================================================
+
+ 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.
+
+=========================================================================*/
+#include "gdcmFileAnonymizer.h"
+
+#include "gdcmTesting.h"
+#include "gdcmSystem.h"
+#include "gdcmReader.h"
+
+int TestFileAnonymize1(const char *filename, bool verbose = false)
+{
+ using namespace gdcm;
+ if( verbose )
+ std::cout << "Processing: " << filename << std::endl;
+
+ // Create directory first:
+ const char subdir[] = "TestFileAnonymize1";
+ std::string tmpdir = Testing::GetTempDirectory( subdir );
+ if( !System::FileIsDirectory( tmpdir.c_str() ) )
+ {
+ System::MakeDirectory( tmpdir.c_str() );
+ //return 1;
+ }
+ std::string outfilename = Testing::GetTempFilename( filename, subdir );
+
+ gdcm::Tag t1(0x0018,0x5100);
+ gdcm::Tag t2(0x0018,0x1312);
+ gdcm::Tag t3(0x0018,0x1313);
+ gdcm::Tag t4(0x0018,0x1317);
+ gdcm::Tag t5(0x0008,0x2112);
+ gdcm::Tag t6(0x0008,0x9215);
+ gdcm::Tag t7(0x0018,0x1020);
+ gdcm::Tag t8(0x0004,0x1130); // Test DICOMDIR !
+ gdcm::Tag t9(0x0008,0x0000); // Earliest possible Data Element
+ gdcm::Tag t0(0xffff,0xffff); // Latest Possible Element
+
+ std::vector<gdcm::Tag> tags;
+ tags.push_back( t0 );
+ tags.push_back( t1 );
+ tags.push_back( t2 );
+ tags.push_back( t3 );
+ tags.push_back( t4 );
+ tags.push_back( t5 );
+ tags.push_back( t6 );
+ tags.push_back( t7 );
+ tags.push_back( t8 );
+ tags.push_back( t9 );
+
+ gdcm::FileAnonymizer fa;
+ fa.SetInputFileName( filename );
+ fa.SetOutputFileName( outfilename.c_str() );
+ for( std::vector<gdcm::Tag>::const_iterator it = tags.begin();
+ it != tags.end(); ++it )
+ {
+ fa.Remove( *it );
+ }
+ if( !fa.Write() )
+ {
+ std::cerr << "Failed to write: " << outfilename << std::endl;
+ return 1;
+ }
+
+ // Read back and check:
+ gdcm::Reader r;
+ r.SetFileName( outfilename.c_str() );
+ if( !r.Read() )
+ {
+ return 1;
+ }
+ const File &f = r.GetFile();
+ const DataSet &ds = f.GetDataSet();
+ for( std::vector<gdcm::Tag>::const_iterator it = tags.begin();
+ it != tags.end(); ++it )
+ {
+ const gdcm::Tag & t = *it;
+ if( ds.FindDataElement( t ) )
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int TestFileAnonymizer1(int argc, char *argv[])
+{
+ if( argc == 2 )
+ {
+ const char *filename = argv[1];
+ return TestFileAnonymize1(filename, true);
+ }
+
+ // else
+ gdcm::Trace::DebugOff();
+ gdcm::Trace::WarningOff();
+ gdcm::Trace::ErrorOff();
+ int r = 0, i = 0;
+ const char *filename;
+ const char * const *filenames = gdcm::Testing::GetFileNames();
+ while( (filename = filenames[i]) )
+ {
+ r += TestFileAnonymize1( filename );
+ ++i;
+ }
+
+ return r;
+}
diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer2.cxx b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer2.cxx
new file mode 100644
index 0000000..c8b5730
--- /dev/null
+++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer2.cxx
@@ -0,0 +1,122 @@
+/*=========================================================================
+
+ 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.
+
+=========================================================================*/
+#include "gdcmFileAnonymizer.h"
+
+#include "gdcmTesting.h"
+#include "gdcmSystem.h"
+#include "gdcmReader.h"
+
+static int TestFileAnonymize2(const char *filename, bool verbose = false)
+{
+ using namespace gdcm;
+ if( verbose )
+ std::cout << "Processing: " << filename << std::endl;
+
+ // Create directory first:
+ const char subdir[] = "TestFileAnonymize2";
+ std::string tmpdir = Testing::GetTempDirectory( subdir );
+ if( !System::FileIsDirectory( tmpdir.c_str() ) )
+ {
+ System::MakeDirectory( tmpdir.c_str() );
+ //return 1;
+ }
+ std::string outfilename = Testing::GetTempFilename( filename, subdir );
+
+ gdcm::Tag t1(0x0018,0x5100);
+ gdcm::Tag t2(0x0018,0x1312);
+ gdcm::Tag t3(0x0018,0x1313);
+ gdcm::Tag t4(0x0018,0x1317);
+ gdcm::Tag t5(0x0008,0x2112);
+ gdcm::Tag t6(0x0008,0x9215);
+ gdcm::Tag t7(0x0018,0x1020);
+ gdcm::Tag t8(0x0004,0x1130); // Test DICOMDIR !
+ gdcm::Tag t9(0x0008,0x0000); // Earliest possible Data Element
+ gdcm::Tag t0(0xffff,0xffff); // Latest Possible Element
+
+ std::vector<gdcm::Tag> tags;
+ tags.push_back( t0 );
+ tags.push_back( t1 );
+ tags.push_back( t2 );
+ tags.push_back( t3 );
+ tags.push_back( t4 );
+ tags.push_back( t5 );
+ tags.push_back( t6 );
+ tags.push_back( t7 );
+ tags.push_back( t8 );
+ tags.push_back( t9 );
+
+ gdcm::FileAnonymizer fa;
+ fa.SetInputFileName( filename );
+ fa.SetOutputFileName( outfilename.c_str() );
+ for( std::vector<gdcm::Tag>::const_iterator it = tags.begin();
+ it != tags.end(); ++it )
+ {
+ fa.Empty( *it );
+ }
+ if( !fa.Write() )
+ {
+ std::cerr << "Failed to write: " << outfilename << std::endl;
+ return 1;
+ }
+
+ // Read back and check:
+ gdcm::Reader r;
+ r.SetFileName( outfilename.c_str() );
+ if( !r.Read() )
+ {
+ return 1;
+ }
+ const File &f = r.GetFile();
+ const DataSet &ds = f.GetDataSet();
+ for( std::vector<gdcm::Tag>::const_iterator it = tags.begin();
+ it != tags.end(); ++it )
+ {
+ const gdcm::Tag & t = *it;
+ if( !ds.FindDataElement( t ) )
+ {
+ return 1;
+ }
+ const gdcm::DataElement & de = ds.GetDataElement( t );
+ if( de.GetVL() != 0 )
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int TestFileAnonymizer2(int argc, char *argv[])
+{
+ if( argc == 2 )
+ {
+ const char *filename = argv[1];
+ return TestFileAnonymize2(filename, true);
+ }
+
+ // else
+ gdcm::Trace::DebugOff();
+ gdcm::Trace::WarningOff();
+ gdcm::Trace::ErrorOff();
+ int r = 0, i = 0;
+ const char *filename;
+ const char * const *filenames = gdcm::Testing::GetFileNames();
+ while( (filename = filenames[i]) )
+ {
+ r += TestFileAnonymize2( filename );
+ ++i;
+ }
+
+ return r;
+}
diff --git a/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer3.cxx b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer3.cxx
new file mode 100644
index 0000000..8ae99a6
--- /dev/null
+++ b/Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer3.cxx
@@ -0,0 +1,123 @@
+/*=========================================================================
+
+ 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.
+
+=========================================================================*/
+#include "gdcmFileAnonymizer.h"
+
+#include "gdcmTesting.h"
+#include "gdcmSystem.h"
+#include "gdcmReader.h"
+
+#include <vector>
+
+static int TestFileAnonymize3(const char *filename, bool verbose = false)
+{
+ using namespace gdcm;
+ if( verbose )
+ std::cout << "Processing: " << filename << std::endl;
+
+ // Create directory first:
+ const char subdir[] = "TestFileAnonymize3";
+ std::string tmpdir = Testing::GetTempDirectory( subdir );
+ if( !System::FileIsDirectory( tmpdir.c_str() ) )
+ {
+ System::MakeDirectory( tmpdir.c_str() );
+ //return 1;
+ }
+ std::string outfilename = Testing::GetTempFilename( filename, subdir );
+
+ gdcm::Tag t1(0x0018,0x5100);
+ gdcm::Tag t2(0x0018,0x1312);
+ gdcm::Tag t3(0x0018,0x1313);
+ gdcm::Tag t4(0x0018,0x1317);
+ //gdcm::Tag t5(0x0008,0x2112); // SQ
+ //gdcm::Tag t6(0x0008,0x9215); // SQ
+ gdcm::Tag t7(0x0018,0x1020);
+ gdcm::Tag t8(0x0004,0x1130); // Test DICOMDIR !
+ gdcm::Tag t9(0x0008,0x0000); // Earliest possible Data Element
+ gdcm::Tag t0(0xffff,0xffff); // Latest Possible Element
+
+ std::vector<gdcm::Tag> tags;
+ tags.push_back( t0 );
+ tags.push_back( t1 );
+ tags.push_back( t2 );
+ tags.push_back( t3 );
+ tags.push_back( t4 );
+ tags.push_back( t7 );
+ tags.push_back( t8 );
+ tags.push_back( t9 );
+
+ gdcm::FileAnonymizer fa;
+ fa.SetInputFileName( filename );
+ fa.SetOutputFileName( outfilename.c_str() );
+ for( std::vector<gdcm::Tag>::const_iterator it = tags.begin();
+ it != tags.end(); ++it )
+ {
+ fa.Replace( *it, "TOTO" );
+ }
+ if( !fa.Write() )
+ {
+ std::cerr << "Failed to write: " << outfilename << std::endl;
+ return 1;
+ }
+
+ // Read back and check:
+ gdcm::Reader r;
+ r.SetFileName( outfilename.c_str() );
+ if( !r.Read() )
+ {
+ return 1;
+ }
+ const File &f = r.GetFile();
+ const DataSet &ds = f.GetDataSet();
+ for( std::vector<gdcm::Tag>::const_iterator it = tags.begin();
+ it != tags.end(); ++it )
+ {
+ const gdcm::Tag & t = *it;
+ if( !ds.FindDataElement( t ) )
+ {
+ return 1;
+ }
+ const gdcm::DataElement & de = ds.GetDataElement( t );
+ if( de.GetVL() != 4 )
+ {
+ return 1;
+ }
+ }
+
+
+ return 0;
+}
+
+int TestFileAnonymizer3(int argc, char *argv[])
+{
+ if( argc == 2 )
+ {
+ const char *filename = argv[1];
+ return TestFileAnonymize3(filename, true);
+ }
+
+ // else
+ gdcm::Trace::DebugOff();
+ gdcm::Trace::WarningOff();
+ gdcm::Trace::ErrorOff();
+ int r = 0, i = 0;
+ const char *filename;
+ const char * const *filenames = gdcm::Testing::GetFileNames();
+ while( (filename = filenames[i]) )
+ {
+ r += TestFileAnonymize3( filename );
+ ++i;
+ }
+
+ return r;
+}
diff --git a/Wrapping/Csharp/gdcm.i b/Wrapping/Csharp/gdcm.i
index e8d533c..29bcc2d 100644
--- a/Wrapping/Csharp/gdcm.i
+++ b/Wrapping/Csharp/gdcm.i
@@ -139,6 +139,7 @@ public class";
#include "gdcmSubject.h"
#include "gdcmCommand.h"
#include "gdcmAnonymizer.h"
+#include "gdcmFileAnonymizer.h"
#include "gdcmSystem.h"
#include "gdcmTrace.h"
#include "gdcmUIDs.h"
@@ -593,7 +594,7 @@ EXTEND_CLASS_PRINT(gdcm::Scanner)
//%feature("unref") Anonymizer "coucou $this->Delete();"
// http://www.swig.org/Doc1.3/SWIGPlus.html#SWIGPlus%5Fnn34
%include "gdcmAnonymizer.h"
-
+%include "gdcmFileAnonymizer.h"
//EXTEND_CLASS_PRINT(gdcm::Anonymizer)
//%extend gdcm::Anonymizer
-----------------------------------------------------------------------
Summary of changes:
Examples/Csharp/CMakeLists.txt | 1 +
Examples/Csharp/FileAnonymize.cs | 56 +++
Examples/Cxx/CMakeLists.txt | 1 -
Examples/Cxx/RemoveInPlace.cxx | 127 -----
.../gdcmDataSet.txx | 20 +
Source/MediaStorageAndFileFormat/CMakeLists.txt | 1 +
.../gdcmFileAnonymizer.cxx | 518 ++++++++++++++++++++
.../MediaStorageAndFileFormat/gdcmFileAnonymizer.h | 83 ++++
.../MediaStorageAndFileFormat/Cxx/CMakeLists.txt | 3 +
.../Cxx/TestFileAnonymizer1.cxx | 117 +++++
.../Cxx/TestFileAnonymizer2.cxx | 122 +++++
.../Cxx/TestFileAnonymizer3.cxx | 123 +++++
Wrapping/Csharp/gdcm.i | 3 +-
13 files changed, 1046 insertions(+), 129 deletions(-)
create mode 100644 Examples/Csharp/FileAnonymize.cs
delete mode 100644 Examples/Cxx/RemoveInPlace.cxx
create mode 100644 Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.cxx
create mode 100644 Source/MediaStorageAndFileFormat/gdcmFileAnonymizer.h
create mode 100644 Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer1.cxx
create mode 100644 Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer2.cxx
create mode 100644 Testing/Source/MediaStorageAndFileFormat/Cxx/TestFileAnonymizer3.cxx
hooks/post-receive
--
Grassroots DICOM
|