comparison Samples/MammographyDeepLearning/src/main/java/Main.java @ 28:43923934e934

added sample: deep learning for mammography
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 12 Jun 2024 13:58:29 +0200
parents
children 08ea8401f7b2
comparison
equal deleted inserted replaced
27:4a750ca9461e 28:43923934e934
1 /**
2 * SPDX-FileCopyrightText: 2023-2024 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-2024 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 ai.djl.ndarray.NDArray;
26 import ai.djl.ndarray.NDManager;
27 import be.uclouvain.orthanc.Callbacks;
28 import be.uclouvain.orthanc.ChangeType;
29 import be.uclouvain.orthanc.Functions;
30 import be.uclouvain.orthanc.HttpMethod;
31 import be.uclouvain.orthanc.ResourceType;
32 import be.uclouvain.orthanc.RestOutput;
33 import org.apache.commons.compress.utils.IOUtils;
34 import org.json.JSONObject;
35
36 import java.awt.image.BufferedImage;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.nio.charset.StandardCharsets;
40 import java.security.NoSuchAlgorithmException;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.concurrent.ExecutorService;
45 import java.util.concurrent.Executors;
46 import java.util.zip.ZipEntry;
47 import java.util.zip.ZipFile;
48
49 public class Main {
50 private static final String MODEL_PATH = "2024-03-08-retina_res50_trained_08_03.torchscript";
51 private static final String STONE_VERSION = "2024-03-15-StoneWebViewer-DICOM-SR";
52 private static final String STONE_PATH = "2024-03-15-StoneWebViewer-DICOM-SR.zip";
53
54 private static ZipFile stone;
55 private static NDManager manager;
56 private static RetinaNet retinaNet;
57 private static Map<String, String> mimeTypes = new HashMap<>();
58
59 static {
60 ExecutorService executor = Executors.newSingleThreadExecutor();
61 try {
62 OrthancConnection.download("2024-03-08-retina_res50_trained_08_03.torchscript", executor,
63 "https://orthanc.uclouvain.be/downloads/cross-platform/orthanc-mammography/models/2024-03-08-retina_res50_trained_08_03.torchscript",
64 146029397L, "b3de8f562de683bc3515fe93ae102fd4");
65 OrthancConnection.download("2024-03-15-StoneWebViewer-DICOM-SR.zip", executor,
66 "https://github.com/jodogne/orthanc-mammography/raw/master/viewer/2024-03-15-StoneWebViewer-DICOM-SR.zip",
67 4742571L, "de952da6fc74a9d4b78ca5064a6a7318");
68 } catch (IOException | NoSuchAlgorithmException | InterruptedException e) {
69 throw new RuntimeException(e);
70 } finally {
71 executor.shutdown();
72 }
73
74 try {
75 stone = new ZipFile(STONE_PATH);
76 } catch (IOException e) {
77 throw new RuntimeException(e);
78 }
79
80 Functions.logWarning("Initializing Deep Java Library");
81 manager = NDManager.newBaseManager();
82
83 Functions.logWarning("Loading RetinaNet model");
84 try {
85 retinaNet = new RetinaNet(MODEL_PATH);
86 } catch (Exception e) {
87 throw new RuntimeException(e);
88 }
89
90 Functions.logWarning("RetinaNet model is ready");
91
92 mimeTypes.put("css", "text/css");
93 mimeTypes.put("gif", "image/gif");
94 mimeTypes.put("html", "text/html");
95 mimeTypes.put("jpeg", "image/jpeg");
96 mimeTypes.put("js", "text/javascript");
97 mimeTypes.put("png", "image/png");
98
99 try (InputStream stream = Main.class.getResourceAsStream("OrthancExplorer.js")) {
100 byte[] content = IOUtils.toByteArray(stream);
101 Functions.extendOrthancExplorer(new String(content, StandardCharsets.UTF_8));
102 } catch (IOException e) {
103 throw new RuntimeException(e);
104 }
105
106 Callbacks.register(new Callbacks.OnChange() {
107 @Override
108 public void call(ChangeType changeType, ResourceType resourceType, String resourceId) {
109 switch (changeType) {
110 case ORTHANC_STARTED:
111 OrthancConnection connection = OrthancConnection.createForPlugin();
112 try {
113 if (!connection.isOrthancVersionAbove(1, 12, 5)) {
114 throw new RuntimeException("Your version of Orthanc must be >= 1.12.5 to run this plugin");
115 }
116 } catch (IOException | InterruptedException e) {
117 throw new RuntimeException(e);
118 }
119
120 break;
121
122 case ORTHANC_STOPPED:
123 Functions.logWarning("Finalizing Deep Java Library");
124 if (retinaNet != null) {
125 retinaNet.close();
126 }
127
128 if (manager != null) {
129 manager.close();
130 }
131
132 System.gc();
133 System.runFinalization();
134 break;
135
136 default:
137 break;
138 }
139 }
140 });
141
142 Callbacks.register("/java-mammography-apply", new Callbacks.OnRestRequest() {
143 @Override
144 public void call(RestOutput output,
145 HttpMethod method,
146 String uri,
147 String[] regularExpressionGroups,
148 Map<String, String> headers,
149 Map<String, String> getParameters,
150 byte[] body) {
151 if (method != HttpMethod.POST) {
152 output.sendMethodNotAllowed("POST");
153 return;
154 }
155
156 JSONObject request = new JSONObject(new String(body, StandardCharsets.UTF_8));
157
158 String instanceId = request.getString("instance");
159 if (instanceId == null) {
160 throw new RuntimeException("Missing instance identifier");
161 }
162
163 Functions.logWarning("Applying RetinaNet to instance: " + instanceId);
164 OrthancConnection connection = OrthancConnection.createForPlugin();
165
166 try {
167 BufferedImage image = connection.getGrayscaleFrame(instanceId, 0);
168
169 double largestSide = Math.max(image.getWidth(), image.getHeight());
170 BufferedImage resized = ImageProcessing.resizeImage(image,
171 (int) Math.round((double) image.getWidth() * 2048.0 / largestSide),
172 (int) Math.round((double) image.getHeight() * 2048.0 / largestSide));
173
174 double aspectRatio = (double) image.getWidth() / (double) resized.getWidth();
175
176 NDArray grayscale = ImageProcessing.imageToTensor(manager, resized);
177 grayscale = ImageProcessing.standardize(grayscale);
178
179 NDArray rgb = grayscale.concat(grayscale).concat(grayscale); // Create a "RGB" image from grayscale pixel values
180 List<Detection> detections = retinaNet.apply(rgb);
181
182 JSONObject created = DicomToolbox.createDicomSR(connection, instanceId, detections, aspectRatio);
183
184 String id = created.getString("ID");
185 Functions.logWarning("Detection results stored in DICOM-SR instance: " + id);
186
187 output.answerBuffer(created.toString().getBytes(StandardCharsets.UTF_8), "application/json");
188 } catch (Exception e) {
189 throw new RuntimeException(e);
190 }
191 }
192 });
193
194 Callbacks.register("/java-mammography-viewer/(.*)", new Callbacks.OnRestRequest() {
195 @Override
196 public void call(RestOutput output,
197 HttpMethod method,
198 String uri,
199 String[] regularExpressionGroups,
200 Map<String, String> headers,
201 Map<String, String> getParameters,
202 byte[] body) {
203 if (method != HttpMethod.GET) {
204 output.sendMethodNotAllowed("GET");
205 return;
206 }
207
208 String path = regularExpressionGroups[0];
209 int dot = path.lastIndexOf(".");
210 if (dot < 0) {
211 output.sendHttpStatus((short) 404, new byte[0]);
212 } else {
213 String extension = path.substring(dot + 1);
214 String mime = mimeTypes.getOrDefault(extension, "application/octet-stream");
215
216 ZipEntry entry = stone.getEntry(STONE_VERSION + "/" + path);
217 if (entry == null) {
218 output.sendHttpStatus((short) 404, new byte[0]);
219 } else {
220 try (InputStream stream = stone.getInputStream(entry)) {
221 output.answerBuffer(IOUtils.toByteArray(stream), mime);
222 } catch (IOException e) {
223 throw new RuntimeException(e);
224 }
225 }
226 }
227 }
228 });
229 }
230 }