changeset 6039:4742b4fe824b pixel-anon

pixel-anon: multiple frames
author Alain Mazy <am@orthanc.team>
date Wed, 12 Mar 2025 09:59:38 +0100
parents ee903fefedbf
children 100a0371fc85
files .hgignore OrthancFramework/Resources/CodeGeneration/DicomTransferSyntaxes.json OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache OrthancFramework/Sources/DicomParsing/DicomPixelMasker.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h OrthancServer/Sources/ServerContext.cpp TODO
diffstat 8 files changed, 457 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue Mar 11 10:47:31 2025 +0100
+++ b/.hgignore	Wed Mar 12 09:59:38 2025 +0100
@@ -8,6 +8,7 @@
 *~
 *.cmake.orig
 .idea/
+OrthancFramework/Resources/CodeGeneration/.venv/
 
 # when opening Orthanc in VSCode, it might find a java project and create files we wan't to ignore:
 .settings/
--- a/OrthancFramework/Resources/CodeGeneration/DicomTransferSyntaxes.json	Tue Mar 11 10:47:31 2025 +0100
+++ b/OrthancFramework/Resources/CodeGeneration/DicomTransferSyntaxes.json	Wed Mar 12 09:59:38 2025 +0100
@@ -5,7 +5,8 @@
     "Value" : "LittleEndianImplicit",
     "Retired" : false,
     "DCMTK" : "EXS_LittleEndianImplicit",
