Skip to content

Commit

Permalink
Add categories for anatomic regions
Browse files Browse the repository at this point in the history
  • Loading branch information
nroduit committed Jun 14, 2024
1 parent 9e3204a commit 520d96b
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.dcm4che3.img.lut.ModalityLutModule;
import org.dcm4che3.img.lut.VoiLutModule;
import org.dcm4che3.img.util.DicomUtils;
import org.weasis.dicom.ref.AnatomicRegion;

/**
* @author Nicolas Roduit
Expand All @@ -33,7 +34,7 @@ public final class ImageDescriptor {
private final int bitsCompressed;
private final int pixelRepresentation;
private final String sopClassUID;
private final String bodyPartExamined;
private final AnatomicRegion anatomicRegion;
private final int frames;
private final List<EmbeddedOverlay> embeddedOverlay;
private final List<OverlayData> overlayData;
Expand Down Expand Up @@ -67,7 +68,7 @@ public ImageDescriptor(Attributes dcm, int bitsCompressed) {
this.pixelRepresentation = dcm.getInt(Tag.PixelRepresentation, 0);
this.planarConfiguration = dcm.getInt(Tag.PlanarConfiguration, 0);
this.sopClassUID = dcm.getString(Tag.SOPClassUID);
this.bodyPartExamined = dcm.getString(Tag.BodyPartExamined);
this.anatomicRegion = AnatomicRegion.read(dcm);
this.stationName = dcm.getString(Tag.StationName);
this.frames = dcm.getInt(Tag.NumberOfFrames, 1);
this.embeddedOverlay = EmbeddedOverlay.getEmbeddedOverlay(dcm);
Expand Down Expand Up @@ -131,8 +132,8 @@ public String getSopClassUID() {
return sopClassUID;
}

public String getBodyPartExamined() {
return bodyPartExamined;
public AnatomicRegion getAnatomicRegion() {
return anatomicRegion;
}

public String getStationName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package org.weasis.dicom.ref;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -19,6 +20,33 @@
import org.weasis.core.util.StringUtil;

public class AnatomicBuilder {
public enum Category {
SURFACE,
ALL_RADIO,
COMMON,
ENDOSCOPY;

public String getTitle() {
return MesCategory.getString(name());
}

@Override
public String toString() {
return getTitle();
}
}

public static final EnumMap<Category, AnatomicItem[]> categoryEnumMap =
new EnumMap<>(Category.class);

static {
categoryEnumMap.put(Category.SURFACE, SurfacePart.values());
categoryEnumMap.put(Category.ALL_RADIO, BodyPart.values());
categoryEnumMap.put(
Category.COMMON, getBodyParts(BodyPart::isCommon).toArray(new AnatomicItem[0]));
categoryEnumMap.put(
Category.ENDOSCOPY, getBodyParts(BodyPart::isEndoscopic).toArray(new AnatomicItem[0]));
}

private static final Map<String, BodyPart> CODE_TO_BODY_PART =
Arrays.stream(BodyPart.values())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 Weasis Team and other contributors.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0, or the Apache
* License, Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package org.weasis.dicom.ref;

public interface AnatomicItem {
String getCodeValue();

String getCodeMeaning();

CodingScheme getScheme();

String getLegacyCode();

boolean isPaired();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2024 Weasis Team and other contributors.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0, or the Apache
* License, Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package org.weasis.dicom.ref;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Sequence;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.VR;
import org.weasis.core.util.StringUtil;
import org.weasis.dicom.macro.Code;
import org.weasis.dicom.ref.AnatomicBuilder.Category;

public class AnatomicRegion {

private final Category category;
private final AnatomicItem region;
private final Set<AnatomicModifier> modifiers;

public AnatomicRegion(AnatomicItem region) {
this(null, region, null);
}

public AnatomicRegion(Category category, AnatomicItem region, Set<AnatomicModifier> modifiers) {
this.category = category;
this.region = Objects.requireNonNull(region);
this.modifiers = modifiers == null ? new HashSet<>() : modifiers;
}

public static AnatomicRegion read(Attributes dcm) {
if (dcm == null) {
return null;
}
// Get only the first item
Attributes regionAttributes = dcm.getNestedDataset(Tag.AnatomicRegionSequence);
if (regionAttributes == null) {
String bodyPart = dcm.getString(Tag.BodyPartExamined);
if (StringUtil.hasText(bodyPart)) {
BodyPart item = AnatomicBuilder.getBodyPartFromLegacyCode(bodyPart);
if (item != null) {
return new AnatomicRegion(item);
}
}
return null;
}

String codeValue = new Code(regionAttributes).getExistingCodeValue();
AnatomicItem item = AnatomicBuilder.getBodyPartFromCode(codeValue);
if (item == null) {
item = AnatomicBuilder.getSurfacePartFromCode(codeValue);
}

AnatomicRegion region = new AnatomicRegion(item);
addModifiers(regionAttributes, region);
return region;
}

public static void write(Attributes dcm, AnatomicRegion region) {
if (dcm == null || region == null) {
return;
}
Attributes regAttributes = new Attributes();
AnatomicItem anatomicItem = region.getRegion();
regAttributes.setString(Tag.CodeValue, VR.SH, anatomicItem.getCodeValue());
regAttributes.setString(Tag.CodeMeaning, VR.LO, anatomicItem.getCodeMeaning());
writeScheme(regAttributes, anatomicItem.getScheme());
if (anatomicItem.getLegacyCode() != null) {
dcm.setString(Tag.BodyPartExamined, VR.CS, anatomicItem.getLegacyCode());
}

Set<AnatomicModifier> modifiers = region.getModifiers();
if (modifiers != null && !modifiers.isEmpty()) {
Sequence seq =
regAttributes.newSequence(Tag.AnatomicRegionModifierSequence, modifiers.size());
for (AnatomicModifier modifier : modifiers) {
Attributes mod = new Attributes();
writeScheme(mod, modifier.getScheme());
mod.setString(Tag.CodeValue, VR.SH, modifier.getCodeValue());
mod.setString(Tag.CodeMeaning, VR.LO, modifier.getCodeMeaning());
seq.add(mod);
}
}
dcm.newSequence(Tag.AnatomicRegionSequence, 1).add(regAttributes);
}

private static void writeScheme(Attributes regionAttributes, CodingScheme scheme) {
regionAttributes.setString(Tag.CodingSchemeDesignator, VR.SH, scheme.getDesignator());
regionAttributes.setString(Tag.CodingSchemeName, VR.SH, scheme.getCodeName());
regionAttributes.setString(Tag.CodingSchemeUID, VR.UI, scheme.getUid());
}

private static void addModifiers(Attributes regionAttributes, AnatomicRegion region) {
Sequence seq = regionAttributes.getSequence(Tag.AnatomicRegionModifierSequence);
if (seq != null) {
for (Attributes attribute : seq) {
AnatomicModifier modifier =
AnatomicBuilder.getAnatomicModifierFromCode(new Code(attribute).getExistingCodeValue());
if (modifier != null) {
region.addModifier(modifier);
}
}
}
}

public AnatomicItem getRegion() {
return region;
}

public Category getCategory() {
return category;
}

public Set<AnatomicModifier> getModifiers() {
return modifiers;
}

public void addModifier(AnatomicModifier modifier) {
modifiers.add(modifier);
}

public void removeModifier(AnatomicModifier modifier) {
modifiers.remove(modifier);
}

@Override
public String toString() {
String modifiersList =
modifiers.stream().map(AnatomicModifier::getCodeMeaning).collect(Collectors.joining(", "));
if (StringUtil.hasText(modifiersList)) {
modifiersList = " (" + modifiersList + ")";
}
return region.getCodeMeaning() + modifiersList;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import static org.weasis.dicom.ref.CodingScheme.SCT;

public enum BodyPart {
public enum BodyPart implements AnatomicItem {
ABDOMEN(SCT, 818981001, "ABDOMEN", false, true, false),
ABDOMEN_AND_PELVIS(SCT, 818982008, "ABDOMENPELVIS", false, true, false),
ABDOMINAL_AORTA(SCT, 7832008, "ABDOMINALAORTA", false, false, false),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2009-2020 Weasis Team and other contributors.
*
* This program and the accompanying materials are made available under the terms of the Eclipse
* Public License 2.0 which is available at https://www.eclipse.org/legal/epl-2.0, or the Apache
* License, Version 2.0 which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package org.weasis.dicom.ref;

import java.util.MissingResourceException;
import java.util.ResourceBundle;

public class MesCategory {
private static final String BUNDLE_NAME = "org.weasis.dicom.ref.category"; // NON-NLS

private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);

private MesCategory() {}

public static String getString(String key) {
try {
return RESOURCE_BUNDLE.getString(key);
} catch (MissingResourceException e) {
return '!' + key + '!';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import static org.weasis.dicom.ref.CodingScheme.FMA;
import static org.weasis.dicom.ref.CodingScheme.SCT;

public enum SurfacePart {
public enum SurfacePart implements AnatomicItem {
ANTERIOR_TRIANGLE_OF_NECK(SCT, 182329002, 41, 0, 42),
CORNEA(SCT, 28726007, 109, 0, 108),
EYELASH(SCT, 85803001, 105, 0, 104),
Expand Down Expand Up @@ -248,6 +248,16 @@ public CodingScheme getScheme() {
return scheme;
}

@Override
public String getLegacyCode() {
return null;
}

@Override
public boolean isPaired() {
return left != 0;
}

public int getLeft() {
return left;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#Sun May 19 20:36:34 CEST 2024
SURFACE=Dermatology (surface)
ALL_RADIO=Radiology
COMMON=Common
ENDOSCOPY=Endoscopy
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#Sun May 19 20:36:34 CEST 2024
SURFACE=Dermatologie (surface)
ALL_RADIO=Radiologie
COMMON=Commun
ENDOSCOPY=Endoscopie
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ void dcm2dcmYBR422RawLossy(String lossyUID) throws Exception {
DicomTranscodeParam params = new DicomTranscodeParam(lossyUID);
if (lossyUID.equals(UID.JPEGLSNearLossless)) {
params.getWriteJpegParam().setNearLosslessError(3);
} else if (lossyUID.equals(UID.JPEG2000)) {
params.getWriteJpegParam().setCompressionRatioFactor(15);
} else {
params.getWriteJpegParam().setCompressionQuality(80);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void shouldCreateImageDescriptorWithValidAttributes() {
assertEquals(15, descriptor.getHighBit());
assertEquals(1, descriptor.getPixelRepresentation());
assertEquals("1.2.840.10008.5.1.4.1.1.2", descriptor.getSopClassUID());
assertEquals("HEAD", descriptor.getBodyPartExamined());
assertEquals("HEAD", descriptor.getAnatomicRegion().getRegion().getLegacyCode());
assertEquals(1, descriptor.getFrames());
assertEquals(1, descriptor.getPixelRepresentation());
assertEquals("COLOR", descriptor.getPixelPresentation());
Expand Down Expand Up @@ -96,7 +96,7 @@ void shouldCreateImageDescriptorWithMissingAttributes() {
assertEquals(7, descriptor.getHighBit());
assertEquals(0, descriptor.getPixelRepresentation());
assertNull(descriptor.getSopClassUID());
assertNull(descriptor.getBodyPartExamined());
assertNull(descriptor.getAnatomicRegion());
assertEquals(1, descriptor.getFrames());
assertEquals(0, descriptor.getPixelRepresentation());
assertNull(descriptor.getModality());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,10 @@ void getSurfacePartFromCode() {

SurfacePart surfacePart = AnatomicBuilder.getSurfacePartFromCode("130319");
assertNotNull(surfacePart);
assertEquals(
"Peau de l'hélix postérieur supérieur de l'oreille",
surfacePart.toString());
assertEquals("Peau de l'hélix postérieur supérieur de l'oreille", surfacePart.toString());
assertEquals(CodingScheme.DCM, surfacePart.getScheme());
assertTrue(surfacePart.isPaired());
assertNull(surfacePart.getLegacyCode());
assertEquals(133, surfacePart.getLeft());
assertEquals(0, surfacePart.getMiddle());
assertEquals(132, surfacePart.getRight());
Expand Down
Loading

0 comments on commit 520d96b

Please sign in to comment.