
์ด ๊ธ์์๋
dcm4che๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํด Spring์์ DICOM ํ์ผ์ ์ฝ๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ค๋ฃน๋๋ค. ๊ตฌํ ๋ฐฉ๋ฒ์๐งญ ํ๊ฒฝ ์ค์ ๋ถํฐ ๋์์์ต๋๋ค.
DICOM์ Digital Imaging and COmmunication in Medicine์ ์ฝ์ด๋ก, ์๋ฃ ์์(Medical imaging)์์ ์ฌ์ฉํ๋ ์ด๋ฏธ์ง ํ์์ด๋ค. ์ ์๋ ์๋์ ๊ฐ๋ค.
์๋ฃ์ฉ ๋์งํธ ์์ ๋ฐ ํต์ (Digital Imaging and Communications in Medicine, DICOM) ํ์ค์ ์๋ฃ์ฉ ๊ธฐ๊ธฐ์์ ๋์งํธ ์์ํํ๊ณผ ํต์ ์ ์ฌ์ฉ๋๋ ์ฌ๋ฌ ๊ฐ์ง ํ์ค์ ์ด์นญํ๋ ๋ง์ด๋ค.
DICOM ํ์ผ์๋ ์ด๋ฏธ์ง์ ๋ฉํ ๋ฐ์ดํฐ(key-value ํ์)๊ฐ ํฌํจ๋์ด ์๋ค. ์ด๋ฏธ์ง์ ํฌ๊ธฐ๊ฐ ํด ๋ฟ๋๋ฌ ์ด์ ๋ํ ๋ฉํ ๋ฐ์ดํฐ๋ ํฌํจํ๊ณ ์๊ธฐ์ ๊ฐ ํ์ผ์ ์ฉ๋์ด 20~30MB ์ ๋๋ก ํฌ๋ค. ๋ด๊ฐ ๋ค๋ค๋ Mammography ์ด๋ฏธ์ง๊ฐ ํ๊ท ์ ์ผ๋ก ๊ทธ๋ฌ๋ค๋๊ฑฐ์ง, ๋ ํด ์๋ ์๋ค.
ํ์ฌ ๊ฐ๋ฐ ์ค์ธ ๊ฒ์ Mammography ์ด๋ฏธ์ง๋ก ์ ๋ฐฉ์ ๋ฐ์๋ฅ ์ ์ง๋จํ๋ ์ ํ์ด๋ค. ์ด๋ Mammography ์ด๋ฏธ์ง๊ฐ DICOM ํ์ผ์ด๋ค. ์ด์ ๋ํ ์ง๋จ ๊ฒฐ๊ณผ(Secondary capture) ๋ํ DICOM ํ์ผ๋ก ๊ตฌ์ฑ๋์ด์๋ค. ์ง๋จ ๊ฒฐ๊ณผ๋ ์ธํผ๋ฐ์ค๋ฅผ ํตํด ๋์ค๋๋ฐ, ์ธํผ๋ฐ์ค๊ฐ ๋ถ๊ฐํ ์กฐ๊ฑด์ ํํฐ๋งํด validํ ์ด๋ฏธ์ง๋ง AI ์๋ฒ์ ์ ๋ฌํด์ผ ํ๋ค. ์ด๋ฅผ ์ํด DICOM ํ์ผ์ ์ฝ์ด ํ๊ทธ๋ฅผ validationํ๋ค. ์๋ฅผ ๋ค์๋ฉด, ์ด๋ฏธ์ง์ ๋ถ๋ฅ๊ฐ Mammography๊ฐ ์๋ ๊ฒฝ์ฐ invalidํ ์ด๋ฏธ์ง๋ก ๋ถ๋ฅ๋๋ ์์ด๋ค.
DICOM ์ด๋ฏธ์ง์ validation์ key-value ํ์์ ๋ฉํ ๋ฐ์ดํฐ, ์ฆ tag๋ฅผ ๊ฒ์ฆํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค. tag๋ฅผ ์ฝ๊ธฐ ์ํด์๋ DICOM ํ์ผ์ ์ฝ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. Java๋ก DICOM ํ์ผ์ ์ฝ๊ธฐ ์ํด ์ฌ๋ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ์๋ดค์ง๋ง, dcm4che๊ฐ Java ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค ์ค ๊ฐ์ฅ ์ฌ์ฉ ๋น๋๊ฐ ๋์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค. ๋ํ dcm4che๋ ์์กด์ฑ ์ค์ ๋ง์ผ๋ก ๊ฐ๋จํ๊ฒ ์ฌ์ฉํ ์ ์๊ธฐ์ ์ ํํ๊ฒ ๋์๋ค.
ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ maven central์ ์๋ ์ํฐํฉํธ์ด๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ repository๋ฅผ ๋ฑ๋กํด์ผ ํ๋ค. repository ๋ฑ๋ก ์์ด ์งํํ๋ฉด ์ ์์ ์ผ๋ก ๋์ํ์ง ์๋๋ค.
์ด ๊ธ์์๋ ์๋์ ์ํฐํฉํธ๋ง์ ์ฌ์ฉํ์ง๋ง, ๋ค๋ฅธ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ maven repository์์ ๊ฒ์ ํ ์ฐพ์์ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค.
...
repositories {
mavenCentral()
maven{ // repository ๋ฑ๋ก
url "https://maven.scijava.org/content/repositories/public/"
}
}
dependencies {
...
// dcm4che
implementation 'org.dcm4che:dcm4che-core:5.31.1'
implementation 'org.dcm4che.tool:dcm4che-tool-common:5.30.0'
implementation 'org.dcm4che.tool:dcm4che-tool-dcm2json:5.30.0'
...
}
...
์๋๋ DICOM ํ์ผ์ ํ๊ทธ๋ฅผ ํ์ฑํ๊ณ ์ด๋ฅผ validationํ๋ ์์ ์ด๋ค. ์ํ๋ ํ๊ทธ๋ง ์ฝ์ด์ ์ ์ฅํด๋ ์ ์๋๋ก DicomTagValue ํด๋์ค๋ฅผ ์๋์ ๊ฐ์ด ์ ์ธํ๋ค.
package example.api.instance.util;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class DicomTagValue {
private String patientName;
private String institutionName;
private String patientId;
private String studyInstanceUid;
private String SOPInstanceUid;
private String SOPClassUid;
private String viewPosition;
private String imageLaterality;
private String imageType;
private Double estimatedRadiographicMagnificationFactor;
private String patientSex;
private String breastImplantPresent;
private String performedProcedureStepDescription;
private String seriesDescription;
private String ProtocolName;
private String presentationLUTShape;
private String exposureStatus;
private String studyDescription;
private Date studyDate;
}
parser๊ฐ ๊ฐ ํ๊ทธ์ ๋ง๋ ์๋ฃํ์ผ๋ก ๊ฐ์ ์ถ์ถํ๊ธธ ๋ฐ๋ฌ๊ธฐ ๋๋ฌธ์ ์ ๋ค๋ฆญ ํ์
์ผ๋ก ๊ฐ ํ๊ทธ๋ฅผ ์ฝ์ด์ฌ ์ ์๋๋ก ํ๋ค. ํ์ง๋ง ๋ญ๊ฐ ๋ง์์ ๋ค์ง ์์ ์ฐจํ ๋ฆฌํฉํ ๋ง์ด ํ์ํ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.
package example.api.instance.util;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import example.global.exception.DicomTagParseException;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.io.DicomInputStream;
/**
* Dicom tag parser
*/
public class DicomTagParser {
/**
* Parse dicom tag
*
* @param dicomFile dicom file
* @return dicom tag value
*/
public static DicomTagValue parseDicomTag(File dicomFile) {
DicomTagValue dicomTagValue = new DicomTagValue(
getDicomTagValue(dicomFile, Tag.PatientName, String.class),
getDicomTagValue(dicomFile, Tag.InstitutionName, String.class),
getDicomTagValue(dicomFile, Tag.PatientID, String.class),
getDicomTagValue(dicomFile, Tag.StudyInstanceUID, String.class),
getDicomTagValue(dicomFile, Tag.SOPInstanceUID, String.class),
getDicomTagValue(dicomFile, Tag.SOPClassUID, String.class),
getDicomTagValue(dicomFile, Tag.ViewPosition, String.class),
getDicomTagValue(dicomFile, Tag.ImageLaterality, String.class),
getDicomTagValue(dicomFile, Tag.ImageType, String.class),
getDicomTagValue(dicomFile, Tag.EstimatedRadiographicMagnificationFactor, Double.class),
getDicomTagValue(dicomFile, Tag.PatientSex, String.class),
getDicomTagValue(dicomFile, Tag.BreastImplantPresent, String.class),
getDicomTagValue(dicomFile, Tag.PerformedProcedureStepDescription, String.class),
getDicomTagValue(dicomFile, Tag.SeriesDescription, String.class),
getDicomTagValue(dicomFile, Tag.ProtocolName, String.class),
getDicomTagValue(dicomFile, Tag.PresentationLUTShape, String.class),
getDicomTagValue(dicomFile, Tag.ExposureStatus, String.class),
getDicomTagValue(dicomFile, Tag.StudyDescription, String.class),
getDicomTagValue(dicomFile, Tag.StudyDate, Date.class)
);
return dicomTagValue;
}
/**
* Get dicom tag value
*
* @param file dicom file
* @param tag dicom tag
* @param type tag value type
* @return tag value
*/
private static <T> T getDicomTagValue(File file, int tag, Class<T> type) {
try (DicomInputStream dis = new DicomInputStream(file)) {
Attributes dicomAttributes = dis.readDataset(-1, -1);
if (type.equals(Double.class)) {
return (T) Double.valueOf(dicomAttributes.getString(tag));
}
if (type.equals(java.util.Date.class)) {
return (T) dicomAttributes.getDate(tag);
}
return (T) dicomAttributes.getString(tag);
} catch (IOException e) {
throw new DicomTagParseException();
}
}
}
DICOM ํ์ผ์ ํ๊ทธ์ ์ ํจ์ฑ ๊ฒ์ฌ ์์๋ ์๋์ ๋ฉ์๋์ ๊ฐ๋ค. ์ค์ ๋ก๋ public ๋ฉ์๋์์ private ๋ฉ์๋ ์ฌ๋ฌ ๊ฐ๋ฅผ ๋ถ๋ฌ์ ๊ฒ์ฆํ๋ ์์ผ๋ก ๊ตฌํํ๋ค. ๋๋จธ์ง ์ ํจ์ฑ ๊ฒ์ฌ๋ ์กฐ๊ฑด์ด ๋๋ฌด ๋ง๊ณ ์ฝ๋๊ฐ ๊ธธ์ด์ ธ ์๋ตํ๋ค.
/**
* validate Image that does not contain "supplemental", "localization", "3d", "tomo", "combo",
* "implant"
*
* @param performedProcedureStepDescription PerformedProcedureStepDescription
* @param SeriesDescription SeriesDescription
* @param protocolName ProtocolName
*/
private void validateImage(String performedProcedureStepDescription, String SeriesDescription, String protocolName) {
List<String> temp = List.of(
performedProcedureStepDescription,
SeriesDescription,
protocolName
);
temp = temp.stream()
.map(String::toLowerCase)
.toList();
List<String> NotContains = List.of("supplemental", "localization", "3d", "tomo", "combo",
"implant");
if (temp.stream().anyMatch(s -> NotContains.stream().anyMatch(s::contains))) {
throw new InvalidDicomTagValueException(
"PerformedProcedureStepDescription, SeriesDescription, ProtocolName");
}
}
์ฌ์ค ์๋ฃ ์์์ ๋ค๋ฃจ์ง ์๋๋ค๋ฉด ๊ตณ์ด dcm4che๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์๋ค. ๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ ์ด๋ ๊ฒ ์ ๋ฆฌํด๋๋ ๊ฒ์ ๋ด๊ฐ ์๋ฃ๊ธฐ๊ธฐ ๊ฐ๋ฐ์ ํ๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด๋ณด๊ณ ์ ๊ตฌ๊ธ๋ง์ ๋ง์ด ํ๋๋ฐ, ํ๊ตญ์ด๋ก dcm4che ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ํด ์ ๋ฆฌํด๋ ๊ธ์ด ๊ฑฐ์ ์์๋ค. ๋ฌผ๋ก ๋ด๊ฐ ๋ชป์ฐพ์ ๊ฒ์ผ์๋ ์๋ค.
dcm4che ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ DICOM ํ์ผ ํธ๋ค๋ง์ด ์ฝ๊ฒ ๋๊ปด์ง๋๋ก ๋ง์ ๊ธฐ๋ฅ์ ์ง์ํด์ค๋ค๋ ๊ฒ์ด๋ค. ๋์ ๊ฒฝ์ฐ, ํ๊ทธ ํ์ฑ ์์
์ ์ํด ๊ฐ ํ๊ทธ๋ฅผ ์ด๋ป๊ฒ ์
๋ ฅํด์ค์ผ ํ๋ ์ง์ ๋ํด ๊ถ๊ธํ์๋๋ฐ, DICOM ํ์์์ ๊ณต์์ ์ผ๋ก ์ง์ํ๋ ํ๊ทธ๋ค์ Tag ํด๋์ค๋ก ๋ฌถ์ด๋ฌ์ ๊ต์ฅํ ํธ๋ฆฌํ๋ค.
์์ฌ์ด ์ ์ ๋น์ทํ ์ด๋ฆ์ ํด๋์ค๋ค์ด ๋ง์ ์ด๋ค ํด๋์ค๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ์ง ๋ชจํธํ๋ค๋ ์ ์ด๋ค. ํ์ง๋ง ์ด๋ ๊ฐ๋ฐ ์ด๊ธฐ์๋ง ๋ถ๊ฐ๋์๋ ๋จ์ ์ด๋ผ ์ฅ์ ์ด ๋ ๋ง๊ฒ ๋๊ปด์ก๋ค.
์์ผ๋ก๋ DICOM ํ์ผ์ ๋ค๋ฃฐ ์ผ์ด ๋ง์ ๊ฒ ๊ฐ์๋ฐ, dcm4che ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ ์ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ฐ๊ฒฌํ๋ฉด ๊ธ์ ์์ฑํด๋ณด๊ณ ์ ํ๋ค.
Reference : ์์ ์ค์์๋ ์๋ฃ ์์?, DICOM์ด๋?