-    "GDCM" : "gdcm::TransferSyntax::ImplicitVRLittleEndian"
+    "GDCM" : "gdcm::TransferSyntax::ImplicitVRLittleEndian",
+    "Raw": true, "Lossless": true
   },
   
   {
@@ -14,7 +15,8 @@
     "Value" : "LittleEndianExplicit",
     "Retired" : false,
     "DCMTK" : "EXS_LittleEndianExplicit",
-    "GDCM" : "gdcm::TransferSyntax::ExplicitVRLittleEndian"
+    "GDCM" : "gdcm::TransferSyntax::ExplicitVRLittleEndian",
+    "Raw": true, "Lossless": true
   },
   
   {
@@ -22,7 +24,8 @@
     "Name" : "Deflated Explicit VR Little Endian",
     "Value" : "DeflatedLittleEndianExplicit",
     "Retired" : false,
-    "DCMTK" : "EXS_DeflatedLittleEndianExplicit"
+    "DCMTK" : "EXS_DeflatedLittleEndianExplicit",
+    "Raw": false, "Lossless": true
   },
   
   {
@@ -30,7 +33,8 @@
     "Name" : "Explicit VR Big Endian",
     "Value" : "BigEndianExplicit",
     "Retired" : false,
-    "DCMTK" : "EXS_BigEndianExplicit"
+    "DCMTK" : "EXS_BigEndianExplicit",
+    "Raw": true, "Lossless": true
   },
   
   {
@@ -41,7 +45,8 @@
     "Note" : "Default Transfer Syntax for Lossy JPEG 8-bit Image Compression",
     "DCMTK" : "EXS_JPEGProcess1",
     "DCMTK360" : "EXS_JPEGProcess1TransferSyntax",
-    "GDCM" : "gdcm::TransferSyntax::JPEGBaselineProcess1"
+    "GDCM" : "gdcm::TransferSyntax::JPEGBaselineProcess1",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -52,7 +57,8 @@
     "Note" : "Default Transfer Syntax for Lossy JPEG (lossy, 8/12 bit), 12-bit Image Compression (Process 4 only)",
     "DCMTK" : "EXS_JPEGProcess2_4",
     "DCMTK360" : "EXS_JPEGProcess2_4TransferSyntax",
-    "GDCM" : "gdcm::TransferSyntax::JPEGExtendedProcess2_4"
+    "GDCM" : "gdcm::TransferSyntax::JPEGExtendedProcess2_4",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -61,7 +67,8 @@
     "Value" : "JPEGProcess3_5",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess3_5",
-    "DCMTK360" : "EXS_JPEGProcess3_5TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess3_5TransferSyntax",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -70,7 +77,8 @@
     "Value" : "JPEGProcess6_8",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess6_8",
-    "DCMTK360" : "EXS_JPEGProcess6_8TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess6_8TransferSyntax",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -79,7 +87,8 @@
     "Value" : "JPEGProcess7_9",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess7_9",
-    "DCMTK360" : "EXS_JPEGProcess7_9TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess7_9TransferSyntax",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -88,7 +97,8 @@
     "Value" : "JPEGProcess10_12",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess10_12",
-    "DCMTK360" : "EXS_JPEGProcess10_12TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess10_12TransferSyntax",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -97,7 +107,8 @@
     "Value" : "JPEGProcess11_13",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess11_13",
-    "DCMTK360" : "EXS_JPEGProcess11_13TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess11_13TransferSyntax",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -107,7 +118,8 @@
     "Retired" : false,
     "DCMTK" : "EXS_JPEGProcess14",
     "DCMTK360" : "EXS_JPEGProcess14TransferSyntax",
-    "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14"
+    "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -116,7 +128,8 @@
     "Value" : "JPEGProcess15",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess15",
-    "DCMTK360" : "EXS_JPEGProcess15TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess15TransferSyntax",
+    "Raw": false, "Lossless": true
   },
   
   {
@@ -125,7 +138,8 @@
     "Value" : "JPEGProcess16_18",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess16_18",
-    "DCMTK360" : "EXS_JPEGProcess16_18TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess16_18TransferSyntax",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -134,7 +148,8 @@
     "Value" : "JPEGProcess17_19",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess17_19",
-    "DCMTK360" : "EXS_JPEGProcess17_19TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess17_19TransferSyntax",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -143,7 +158,8 @@
     "Value" : "JPEGProcess20_22",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess20_22",
-    "DCMTK360" : "EXS_JPEGProcess20_22TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess20_22TransferSyntax",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -152,7 +168,8 @@
     "Value" : "JPEGProcess21_23",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess21_23",
-    "DCMTK360" : "EXS_JPEGProcess21_23TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess21_23TransferSyntax",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -161,7 +178,8 @@
     "Value" : "JPEGProcess24_26",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess24_26",
-    "DCMTK360" : "EXS_JPEGProcess24_26TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess24_26TransferSyntax",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -170,7 +188,8 @@
     "Value" : "JPEGProcess25_27",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess25_27",
-    "DCMTK360" : "EXS_JPEGProcess25_27TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess25_27TransferSyntax",
+    "Raw": false, "Lossless": false
   },
   
   {
@@ -179,7 +198,8 @@
     "Value" : "JPEGProcess28",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess28",
-    "DCMTK360" : "EXS_JPEGProcess28TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess28TransferSyntax",
+    "Raw": false, "Lossless": true
   },
   
   {
@@ -188,7 +208,8 @@
     "Value" : "JPEGProcess29",
     "Retired" : true,
     "DCMTK" : "EXS_JPEGProcess29",
-    "DCMTK360" : "EXS_JPEGProcess29TransferSyntax"
+    "DCMTK360" : "EXS_JPEGProcess29TransferSyntax",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -199,7 +220,8 @@
     "Note" : "Default Transfer Syntax for Lossless JPEG Image Compression",
     "DCMTK" : "EXS_JPEGProcess14SV1",
     "DCMTK360" : "EXS_JPEGProcess14SV1TransferSyntax",
-    "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14_1"
+    "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14_1",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -208,7 +230,8 @@
     "Value" : "JPEGLSLossless",
     "Retired" : false,
     "DCMTK" : "EXS_JPEGLSLossless",
-    "GDCM" : "gdcm::TransferSyntax::JPEGLSLossless"
+    "GDCM" : "gdcm::TransferSyntax::JPEGLSLossless",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -217,7 +240,8 @@
     "Value" : "JPEGLSLossy",
     "Retired" : false,
     "DCMTK" : "EXS_JPEGLSLossy",
-    "GDCM" : "gdcm::TransferSyntax::JPEGLSNearLossless"
+    "GDCM" : "gdcm::TransferSyntax::JPEGLSNearLossless",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -226,7 +250,8 @@
     "Value" : "JPEG2000LosslessOnly",
     "Retired" : false,
     "DCMTK" : "EXS_JPEG2000LosslessOnly",
-    "GDCM" : "gdcm::TransferSyntax::JPEG2000Lossless"
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000Lossless",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -235,7 +260,8 @@
     "Value" : "JPEG2000",
     "Retired" : false,
     "DCMTK" : "EXS_JPEG2000",
-    "GDCM" : "gdcm::TransferSyntax::JPEG2000"
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -244,7 +270,8 @@
     "Value" : "JPEG2000MulticomponentLosslessOnly",
     "Retired" : false,
     "DCMTK" : "EXS_JPEG2000MulticomponentLosslessOnly",
-    "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2Lossless"
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2Lossless",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -253,7 +280,8 @@
     "Value" : "JPEG2000Multicomponent",
     "Retired" : false,
     "DCMTK" : "EXS_JPEG2000Multicomponent",
-    "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2"
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -261,7 +289,8 @@
     "Name" : "JPIP Referenced",
     "Value" : "JPIPReferenced",
     "Retired" : false,
-    "DCMTK" : "EXS_JPIPReferenced"
+    "DCMTK" : "EXS_JPIPReferenced",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -269,7 +298,8 @@
     "Name" : "JPIP Referenced Deflate",
     "Value" : "JPIPReferencedDeflate",
     "Retired" : false,
-    "DCMTK" : "EXS_JPIPReferencedDeflate"
+    "DCMTK" : "EXS_JPIPReferencedDeflate",
+    "Raw": false, "Lossless": true
   },
 
   {
@@ -277,7 +307,8 @@
     "Name" : "MPEG2 Main Profile / Main Level",
     "Value" : "MPEG2MainProfileAtMainLevel",
     "Retired" : false,
-    "DCMTK" : "EXS_MPEG2MainProfileAtMainLevel"
+    "DCMTK" : "EXS_MPEG2MainProfileAtMainLevel",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -285,7 +316,8 @@
     "Name" : "MPEG2 Main Profile / High Level",
     "Value" : "MPEG2MainProfileAtHighLevel",
     "Retired" : false,
-    "DCMTK" : "EXS_MPEG2MainProfileAtHighLevel"
+    "DCMTK" : "EXS_MPEG2MainProfileAtHighLevel",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -294,7 +326,8 @@
     "Value" : "MPEG4HighProfileLevel4_1",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4HighProfileLevel4_1",
-    "SinceDCMTK" : "361"
+    "SinceDCMTK" : "361",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -303,7 +336,8 @@
     "Value" : "MPEG4BDcompatibleHighProfileLevel4_1",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4BDcompatibleHighProfileLevel4_1",
-    "SinceDCMTK" : "361"
+    "SinceDCMTK" : "361",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -312,7 +346,8 @@
     "Value" : "MPEG4HighProfileLevel4_2_For2DVideo",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For2DVideo",
-    "SinceDCMTK" : "361"
+    "SinceDCMTK" : "361",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -321,7 +356,8 @@
     "Value" : "MPEG4HighProfileLevel4_2_For3DVideo",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For3DVideo",
-    "SinceDCMTK" : "361"
+    "SinceDCMTK" : "361",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -330,7 +366,8 @@
     "Value" : "MPEG4StereoHighProfileLevel4_2",
     "Retired" : false,
     "DCMTK" : "EXS_MPEG4StereoHighProfileLevel4_2",
-    "SinceDCMTK" : "361"
+    "SinceDCMTK" : "361",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -339,7 +376,8 @@
     "Value" : "HEVCMainProfileLevel5_1",
     "Retired" : false,
     "DCMTK" : "EXS_HEVCMainProfileLevel5_1",
-    "SinceDCMTK" : "362"
+    "SinceDCMTK" : "362",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -348,7 +386,8 @@
     "Value" : "HEVCMain10ProfileLevel5_1",
     "Retired" : false,
     "DCMTK" : "EXS_HEVCMain10ProfileLevel5_1",
-    "SinceDCMTK" : "362"
+    "SinceDCMTK" : "362",
+    "Raw": false, "Lossless": false
   },
 
   {
@@ -357,7 +396,8 @@
     "Value" : "RLELossless",
     "Retired" : false,
     "DCMTK" : "EXS_RLELossless",
-    "GDCM" : "gdcm::TransferSyntax::RLELossless"
+    "GDCM" : "gdcm::TransferSyntax::RLELossless",
+    "Raw": false, "Lossless": true
   },
 
   {
--- a/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache	Tue Mar 11 10:47:31 2025 +0100
+++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache	Wed Mar 12 09:59:38 2025 +0100
@@ -82,4 +82,44 @@
     target.insert(DicomTransferSyntax_{{Value}});
     {{/Syntaxes}}
   }
