7 #include "fwGdcmIO/helper/DicomAnonymizer.hpp" 9 #include "fwGdcmIO/exception/InvalidTag.hpp" 10 #include "fwGdcmIO/helper/CsvIO.hpp" 11 #include "fwGdcmIO/helper/DicomSearch.hpp" 12 #include "fwGdcmIO/helper/tags.hpp" 14 #include <fwCore/base.hpp> 16 #include <fwJobs/IJob.hpp> 17 #include <fwJobs/Observer.hpp> 19 #include <fwRuntime/operations.hpp> 21 #include <fwTools/System.hpp> 23 #include <boost/algorithm/string/join.hpp> 24 #include <boost/assign/list_of.hpp> 25 #include <boost/date_time/gregorian/gregorian.hpp> 26 #include <boost/exception/all.hpp> 27 #include <boost/filesystem/fstream.hpp> 28 #include <boost/filesystem/operations.hpp> 29 #include <boost/range/algorithm/for_each.hpp> 31 #include <gdcmGlobal.h> 32 #include <gdcmReader.h> 33 #include <gdcmUIDGenerator.h> 34 #include <gdcmWriter.h> 45 const std::string c_MIN_DATE_STRING =
"19000101";
47 ::gdcm::UIDGenerator GENERATOR;
50 m_publicDictionary(::gdcm::Global::GetInstance().GetDicts().GetPublicDict()),
51 m_observer(::
fwJobs::Observer::New(
"Anonymization process")),
54 m_referenceDate(::
boost::gregorian::from_undelimited_string(c_MIN_DATE_STRING))
56 const ::boost::filesystem::path tagsPathStr = ::fwRuntime::getLibraryResourceFilePath(
57 "fwGdcmIO-" FWGDCMIO_VER
"/tags.csv");
58 ::boost::filesystem::path tagsPath = tagsPathStr;
59 SLM_ASSERT(
"File '" + tagsPath.string() +
"' must exists",
60 ::boost::filesystem::is_regular_file(tagsPath));
62 auto csvStream = std::ifstream(tagsPath.string());
66 while(!tagVec.empty())
70 const std::string errorMessage =
"Error when loading tag '" + ::boost::algorithm::join(tagVec,
", ") +
"'";
75 const std::string& actionCode = tagVec[0];
76 ::gdcm::Tag tag = ::fwGdcmIO::helper::getGdcmTag(tagVec[1], tagVec[2]);
80 m_actionCodeDTags.insert(tag);
82 else if(actionCode ==
"Z" || actionCode ==
"Z/D")
84 m_actionCodeZTags.insert(tag);
86 else if(actionCode ==
"X" 87 || actionCode ==
"X/Z" 88 || actionCode ==
"X/D" 89 || actionCode ==
"X/Z/D" 90 || actionCode ==
"X/Z/U*")
92 m_actionCodeXTags.insert(tag);
94 else if(actionCode ==
"K")
96 m_actionCodeKTags.insert(tag);
98 else if(actionCode ==
"C")
100 m_actionCodeCTags.insert(tag);
102 else if(actionCode ==
"U")
104 m_actionCodeUTags.insert(tag);
108 OSLM_ERROR(
"Action code '" + actionCode +
"' is not managed.");
111 tagVec = csvReader.getLine();
125 m_referenceDate = referenceDate;
133 m_observer->setTotalWorkUnits(100);
134 this->anonymizationProcess(dirPath);
135 m_observer->finish();
140 void moveDirectory(const ::boost::filesystem::path& input,
141 const ::boost::filesystem::path& output)
143 ::boost::system::error_code ec;
144 ::boost::filesystem::copy_directory(input, output, ec);
145 FW_RAISE_IF(
"copy_directory " << input.string() <<
" " << output.string()
146 <<
" error : " << ec.message(), ec.value());
148 ::boost::filesystem::directory_iterator it(input);
149 ::boost::filesystem::directory_iterator end;
150 ::boost::filesystem::permissions(output, ::boost::filesystem::owner_all, ec);
151 OSLM_ERROR_IF(
"set " << output.string() <<
" permission error : " << ec.message(), ec.value());
153 for(; it != end; ++it)
155 ::boost::filesystem::path dest = output / it->path().filename();
156 if(::boost::filesystem::is_directory(*it))
158 moveDirectory(*it, dest);
162 ::boost::filesystem::rename(*it, dest, ec);
163 FW_RAISE_IF(
"rename " << it->path().string() <<
" " << dest.string()
164 <<
" error : " << ec.message(), ec.value());
167 ::boost::filesystem::permissions(dest, ::boost::filesystem::owner_all, ec);
168 OSLM_ERROR_IF(
"set " << dest.string() <<
" permission error : " << ec.message(), ec.value());
176 m_actionCodeDTags.erase(tag);
177 m_actionCodeZTags.erase(tag);
178 m_actionCodeXTags.erase(tag);
179 m_actionCodeKTags.erase(tag);
180 m_actionCodeCTags.erase(tag);
181 m_actionCodeUTags.erase(tag);
186 const DicomAnonymizer::TagContainerType& DicomAnonymizer::getActionCodeDTags()
188 return m_actionCodeDTags;
193 const DicomAnonymizer::TagContainerType& DicomAnonymizer::getActionCodeZTags()
195 return m_actionCodeZTags;
200 const DicomAnonymizer::TagContainerType& DicomAnonymizer::getActionCodeXTags()
202 return m_actionCodeXTags;
207 const DicomAnonymizer::TagContainerType& DicomAnonymizer::getActionCodeKTags()
209 return m_actionCodeKTags;
214 const DicomAnonymizer::TagContainerType& DicomAnonymizer::getActionCodeCTags()
216 return m_actionCodeCTags;
221 const DicomAnonymizer::TagContainerType& DicomAnonymizer::getActionCodeUTags()
223 return m_actionCodeUTags;
228 void DicomAnonymizer::anonymizationProcess(const ::boost::filesystem::path& dirPath)
235 moveDirectory(dirPath, tmpPath);
237 ::boost::system::error_code ec;
239 ::boost::filesystem::directory_iterator it(dirPath);
240 ::boost::filesystem::directory_iterator end;
242 for(; it != end; ++it)
244 if(::boost::filesystem::is_directory(*it))
246 ::boost::filesystem::remove_all((*it), ec);
247 FW_RAISE_IF(
"remove_all " + dirPath.string() +
" error : " + ec.message(), ec.value());
251 std::vector< ::boost::filesystem::path > dicomFiles;
254 unsigned int fileIndex = 0;
255 for(
const auto file : dicomFiles)
257 if(m_observer->cancelRequested())
262 ::boost::filesystem::ifstream inStream(file, std::ios::binary);
264 std::stringstream ss;
265 ss << std::setfill(
'0') << std::setw(7) << fileIndex++;
267 ::boost::filesystem::ofstream outStream(dirPath / ss.str(), std::ios::binary | std::ios::trunc);
271 std::uint64_t progress =
static_cast<std::uint64_t
>(
272 ((m_archiving) ? 50 : 100) *
static_cast<float>(fileIndex) / static_cast<float>(dicomFiles.size()));
273 m_observer->doneWork(progress);
289 return m_fileIndex++;
297 ::gdcm::Reader reader;
298 reader.SetStream(inputStream);
299 FW_RAISE_IF(
"Unable to anonymize (file read failed)", !reader.Read());
302 m_stringFilter.SetFile(reader.GetFile());
306 ::gdcm::DataElement dataElement;
307 const ::gdcm::File& datasetFile = reader.GetFile();
308 ::gdcm::DataSet dataset = datasetFile.GetDataSet();
310 std::vector< ::gdcm::DataElement > preservedTags;
311 for(const ::gdcm::Tag& t : m_privateTags)
313 if(dataset.FindDataElement(t))
315 preservedTags.push_back(dataset.GetDataElement(t));
319 m_anonymizer.SetFile( datasetFile );
321 m_anonymizer.RemoveGroupLength();
322 m_anonymizer.RemoveRetired();
324 for(ExceptionTagMapType::value_type exception : m_exceptionTagMap)
326 m_anonymizer.Replace(exception.first, exception.second.c_str());
330 for(
const auto& dateTag : m_actionShiftDateTags)
332 this->applyActionShiftDate(dateTag);
335 for(
const auto& tag : m_actionCodeDTags)
337 this->applyActionCodeD(tag);
339 for(
const auto& tag : m_actionCodeZTags)
341 this->applyActionCodeZ(tag);
343 for(
const auto& tag : m_actionCodeXTags)
345 this->applyActionCodeX(tag);
347 for(
const auto& tag : m_actionCodeKTags)
349 this->applyActionCodeK(tag);
351 for(
const auto& tag : m_actionCodeCTags)
353 this->applyActionCodeC(tag);
355 for(
const auto& tag : m_actionCodeUTags)
357 this->applyActionCodeU(tag);
360 auto applyActionCodeXWithException = [
this](const ::gdcm::Tag& tag)
362 if(m_exceptionTagMap.find(tag) == m_exceptionTagMap.end())
364 this->applyActionCodeX(tag);
368 const auto dataElementSet = dataset.GetDES();
371 auto element = dataElementSet.lower_bound(::gdcm::Tag(0x5000, 0x0));
372 auto end = dataElementSet.upper_bound(::gdcm::Tag(0x50ff, 0xffff));
373 for(; element != end; ++element)
375 tag = element->GetTag();
376 applyActionCodeXWithException(tag);
380 element = dataElementSet.lower_bound(::gdcm::Tag(0x6000, 0x3000));
381 end = dataElementSet.upper_bound(::gdcm::Tag(0x60ff, 0x4000));
382 for(; element != end; ++element)
384 tag = element->GetTag();
385 if(tag.GetElement() == 0x3000 || tag.GetElement() == 0x4000)
387 applyActionCodeXWithException(tag);
391 m_anonymizer.RemovePrivateTags();
393 for(const ::gdcm::DataElement& de : preservedTags)
399 ::gdcm::Writer writer;
400 writer.SetStream(outputStream);
401 writer.SetFile(datasetFile);
403 FW_RAISE_IF(
"Unable to anonymize (file write failed)", !writer.Write());
410 ::gdcm::Tag tag(group, element);
412 m_exceptionTagMap[tag] = value;
414 m_actionCodeDTags.erase(tag);
415 m_actionCodeZTags.erase(tag);
416 m_actionCodeXTags.erase(tag);
417 m_actionCodeKTags.erase(tag);
418 m_actionCodeCTags.erase(tag);
419 m_actionCodeUTags.erase(tag);
426 const bool found = std::find(m_privateTags.begin(), m_privateTags.end(), tag) != m_privateTags.end();
427 OSLM_WARN_IF(
"Private tag " << tag.GetGroup() <<
", " << tag.GetElement() <<
" has already been added", !found);
431 m_privateTags.push_back(tag);
437 void DicomAnonymizer::applyActionCodeD(const ::gdcm::Tag& tag)
440 if(m_publicDictionary.GetDictEntry(tag).GetVR() == ::gdcm::VR::SQ)
442 m_anonymizer.Empty(tag);
446 this->generateDummyValue(tag);
452 void DicomAnonymizer::applyActionCodeZ(const ::gdcm::Tag& tag)
454 this->applyActionCodeD(tag);
459 void DicomAnonymizer::applyActionCodeX(const ::gdcm::Tag& tag)
461 m_anonymizer.Remove(tag);
466 void DicomAnonymizer::applyActionCodeK(const ::gdcm::Tag& tag)
469 if(m_publicDictionary.GetDictEntry(tag).GetVR() == ::gdcm::VR::SQ)
471 m_anonymizer.Empty(tag);
477 void DicomAnonymizer::applyActionCodeC(const ::gdcm::Tag& tag)
479 SLM_FATAL(
"Basic profile \"C\" is not supported yet: " 480 "Only basic profile is supported by the current implementation.");
485 void DicomAnonymizer::applyActionCodeU(const ::gdcm::Tag& tag)
487 const std::string oldUID = m_stringFilter.ToString(tag);
490 auto it = m_uidMap.find(oldUID);
493 if(it == m_uidMap.end())
495 uid = GENERATOR.Generate();
496 m_uidMap.insert(std::pair<std::string, std::string>(oldUID, uid));
503 m_anonymizer.Replace(tag, uid.c_str());
511 m_actionCodeDTags.erase(tag);
512 m_actionCodeZTags.erase(tag);
513 m_actionCodeXTags.erase(tag);
514 m_actionCodeKTags.erase(tag);
515 m_actionCodeCTags.erase(tag);
516 m_actionCodeUTags.erase(tag);
518 m_actionShiftDateTags.insert(tag);
523 void DicomAnonymizer::applyActionShiftDate(const ::gdcm::Tag& tag)
525 const std::string oldDate = m_stringFilter.ToString(tag);
526 const ::boost::gregorian::date date = ::boost::gregorian::from_undelimited_string(oldDate);
528 const auto shift = date - m_referenceDate;
531 const ::boost::gregorian::date min_date = ::boost::gregorian::from_undelimited_string(c_MIN_DATE_STRING);
533 const auto shiftedDate = min_date + shift;
535 m_anonymizer.Replace(tag, ::boost::gregorian::to_iso_string(shiftedDate).c_str());
540 void DicomAnonymizer::generateDummyValue(const ::gdcm::Tag& tag)
543 switch (m_publicDictionary.GetDictEntry(tag).GetVR())
547 m_anonymizer.Replace(tag,
"ANONYMIZED");
552 m_anonymizer.Replace(tag,
"000Y");
557 m_anonymizer.Replace(tag,
"00H,00H,00H,00H");
563 if(tag == ::gdcm::Tag(0x0010, 0x0040))
565 m_anonymizer.Replace(tag,
"O");
569 m_anonymizer.Replace(tag,
"ANONYMIZED");
575 m_anonymizer.Replace(tag, c_MIN_DATE_STRING.c_str());
580 m_anonymizer.Replace(tag,
"0");
585 m_anonymizer.Replace(tag, std::string(c_MIN_DATE_STRING +
"000000.000000").c_str());
590 m_anonymizer.Replace(tag,
"0");
595 m_anonymizer.Replace(tag,
"0");
600 m_anonymizer.Replace(tag,
"0");
605 m_anonymizer.Replace(tag,
"ANONYMIZED");
610 m_anonymizer.Replace(tag,
"ANONYMIZED");
615 m_anonymizer.Replace(tag,
"00H00H");
620 m_anonymizer.Replace(tag,
"0");
625 m_anonymizer.Replace(tag,
"0");
630 m_anonymizer.Replace(tag,
"ANONYMIZED^ANONYMIZED");
635 m_anonymizer.Replace(tag,
"ANONYMIZED");
640 m_anonymizer.Replace(tag,
"0");
645 m_anonymizer.Empty(tag);
650 m_anonymizer.Replace(tag,
"0");
655 m_anonymizer.Replace(tag,
"ANONYMIZED");
660 m_anonymizer.Replace(tag,
"000000.000000");
665 m_anonymizer.Replace(tag,
"ANONYMIZED");
670 m_anonymizer.Replace(tag,
"0");
675 m_anonymizer.Replace(tag,
"ANONYMIZED");
680 m_anonymizer.Replace(tag,
"0");
685 m_anonymizer.Replace(tag,
"ANONYMIZED");
690 OSLM_ERROR(tag<<
" is not supported. Emptied value. ");
691 m_anonymizer.Empty(tag);
700 const ::boost::filesystem::path& output)
702 ::boost::system::error_code ec;
703 ::boost::filesystem::copy_directory(input, output, ec);
704 FW_RAISE_IF(
"copy_directory " << input.string() <<
" " << output.string()
705 <<
" error : " << ec.message(), ec.value());
707 ::boost::filesystem::directory_iterator it(input);
708 ::boost::filesystem::directory_iterator end;
711 ::boost::filesystem::permissions(output, ::boost::filesystem::owner_all, ec);
712 SLM_ERROR_IF(
"set " + output.string() +
" permission error : " + ec.message(), ec.value());
714 for(; it != end; ++it)
716 ::boost::filesystem::path dest = output / it->path().filename();
717 if(::boost::filesystem::is_directory(*it))
724 ::boost::filesystem::ifstream inStream(it->path(), std::ios::binary);
725 FW_RAISE_IF(
"Unable to read file :" << it->path().string(), !inStream.good());
726 ::boost::filesystem::ofstream outStream(dest, std::ios::binary);
727 FW_RAISE_IF(
"Unable to write file :" << dest.string(), !outStream.good());
729 outStream << inStream.rdbuf();
736 ::boost::filesystem::permissions(dest, ::boost::filesystem::owner_all, ec);
737 SLM_ERROR_IF(
"set " + dest.string() +
" permission error : " + ec.message(), ec.value());
FWGDCMIO_API void setReferenceDate(const ::boost::gregorian::date &referenceDate)
Set Reference date for shifting.
FWGDCMIO_API void addShiftDateTag(const ::gdcm::Tag &tag)
Add a date tag that must be shifted. The shift is made according to the interval between the date and...
virtual FWGDCMIO_API ~DicomAnonymizer()
Destructor.
This class is an interface for class managing job.
FWGDCMIO_API void addExceptionTag(uint16_t group, uint16_t element, const std::string &value="")
Add an exceptional value for a tag.
FWGDCMIO_API DicomAnonymizer()
Constructor.
The namespace fwGdcmIO contains reader, writer and helper for dicom data.
FWGDCMIO_API void preservePrivateTag(const ::gdcm::Tag &tag)
Tells the anonymizer to do not anonymize the given private tag.
FWGDCMIO_API void resetIndex()
Reset file index to 0.
#define SLM_FATAL(message)
#define OSLM_ERROR(message)
This class contains helpers to anonymize dicom files on filesystem. Anonymization is performed accord...
#define SLM_ERROR_IF(message, cond)
static FWGDCMIO_API void searchRecursively(const ::boost::filesystem::path &dirPath, std::vector< ::boost::filesystem::path > &dicomFiles, bool checkIsDicom, const std::shared_ptr< ::fwJobs::Observer > &fileLookupObserver=nullptr)
Search Dicom files recursively by excluding files with known extensions.
#define SLM_ASSERT(message, cond)
work like 'assert' from 'cassert', with in addition a message logged by spylog (with FATAL loglevel) ...
std::vector< std::string > TokenContainerType
Containers to store parsed tokens.
static FWGDCMIO_API void copyDirectory(const ::boost::filesystem::path &input, const ::boost::filesystem::path &output)
Copy a directory recursively.
FWGDCMIO_API std::shared_ptr< ::fwJobs::IJob > getJob() const
Get job observer.
FWGDCMIO_API void removeAnonymizeTag(const ::gdcm::Tag &tag)
The removed tag will not be process by anonymization tag.
Read CSV file and returns parsed tokens. The input file is supposed to use comma separator, but another separator can be used when reading file.
#define OSLM_WARN_IF(message, cond)
This namespace fwJobs provides jobs management.
#define OSLM_ERROR_IF(message, cond)
FWGDCMIO_API void anonymize(const ::boost::filesystem::path &dirPath)
Anonymize a folder containing Dicom files.
FWGDCMIO_API unsigned int getNextIndex()
Return next file index.