Skip to content

Commit c34fe34

Browse files
committed
first working version
1 parent c416f6e commit c34fe34

File tree

8 files changed

+644
-12
lines changed

8 files changed

+644
-12
lines changed

src/main/java/qupath/ext/biop/abba/ABBAExtension.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,39 @@
33
import org.slf4j.Logger;
44
import org.slf4j.LoggerFactory;
55
import qupath.lib.common.Version;
6+
import qupath.lib.gui.ActionTools;
67
import qupath.lib.gui.QuPathGUI;
78
import qupath.lib.gui.extensions.GitHubProject;
89
import qupath.lib.gui.extensions.QuPathExtension;
10+
import qupath.lib.gui.tools.MenuTools;
911

1012
/**
11-
* Install Warpy as an extension.
13+
* Install ABBA extension as an extension.
1214
* <p>
13-
* Installs Warpy into QuPath, adding some metadata and adds the necessary global variables to QuPath's Preferences
15+
* Installs ABBA extension into QuPath, adding some metadata and adds the necessary global variables to QuPath's Preferences
1416
*
1517
* @author Nicolas Chiaruttini
1618
*/
1719
public class ABBAExtension implements QuPathExtension, GitHubProject {
1820
private final static Logger logger = LoggerFactory.getLogger(ABBAExtension.class);
1921

20-
2122
@Override
2223
public GitHubRepo getRepository() {
2324
return GitHubRepo.create("QuPath ABBA Extension", "biop", "qupath-extension-abba");
2425
}
2526

27+
private static boolean alreadyInstalled = false;
28+
2629
@Override
2730
public void installExtension(QuPathGUI qupath) {
28-
31+
if (alreadyInstalled)
32+
return;
33+
alreadyInstalled = true;
34+
var actionLoadAtlasRois = ActionTools.createAction(new LoadAtlasRoisToQuPathCommand(qupath), "Load Atlas Annotations into Open Image");
35+
36+
MenuTools.addMenuItems(qupath.getMenu("Extensions", false),
37+
MenuTools.createMenu("ABBA",actionLoadAtlasRois)
38+
);
2939
}
3040

3141
@Override
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package qupath.ext.biop.abba;
2+
3+
import ij.gui.Roi;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import qupath.ext.biop.abba.struct.AtlasHelper;
7+
import qupath.ext.biop.abba.struct.AtlasNode;
8+
import qupath.ext.biop.abba.struct.AtlasOntology;
9+
import qupath.imagej.tools.IJTools;
10+
import qupath.lib.common.ColorTools;
11+
import qupath.lib.gui.QuPathGUI;
12+
import qupath.lib.images.ImageData;
13+
import qupath.lib.images.servers.ImageServer;
14+
import qupath.lib.images.servers.RotatedImageServer;
15+
import qupath.lib.measurements.MeasurementList;
16+
import qupath.lib.measurements.MeasurementListFactory;
17+
import qupath.lib.objects.PathObject;
18+
import qupath.lib.objects.PathObjectTools;
19+
import qupath.lib.objects.PathObjects;
20+
import qupath.lib.projects.Project;
21+
import qupath.lib.projects.ProjectImageEntry;
22+
import qupath.lib.projects.Projects;
23+
import qupath.lib.roi.RoiTools;
24+
import qupath.lib.roi.interfaces.ROI;
25+
import qupath.lib.scripting.QP;
26+
27+
import java.awt.*;
28+
import java.awt.geom.AffineTransform;
29+
import java.nio.file.Files;
30+
import java.nio.file.Path;
31+
import java.nio.file.Paths;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.concurrent.atomic.AtomicReference;
36+
import java.util.stream.Collectors;
37+
38+
public class AtlasTools {
39+
40+
final static Logger logger = LoggerFactory.getLogger(AtlasTools.class);
41+
42+
private static QuPathGUI qupath = QuPathGUI.getInstance();
43+
44+
private static String title = "Load ABBA RoiSets from current QuPath project";
45+
46+
static private PathObject createAnnotationHierarchy(List<PathObject> annotations) {
47+
48+
// Map the ID of the annotation to ease finding parents
49+
Map<Integer, PathObject> mappedAnnotations =
50+
annotations
51+
.stream()
52+
.collect(
53+
Collectors.toMap(e -> (int) (e.getMeasurementList().getMeasurementValue("ID")), e -> e)
54+
);
55+
56+
AtomicReference<PathObject> rootReference = new AtomicReference<>();
57+
58+
mappedAnnotations.forEach((id, annotation) -> {
59+
PathObject parent = mappedAnnotations.get((int) annotation.getMeasurementList().getMeasurementValue("Parent ID"));
60+
if (parent != null) {
61+
parent.addPathObject(annotation);
62+
} else {
63+
// Found the root Path Object
64+
rootReference.set(annotation);
65+
}
66+
});
67+
68+
// Return just the root annotation from the atlas
69+
return rootReference.get();
70+
}
71+
72+
static PathObject getWarpedAtlasRegions(ImageData imageData, String atlasName, boolean splitLeftRight) {
73+
74+
List<PathObject> annotations = getFlattenedWarpedAtlasRegions(imageData, atlasName, splitLeftRight); // TODO
75+
if (splitLeftRight) {
76+
List<PathObject> annotationsLeft = annotations
77+
.stream()
78+
.filter(po -> po.getPathClass().isDerivedFrom(QP.getPathClass("Left")))
79+
.collect(Collectors.toList());
80+
81+
List<PathObject> annotationsRight = annotations
82+
.stream()
83+
.filter(po -> po.getPathClass().isDerivedFrom(QP.getPathClass("Right")))
84+
.collect(Collectors.toList());
85+
86+
PathObject rootLeft = createAnnotationHierarchy(annotationsLeft);
87+
PathObject rootRight = createAnnotationHierarchy(annotationsRight);
88+
ROI rootFused = RoiTools.combineROIs(rootLeft.getROI(), rootRight.getROI(), RoiTools.CombineOp.ADD);
89+
PathObject rootObject = PathObjects.createAnnotationObject(rootFused);
90+
rootObject.setName("Root");
91+
rootObject.addPathObject(rootLeft);
92+
rootObject.addPathObject(rootRight);
93+
return rootObject; // TODO
94+
} else {
95+
return createAnnotationHierarchy(annotations);
96+
}
97+
98+
}
99+
100+
public static List<PathObject> getFlattenedWarpedAtlasRegions(ImageData imageData, String atlasName, boolean splitLeftRight) {
101+
Project project = qupath.getProject();
102+
103+
// Get the project folder and get the ontology
104+
Path ontologyPath = Paths.get(Projects.getBaseDirectory(project).getAbsolutePath(), atlasName+"-Ontology.json");
105+
AtlasOntology ontology = AtlasHelper.openOntologyFromJsonFile(ontologyPath.toString());
106+
107+
// Loop through each ImageEntry
108+
ProjectImageEntry entry = project.getEntry(imageData);
109+
110+
Path roisetPath = Paths.get(entry.getEntryPath().toString(), "ABBA-Roiset-"+atlasName+".zip");
111+
if (!Files.exists(roisetPath)) {
112+
logger.info("No RoiSets found in {}", roisetPath);
113+
return null;
114+
}
115+
116+
// Get all the ROIs and add them as PathAnnotations
117+
List<Roi> rois = RoiSetLoader.openRoiSet(roisetPath.toAbsolutePath().toFile());
118+
logger.info("Loading {} Atlas Regions for {}", rois.size(), entry.getImageName());
119+
120+
Roi left = rois.get(rois.size() - 2);
121+
Roi right = rois.get(rois.size() - 1);
122+
123+
rois.remove(left);
124+
rois.remove(right);
125+
126+
// Rotation for rotated servers
127+
ImageServer<?> server = imageData.getServer();
128+
129+
AffineTransform transform = null;
130+
131+
if (server instanceof RotatedImageServer) {
132+
// The roi will need to be transformed before being imported
133+
// First : get the rotation
134+
RotatedImageServer ris = (RotatedImageServer) server;
135+
switch (ris.getRotation()) {
136+
case ROTATE_NONE: // No rotation.
137+
break;
138+
case ROTATE_90: // Rotate 90 degrees clockwise.
139+
transform = AffineTransform.getRotateInstance(Math.PI/2.0);
140+
transform.translate(0, -server.getWidth());
141+
break;
142+
case ROTATE_180: // Rotate 180 degrees.
143+
transform = AffineTransform.getRotateInstance(Math.PI);
144+
transform.translate(-server.getWidth(), -server.getHeight());
145+
break;
146+
case ROTATE_270: // Rotate 270 degrees
147+
transform = AffineTransform.getRotateInstance(Math.PI*3.0/2.0);
148+
transform.translate(-server.getHeight(), 0);
149+
break;
150+
default:
151+
System.err.println("Unknow rotation for rotated image server: "+ris.getRotation());
152+
}
153+
}
154+
155+
AffineTransform finalTransform = transform;
156+
157+
List<PathObject> annotations = rois.stream().map(roi -> {
158+
// Create the PathObject
159+
160+
//PathObject object = IJTools.convertToAnnotation( imp, imageData.getServer(), roi, 1, null );
161+
PathObject object = PathObjects.createAnnotationObject(IJTools.convertToROI(roi, 0, 0, 1, null));
162+
163+
// Handles rotated image server
164+
if (finalTransform !=null) {
165+
object = PathObjectTools.transformObject(object, finalTransform, true);
166+
}
167+
168+
// Add metadata to object as acquired from the Ontology
169+
int object_id = Integer.parseInt(roi.getName());
170+
// Get associated information
171+
AtlasNode node = ontology.getNodeFromId(object_id);
172+
String name = node.data().get(ontology.getNamingProperty());
173+
System.out.println("node:"+node.getId()+":"+name);
174+
object.setName(name);
175+
object.getMeasurementList().putMeasurement("ID", node.getId());
176+
if (node.parent()!=null) {
177+
object.getMeasurementList().putMeasurement("Parent ID", node.parent().getId());
178+
}
179+
object.getMeasurementList().putMeasurement("Side", 0);
180+
object.setPathClass(QP.getPathClass(name));
181+
object.setLocked(true);
182+
int[] rgba = node.getColor();
183+
int color = ColorTools.packRGB(rgba[0], rgba[1], rgba[2]);
184+
object.setColorRGB(color);
185+
return object;
186+
}).collect(Collectors.toList());
187+
188+
if (splitLeftRight) {
189+
ROI leftROI = IJTools.convertToROI(left, 0, 0, 1, null);
190+
ROI rightROI = IJTools.convertToROI(right, 0, 0, 1, null);
191+
List<PathObject> splitObjects = new ArrayList<>();
192+
for (PathObject annotation : annotations) {
193+
ROI shapeLeft = RoiTools.combineROIs(leftROI, annotation.getROI(), RoiTools.CombineOp.INTERSECT);
194+
if (!shapeLeft.isEmpty()) {
195+
PathObject objectLeft = PathObjects.createAnnotationObject(shapeLeft, annotation.getPathClass(), duplicateMeasurements(annotation.getMeasurementList()));
196+
objectLeft.setName(annotation.getName());
197+
objectLeft.setPathClass(QP.getDerivedPathClass(QP.getPathClass("Left"), annotation.getPathClass().getName()));
198+
objectLeft.setColorRGB(annotation.getColorRGB());
199+
objectLeft.setLocked(true);
200+
splitObjects.add(objectLeft);
201+
}
202+
203+
ROI shapeRight = RoiTools.combineROIs(rightROI, annotation.getROI(), RoiTools.CombineOp.INTERSECT);
204+
if (!shapeRight.isEmpty()) {
205+
PathObject objectRight = PathObjects.createAnnotationObject(shapeRight, annotation.getPathClass(), duplicateMeasurements(annotation.getMeasurementList()));
206+
objectRight.setName(annotation.getName());
207+
objectRight.setPathClass(QP.getDerivedPathClass(QP.getPathClass("Right"), annotation.getPathClass().getName()));
208+
objectRight.setColorRGB(annotation.getColorRGB());
209+
objectRight.setLocked(true);
210+
splitObjects.add(objectRight);
211+
}
212+
213+
}
214+
return splitObjects;
215+
} else {
216+
return annotations;
217+
}
218+
}
219+
220+
public static void loadWarpedAtlasAnnotations(ImageData imageData, String atlasName, boolean splitLeftRight) {
221+
imageData.getHierarchy().addPathObject(getWarpedAtlasRegions(imageData, atlasName, splitLeftRight));
222+
imageData.getHierarchy().fireHierarchyChangedEvent(AtlasTools.class);
223+
}
224+
225+
private static MeasurementList duplicateMeasurements(MeasurementList measurements) {
226+
MeasurementList list = MeasurementListFactory.createMeasurementList(measurements.size(), MeasurementList.MeasurementListType.GENERAL);
227+
228+
for (int i = 0; i < measurements.size(); i++) {
229+
String name = measurements.getMeasurementName(i);
230+
double value = measurements.getMeasurementValue(i);
231+
list.addMeasurement(name, value);
232+
}
233+
return list;
234+
}
235+
236+
}

src/main/java/qupath/ext/biop/abba/Dummy.java

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package qupath.ext.biop.abba;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import qupath.lib.display.ImageDisplay;
6+
import qupath.lib.gui.QuPathGUI;
7+
import qupath.lib.gui.dialogs.Dialogs;
8+
import qupath.lib.images.ImageData;
9+
10+
public class LoadAtlasRoisToQuPathCommand implements Runnable {
11+
12+
private static QuPathGUI qupath;
13+
14+
private boolean splitLeftRight;
15+
private boolean doRun;
16+
17+
public LoadAtlasRoisToQuPathCommand( final QuPathGUI qupath) {
18+
this.qupath = qupath;
19+
}
20+
21+
public void run() {
22+
23+
String splitMode =
24+
Dialogs.showChoiceDialog("Load Brain RoiSets into Image",
25+
"This will load any RoiSets Exported using the ABBA tool onto the current image.\nContinue?", new String[]{"Split Left and Right Regions", "Do not split"}, "Do not split");
26+
27+
switch (splitMode) {
28+
case "Do not split" :
29+
splitLeftRight = false;
30+
doRun = true;
31+
break;
32+
case "Split Left and Right Regions" :
33+
splitLeftRight = true;
34+
doRun = true;
35+
break;
36+
default:
37+
// null returned -> cancelled
38+
doRun = false;
39+
return;
40+
}
41+
if (doRun) {
42+
ImageData imageData = qupath.getImageData();
43+
// TODO : Find atlas name
44+
AtlasTools.loadWarpedAtlasAnnotations(imageData, "Adult Mouse Brain - Allen Brain Atlas V3", splitLeftRight);
45+
System.out.println("Import DONE");
46+
}
47+
}
48+
49+
}

0 commit comments

Comments
 (0)