Skip to content

Commit

Permalink
Reworked bulk annotation to be compliant with the standard. Applied c…
Browse files Browse the repository at this point in the history
…ode suggestions.
  • Loading branch information
Rui-Jesus committed Jun 7, 2024
1 parent c0baaec commit ab6a824
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import pt.ua.dicoogle.plugins.PluginController;
import pt.ua.dicoogle.sdk.datastructs.SearchResult;
import pt.ua.dicoogle.sdk.datastructs.dim.BulkAnnotation;
import pt.ua.dicoogle.sdk.datastructs.dim.Point2D;
import pt.ua.dicoogle.sdk.mlprovider.*;
import pt.ua.dicoogle.server.web.dicom.ROIExtractor;
import pt.ua.dicoogle.server.web.utils.cache.WSICache;
Expand Down Expand Up @@ -109,12 +110,14 @@ public MLDataset call() throws Exception {
dcm.putString(Tag.TransferSyntaxUID, VR.CS, "1.2.840.10008.1.2.4.50");

for(BulkAnnotation annotation: entry.getValue()){
BufferedImage roi = roiExtractor.extractROI(image.get("SOPInstanceUID").toString(), annotation);
String roiFileName = annotation.getLabel().getName() + c++;
classes.add(annotation.getLabel().getName());
File output = new File(path + File.separator + roiFileName + ".jpeg");
ImageIO.write(roi, "jpeg", output);
dataset.put(new ImageEntry(dcm, output.toURI()), annotation.getLabel());
for(List<Point2D> points: annotation.getAnnotations()){
BufferedImage roi = roiExtractor.extractROI(image.get("SOPInstanceUID").toString(), annotation.getAnnotationType(), points);
String roiFileName = annotation.getLabel().getName() + c++;
classes.add(annotation.getLabel().getName());
File output = new File(path + File.separator + roiFileName + ".jpeg");
ImageIO.write(roi, "jpeg", output);
dataset.put(new ImageEntry(dcm, output.toURI()), annotation.getLabel());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import pt.ua.dicoogle.sdk.datastructs.dim.Point2D;
import pt.ua.dicoogle.sdk.datastructs.wsi.WSIFrame;
import pt.ua.dicoogle.server.web.utils.cache.WSICache;
import sun.reflect.annotation.AnnotationType;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
Expand All @@ -37,6 +38,8 @@
import java.util.*;
import java.util.List;

import static pt.ua.dicoogle.sdk.datastructs.dim.BulkAnnotation.AnnotationType.*;

public class ROIExtractor {

private static final Logger logger = LoggerFactory.getLogger(ROIExtractor.class);
Expand All @@ -49,18 +52,18 @@ public ROIExtractor() {
this.wsiCache = WSICache.getInstance();
}

public BufferedImage extractROI(String sopInstanceUID, BulkAnnotation bulkAnnotation) {
public BufferedImage extractROI(String sopInstanceUID, BulkAnnotation.AnnotationType type, List<Point2D> annotation) {
DicomMetaData metaData;
try {
metaData = getDicomMetadata(sopInstanceUID);
} catch (IOException e) {
logger.error("Could not extract metadata", e);
return null;
}
return extractROI(metaData, bulkAnnotation);
return extractROI(metaData, type, annotation);
}

public BufferedImage extractROI(DicomMetaData dicomMetaData, BulkAnnotation bulkAnnotation) {
public BufferedImage extractROI(DicomMetaData dicomMetaData, BulkAnnotation.AnnotationType type, List<Point2D> annotation) {

ImageReader imageReader = getImageReader();

Expand All @@ -84,7 +87,7 @@ public BufferedImage extractROI(DicomMetaData dicomMetaData, BulkAnnotation bulk
descriptor.extractData(dicomMetaData.getAttributes());

try {
return getROIFromAnnotation(bulkAnnotation, descriptor, imageReader, param);
return getROIFromAnnotation(type, annotation, descriptor, imageReader, param);
} catch (IllegalArgumentException e) {
logger.error("Error writing ROI", e);
}
Expand All @@ -105,19 +108,20 @@ private static ImageReader getImageReader() {
/**
* Given an annotation and an image, return the section of the image the annotation intersects.
* It only works with rectangle type annotations.
* @param annotation the annotation to intersect
* @param type the annotation type
* @param annotation a list of points defining the annotation to extract
* @param descriptor descriptor of the WSI pyramid, contains information about the dimensions of the image.
* @param imageReader
* @param param
* @return the intersection of the annotation on the image.
* @throws IllegalArgumentException when the annotation is not one of the supported types.
*/
private BufferedImage getROIFromAnnotation(BulkAnnotation annotation, WSISopDescriptor descriptor, ImageReader imageReader, DicomImageReadParam param) throws IllegalArgumentException {
if(notSupportedTypes.contains(annotation.getAnnotationType())){
private BufferedImage getROIFromAnnotation(BulkAnnotation.AnnotationType type, List<Point2D> annotation, WSISopDescriptor descriptor, ImageReader imageReader, DicomImageReadParam param) throws IllegalArgumentException {
if(notSupportedTypes.contains(type)){
throw new IllegalArgumentException("Trying to build a ROI with an unsupported annotation type");
}

List<Point2D> constructionPoints = annotation.getBoundingBox(); //Points that will be used to construct the ROI.
List<Point2D> constructionPoints = BulkAnnotation.getBoundingBox(type, annotation); //Points that will be used to construct the ROI.

Point2D annotationPoint1 = constructionPoints.get(0);
Point2D annotationPoint2 = constructionPoints.get(3);
Expand All @@ -134,8 +138,8 @@ private BufferedImage getROIFromAnnotation(BulkAnnotation annotation, WSISopDesc

// We need to perform clipping if annotation is not a rectangle
Shape clippingShape = null;
if(annotation.getAnnotationType() != BulkAnnotation.AnnotationType.RECTANGLE){
clippingShape = getClippingShape(annotation, constructionPoints.get(0));
if(type != BulkAnnotation.AnnotationType.RECTANGLE){
clippingShape = getClippingShape(type, annotation, constructionPoints.get(0));
if (clippingShape != null) g.setClip(clippingShape);
}

Expand Down Expand Up @@ -199,25 +203,26 @@ private BufferedImage getROIFromAnnotation(BulkAnnotation annotation, WSISopDesc
* Given an annotation, get it as a Shape to apply as a clipping shape for the ROIs.
* The points of this shape are normalized according to the starting point.
* This is only needed when dealing with non-rectangle annotations.
* @param annotation
* @param type the type of annotation
* @param annotation the list of points of the annotation
* @param startingPoint starting point of the rectangle that contains the annotation
* @return a shape to use to clip the ROI
*/
private Shape getClippingShape(BulkAnnotation annotation, Point2D startingPoint){
switch (annotation.getAnnotationType()){
private Shape getClippingShape(BulkAnnotation.AnnotationType type, List<Point2D> annotation, Point2D startingPoint){
switch (type){
case POLYLINE:
case POLYGON:
Polygon polygon = new Polygon();
for(Point2D p : annotation.getPoints()){
for(Point2D p : annotation){
polygon.addPoint((int) (p.getX() - startingPoint.getX()), (int) (p.getY() - startingPoint.getY()));
}
return polygon;
case ELLIPSE:
double minX = annotation.getPoints().get(0).getX();
double maxX = annotation.getPoints().get(1).getX();
double minX = annotation.get(0).getX();
double maxX = annotation.get(1).getX();

double minY = annotation.getPoints().get(2).getY();
double maxY = annotation.getPoints().get(3).getY();
double minY = annotation.get(2).getY();
double maxY = annotation.get(3).getY();

return new Ellipse2D.Double(minX - startingPoint.getX(), minY - startingPoint.getY(), Math.abs(maxX - minX), Math.abs(maxY - minY));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,29 +78,26 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
return;
}

BulkAnnotation annotation;
List<Point2D> annotation;
BulkAnnotation.AnnotationType annotationType = BulkAnnotation.AnnotationType.RECTANGLE;
try{
int nX = Integer.parseInt(x);
int nY = Integer.parseInt(y);
int nWidth = Integer.parseInt(width);
int nHeight = Integer.parseInt(height);
annotation = new BulkAnnotation();
Point2D tl = new Point2D(nX, nY);
Point2D tr = new Point2D(nX + nWidth, nY);
Point2D bl = new Point2D(nX, nY + nHeight);
Point2D br = new Point2D(nX + nWidth, nY + nHeight);
List<Point2D> points = new ArrayList<>();
points.add(tl); points.add(tr); points.add(bl); points.add(br);
annotation.setPoints(points);
annotation.setAnnotationType(BulkAnnotation.AnnotationType.RECTANGLE);
annotation = Arrays.asList(tl, tr, bl, br);
} catch (NumberFormatException e){
response.sendError(Status.CLIENT_ERROR_BAD_REQUEST.getCode(), "ROI provided was invalid");
return;
}

DicomMetaData metaData = getDicomMetadata(sopInstanceUID);

BufferedImage bi = roiExtractor.extractROI(metaData, annotation);
BufferedImage bi = roiExtractor.extractROI(metaData, annotationType, annotation);

if(bi != null){
response.setContentType("image/jpeg");
Expand Down Expand Up @@ -136,13 +133,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
String type = body.get("type").asText();
List<Point2D> points = mapper.readValue(body.get("points").toString(), new TypeReference<List<Point2D>>(){});

BulkAnnotation annotation = new BulkAnnotation();
annotation.setPoints(points);
annotation.setAnnotationType(BulkAnnotation.AnnotationType.valueOf(type));

DicomMetaData dicomMetaData = this.getDicomMetadata(sopInstanceUID);

BufferedImage bi = roiExtractor.extractROI(dicomMetaData, annotation);
BufferedImage bi = roiExtractor.extractROI(dicomMetaData, BulkAnnotation.AnnotationType.valueOf(type), points);

if(bi != null){
response.setContentType("image/jpeg");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.ExecutionException;

public class InferServlet extends HttpServlet {
Expand Down Expand Up @@ -150,8 +147,8 @@ private Task<MLInference> sendWSIRequest(String provider, String modelID, String
ObjectMapper mapper = new ObjectMapper();
MLInferenceRequest predictionRequest = new MLInferenceRequest(true, DimLevel.INSTANCE, uid, modelID);
predictionRequest.setParameters(parameters);
BulkAnnotation annotation = new BulkAnnotation();
annotation.setPoints(roi);
BulkAnnotation annotation = new BulkAnnotation(roiType, BulkAnnotation.PixelOrigin.VOLUME);
annotation.setAnnotations(Collections.singletonList(roi));
annotation.setAnnotationType(roiType);

try {
Expand All @@ -171,11 +168,11 @@ private Task<MLInference> sendWSIRequest(String provider, String modelID, String
}

// Verify dimensions of annotation, reject if too big. In the future, adopt a sliding window strategy to process large processing windows.
double area = annotation.getArea();
double area = annotation.getArea(annotation.getAnnotations().get(0));
if(area > 16000000) // This equates to a maximum of 4000x4000 which represents in RGB an image of 48MB
return null;

BufferedImage bi = roiExtractor.extractROI(dicomMetaData, annotation);
BufferedImage bi = roiExtractor.extractROI(dicomMetaData, annotation.getAnnotationType(), annotation.getAnnotations().get(0));
predictionRequest.setRoi(bi);
Task<MLInference> task = PluginController.getInstance().infer(provider, predictionRequest);
if(task != null){
Expand All @@ -191,15 +188,14 @@ private Task<MLInference> sendWSIRequest(String provider, String modelID, String

// Coordinates need to be converted if we're working with WSI
if(!prediction.getAnnotations().isEmpty()){
Point2D tl = annotation.getBoundingBox().get(0);
Point2D tl = annotation.getBoundingBox(annotation.getAnnotations().get(0)).get(0);
convertCoordinates(prediction, tl, scale);
}

response.setContentType("application/json");
PrintWriter out = response.getWriter();
mapper.writeValue(out, prediction);
out.close();
out.flush();
} catch (InterruptedException | ExecutionException e) {
log.error("Could not make prediction", e);
try {
Expand Down Expand Up @@ -247,39 +243,36 @@ private Task<MLInference> sendRequest(String provider, String modelID, DimLevel
String boundary = UUID.randomUUID().toString();
response.setContentType("multipart/form-data; boundary=" + boundary);

ServletOutputStream out = response.getOutputStream();

out.print("--" + boundary);
out.println();
out.print("Content-Disposition: form-data; name=\"params\"");
out.println();
out.print("Content-Type: application/json");
out.println(); out.println();
out.print(mapper.writeValueAsString(prediction));
out.println();
out.print("--" + boundary);
out.println();
out.print("Content-Disposition: form-data; name=\"dicomseg\"; filename=\"dicomseg.dcm\"");
out.println();
out.print("Content-Type: application/dicom");
out.println(); out.println();

try (InputStream fi = Files.newInputStream(prediction.getDicomSEG())) {
IOUtils.copy(fi, out);
out.flush();
try (ServletOutputStream out = response.getOutputStream()){
out.print("--" + boundary);
out.println();
out.print("Content-Disposition: form-data; name=\"params\"");
out.println();
out.print("Content-Type: application/json");
out.println(); out.println();
out.print(mapper.writeValueAsString(prediction));
out.println();
out.print("--" + boundary);
out.println();
out.print("Content-Disposition: form-data; name=\"dicomseg\"; filename=\"dicomseg.dcm\"");
out.println();
out.print("Content-Type: application/dicom");
out.println(); out.println();

try (InputStream fi = Files.newInputStream(prediction.getDicomSEG())) {
IOUtils.copy(fi, out);
out.flush();
}

out.println();
out.print("--" + boundary + "--");
}

out.println();
out.print("--" + boundary + "--");
out.flush();
out.close();

} else {
response.setContentType("application/json");
PrintWriter out = response.getWriter();
mapper.writeValue(out, prediction);
out.close();
out.flush();
}

try{
Expand Down Expand Up @@ -320,9 +313,11 @@ private DicomMetaData getDicomMetadata(String sop) throws IOException{
*/
private void convertCoordinates(MLInference prediction, Point2D tl, double scale){
for(BulkAnnotation ann : prediction.getAnnotations()){
for(Point2D p : ann.getPoints()){
p.setX((p.getX() + tl.getX()) * scale);
p.setY((p.getY() + tl.getY()) * scale);
for(List<Point2D> points : ann.getAnnotations()){
for(Point2D p : points){
p.setX((p.getX() + tl.getX()) * scale);
p.setY((p.getY() + tl.getY()) * scale);
}
}
}
}
Expand All @@ -334,9 +329,11 @@ private void convertCoordinates(MLInference prediction, Point2D tl, double scale
* @return the ml prediction with the converted coordinates.
*/
private void scaleAnnotation(BulkAnnotation annotation, double scale){
for(Point2D p : annotation.getPoints()){
p.setX((p.getX()) * scale);
p.setY((p.getY()) * scale);
for(List<Point2D> ann : annotation.getAnnotations()){
for(Point2D p : ann){
p.setX((p.getX()) * scale);
p.setY((p.getY()) * scale);
}
}
}
}
Loading

0 comments on commit ab6a824

Please sign in to comment.