Mercurial > hg > orthanc-java
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Samples/MammographyDeepLearning/src/main/java/Main.java Wed Jun 12 13:58:29 2024 +0200 @@ -0,0 +1,230 @@ +/** + * 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 ai.djl.ndarray.NDArray; +import ai.djl.ndarray.NDManager; +import be.uclouvain.orthanc.Callbacks; +import be.uclouvain.orthanc.ChangeType; +import be.uclouvain.orthanc.Functions; +import be.uclouvain.orthanc.HttpMethod; +import be.uclouvain.orthanc.ResourceType; +import be.uclouvain.orthanc.RestOutput; +import org.apache.commons.compress.utils.IOUtils; +import org.json.JSONObject; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Main { + private static final String MODEL_PATH = "2024-03-08-retina_res50_trained_08_03.torchscript"; + private static final String STONE_VERSION = "2024-03-15-StoneWebViewer-DICOM-SR"; + private static final String STONE_PATH = "2024-03-15-StoneWebViewer-DICOM-SR.zip"; + + private static ZipFile stone; + private static NDManager manager; + private static RetinaNet retinaNet; + private static Map<String, String> mimeTypes = new HashMap<>(); + + static { + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + OrthancConnection.download("2024-03-08-retina_res50_trained_08_03.torchscript", executor, + "https://orthanc.uclouvain.be/downloads/cross-platform/orthanc-mammography/models/2024-03-08-retina_res50_trained_08_03.torchscript", + 146029397L, "b3de8f562de683bc3515fe93ae102fd4"); + OrthancConnection.download("2024-03-15-StoneWebViewer-DICOM-SR.zip", executor, + "https://github.com/jodogne/orthanc-mammography/raw/master/viewer/2024-03-15-StoneWebViewer-DICOM-SR.zip", + 4742571L, "de952da6fc74a9d4b78ca5064a6a7318"); + } catch (IOException | NoSuchAlgorithmException | InterruptedException e) { + throw new RuntimeException(e); + } finally { + executor.shutdown(); + } + + try { + stone = new ZipFile(STONE_PATH); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Functions.logWarning("Initializing Deep Java Library"); + manager = NDManager.newBaseManager(); + + Functions.logWarning("Loading RetinaNet model"); + try { + retinaNet = new RetinaNet(MODEL_PATH); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Functions.logWarning("RetinaNet model is ready"); + + mimeTypes.put("css", "text/css"); + mimeTypes.put("gif", "image/gif"); + mimeTypes.put("html", "text/html"); + mimeTypes.put("jpeg", "image/jpeg"); + mimeTypes.put("js", "text/javascript"); + mimeTypes.put("png", "image/png"); + + try (InputStream stream = Main.class.getResourceAsStream("OrthancExplorer.js")) { + byte[] content = IOUtils.toByteArray(stream); + Functions.extendOrthancExplorer(new String(content, StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + Callbacks.register(new Callbacks.OnChange() { + @Override + public void call(ChangeType changeType, ResourceType resourceType, String resourceId) { + switch (changeType) { + case ORTHANC_STARTED: + OrthancConnection connection = OrthancConnection.createForPlugin(); + try { + if (!connection.isOrthancVersionAbove(1, 12, 5)) { + throw new RuntimeException("Your version of Orthanc must be >= 1.12.5 to run this plugin"); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + + break; + + case ORTHANC_STOPPED: + Functions.logWarning("Finalizing Deep Java Library"); + if (retinaNet != null) { + retinaNet.close(); + } + + if (manager != null) { + manager.close(); + } + + System.gc(); + System.runFinalization(); + break; + + default: + break; + } + } + }); + + Callbacks.register("/java-mammography-apply", new Callbacks.OnRestRequest() { + @Override + public void call(RestOutput output, + HttpMethod method, + String uri, + String[] regularExpressionGroups, + Map<String, String> headers, + Map<String, String> getParameters, + byte[] body) { + if (method != HttpMethod.POST) { + output.sendMethodNotAllowed("POST"); + return; + } + + JSONObject request = new JSONObject(new String(body, StandardCharsets.UTF_8)); + + String instanceId = request.getString("instance"); + if (instanceId == null) { + throw new RuntimeException("Missing instance identifier"); + } + + Functions.logWarning("Applying RetinaNet to instance: " + instanceId); + OrthancConnection connection = OrthancConnection.createForPlugin(); + + try { + BufferedImage image = connection.getGrayscaleFrame(instanceId, 0); + + double largestSide = Math.max(image.getWidth(), image.getHeight()); + BufferedImage resized = ImageProcessing.resizeImage(image, + (int) Math.round((double) image.getWidth() * 2048.0 / largestSide), + (int) Math.round((double) image.getHeight() * 2048.0 / largestSide)); + + double aspectRatio = (double) image.getWidth() / (double) resized.getWidth(); + + NDArray grayscale = ImageProcessing.imageToTensor(manager, resized); + grayscale = ImageProcessing.standardize(grayscale); + + NDArray rgb = grayscale.concat(grayscale).concat(grayscale); // Create a "RGB" image from grayscale pixel values + List<Detection> detections = retinaNet.apply(rgb); + + JSONObject created = DicomToolbox.createDicomSR(connection, instanceId, detections, aspectRatio); + + String id = created.getString("ID"); + Functions.logWarning("Detection results stored in DICOM-SR instance: " + id); + + output.answerBuffer(created.toString().getBytes(StandardCharsets.UTF_8), "application/json"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + Callbacks.register("/java-mammography-viewer/(.*)", new Callbacks.OnRestRequest() { + @Override + public void call(RestOutput output, + HttpMethod method, + String uri, + String[] regularExpressionGroups, + Map<String, String> headers, + Map<String, String> getParameters, + byte[] body) { + if (method != HttpMethod.GET) { + output.sendMethodNotAllowed("GET"); + return; + } + + String path = regularExpressionGroups[0]; + int dot = path.lastIndexOf("."); + if (dot < 0) { + output.sendHttpStatus((short) 404, new byte[0]); + } else { + String extension = path.substring(dot + 1); + String mime = mimeTypes.getOrDefault(extension, "application/octet-stream"); + + ZipEntry entry = stone.getEntry(STONE_VERSION + "/" + path); + if (entry == null) { + output.sendHttpStatus((short) 404, new byte[0]); + } else { + try (InputStream stream = stone.getInputStream(entry)) { + output.answerBuffer(IOUtils.toByteArray(stream), mime); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + }); + } +}