comparison Samples/FHIR/src/main/java/OrthancResource.java @ 11:8d876a4f541b

added sample FHIR server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 21 Oct 2023 09:53:25 +0200
parents
children 0dc05fe76bd5
comparison
equal deleted inserted replaced
10:6b9433432ee0 11:8d876a4f541b
1 /**
2 * SPDX-FileCopyrightText: 2023 Sebastien Jodogne, UCLouvain, Belgium
3 * SPDX-License-Identifier: GPL-3.0-or-later
4 */
5
6 /**
7 * Java plugin for Orthanc
8 * Copyright (C) 2023 Sebastien Jodogne, UCLouvain, Belgium
9 *
10 * This program is free software: you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License as
12 * published by the Free Software Foundation, either version 3 of the
13 * License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 **/
23
24
25 import be.uclouvain.orthanc.ResourceType;
26 import org.hl7.fhir.r5.model.*;
27 import org.json.JSONArray;
28 import org.json.JSONObject;
29
30 import java.nio.charset.StandardCharsets;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35
36 public class OrthancResource {
37 private ResourceType type;
38 private String id;
39 private String lastUpdate;
40 private Map<String, String> tags;
41 private List<String> children;
42
43 public OrthancResource(JSONObject info) {
44 String s = info.getString("Type");
45 if (s.equals("Patient")) {
46 type = ResourceType.PATIENT;
47 } else if (s.equals("Study")) {
48 type = ResourceType.STUDY;
49 } else if (s.equals("Series")) {
50 type = ResourceType.SERIES;
51 } else if (s.equals("Instance")) {
52 type = ResourceType.INSTANCE;
53 } else {
54 throw new RuntimeException("Unknown resource type");
55 }
56
57 id = info.getString("ID");
58 lastUpdate = info.optString("LastUpdate");
59 tags = new HashMap<>();
60 addToDictionary(tags, info.getJSONObject("MainDicomTags"));
61
62 if (type != ResourceType.INSTANCE) {
63 String childKey;
64 switch (type) {
65 case PATIENT:
66 childKey = "Studies";
67 break;
68 case STUDY:
69 childKey = "Series";
70 addToDictionary(tags, info.getJSONObject("PatientMainDicomTags"));
71 break;
72 case SERIES:
73 childKey = "Instances";
74 break;
75 default:
76 throw new RuntimeException();
77 }
78
79 children = new ArrayList<>();
80 addToListOfStrings(children, info.getJSONArray(childKey));
81 }
82 }
83
84 static public void addToDictionary(Map<String, String> target,
85 JSONObject source) {
86 for (String key : source.keySet()) {
87 target.put(key, source.getString(key));
88 }
89 }
90
91 static public void addToListOfStrings(List<String> target,
92 JSONArray source) {
93 for (int i = 0; i < source.length(); i++) {
94 target.add(source.getString(i));
95 }
96 }
97
98 public ResourceType getType() {
99 return type;
100 }
101
102 public String getId() {
103 return id;
104 }
105
106 public String getLastUpdate() {
107 return lastUpdate;
108 }
109
110 public Map<String, String> getTags() {
111 return tags;
112 }
113
114 public List<String> getChildren() {
115 if (type == ResourceType.INSTANCE) {
116 throw new RuntimeException("A DICOM instance has no child");
117 } else {
118 return children;
119 }
120 }
121
122 public static List<OrthancResource> find(IOrthancConnection connection,
123 ResourceType type,
124 Map<String, String> tags,
125 boolean caseSensitive) {
126 JSONObject query = new JSONObject();
127 for (Map.Entry<String, String> entry : tags.entrySet()) {
128 query.put(entry.getKey(), entry.getValue());
129 }
130
131 JSONObject request = new JSONObject();
132 request.put("Expand", true);
133 request.put("Query", query);
134 request.put("Short", true);
135 request.put("CaseSensitive", caseSensitive);
136
137 switch (type) {
138 case PATIENT:
139 request.put("Level", "Patient");
140 break;
141 case STUDY:
142 request.put("Level", "Study");
143 break;
144 case SERIES:
145 request.put("Level", "Series");
146 break;
147 case INSTANCE:
148 request.put("Level", "Instance");
149 break;
150 default:
151 throw new RuntimeException();
152 }
153
154 byte[] response = connection.doPost("/tools/find", request.toString().getBytes(StandardCharsets.UTF_8));
155
156 JSONArray arr = new JSONArray(new String(response, StandardCharsets.UTF_8));
157
158 List<OrthancResource> result = new ArrayList<>();
159 for (int i = 0; i < arr.length(); i++) {
160 result.add(new OrthancResource(arr.getJSONObject(i)));
161 }
162
163 return result;
164 }
165
166
167 public Patient getFhirPatient() {
168 if (type != ResourceType.PATIENT) {
169 throw new RuntimeException("Not a patient");
170 }
171
172 Patient patient = new Patient();
173 patient.setId(getTags().getOrDefault(Toolbox.TAG_PATIENT_ID, ""));
174
175 String birthDate = getTags().getOrDefault(Toolbox.TAG_PATIENT_BIRTH_DATE, "");
176 if (birthDate != null) {
177 patient.setBirthDate(Toolbox.parseDicomDate(birthDate));
178 }
179
180 String patientName = getTags().getOrDefault(Toolbox.TAG_PATIENT_NAME, "");
181 if (!patientName.isEmpty()) {
182 patient.addName();
183 HumanName name = patient.getName().get(0);
184
185 String[] parts = patientName.split("\\^");
186
187 // https://dicom.nema.org/medical/dicom/current/output/chtml/part19/sect_10.2.html
188 // https://www.hl7.org/fhir/datatypes.html#HumanName
189 if (parts.length > 0) {
190 name.setFamily(parts[0]);
191 }
192 for (int i = 1; i < parts.length; i++) {
193 name.addGiven(parts[i]);
194 }
195 }
196
197 String sex = getTags().getOrDefault(Toolbox.TAG_PATIENT_SEX, "");
198 if (sex.equals("M")) {
199 patient.setGender(Enumerations.AdministrativeGender.MALE);
200 } else if (sex.equals("F")) {
201 patient.setGender(Enumerations.AdministrativeGender.FEMALE);
202 }
203
204 return patient;
205 }
206
207 public ImagingStudy getFhirStudy(IOrthancConnection orthanc) {
208 if (type != ResourceType.STUDY) {
209 throw new RuntimeException("Not a study");
210 }
211
212 boolean hasDicomWeb = IOrthancConnection.hasPluginInstalled(orthanc, "dicom-web");
213
214 // https://build.fhir.org/imagingstudy-example.json.html
215
216 ImagingStudy study = new ImagingStudy();
217 study.setId(getTags().getOrDefault(Toolbox.TAG_STUDY_INSTANCE_UID, ""));
218 study.setStatus(ImagingStudy.ImagingStudyStatus.AVAILABLE);
219
220 if (hasDicomWeb) {
221 study.addEndpoint(Toolbox.createLocalReference("Endpoint", EndpointProvider.ID));
222 }
223
224 study.setSubject(Toolbox.createLocalReference("Patient", getTags().getOrDefault(Toolbox.TAG_PATIENT_ID, "")));
225
226 String studyDate = getTags().getOrDefault(Toolbox.TAG_STUDY_DATE, "");
227 if (!studyDate.isEmpty()) {
228 study.setStarted(Toolbox.parseDicomDate(studyDate));
229 }
230
231 study.addIdentifier();
232 study.getIdentifier().get(0).setSystem("urn:dicom:uid");
233 study.getIdentifier().get(0).setValue("urn:oid:" + study.getId());
234
235 study.setNumberOfSeries(getChildren().size());
236
237 int countInstances = 0;
238
239 Map<String, String> shortTags = new HashMap<>();
240 shortTags.put("short", "");
241
242 Map<String, String> expand = new HashMap<>();
243 expand.put("expand", "");
244
245 for (int i = 0; i < getChildren().size(); i++) {
246 String seriesUri = "/series/" + getChildren().get(i);
247 OrthancResource orthancSeries = new OrthancResource(IOrthancConnection.getJSONObject(orthanc, seriesUri, shortTags));
248
249 ImagingStudy.ImagingStudySeriesComponent fhirSeries = study.addSeries();
250 fhirSeries.setUid(orthancSeries.getTags().getOrDefault(Toolbox.TAG_SERIES_INSTANCE_UID, ""));
251
252 String modality = orthancSeries.getTags().getOrDefault(Toolbox.TAG_MODALITY, "");
253 if (!modality.isEmpty()) {
254 fhirSeries.setModality(Toolbox.createDicomCodeableConcept(modality));
255 }
256
257 String seriesDescription = orthancSeries.getTags().getOrDefault(Toolbox.TAG_SERIES_DESCRIPTION, "");
258 if (!seriesDescription.isEmpty()) {
259 fhirSeries.setDescription(seriesDescription);
260 }
261
262 String seriesNumber = orthancSeries.getTags().getOrDefault(Toolbox.TAG_SERIES_NUMBER, "");
263 if (!seriesNumber.isEmpty()) {
264 fhirSeries.setNumber(Integer.parseInt(seriesNumber));
265 }
266
267 fhirSeries.setNumberOfInstances(orthancSeries.getChildren().size());
268
269 for (int j = 0; j < orthancSeries.getChildren().size(); j++) {
270 String instanceUri = "/instances/" + orthancSeries.getChildren().get(j);
271 OrthancResource orthancInstance = new OrthancResource(IOrthancConnection.getJSONObject(orthanc, instanceUri, shortTags));
272
273 JSONObject instanceMetadata = IOrthancConnection.getJSONObject(orthanc, instanceUri + "/metadata", expand);
274
275 ImagingStudy.ImagingStudySeriesInstanceComponent fhirInstance = fhirSeries.addInstance();
276 fhirInstance.setUid(orthancInstance.getTags().getOrDefault(Toolbox.TAG_SOP_INSTANCE_UID, ""));
277
278 String instanceNumber = orthancInstance.getTags().getOrDefault(Toolbox.TAG_INSTANCE_NUMBER, "");
279 if (!instanceNumber.isEmpty()) {
280 fhirInstance.setNumber(Integer.parseInt(instanceNumber));
281 }
282
283 String sopClassUid = instanceMetadata.optString("SopClassUid", "");
284 if (!sopClassUid.isEmpty()) {
285 fhirInstance.setSopClass(new Coding("urn:ietf:rfc:3986", "urn:oid:" + sopClassUid, ""));
286 }
287 }
288
289 countInstances += orthancSeries.getChildren().size();
290 }
291
292 study.setNumberOfInstances(countInstances);
293
294 return study;
295 }
296 }