Skip to content

Commit 6f45118

Browse files
authored
Merge pull request #23 from BIOP/qp-0.6
tries a more lenient approach to errors
2 parents c0107b2 + 8e84bbd commit 6f45118

1 file changed

Lines changed: 71 additions & 79 deletions

File tree

src/main/java/qupath/ext/warpy/Warpy.java

Lines changed: 71 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,24 @@
1212
import org.slf4j.Logger;
1313
import org.slf4j.LoggerFactory;
1414
import qupath.lib.analysis.features.ObjectMeasurements;
15-
import qupath.lib.common.GeneralTools;
16-
import qupath.lib.common.Version;
1715
import qupath.lib.gui.QuPathApp;
1816
import qupath.lib.images.ImageData;
1917
import qupath.lib.images.servers.ImageServer;
18+
import qupath.lib.images.servers.TransformedServerBuilder;
2019
import qupath.lib.objects.*;
2120
import qupath.lib.objects.hierarchy.TMAGrid;
2221
import qupath.lib.projects.Project;
2322
import qupath.lib.projects.ProjectImageEntry;
2423
import qupath.lib.roi.GeometryTools;
2524
import qupath.lib.roi.ROIs;
2625
import qupath.lib.roi.interfaces.ROI;
27-
import qupath.lib.scripting.QP;
28-
2926
import java.awt.image.BufferedImage;
3027
import java.io.File;
3128
import java.io.FileNotFoundException;
3229
import java.io.FileReader;
3330
import java.io.IOException;
3431
import java.nio.file.Path;
35-
import java.util.ArrayList;
36-
import java.util.Arrays;
37-
import java.util.Collection;
38-
import java.util.List;
32+
import java.util.*;
3933
import java.util.function.Predicate;
4034
import java.util.regex.Matcher;
4135
import java.util.regex.Pattern;
@@ -47,8 +41,8 @@
4741
* Utility class which bridges real transformation of the ImgLib2 world and
4842
* makes it easily usable into JTS world, mainly used by QuPath
4943
* <p>
50-
* See initial forum thread : https://forum.image.sc/t/qupath-arbitrarily-transform-detections-and-annotations/49674
51-
* For documentation regarding this tool, see https://c4science.ch/w/bioimaging_and_optics_platform_biop/image-processing/wsi_registration_fjii_qupath/
44+
* See initial forum thread : <a href="https://forum.image.sc/t/qupath-arbitrarily-transform-detections-and-annotations/49674">...</a>
45+
* For documentation regarding this tool, see <a href="https://c4science.ch/w/bioimaging_and_optics_platform_biop/image-processing/wsi_registration_fjii_qupath/">...</a>
5246
* <p>
5347
* Extra dependencies required for QuPath:
5448
* <p>
@@ -69,47 +63,47 @@ public class Warpy {
6963
final private static Logger logger = LoggerFactory.getLogger(Warpy.class);
7064

7165
// Pattern to match the transform file
72-
private static Pattern transformFilePattern = Pattern.compile("transform\\_(?<target>\\d+)\\_(?<source>\\d+)\\.json");
66+
private static final Pattern transformFilePattern = Pattern.compile("transform_(?<target>\\d+)_(?<source>\\d+)\\.json");
7367

7468
/**
7569
* Recovers a list of candidate entries in this project that have a RealTransform file that matches the pattern in
7670
* 'transformFilePattern'
7771
* @param targetEntry the entry which should *receive* transformed objects. Typically the active entry.
7872
* @return a collection of project entries that have a useable and valid RealTransform (forward or valid inverse)
7973
*/
80-
public static Collection<ProjectImageEntry> getCandidateSourceEntries(ProjectImageEntry targetEntry) {
74+
public static Collection<ProjectImageEntry<?>> getCandidateSourceEntries(ProjectImageEntry<?> targetEntry) {
8175

82-
Project project = getProject();
76+
Project<?> project = getProject();
8377
// Find in the targetfolder, the source entries that have a Serialized RealTransform file
84-
List<ProjectImageEntry<BufferedImage>> entries = project.getImageList();
78+
List<? extends ProjectImageEntry<?>> entries = project.getImageList();
8579

8680
String targetID = targetEntry.getID();
8781

8882
// Find if there is a forward transform file and return the entry source
8983
Path targetEntryPath = targetEntry.getEntryPath();
90-
Collection<ProjectImageEntry> candidateTransformableEntries = new ArrayList<>();
91-
for (File currentFile : targetEntryPath.toFile().listFiles()) {
84+
Collection<ProjectImageEntry<?>> candidateTransformableEntries = new ArrayList<>();
85+
for (File currentFile : Objects.requireNonNull(targetEntryPath.toFile().listFiles())) {
9286
Matcher matcher = transformFilePattern.matcher(currentFile.getName());
9387
if (matcher.matches()) {
9488
if (matcher.group("target").equals(targetID)) {
9589
// Check the source ID and return if
9690
String sourceID = matcher.group("source");
97-
ProjectImageEntry sourceEntry = getEntryFromID(sourceID);
91+
ProjectImageEntry<?> sourceEntry = getEntryFromID(sourceID);
9892
if (sourceEntry != null) candidateTransformableEntries.add(sourceEntry);
9993
}
10094
}
10195
}
10296

10397
// Find is there are inverse transforms available by going through all entries
104-
for (ProjectImageEntry entry : entries) {
98+
for (ProjectImageEntry<?> entry : entries) {
10599
if (!entry.equals(targetEntry)) {
106-
for (File currentFile : entry.getEntryPath().toFile().listFiles()) {
100+
for (File currentFile : Objects.requireNonNull(entry.getEntryPath().toFile().listFiles())) {
107101
Matcher matcher = transformFilePattern.matcher(currentFile.getName());
108102
if (matcher.matches()) {
109103
if (matcher.group("source").equals(targetID)) {
110104
// Check the source ID and return it
111105
String inverseSourceID = matcher.group("target");
112-
ProjectImageEntry inverseSourceEntry = getEntryFromID(inverseSourceID);
106+
ProjectImageEntry<?> inverseSourceEntry = getEntryFromID(inverseSourceID);
113107
if (inverseSourceEntry != null) {
114108
// Try to get the transform and see if it works
115109
RealTransform rt = getRealTransform(currentFile);
@@ -138,7 +132,7 @@ public static Collection<ProjectImageEntry> getCandidateSourceEntries(ProjectIma
138132
* @param sourceEntry the entry from which we want to extract the objects
139133
* @return a collection of PathObjects, in hierarchical form
140134
*/
141-
public static Collection<PathObject> getPathObjectsFromEntry(ProjectImageEntry sourceEntry) {
135+
public static Collection<PathObject> getPathObjectsFromEntry(ProjectImageEntry<?> sourceEntry) {
142136
try {
143137
// Do not return the TMA cores
144138
return sourceEntry.readHierarchy().getRootObject().getChildObjects().stream().filter(Predicate.not(PathObject::isTMACore)).collect(Collectors.toList());
@@ -148,7 +142,7 @@ public static Collection<PathObject> getPathObjectsFromEntry(ProjectImageEntry s
148142
return null;
149143
}
150144

151-
public static TMAGrid getTMAGridFromEntry(ProjectImageEntry sourceEntry) {
145+
public static TMAGrid getTMAGridFromEntry(ProjectImageEntry<?> sourceEntry) {
152146
try {
153147
return sourceEntry.readHierarchy().getTMAGrid();
154148
} catch (IOException e) {
@@ -214,16 +208,9 @@ public static Collection<PathObject> transformPathObjects(Collection<PathObject>
214208
CoordinateSequenceFilter transformer = getJTSFilter(transform);
215209

216210
// Transforms all objects and add them to a new list
217-
List<PathObject> transformedObjects = new ArrayList<>();
218-
219-
for (PathObject o : objects) {
220-
try {
221-
transformedObjects.add(transformPathObjectAndChildren(o, transformer, true, true));
222-
} catch (Exception e) {
223-
logger.info("Could not transform object " + o, e);
224-
}
225-
}
226-
return transformedObjects;
211+
return objects.stream().map(o-> transformPathObjectAndChildren(o, transformer, true, true) )
212+
.filter(Objects::nonNull)
213+
.collect(Collectors.toList());
227214
}
228215

229216
/**
@@ -232,8 +219,8 @@ public static Collection<PathObject> transformPathObjects(Collection<PathObject>
232219
* @param id the ID to get the ImageEntry from
233220
* @return the corresponding ProjectImageEntry or null if none is found
234221
*/
235-
private static ProjectImageEntry getEntryFromID(String id) {
236-
for (ProjectImageEntry entry : QP.getProject().getImageList()) {
222+
private static ProjectImageEntry<?> getEntryFromID(String id) {
223+
for (ProjectImageEntry<?> entry : getProject().getImageList()) {
237224
if (entry.getID().equals(id)) return entry;
238225
}
239226
return null;
@@ -247,13 +234,13 @@ private static ProjectImageEntry getEntryFromID(String id) {
247234
* Warpy can work out if the serialized transform is a forward one or an inverse transform.
248235
* @return the RealTransform to use for warping pathObjects
249236
*/
250-
public static RealTransform getRealTransform(ProjectImageEntry sourceEntry, ProjectImageEntry targetEntry) {
237+
public static RealTransform getRealTransform(ProjectImageEntry<?> sourceEntry, ProjectImageEntry<?> targetEntry) {
251238

252239
// Search Forward
253240
String targetID = targetEntry.getID();
254241
String sourceID = sourceEntry.getID();
255242
Path targetEntryPath = targetEntry.getEntryPath();
256-
for (File currentFile : targetEntryPath.toFile().listFiles()) {
243+
for (File currentFile : Objects.requireNonNull(targetEntryPath.toFile().listFiles())) {
257244
Matcher matcher = transformFilePattern.matcher(currentFile.getName());
258245
if (matcher.matches()) {
259246
if (matcher.group("target").equals(targetID)) {
@@ -268,7 +255,7 @@ public static RealTransform getRealTransform(ProjectImageEntry sourceEntry, Proj
268255

269256
// Search Backwards
270257
Path sourceEntryPath = sourceEntry.getEntryPath();
271-
for (File currentFile : sourceEntryPath.toFile().listFiles()) {
258+
for (File currentFile : Objects.requireNonNull(sourceEntryPath.toFile().listFiles())) {
272259
Matcher matcher = transformFilePattern.matcher(currentFile.getName());
273260
if (matcher.matches()) {
274261
if (matcher.group("source").equals(targetID)) {
@@ -279,7 +266,7 @@ public static RealTransform getRealTransform(ProjectImageEntry sourceEntry, Proj
279266
if (rt instanceof InvertibleRealTransform) {
280267
return ((InvertibleRealTransform) rt).inverse();
281268
} else {
282-
logger.error("Could not invert transform from file {}. This error should not exist.");
269+
logger.error("Could not invert transform from file {}. This error should not exist.", currentFile.getAbsolutePath());
283270
return null;
284271
}
285272
}
@@ -299,17 +286,16 @@ public static RealTransform getRealTransform(ProjectImageEntry sourceEntry, Proj
299286
* @throws Exception an error in case that the objects could not be measured
300287
*/
301288
public static void addIntensityMeasurements(Collection<PathObject> objects, double downsample) throws Exception {
302-
ImageServer server = getCurrentServer();
303-
304289

290+
ImageServer<BufferedImage> server = (ImageServer<BufferedImage>) getCurrentServer();
305291
//If the image is RGB, this line can be added to import the correct measurements (DAB, etc.):
306292
//cf https://forum.image.sc/t/transferring-segmentation-predictions-from-custom-masks-to-qupath/43408/15
307293
ImageData.ImageType type = getProjectEntry().readImageData().getImageType();
308294

309295
if (type.equals(ImageData.ImageType.BRIGHTFIELD_H_DAB) ||
310-
type.equals(ImageData.ImageType.BRIGHTFIELD_H_DAB) ||
296+
type.equals(ImageData.ImageType.BRIGHTFIELD_H_E) ||
311297
type.equals(ImageData.ImageType.BRIGHTFIELD_OTHER)) {
312-
server = new qupath.lib.images.servers.TransformedServerBuilder(server)
298+
server = new TransformedServerBuilder(server)
313299
.deconvolveStains(getCurrentImageData().getColorDeconvolutionStains(), 1, 2)
314300
.build();
315301
}
@@ -344,20 +330,25 @@ public static void addIntensityMeasurements(Collection<PathObject> objects, Imag
344330
*
345331
* @param object qupath annotation or detection object
346332
* @param transform jts free form transformation
347-
* @param copyMeasurements whether or not to transfer all the source PathObject Measurements to the resulting PathObject
333+
* @param copyMeasurements whether to transfer all the source PathObject Measurements to the resulting PathObject
348334
*/
349-
private static PathObject transformPathObjectAndChildren(PathObject object, CoordinateSequenceFilter transform, boolean checkGeometryValidity, boolean copyMeasurements) throws Exception {
335+
private static PathObject transformPathObjectAndChildren(PathObject object, CoordinateSequenceFilter transform, boolean checkGeometryValidity, boolean copyMeasurements) {
350336

351-
PathObject transformedObject = transformPathObject(object, transform, checkGeometryValidity, copyMeasurements);
352-
353-
if (object.hasChildObjects()) {
354-
for (PathObject child : object.getChildObjects()) {
355-
transformedObject.addChildObject(transformPathObjectAndChildren(child, transform, checkGeometryValidity, copyMeasurements));
356-
}
337+
PathObject transformedObject = null;
338+
try {
339+
transformedObject = transformPathObject(object, transform, checkGeometryValidity, copyMeasurements);
340+
} catch (Exception e) {
341+
logger.error("Could not transform object {}, error is {}", object, e.getLocalizedMessage());
357342
}
358343

359-
// Re-add name if it exists
360-
transformedObject.setName(object.getName());
344+
if (object.hasChildObjects() && transformedObject != null ) {
345+
logger.info("Transforming {}", object);
346+
List<PathObject> children = object.getChildObjects().stream()
347+
.map(child -> transformPathObjectAndChildren(child, transform, checkGeometryValidity, copyMeasurements))
348+
.filter(Objects::nonNull)
349+
.collect(Collectors.toList());
350+
transformedObject.addChildObjects(children);
351+
}
361352

362353
return transformedObject;
363354
}
@@ -391,33 +382,36 @@ private static PathObject transformPathObject(PathObject object, CoordinateSeque
391382
ROI transformed_roi = GeometryTools.geometryToROI(geometry, original_roi.getImagePlane());
392383

393384
PathObject transformedObject;
394-
if (object instanceof PathAnnotationObject) {
395-
transformedObject = PathObjects.createAnnotationObject(transformed_roi, object.getPathClass(), copyMeasurements ? object.getMeasurementList() : null);
396-
} else if (object instanceof PathCellObject) {
397-
// Need to transform the nucleus as well
398-
ROI original_nuc = ((PathCellObject) object).getNucleusROI();
399-
ROI transformed_nuc_roi = null;
400-
if (original_nuc != null) {
401-
402-
Geometry nuc_geometry = original_nuc.getGeometry();
403-
404-
GeometryTools.attemptOperation(nuc_geometry, (g) -> {
405-
g.apply(transform);
406-
return g;
407-
});
408-
transformed_nuc_roi = GeometryTools.geometryToROI(nuc_geometry, original_roi.getImagePlane());
385+
switch (object) {
386+
case PathAnnotationObject pathAnnotationObject ->
387+
transformedObject = PathObjects.createAnnotationObject(transformed_roi, pathAnnotationObject.getPathClass(), copyMeasurements ? object.getMeasurementList() : null);
388+
case PathCellObject pathCellObject -> {
389+
// Need to transform the nucleus as well
390+
ROI original_nuc = pathCellObject.getNucleusROI();
391+
ROI transformed_nuc_roi = null;
392+
if (original_nuc != null) {
393+
394+
Geometry nuc_geometry = original_nuc.getGeometry();
395+
396+
GeometryTools.attemptOperation(nuc_geometry, (g) -> {
397+
g.apply(transform);
398+
return g;
399+
});
400+
transformed_nuc_roi = GeometryTools.geometryToROI(nuc_geometry, original_roi.getImagePlane());
401+
}
402+
transformedObject = PathObjects.createCellObject(transformed_roi, transformed_nuc_roi, object.getPathClass(), copyMeasurements ? object.getMeasurementList() : null);
409403
}
410-
transformedObject = PathObjects.createCellObject(transformed_roi, transformed_nuc_roi, object.getPathClass(), copyMeasurements ? object.getMeasurementList() : null);
411-
412-
} else if (object instanceof PathDetectionObject) {
413-
transformedObject = PathObjects.createDetectionObject(transformed_roi, object.getPathClass(), copyMeasurements ? object.getMeasurementList() : null);
414-
} else {
415-
throw new Exception("Unknown PathObject class for class " + object.getClass().getSimpleName());
404+
case PathDetectionObject pathDetectionObject ->
405+
transformedObject = PathObjects.createDetectionObject(transformed_roi, pathDetectionObject.getPathClass(), copyMeasurements ? object.getMeasurementList() : null);
406+
default -> throw new Exception("Unknown PathObject class for class " + object.getClass().getSimpleName());
416407
}
417408

418409
// Return the same ID as the original object
419-
transformedObject.setID(object.getID());
410+
// Add the name and ID here
420411
transformedObject.setName(object.getName());
412+
transformedObject.setID(object.getID());
413+
transformedObject.setLocked(object.isLocked());
414+
421415
return transformedObject;
422416
}
423417

@@ -434,8 +428,7 @@ public static RealTransform getRealTransform(File f) {
434428
JsonObject element = new Gson().fromJson(fileReader, JsonObject.class);
435429
fileReader.close();
436430
element = (JsonObject) RealTransformSerializer.fixAffineTransform(element); // Fix missing type element in old versions
437-
RealTransform rt = RealTransformSerializer.getRealTransformAdapter().fromJson(element, RealTransform.class);
438-
return rt;
431+
return RealTransformSerializer.getRealTransformAdapter().fromJson(element, RealTransform.class);
439432
} catch (FileNotFoundException e) {
440433
logger.error("Transform file " + f.getName() + " not found", e);
441434
} catch (IOException e) {
@@ -482,10 +475,9 @@ public boolean isGeometryChanged() {
482475
/**
483476
* Main class for debugging
484477
*
485-
* @param args
486-
* @throws Exception
478+
* @param args some inputs we do not need
487479
*/
488-
public static void main(String... args) throws Exception {
480+
public static void main(String... args) {
489481
//String projectPath = "\\\\svfas6.epfl.ch\\biop\\public\\luisa.spisak_UPHUELSKEN\\Overlay\\qp\\project.qpproj";
490482
QuPathApp.launch(QuPathApp.class);//, projectPath);
491483
}

0 commit comments

Comments
 (0)