view Samples/FHIR/src/main/java/OrthancResource.java @ 43:678bbed285a1 default tip

improved import of JNI in cmake
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 06 Sep 2024 13:53:54 +0200
parents 1c407ba1d311
children
line wrap: on
line source

/**
 * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
 * SPDX-License-Identifier: GPL-3.0-or-later
 **/

/**
 * Java plugin for Orthanc
 * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/


import be.uclouvain.orthanc.ResourceType;
import org.hl7.fhir.r5.model.*;
import org.json.JSONArray;
import org.json.JSONObject;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OrthancResource {
    private ResourceType type;
    private String id;
    private String lastUpdate;
    private Map<String, String> tags;
    private List<String> children;

    public OrthancResource(JSONObject info) {
        String s = info.getString("Type");
        if (s.equals("Patient")) {
            type = ResourceType.PATIENT;
        } else if (s.equals("Study")) {
            type = ResourceType.STUDY;
        } else if (s.equals("Series")) {
            type = ResourceType.SERIES;
        } else if (s.equals("Instance")) {
            type = ResourceType.INSTANCE;
        } else {
            throw new RuntimeException("Unknown resource type");
        }

        id = info.getString("ID");
        lastUpdate = info.optString("LastUpdate");
        tags = new HashMap<>();
        addToDictionary(tags, info.getJSONObject("MainDicomTags"));

        if (type != ResourceType.INSTANCE) {
            String childKey;
            switch (type) {
                case PATIENT:
                    childKey = "Studies";
                    break;
                case STUDY:
                    childKey = "Series";
                    addToDictionary(tags, info.getJSONObject("PatientMainDicomTags"));
                    break;
                case SERIES:
                    childKey = "Instances";
                    break;
                default:
                    throw new RuntimeException();
            }

            children = new ArrayList<>();
            addToListOfStrings(children, info.getJSONArray(childKey));
        }
    }

    static public void addToDictionary(Map<String, String> target,
                                       JSONObject source) {
        for (String key : source.keySet()) {
            target.put(key, source.getString(key));
        }
    }

    static public void addToListOfStrings(List<String> target,
                                          JSONArray source) {
        for (int i = 0; i < source.length(); i++) {
            target.add(source.getString(i));
        }
    }

    public ResourceType getType() {
        return type;
    }

    public String getId() {
        return id;
    }

    public String getLastUpdate() {
        return lastUpdate;
    }

    public Map<String, String> getTags() {
        return tags;
    }

    public List<String> getChildren() {
        if (type == ResourceType.INSTANCE) {
            throw new RuntimeException("A DICOM instance has no child");
        } else {
            return children;
        }
    }

    private static List<OrthancResource> find(IOrthancConnection connection,
                                              ResourceType type,
                                              Map<String, String> tags,
                                              boolean caseSensitive,
                                              boolean hasPaging,
                                              int since,
                                              int limit) {
        JSONObject query = new JSONObject();
        for (Map.Entry<String, String> entry : tags.entrySet()) {
            query.put(entry.getKey(), entry.getValue());
        }

        JSONObject request = new JSONObject();
        request.put("Expand", true);
        request.put("Query", query);
        request.put("Short", true);
        request.put("CaseSensitive", caseSensitive);

        if (hasPaging) {
            request.put("Since", since);
            request.put("Limit", limit);
        }

        switch (type) {
            case PATIENT:
                request.put("Level", "Patient");
                break;
            case STUDY:
                request.put("Level", "Study");
                break;
            case SERIES:
                request.put("Level", "Series");
                break;
            case INSTANCE:
                request.put("Level", "Instance");
                break;
            default:
                throw new RuntimeException();
        }

        byte[] response = connection.doPost("/tools/find", request.toString().getBytes(StandardCharsets.UTF_8));

        JSONArray arr = new JSONArray(new String(response, StandardCharsets.UTF_8));

        List<OrthancResource> result = new ArrayList<>();
        for (int i = 0; i < arr.length(); i++) {
            result.add(new OrthancResource(arr.getJSONObject(i)));
        }

        return result;
    }

    public static List<OrthancResource> find(IOrthancConnection connection,
                                             ResourceType type,
                                             Map<String, String> tags,
                                             boolean caseSensitive,
                                             int since,
                                             int limit) {
        return find(connection, type, tags, caseSensitive, true, since, limit);
    }

    public static List<OrthancResource> find(IOrthancConnection connection,
                                             ResourceType type,
                                             Map<String, String> tags,
                                             boolean caseSensitive) {
        return find(connection, type, tags, caseSensitive, false, 0, 0);
    }

    public Patient getFhirPatient() {
        if (type != ResourceType.PATIENT) {
            throw new RuntimeException("Not a patient");
        }

        Patient patient = new Patient();
        patient.setId(getTags().getOrDefault(Toolbox.TAG_PATIENT_ID, ""));

        String birthDate = getTags().getOrDefault(Toolbox.TAG_PATIENT_BIRTH_DATE, "");
        if (birthDate != null) {
            try {
                patient.setBirthDate(Toolbox.parseDicomDate(birthDate));
            } catch (IllegalArgumentException e) {
                // Ignore incorrect dates
            }
        }

        String patientName = getTags().getOrDefault(Toolbox.TAG_PATIENT_NAME, "");
        if (!patientName.isEmpty()) {
            patient.addName();
            HumanName name = patient.getName().get(0);

            String[] parts = patientName.split("\\^");

            // https://dicom.nema.org/medical/dicom/current/output/chtml/part19/sect_10.2.html
            // https://www.hl7.org/fhir/datatypes.html#HumanName
            if (parts.length > 0) {
                name.setFamily(parts[0]);
            }
            for (int i = 1; i < parts.length; i++) {
                name.addGiven(parts[i]);
            }
        }

        String sex = getTags().getOrDefault(Toolbox.TAG_PATIENT_SEX, "");
        if (sex.equals("M")) {
            patient.setGender(Enumerations.AdministrativeGender.MALE);
        } else if (sex.equals("F")) {
            patient.setGender(Enumerations.AdministrativeGender.FEMALE);
        }

        return patient;
    }

    public ImagingStudy getFhirStudy(IOrthancConnection orthanc) {
        if (type != ResourceType.STUDY) {
            throw new RuntimeException("Not a study");
        }

        boolean hasDicomWeb = IOrthancConnection.hasPluginInstalled(orthanc, "dicom-web");

        // https://build.fhir.org/imagingstudy-example.json.html

        ImagingStudy study = new ImagingStudy();
        study.setId(getTags().getOrDefault(Toolbox.TAG_STUDY_INSTANCE_UID, ""));
        study.setStatus(ImagingStudy.ImagingStudyStatus.AVAILABLE);

        if (hasDicomWeb) {
            study.addEndpoint(Toolbox.createLocalReference("Endpoint", EndpointProvider.ID));
        }

        study.setSubject(Toolbox.createLocalReference("Patient", getTags().getOrDefault(Toolbox.TAG_PATIENT_ID, "")));

        String studyDate = getTags().getOrDefault(Toolbox.TAG_STUDY_DATE, "");
        if (!studyDate.isEmpty()) {
            try {
                study.setStarted(Toolbox.parseDicomDate(studyDate));
            } catch (IllegalArgumentException e) {
                // Ignore incorrect dates
            }
        }

        String studyDescription = getTags().getOrDefault(Toolbox.TAG_STUDY_DESCRIPTION, "");
        if (!studyDescription.isEmpty()) {
            study.setDescription(studyDescription);
        }

        study.addIdentifier();
        study.getIdentifier().get(0).setSystem("urn:dicom:uid");
        study.getIdentifier().get(0).setValue("urn:oid:" + study.getId());

        study.setNumberOfSeries(getChildren().size());

        int countInstances = 0;

        Map<String, String> shortTags = new HashMap<>();
        shortTags.put("short", "");

        Map<String, String> expand = new HashMap<>();
        expand.put("expand", "");

        for (int i = 0; i < getChildren().size(); i++) {
            String seriesUri = "/series/" + getChildren().get(i);
            OrthancResource orthancSeries = new OrthancResource(IOrthancConnection.getJSONObject(orthanc, seriesUri, shortTags));

            ImagingStudy.ImagingStudySeriesComponent fhirSeries = study.addSeries();
            fhirSeries.setUid(orthancSeries.getTags().getOrDefault(Toolbox.TAG_SERIES_INSTANCE_UID, ""));

            String modality = orthancSeries.getTags().getOrDefault(Toolbox.TAG_MODALITY, "");
            if (!modality.isEmpty()) {
                fhirSeries.setModality(Toolbox.createDicomCodeableConcept(modality));
            }

            String seriesDescription = orthancSeries.getTags().getOrDefault(Toolbox.TAG_SERIES_DESCRIPTION, "");
            if (!seriesDescription.isEmpty()) {
                fhirSeries.setDescription(seriesDescription);
            }

            String seriesNumber = orthancSeries.getTags().getOrDefault(Toolbox.TAG_SERIES_NUMBER, "");
            if (!seriesNumber.isEmpty()) {
                fhirSeries.setNumber(Integer.parseInt(seriesNumber));
            }

            fhirSeries.setNumberOfInstances(orthancSeries.getChildren().size());

            for (int j = 0; j < orthancSeries.getChildren().size(); j++) {
                String instanceUri = "/instances/" + orthancSeries.getChildren().get(j);
                OrthancResource orthancInstance = new OrthancResource(IOrthancConnection.getJSONObject(orthanc, instanceUri, shortTags));

                JSONObject instanceMetadata = IOrthancConnection.getJSONObject(orthanc, instanceUri + "/metadata", expand);

                ImagingStudy.ImagingStudySeriesInstanceComponent fhirInstance = fhirSeries.addInstance();
                fhirInstance.setUid(orthancInstance.getTags().getOrDefault(Toolbox.TAG_SOP_INSTANCE_UID, ""));

                String instanceNumber = orthancInstance.getTags().getOrDefault(Toolbox.TAG_INSTANCE_NUMBER, "");
                if (!instanceNumber.isEmpty()) {
                    fhirInstance.setNumber(Integer.parseInt(instanceNumber));
                }

                String sopClassUid = instanceMetadata.optString("SopClassUid", "");
                if (!sopClassUid.isEmpty()) {
                    fhirInstance.setSopClass(new Coding("urn:ietf:rfc:3986", "urn:oid:" + sopClassUid, ""));
                }
            }

            countInstances += orthancSeries.getChildren().size();
        }

        study.setNumberOfInstances(countInstances);

        return study;
    }
}