Mercurial > hg > orthanc-java
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 } |