Mercurial > hg > orthanc
changeset 6056:7902d830c9a5 pixel-anon
pixel masking: handling of multiframe images
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Mon, 24 Mar 2025 16:34:15 +0100 |
parents | 0a60d2d482d4 |
children | 035a13b295a7 |
files | OrthancFramework/Sources/DicomFormat/DicomTag.h OrthancFramework/Sources/DicomParsing/DicomPixelMasker.cpp TODO |
diffstat | 3 files changed, 107 insertions(+), 15 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancFramework/Sources/DicomFormat/DicomTag.h Tue Mar 18 10:08:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h Mon Mar 24 16:34:15 2025 +0100 @@ -102,6 +102,8 @@ static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e); static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060); + static const DicomTag DICOM_TAG_DETECTOR_INFORMATION_SEQUENCE(0x0054, 0x0022); + // The following is used for "modify/anonymize" operations static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002); @@ -194,6 +196,7 @@ static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); + static const DicomTag DICOM_TAG_SPACING_BETWEEN_SLICES(0x0018, 0x0088); static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); static const DicomTag DICOM_TAG_DOSE_GRID_SCALING(0x3004, 0x000e);
--- a/OrthancFramework/Sources/DicomParsing/DicomPixelMasker.cpp Tue Mar 18 10:08:43 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/DicomPixelMasker.cpp Mon Mar 24 16:34:15 2025 +0100 @@ -116,18 +116,12 @@ return false; } - static void GetDoubleVector(std::vector<double>& target, const ParsedDicomFile& file, const DicomTag& tag, size_t expectedSize) + static void GetDoubleVector(std::vector<double>& target, const std::string& strValue, const DicomTag& tag, size_t expectedSize) { target.clear(); - std::string str; - if (!file.GetTagValue(str, tag)) - { - throw OrthancException(ErrorCode_InexistentTag, "Unable to perform 3D -> 2D conversion, missing tag" + tag.Format()); - } - std::vector<std::string> strVector; - Toolbox::SplitString(strVector, str, '\\'); + Toolbox::SplitString(strVector, strValue, '\\'); if (strVector.size() != expectedSize) { @@ -147,32 +141,88 @@ } } + static void GetDoubleVector(std::vector<double>& target, const ParsedDicomFile& file, const DicomTag& tag, size_t expectedSize) + { + std::string str; + if (!file.GetTagValue(str, tag)) + { + throw OrthancException(ErrorCode_InexistentTag, "Unable to perform 3D -> 2D conversion, missing tag" + tag.Format()); + } + + GetDoubleVector(target, str, tag, expectedSize); + } + bool DicomPixelMasker::Region3D::GetPixelMaskArea(unsigned int& x1, unsigned int& y1, unsigned int& x2, unsigned int& y2, const ParsedDicomFile& file, unsigned int frameIndex) const { if (IsTargeted(file)) { + DicomMap tags; + file.ExtractDicomSummary(tags, 256); + std::vector<double> imagePositionPatient; std::vector<double> imageOrientationPatient; std::vector<double> pixelSpacing; + double sliceSpacing = 0.0; - GetDoubleVector(imagePositionPatient, file, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); - GetDoubleVector(imageOrientationPatient, file, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6); + if (file.HasTag(DICOM_TAG_IMAGE_POSITION_PATIENT) && file.HasTag(DICOM_TAG_IMAGE_ORIENTATION_PATIENT)) + { + GetDoubleVector(imagePositionPatient, file, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); + GetDoubleVector(imageOrientationPatient, file, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6); + } + else if (file.HasTag(DICOM_TAG_DETECTOR_INFORMATION_SEQUENCE)) // find it in the detector info sequence (for some multi frame instances like NM or scintigraphy) TODO-PIXEL-ANON: to validate + { + const Json::Value& jsonSequence = tags.GetValue(DICOM_TAG_DETECTOR_INFORMATION_SEQUENCE).GetSequenceContent(); + if (jsonSequence.size() == 1) + { + std::string strImagePositionPatient = jsonSequence[0]["0020,0032"]["Value"].asString(); + std::string strImageOrientationPatient = jsonSequence[0]["0020,0037"]["Value"].asString(); + + GetDoubleVector(imagePositionPatient, strImagePositionPatient, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); + GetDoubleVector(imageOrientationPatient, strImageOrientationPatient, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, 6); + } + else + { + throw OrthancException(ErrorCode_InternalError, "Unable to find ImagePositionPatient in DetectorInformationSequence, invalid sequence size"); + } + } + else + { + throw OrthancException(ErrorCode_InternalError, "Unable to find ImagePositionPatient or ImageOrientationPatient"); + } + GetDoubleVector(pixelSpacing, file, DICOM_TAG_PIXEL_SPACING, 2); + if (file.HasTag(DICOM_TAG_SPACING_BETWEEN_SLICES)) + { + std::string strSliceSpacing; + if (file.GetTagValue(strSliceSpacing, DICOM_TAG_SPACING_BETWEEN_SLICES)) + { + sliceSpacing = boost::lexical_cast<double>(strSliceSpacing); + } + } + + double z = imagePositionPatient[2]; + + if (sliceSpacing != 0.0) + { + z = z - frameIndex * sliceSpacing; + } + // note: To simplify, for the z, we only check that imagePositionPatient is between the authorized z values. // This won't be perfectly true for weird images with slices that are not parallel but let's wait for someone to complain ... - if (imagePositionPatient[2] < std::min(z1_, z2_) || - imagePositionPatient[2] > std::max(z1_, z2_)) + if (z < std::min(z1_, z2_) || + z > std::max(z1_, z2_)) { return false; } + double deltaX1 = x1_ - imagePositionPatient[0]; double deltaY1 = y1_ - imagePositionPatient[1]; - double deltaZ1 = z1_ - imagePositionPatient[2]; + double deltaZ1 = z1_ - z; double deltaX2 = x2_ - imagePositionPatient[0]; double deltaY2 = y2_ - imagePositionPatient[1]; - double deltaZ2 = z2_ - imagePositionPatient[2]; + double deltaZ2 = z2_ - z; double ix1 = (deltaX1 * imageOrientationPatient[0] + deltaY1 * imageOrientationPatient[1] + deltaZ1 * imageOrientationPatient[2]) / pixelSpacing[0]; double iy1 = (deltaX1 * imageOrientationPatient[3] + deltaY1 * imageOrientationPatient[4] + deltaZ1 * imageOrientationPatient[5]) / pixelSpacing[1];
--- a/TODO Tue Mar 18 10:08:43 2025 +0100 +++ b/TODO Mon Mar 24 16:34:15 2025 +0100 @@ -570,4 +570,43 @@ }] } } -EOF \ No newline at end of file +EOF + + +# modify some frames of a multiframe scintigraphy study +curl -X POST http://localhost:8043/studies/ab67d5f8-95865506-8fb83c8b-93610651-ddce6e77/modify \ +--data-binary @- << EOF +{ + "Replace" : {"StudyInstanceUID": "1.2.6", "StudyDescription": "modified-scinti"}, + "Force": true, + "MaskPixelData": { + "Regions": [{ + "MaskType": "Fill", + "FillValue": 3000, + "RegionType" : "3D", + "Origin": [-150.0, -200, 200], + "End": [150.0, -150, 100] + }] + } +} +EOF + + +# modify some frames of a multiframe PET-CT study +curl -X POST http://localhost:8043/studies/890e1167-55ad171a-7721ffec-db91e2c1-700778c0/modify \ +--data-binary @- << EOF +{ + "Replace" : {"StudyInstanceUID": "1.2.7", "StudyDescription": "modified-pet-ct"}, + "Force": true, + "MaskPixelData": { + "Regions": [{ + "MaskType": "Fill", + "FillValue": 3000, + "RegionType" : "3D", + "Origin": [-150.0, -100, -85], + "End": [150.0, 100, -250] + }] + } +} +EOF +