Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply masks & clean pixel to any type of non compliant image #231

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand All @@ -33,6 +29,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
class ProfileTest {

@Test
Expand Down Expand Up @@ -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<ProfileElementEntity> 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);

}
}