+
+
+  bool IsLossyTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      {{#Syntaxes}}
+      case DicomTransferSyntax_{{Value}}:
+        {{#Lossless}}
+        return false;
+        {{/Lossless}}
+        {{^Lossless}}
+        return true;
+        {{/Lossless}}
+
+      {{/Syntaxes}}
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool IsRawTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      {{#Syntaxes}}
+      case DicomTransferSyntax_{{Value}}:
+        {{#Raw}}
+        return true;
+        {{/Raw}}
+        {{^Raw}}
+        return false;
+        {{/Raw}}
+
+      {{/Syntaxes}}
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/OrthancFramework/Sources/DicomParsing/DicomPixelMasker.cpp	Tue Mar 11 10:47:31 2025 +0100
+++ b/OrthancFramework/Sources/DicomParsing/DicomPixelMasker.cpp	Wed Mar 12 09:59:38 2025 +0100
@@ -58,7 +58,6 @@
 
     for (std::list<Region>::const_iterator r = regions_.begin(); r != regions_.end(); ++r)
     {
-      // LOG(INFO) << " +++ " << seriesId << "   " << instanceId;
       if (r->targetSeries_.size() > 0 && r->targetSeries_.find(seriesId) == r->targetSeries_.end())
       {
         continue;
@@ -69,16 +68,21 @@
         continue;
       }
 
-      ImageAccessor imageRegion;
-      toModify.GetRawFrame(0)->GetRegion(imageRegion, r->x_, r->y_, r->width_, r->height_);  // TODO-PIXEL-ANON: handle frames
-
-      if (r->mode_ == DicomPixelMaskerMode_MeanFilter)
+      for (unsigned int i = 0; i < toModify.GetFramesCount(); ++i)
       {
-        ImageProcessing::MeanFilter(imageRegion, r->filterWidth_, r->filterWidth_);
-      }
-      else if (r->mode_ == DicomPixelMaskerMode_Fill)
-      {
-        ImageProcessing::Set(imageRegion, r->fillValue_);
+        // TODO-PIXEL-ANON: skip unwanted frames
+
+        ImageAccessor imageRegion;
+        toModify.GetRawFrame(i)->GetRegion(imageRegion, r->x_, r->y_, r->width_, r->height_);  
+
+        if (r->mode_ == DicomPixelMaskerMode_MeanFilter)
+        {
+          ImageProcessing::MeanFilter(imageRegion, r->filterWidth_, r->filterWidth_);
+        }
+        else if (r->mode_ == DicomPixelMaskerMode_Fill)
+        {
+          ImageProcessing::Set(imageRegion, r->fillValue_);
+        }
       }
     }
   }
--- a/OrthancFramework/Sources/Enumerations.h	Tue Mar 11 10:47:31 2025 +0100
+++ b/OrthancFramework/Sources/Enumerations.h	Wed Mar 12 09:59:38 2025 +0100
@@ -953,6 +953,12 @@
   DicomTransferSyntax GetTransferSyntax(const std::string& uid);
 
   ORTHANC_PUBLIC
+  bool IsRawTransferSyntax(DicomTransferSyntax syntax);
+
+  ORTHANC_PUBLIC
+  bool IsLossyTransferSyntax(DicomTransferSyntax syntax);
+
+  ORTHANC_PUBLIC
   const char* GetResourceTypeText(ResourceType type,
                                   bool isPlural,
                                   bool isUpperCase);
--- a/OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h	Tue Mar 11 10:47:31 2025 +0100
+++ b/OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h	Wed Mar 12 09:59:38 2025 +0100
@@ -602,4 +602,276 @@
     target.insert(DicomTransferSyntax_RFC2557MimeEncapsulation);
     target.insert(DicomTransferSyntax_XML);
   }
+
+
+  bool IsLossyTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case DicomTransferSyntax_LittleEndianImplicit:
+        return false;
+
+      case DicomTransferSyntax_LittleEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_DeflatedLittleEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_BigEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess1:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess2_4:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess3_5:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess6_8:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess7_9:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess10_12:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess11_13:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess14:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess15:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess16_18:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess17_19:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess20_22:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess21_23:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess24_26:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess25_27:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess28:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess29:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess14SV1:
+        return false;
+
+      case DicomTransferSyntax_JPEGLSLossless:
+        return false;
+
+      case DicomTransferSyntax_JPEGLSLossy:
+        return true;
+
+      case DicomTransferSyntax_JPEG2000LosslessOnly:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000:
+        return true;
+
+      case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000Multicomponent:
+        return true;
+
+      case DicomTransferSyntax_JPIPReferenced:
+        return false;
+
+      case DicomTransferSyntax_JPIPReferencedDeflate:
+        return false;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtMainLevel:
+        return true;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtHighLevel:
+        return true;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_1:
+        return true;
+
+      case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1:
+        return true;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo:
+        return true;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo:
+        return true;
+
+      case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2:
+        return true;
+
+      case DicomTransferSyntax_HEVCMainProfileLevel5_1:
+        return true;
+
+      case DicomTransferSyntax_HEVCMain10ProfileLevel5_1:
+        return true;
+
+      case DicomTransferSyntax_RLELossless:
+        return false;
+
+      case DicomTransferSyntax_RFC2557MimeEncapsulation:
+        return true;
+
+      case DicomTransferSyntax_XML:
+        return true;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool IsRawTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case DicomTransferSyntax_LittleEndianImplicit:
+        return true;
+
+      case DicomTransferSyntax_LittleEndianExplicit:
+        return true;
+
+      case DicomTransferSyntax_DeflatedLittleEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_BigEndianExplicit:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess1:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess2_4:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess3_5:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess6_8:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess7_9:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess10_12:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess11_13:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess14:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess15:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess16_18:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess17_19:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess20_22:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess21_23:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess24_26:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess25_27:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess28:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess29:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess14SV1:
+        return false;
+
+      case DicomTransferSyntax_JPEGLSLossless:
+        return false;
+
+      case DicomTransferSyntax_JPEGLSLossy:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000LosslessOnly:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000Multicomponent:
+        return false;
+
+      case DicomTransferSyntax_JPIPReferenced:
+        return false;
+
+      case DicomTransferSyntax_JPIPReferencedDeflate:
+        return false;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtMainLevel:
+        return false;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtHighLevel:
+        return false;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_1:
+        return false;
+
+      case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1:
+        return false;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo:
+        return false;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo:
+        return false;
+
+      case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2:
+        return false;
+
+      case DicomTransferSyntax_HEVCMainProfileLevel5_1:
+        return false;
+
+      case DicomTransferSyntax_HEVCMain10ProfileLevel5_1:
+        return false;
+
+      case DicomTransferSyntax_RLELossless:
+        return false;
+
+      case DicomTransferSyntax_RFC2557MimeEncapsulation:
+        return false;
+
+      case DicomTransferSyntax_XML:
+        return false;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/OrthancServer/Sources/ServerContext.cpp	Tue Mar 11 10:47:31 2025 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Wed Mar 12 09:59:38 2025 +0100
@@ -2027,7 +2027,7 @@
     DicomTransferSyntax currentTransferSyntax;
     if (modification.RequiresUncompressedTransferSyntax() && 
         dicomFile->LookupTransferSyntax(currentTransferSyntax) &&
-        currentTransferSyntax > DicomTransferSyntax_BigEndianExplicit)  // TODO-PIXEL-ANON: write a function IsRawTransferSyntax()
+        !IsRawTransferSyntax(currentTransferSyntax))
     {
       IDicomTranscoder::DicomImage source;
       source.AcquireParsed(*dicomFile);  // "dicomFile" is invalid below this point
@@ -2038,13 +2038,15 @@
       uncompressedTransferSyntax.insert(DicomTransferSyntax_LittleEndianExplicit);
       Transcode(transcoded, source, uncompressedTransferSyntax, true);
 
-      if (currentTransferSyntax == DicomTransferSyntax_JPEGProcess1)  // TODO-PIXEL-ANON: write a function IsLossyTransferSyntax()
+      dicomFile.reset(transcoded.ReleaseAsParsedDicomFile());
+
+      if (IsLossyTransferSyntax(currentTransferSyntax))
       {
         // TODO-PIXEL-ANON:  Test this path with IngestTranscoding = lossy syntax
         // TODO-PIXEL-ANON:  + Test with source = lossy + modification requires a transcoding to a raw TS -> the transcoding shall not be performed after pixel modification since it is already being done now but the SOPInstance UID must be changed afterwards
         
         // this means we have moved from lossy to raw -> the SOPInstanceUID should have changed here but, 
-        // keep the SOPInstanceUID unchanged during this pre-transcoding to make sure the orthanc ids are 
+        // let's keep the SOPInstanceUID unchanged during this pre-transcoding to make sure the orthanc ids are 
         // still the original ones when the pixelMasker is applied (since the pixelMasker has a filter on Orthanc ids).
         // however, after the modification, we must make sure that we change the SOPInstanceUID.
         if (dicomFile.get() && dicomFile->GetDcmtkObject().getDataset())
@@ -2063,7 +2065,7 @@
         transcode = true;
         targetSyntax = currentTransferSyntax;
       }
-      dicomFile.reset(transcoded.ReleaseAsParsedDicomFile());
+      
     }
 
     modification.Apply(*dicomFile);
--- a/TODO	Tue Mar 11 10:47:31 2025 +0100
+++ b/TODO	Wed Mar 12 09:59:38 2025 +0100
@@ -465,7 +465,7 @@
 curl -X POST http://localhost:8043/studies/321d3848-40c81c82-49f6f235-df6b1ec7-ed52f2fa/modify \
 --data-binary @- << EOF
 {
-    "Replace" : {"StudyInstanceUID": "1.2.3", "StudyDescription": "modified-all-slices"},
+    "Replace" : {"StudyInstanceUID": "1.2.2", "StudyDescription": "modified-all-slices"},
     "Force": true,
     "MaskPixelData": {
         "Regions": [{
@@ -499,3 +499,40 @@
     }
 }
 EOF
+
+
+# modify all frames of a multiframe study (CARDIO)
+curl -X POST http://localhost:8043/studies/595df1a1-74fe920a-4b9e3509-826f17a3-762a2dc3/modify \
+--data-binary @- << EOF
+{
+    "Replace" : {"StudyInstanceUID": "1.2.4", "StudyDescription": "modified-multi-frame"},
+    "Force": true,
+    "MaskPixelData": {
+        "Regions": [{
+            "MaskType": "MeanFilter",
+            "FilterWidth": 30,
+            "RegionType" : "Pixels",
+            "Origin": [150, 100],
+            "End": [400, 200]
+      }]
+    }
+}
+EOF
+
+# modify all frames of a multiframe US study (Cine US, TS = .50)
+curl -X POST http://localhost:8043/studies/50f2961c-c35755eb-8762b05b-0e01cd97-dd8a294c/modify \
+--data-binary @- << EOF
+{
+    "Replace" : {"StudyInstanceUID": "1.2.5", "StudyDescription": "modified-multi-frame-us"},
+    "Force": true,
+    "MaskPixelData": {
+        "Regions": [{
+            "MaskType": "MeanFilter",
+            "FilterWidth": 30,
+            "RegionType" : "Pixels",
+            "Origin": [0, 0],
+            "End": [768, 45]
+      }]
+    }
+}
+EOF
\ No newline at end of file