From 5e749eab6fee9e70706ded27b1e4073b7973aa69 Mon Sep 17 00:00:00 2001 From: mhex Date: Tue, 25 Feb 2025 14:29:43 +0100 Subject: [PATCH] Test & small refactoring in isCleanPixelAllowedDependingImageType method --- .../backend/service/profilepipe/Profile.java | 17 ++-- .../service/profilepipe/ProfileTest.java | 87 +++++++++++++++++-- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/karnak/backend/service/profilepipe/Profile.java b/src/main/java/org/karnak/backend/service/profilepipe/Profile.java index 7dbe77c5..7c071854 100644 --- a/src/main/java/org/karnak/backend/service/profilepipe/Profile.java +++ b/src/main/java/org/karnak/backend/service/profilepipe/Profile.java @@ -197,12 +197,11 @@ public void applyCleanPixelData(Attributes dcmCopy, AttributeEditorContext conte if (!StringUtil.hasText(sopClassUID)) { throw new IllegalStateException("DICOM Object does not contain sopClassUID"); } - String scuPattern = sopClassUID + "."; MaskArea mask = getMask(dcmCopy.getString(Tag.StationName)); // A mask must be applied with all the US and Secondary Capture sopClassUID, // and with // BurnedInAnnotation - if (isCleanPixelAllowedDependingImageType(dcmCopy, sopClassUID, scuPattern) + if (isCleanPixelAllowedDependingImageType(dcmCopy, sopClassUID) && evaluateConditionCleanPixelData(dcmCopy)) { context.setMaskArea(mask); if (mask == null) { @@ -219,16 +218,16 @@ && evaluateConditionCleanPixelData(dcmCopy)) { * Determine if the clean pixel should be applied depending on the image type * @param dcmCopy Attributes * @param sopClassUID SopClassUID - * @param scuPattern Pattern * @return true if the clean pixel could be applied */ - private boolean isCleanPixelAllowedDependingImageType(Attributes dcmCopy, String sopClassUID, String scuPattern) { + boolean isCleanPixelAllowedDependingImageType(Attributes dcmCopy, String sopClassUID) { // A mask must be applied with all the US and Secondary Capture sopClassUID, and - // with - // BurnedInAnnotation - return scuPattern.startsWith("1.2.840.10008.5.1.4.1.1.6.") - || scuPattern.startsWith("1.2.840.10008.5.1.4.1.1.7.") - || scuPattern.startsWith("1.2.840.10008.5.1.4.1.1.3.") + // with BurnedInAnnotation + String sopPattern = sopClassUID + "."; + + return sopPattern.startsWith("1.2.840.10008.5.1.4.1.1.6.") + || sopPattern.startsWith("1.2.840.10008.5.1.4.1.1.7.") + || sopPattern.startsWith("1.2.840.10008.5.1.4.1.1.3.") || sopClassUID.equals("1.2.840.10008.5.1.4.1.1.77.1.1") || "YES".equalsIgnoreCase(dcmCopy.getString(Tag.BurnedInAnnotation)); } diff --git a/src/test/java/org/karnak/backend/service/profilepipe/ProfileTest.java b/src/test/java/org/karnak/backend/service/profilepipe/ProfileTest.java index 83b9cf51..bc9586f6 100644 --- a/src/test/java/org/karnak/backend/service/profilepipe/ProfileTest.java +++ b/src/test/java/org/karnak/backend/service/profilepipe/ProfileTest.java @@ -13,14 +13,10 @@ import org.dcm4che3.data.Tag; import org.dcm4che3.data.VR; import org.junit.jupiter.api.Test; -import org.karnak.backend.data.entity.DestinationEntity; -import org.karnak.backend.data.entity.MaskEntity; -import org.karnak.backend.data.entity.ProfileElementEntity; -import org.karnak.backend.data.entity.ProfileEntity; -import org.karnak.backend.data.entity.ProjectEntity; -import org.karnak.backend.data.entity.SecretEntity; +import org.karnak.backend.data.entity.*; import org.karnak.backend.enums.DestinationType; import org.karnak.backend.enums.PseudonymType; +import org.springframework.boot.test.context.SpringBootTest; import org.weasis.dicom.param.AttributeEditorContext; import java.awt.*; @@ -33,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +@SpringBootTest class ProfileTest { @Test @@ -175,4 +172,82 @@ void should_evaluate_condition_clean_pixel_case_include_station_name() { assertTrue(evaluation); } + @Test + void cleanPixelData_imageTypeIsXA_forceCleanPixelByStationNumber() { + // Use case : X Ray Angiography that contains identifying information + // but the BurnedInAnnotation attribute is not set + ProfileEntity profileEntity = new ProfileEntity(); + Attributes attributes = new Attributes(); + attributes.setString(Tag.Modality, VR.CS, "XA"); + attributes.setString(Tag.SOPClassUID, VR.UI, "1.2.840.10008.5.1.4.1.1.12.1"); + attributes.setString(Tag.StationName, VR.SH, "ICT256"); + // Similar object but not from a station that put information in the image + // The BurnedInAnnotation attribute should not be added and the mask not applied + Attributes attributes2 = new Attributes(); + attributes2.setString(Tag.Modality, VR.CS, "XA"); + attributes2.setString(Tag.SOPClassUID, VR.UI, "1.2.840.10008.5.1.4.1.1.12.1"); + attributes2.setString(Tag.StationName, VR.SH, "ICT258"); + + ProfileElementEntity profileElementEntityAddBurnedAttr = new ProfileElementEntity(); + profileElementEntityAddBurnedAttr.setCodename("action.add.tag"); + profileElementEntityAddBurnedAttr.setName("Add tag BurnedInAnnotation if does not exist"); + profileElementEntityAddBurnedAttr.setCondition("tagValueContains(#Tag.StationName,'ICT256') && !tagIsPresent(#Tag.BurnedInAnnotation)"); + profileElementEntityAddBurnedAttr.addArgument(new ArgumentEntity("value", "YES", profileElementEntityAddBurnedAttr)); + profileElementEntityAddBurnedAttr.addArgument(new ArgumentEntity("vr", "CS", profileElementEntityAddBurnedAttr)); + profileElementEntityAddBurnedAttr.addIncludedTag(new IncludedTagEntity("(0028,0301)", profileElementEntityAddBurnedAttr)); + profileElementEntityAddBurnedAttr.setPosition(1); + + ProfileElementEntity profileElementEntityCleanPixelPerMachine = new ProfileElementEntity(); + profileElementEntityCleanPixelPerMachine.setCodename("expression.on.tags"); + profileElementEntityCleanPixelPerMachine.setName("Set BurnedInAnnotation to YES"); + profileElementEntityCleanPixelPerMachine.setCondition("tagValueContains(#Tag.StationName,'ICT256')"); + profileElementEntityCleanPixelPerMachine.addArgument(new ArgumentEntity("expr", "Replace('YES')", profileElementEntityCleanPixelPerMachine)); + profileElementEntityCleanPixelPerMachine.addIncludedTag(new IncludedTagEntity("(0028,0301)", profileElementEntityCleanPixelPerMachine)); + profileElementEntityCleanPixelPerMachine.setPosition(2); + + Set profileElementEntities = new HashSet<>(); + ProfileElementEntity profileElementEntityCleanPixelData = new ProfileElementEntity(); + profileElementEntityCleanPixelData.setCodename("clean.pixel.data"); + profileElementEntityCleanPixelData.setName("nameCleanPixel"); + profileElementEntityCleanPixelData.setAction("ReplaceNull"); + profileElementEntityCleanPixelData.setPosition(3); + + profileElementEntities.add(profileElementEntityCleanPixelData); + profileElementEntities.add(profileElementEntityAddBurnedAttr); + profileElementEntities.add(profileElementEntityCleanPixelPerMachine); + profileEntity.setProfileElementEntities(profileElementEntities); + Profile profile = new Profile(profileEntity); + + // First object : should not match the mask application conditions + boolean evaluation = profile.evaluateConditionCleanPixelData(attributes); + String sopClassUID = attributes.getString(Tag.SOPClassUID); + boolean cleanPixelAllowed = profile.isCleanPixelAllowedDependingImageType(attributes, sopClassUID); + // As such the image doesn't comply with the requirements to apply a mask + assertFalse(cleanPixelAllowed && evaluation); + + // Second object : should not match the mask application conditions + evaluation = profile.evaluateConditionCleanPixelData(attributes2); + sopClassUID = attributes2.getString(Tag.SOPClassUID); + cleanPixelAllowed = profile.isCleanPixelAllowedDependingImageType(attributes2, sopClassUID); + // As such the image doesn't comply with the requirements to apply a mask + assertFalse(cleanPixelAllowed && evaluation); + + // Apply the profile that adds the BurnedInAnnotation attribute to both objects + profile.applyAction(attributes, attributes, null, null, null, null); + profile.applyAction(attributes2, attributes2, null, null, null, null); + + // The BurnedInAnnotation attribute is set to YES in the first object + assertEquals("YES", attributes.getString(Tag.BurnedInAnnotation)); + // The BurnedInAnnotation attribute does not exist in the second object + assertNull(attributes2.getString(Tag.BurnedInAnnotation)); + + // First object : should match the mask application conditions + evaluation = profile.evaluateConditionCleanPixelData(attributes); + sopClassUID = attributes.getString(Tag.SOPClassUID); + cleanPixelAllowed = profile.isCleanPixelAllowedDependingImageType(attributes, sopClassUID); + // Since the attribute BurnedInAnnotation is set to YES, + // the application conditions for the mask are fulfilled + assertTrue(cleanPixelAllowed && evaluation); + + } }