use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class ObservableMeasurementTableData method updateMeasurementList.
/**
* Update the entire measurement list for the current objects.
* @see #setImageData(ImageData, Collection)
*/
public synchronized void updateMeasurementList() {
// PathPrefs.setAllredMinPercentagePositive(0);
builderMap.clear();
// Add the image name
if (!PathPrefs.maskImageNamesProperty().get())
builderMap.put("Image", new ImageNameMeasurementBuilder(imageData));
// Check if we have any annotations / TMA cores
boolean containsDetections = false;
boolean containsAnnotations = false;
// boolean containsParentAnnotations = false;
boolean containsTMACores = false;
boolean containsRoot = false;
List<PathObject> pathObjectListCopy = new ArrayList<>(list);
for (PathObject temp : pathObjectListCopy) {
if (temp instanceof PathAnnotationObject) {
// if (temp.hasChildren())
// containsParentAnnotations = true;
containsAnnotations = true;
} else if (temp instanceof TMACoreObject) {
containsTMACores = true;
} else if (temp instanceof PathDetectionObject) {
containsDetections = true;
} else if (temp.isRootObject())
containsRoot = true;
}
boolean detectionsAnywhere = imageData == null ? containsDetections : !imageData.getHierarchy().getDetectionObjects().isEmpty();
// Include the object displayed name
// if (containsDetections || containsAnnotations || containsTMACores)
builderMap.put("Name", new ObjectNameMeasurementBuilder());
// Include the class
if (containsAnnotations || containsDetections) {
builderMap.put("Class", new PathClassMeasurementBuilder());
// Get the name of the containing TMA core if we have anything other than cores
if (imageData != null && imageData.getHierarchy().getTMAGrid() != null) {
builderMap.put("TMA core", new TMACoreNameMeasurementBuilder());
}
// Get the name of the first parent object
builderMap.put("Parent", new ParentNameMeasurementBuilder());
}
// Include the TMA missing status, if appropriate
if (containsTMACores) {
builderMap.put("Missing", new MissingTMACoreMeasurementBuilder());
}
if (containsAnnotations || containsDetections) {
builderMap.put("ROI", new ROINameMeasurementBuilder());
}
// Add centroids
if (containsAnnotations || containsDetections || containsTMACores) {
// ROICentroidMeasurementBuilder builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.X);
// builderMap.put("Centroid X", builder);
// builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.Y);
// builderMap.put("Centroid Y", builder);
ROICentroidMeasurementBuilder builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.X);
builderMap.put(builder.getName(), builder);
builder = new ROICentroidMeasurementBuilder(imageData, CentroidType.Y);
builderMap.put(builder.getName(), builder);
}
// If we have metadata, store it
Set<String> metadataNames = new LinkedHashSet<>();
metadataNames.addAll(builderMap.keySet());
for (PathObject pathObject : pathObjectListCopy) {
if (pathObject instanceof MetadataStore) {
metadataNames.addAll(((MetadataStore) pathObject).getMetadataKeys());
}
}
// Ensure we have suitable builders
for (String name : metadataNames) {
if (!builderMap.containsKey(name))
builderMap.put(name, new StringMetadataMeasurementBuilder(name));
}
// Get all the 'built-in' feature measurements, stored in the measurement list
Collection<String> features = PathClassifierTools.getAvailableFeatures(pathObjectListCopy);
// Add derived measurements if we don't have only detections
if (containsAnnotations || containsTMACores || containsRoot) {
if (detectionsAnywhere) {
var builder = new ObjectTypeCountMeasurementBuilder(PathDetectionObject.class);
builderMap.put(builder.getName(), builder);
features.add(builder.getName());
}
// Here, we allow TMA cores to act like annotations
manager = new DerivedMeasurementManager(getImageData(), containsAnnotations || containsTMACores);
for (MeasurementBuilder<?> builder2 : manager.getMeasurementBuilders()) {
builderMap.put(builder2.getName(), builder2);
features.add(builder2.getName());
}
}
// If we have an annotation, add shape features
if (containsAnnotations) {
boolean anyPoints = false;
boolean anyAreas = false;
boolean anyLines = false;
@SuppressWarnings("unused") boolean anyPolygons = false;
for (PathObject pathObject : pathObjectListCopy) {
if (!pathObject.isAnnotation())
continue;
ROI roi = pathObject.getROI();
if (roi == null)
continue;
if (roi.isPoint())
anyPoints = true;
if (roi.isArea())
anyAreas = true;
if (roi.isLine())
anyLines = true;
if (pathObject.getROI() instanceof PolygonROI)
anyPolygons = true;
}
// Add point count, if needed
if (anyPoints) {
MeasurementBuilder<?> builder = new NumPointsMeasurementBuilder();
builderMap.put(builder.getName(), builder);
features.add(builder.getName());
}
// Add spatial measurements, if needed
if (anyAreas) {
MeasurementBuilder<?> builder = new AreaMeasurementBuilder(imageData);
builderMap.put(builder.getName(), builder);
features.add(builder.getName());
builder = new PerimeterMeasurementBuilder(imageData);
builderMap.put(builder.getName(), builder);
features.add(builder.getName());
}
if (anyLines) {
MeasurementBuilder<?> builder = new LineLengthMeasurementBuilder(imageData);
builderMap.put(builder.getName(), builder);
features.add(builder.getName());
}
// if (anyPolygons) {
// MeasurementBuilder<?> builder = new MaxDiameterMeasurementBuilder(imageData);
// builderMap.put(builder.getName(), builder);
// features.add(builder.getName());
//
// builder = new MinDiameterMeasurementBuilder(imageData);
// builderMap.put(builder.getName(), builder);
// features.add(builder.getName());
// }
}
if (containsAnnotations || containsTMACores || containsRoot) {
var pixelClassifier = getPixelLayer(imageData);
if (pixelClassifier instanceof ImageServer<?>) {
ImageServer<BufferedImage> server = (ImageServer<BufferedImage>) pixelClassifier;
if (server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.CLASSIFICATION || server.getMetadata().getChannelType() == ImageServerMetadata.ChannelType.PROBABILITY) {
var pixelManager = new PixelClassificationMeasurementManager(server);
for (String name : pixelManager.getMeasurementNames()) {
// String nameLive = name + " (live)";
String nameLive = "(Live) " + name;
builderMap.put(nameLive, new PixelClassifierMeasurementBuilder(pixelManager, name));
features.add(nameLive);
}
}
}
}
// Update all the lists, if necessary
boolean changes = false;
if (metadataNames.size() != metadataList.size() || !metadataNames.containsAll(metadataList)) {
changes = metadataList.setAll(metadataNames);
}
if (features.size() != measurementList.size() || !features.containsAll(measurementList))
changes = measurementList.setAll(features);
if (changes) {
if (metadataList.isEmpty())
fullList.setAll(measurementList);
else {
fullList.setAll(metadataList);
fullList.addAll(measurementList);
}
}
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class PathClassificationLabellingHelper method getClassificationMap.
/**
* Get a map of training data, based on the child objects of some classified annotations.
*
* @param hierarchy the hierarchy containing all the objects and annotations.
* @param pointsOnly if true, only Point annotations will be used for training.
*
* @return
*/
public static Map<PathClass, List<PathObject>> getClassificationMap(final PathObjectHierarchy hierarchy, final boolean pointsOnly) {
Map<PathClass, List<PathObject>> classifications = new TreeMap<>();
// Get the annotations & filter out those that are useful
List<PathObject> annotations = new ArrayList<>(getAnnotations(hierarchy));
Iterator<PathObject> iter = annotations.iterator();
while (iter.hasNext()) {
PathObject pathObject = iter.next();
// We need a PathClass, and may need to only include points
if (pathObject.getPathClass() == null || pathObject.getPathClass() == PathClassFactory.getPathClass(StandardPathClasses.REGION) || (pointsOnly && !PathObjectTools.hasPointROI(pathObject)))
iter.remove();
else
classifications.put(pathObject.getPathClass(), new ArrayList<>());
}
// from the hierarchy
if (annotations.size() > 1) {
annotations.sort(new Comparator<PathObject>() {
@Override
public int compare(PathObject o1, PathObject o2) {
PathAnnotationObject p1 = (PathAnnotationObject) o1;
PathAnnotationObject p2 = (PathAnnotationObject) o2;
int comp = 0;
if (p1.hasROI()) {
if (p2.hasROI()) {
comp = Double.compare(p1.getROI().getCentroidY(), p2.getROI().getCentroidY());
if (comp == 0)
comp = Double.compare(p1.getROI().getCentroidX(), p2.getROI().getCentroidX());
if (comp == 0)
comp = p1.getROI().toString().compareTo(p2.getROI().toString());
}
}
if (comp == 0)
return Integer.compare(o1.hashCode(), o2.hashCode());
else
return comp;
}
});
}
// StringBuilder sb = new StringBuilder("DETECTIONS:\t");
for (PathObject pathObject : annotations) {
PathClass pathClass = pathObject.getPathClass();
List<PathObject> list = classifications.get(pathClass);
// sb.append(list.size() + ", ");
if (PathObjectTools.hasPointROI(pathObject)) {
for (Point2 p : ((PointsROI) pathObject.getROI()).getAllPoints()) {
// TODO: Pay attention to z & t position!
Collection<PathObject> pathObjectsTemp = PathObjectTools.getObjectsForLocation(hierarchy, p.getX(), p.getY(), 0, 0, -1);
pathObjectsTemp = PathObjectTools.getObjectsOfClass(pathObjectsTemp, PathDetectionObject.class);
// Clumsy way to avoid duplicates...
list.removeAll(pathObjectsTemp);
list.addAll(pathObjectsTemp);
}
} else
list.addAll(hierarchy.getObjectsForROI(PathDetectionObject.class, pathObject.getROI()));
}
for (Entry<PathClass, List<PathObject>> entry : classifications.entrySet()) {
logger.info(entry.getKey() + ": " + entry.getValue().size());
}
return classifications;
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class ClassifierBuilderPane method hierarchyChanged.
@Override
public void hierarchyChanged(PathObjectHierarchyEvent event) {
if (event.getSource() == this)
return;
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> hierarchyChanged(event));
return;
}
// Flag that the hierarchy has changed if this is any kind of event other than an object classification event
hierarchyChanged = hierarchyChanged || !event.isObjectClassificationEvent();
// TODO: Avoid reliance on this, and instead check the training objects used
if (event.isStructureChangeEvent()) {
// Don't respond to adding/removing annotation objects without classes set
if (event.isAddedOrRemovedEvent()) {
PathObject pathObjectChanged = event.getChangedObjects().get(0);
if (!(pathObjectChanged instanceof PathAnnotationObject) || pathObjectChanged.getPathClass() == null)
return;
}
} else if (event.isObjectClassificationEvent()) {
// If classifications have changed, we only care if these contain annotations
boolean containsAnnotations = containsObjectsOfClass(event.getChangedObjects(), PathAnnotationObject.class);
if (!containsAnnotations)
return;
} else if (event.getEventType() == HierarchyEventType.CHANGE_OTHER) {
return;
}
// TODO: See if calls can be reduced
maybeUpdate();
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class QuPathGUI method updateSetAnnotationPathClassMenu.
void updateSetAnnotationPathClassMenu(final ObservableList<MenuItem> menuSetClassItems, final QuPathViewer viewer, final boolean useFancyIcons) {
// We need a viewer and an annotation, as well as some PathClasses, otherwise we just need to ensure the menu isn't visible
if (viewer == null || !(viewer.getSelectedObject() instanceof PathAnnotationObject) || availablePathClasses.isEmpty()) {
menuSetClassItems.clear();
return;
}
PathObject mainPathObject = viewer.getSelectedObject();
PathClass currentClass = mainPathObject.getPathClass();
ToggleGroup group = new ToggleGroup();
List<MenuItem> itemList = new ArrayList<>();
RadioMenuItem selected = null;
for (PathClass pathClass : availablePathClasses) {
PathClass pathClassToSet = pathClass.getName() == null ? null : pathClass;
String name = pathClass.getName() == null ? "None" : pathClass.toString();
Action actionSetClass = new Action(name, e -> {
List<PathObject> changed = new ArrayList<>();
for (PathObject pathObject : viewer.getAllSelectedObjects()) {
if (!pathObject.isAnnotation() || pathObject.getPathClass() == pathClassToSet)
continue;
pathObject.setPathClass(pathClassToSet);
changed.add(pathObject);
}
if (!changed.isEmpty())
viewer.getHierarchy().fireObjectClassificationsChangedEvent(this, changed);
});
Node shape;
if (useFancyIcons) {
Ellipse r = new Ellipse(TOOLBAR_ICON_SIZE / 2.0, TOOLBAR_ICON_SIZE / 2.0, TOOLBAR_ICON_SIZE, TOOLBAR_ICON_SIZE);
if ("None".equals(name)) {
r.setFill(Color.rgb(255, 255, 255, 0.75));
} else
r.setFill(ColorToolsFX.getCachedColor(pathClass.getColor()));
r.setOpacity(0.8);
DropShadow effect = new DropShadow(6, -3, 3, Color.GRAY);
r.setEffect(effect);
shape = r;
} else {
Rectangle r = new Rectangle(0, 0, 8, 8);
r.setFill("None".equals(name) ? Color.TRANSPARENT : ColorToolsFX.getCachedColor(pathClass.getColor()));
shape = r;
}
// actionSetClass.setGraphic(r);
RadioMenuItem item = ActionUtils.createRadioMenuItem(actionSetClass);
item.graphicProperty().unbind();
item.setGraphic(shape);
item.setToggleGroup(group);
itemList.add(item);
if (pathClassToSet == currentClass)
selected = item;
}
group.selectToggle(selected);
menuSetClassItems.setAll(itemList);
}
use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.
the class ImageJMacroRunner method runMacro.
static void runMacro(final ParameterList params, final ImageData<BufferedImage> imageData, final ImageDisplay imageDisplay, final PathObject pathObject, final String macroText) {
// Don't try if interrupted
if (Thread.currentThread().isInterrupted()) {
logger.warn("Skipping macro for {} - thread interrupted", pathObject);
return;
}
PathImage<ImagePlus> pathImage;
// Extract parameters
double downsampleFactor = params.getDoubleParameterValue("downsampleFactor");
boolean sendROI = params.getBooleanParameterValue("sendROI");
boolean sendOverlay = params.getBooleanParameterValue("sendOverlay");
ROI pathROI = pathObject.getROI();
ImageDisplay imageDisplay2 = params.containsKey("useTransform") && Boolean.TRUE.equals(params.getBooleanParameterValue("useTransform")) ? imageDisplay : null;
ImageServer<BufferedImage> server = imageDisplay2 == null || imageDisplay2.availableChannels().isEmpty() ? imageData.getServer() : ChannelDisplayTransformServer.createColorTransformServer(imageData.getServer(), imageDisplay.availableChannels());
RegionRequest region = RegionRequest.createInstance(imageData.getServer().getPath(), downsampleFactor, pathROI);
// Check the size of the region to extract - abort if it is too large of if ther isn't enough RAM
try {
IJTools.isMemorySufficient(region, imageData);
} catch (Exception e1) {
Dialogs.showErrorMessage("ImageJ macro error", e1.getMessage());
return;
}
try {
if (sendOverlay)
pathImage = IJExtension.extractROIWithOverlay(server, pathObject, imageData.getHierarchy(), region, sendROI, null);
else
pathImage = IJExtension.extractROI(server, pathObject, region, sendROI);
} catch (IOException e) {
logger.error("Unable to extract image region " + region, e);
return;
}
// IJHelpers.getImageJInstance();
// ImageJ ij = IJHelpers.getImageJInstance();
// if (ij != null && WindowManager.getIDList() == null)
// ij.setVisible(false);
// Determine a sensible argument to pass
String argument;
if (pathObject instanceof TMACoreObject || !pathObject.hasROI())
argument = pathObject.getDisplayedName();
else
argument = String.format("Region (%d, %d, %d, %d)", region.getX(), region.getY(), region.getWidth(), region.getHeight());
// Check if we have an image already - if so, we need to be more cautious so we don't accidentally use it...
// boolean hasImage = WindowManager.getCurrentImage() != null;
// Actually run the macro
final ImagePlus imp = pathImage.getImage();
imp.setProperty("QuPath region", argument);
WindowManager.setTempCurrentImage(imp);
// Ensure we've requested an instance, since this also loads any required extra plugins
IJExtension.getImageJInstance();
// TODO: Pay attention to how threading should be done... I think Swing EDT ok?
try {
// SwingUtilities.invokeAndWait(() -> {
boolean cancelled = false;
ImagePlus impResult = null;
try {
IJ.redirectErrorMessages();
Interpreter interpreter = new Interpreter();
impResult = interpreter.runBatchMacro(macroText, imp);
// If we had an error, return
if (interpreter.wasError()) {
Thread.currentThread().interrupt();
return;
}
// Get the resulting image, if available
if (impResult == null)
impResult = WindowManager.getCurrentImage();
} catch (RuntimeException e) {
logger.error(e.getLocalizedMessage());
// DisplayHelpers.showErrorMessage("ImageJ macro error", e.getLocalizedMessage());
Thread.currentThread().interrupt();
cancelled = true;
} finally {
// IJ.runMacro(macroText, argument);
WindowManager.setTempCurrentImage(null);
// IJ.run("Close all");
}
if (cancelled)
return;
// Get the current image when the macro has finished - which may or may not be the same as the original
if (impResult == null)
impResult = imp;
boolean changes = false;
if (params.getBooleanParameterValue("clearObjects") && pathObject.hasChildren()) {
pathObject.clearPathObjects();
changes = true;
}
if (params.getBooleanParameterValue("getROI") && impResult.getRoi() != null) {
Roi roi = impResult.getRoi();
Calibration cal = impResult.getCalibration();
PathObject pathObjectNew = roi == null ? null : IJTools.convertToAnnotation(roi, cal.xOrigin, cal.yOrigin, downsampleFactor, region.getPlane());
if (pathObjectNew != null) {
// If necessary, trim any returned annotation
if (pathROI != null && !(pathROI instanceof RectangleROI) && pathObjectNew.isAnnotation() && RoiTools.isShapeROI(pathROI) && RoiTools.isShapeROI(pathObjectNew.getROI())) {
ROI roiNew = RoiTools.combineROIs(pathROI, pathObjectNew.getROI(), CombineOp.INTERSECT);
((PathAnnotationObject) pathObjectNew).setROI(roiNew);
}
// Only add if we have something
if (pathObjectNew.getROI() instanceof LineROI || !pathObjectNew.getROI().isEmpty()) {
pathObject.addPathObject(pathObjectNew);
// imageData.getHierarchy().addPathObject(IJHelpers.convertToPathObject(imp, imageData.getServer(), imp.getRoi(), downsampleFactor, false), true);
changes = true;
}
}
}
boolean exportAsDetection = ((String) params.getChoiceParameterValue("getOverlayAs")).equals("Detections") ? true : false;
if (params.getBooleanParameterValue("getOverlay") && impResult.getOverlay() != null) {
var overlay = impResult.getOverlay();
List<PathObject> childObjects = QuPath_Send_Overlay_to_QuPath.createObjectsFromROIs(imp, Arrays.asList(overlay.toArray()), downsampleFactor, exportAsDetection, true, region.getPlane());
if (!childObjects.isEmpty()) {
pathObject.addPathObjects(childObjects);
changes = true;
}
// for (Roi roi : impResult.getOverlay().toArray()) {
// pathObject.addPathObject(IJTools.convertToPathObject(imp, imageData.getServer(), roi, downsampleFactor, true));
// changes = true;
// }
}
if (changes) {
Platform.runLater(() -> imageData.getHierarchy().fireHierarchyChangedEvent(null));
}
// });
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Aggregations