use of qupath.lib.objects.PathObject in project qupath by qupath.
the class PathObjectHierarchyView method selectSingleObject.
/**
* Select just one object within the tree - everything else will be cleared.
*
* @param pathObjectSelected
*/
private void selectSingleObject(final PathObject pathObjectSelected) {
if (pathObjectSelected == null)
return;
// Search for a path to select the object... opening the tree accordingly
List<PathObject> ancestors = PathObjectTools.getAncestorList(pathObjectSelected);
if (ancestors.isEmpty() || treeView.getRoot() == null)
return;
List<TreeItem<PathObject>> treeItems = new ArrayList<>();
treeItems.add(treeView.getRoot());
TreeItem<PathObject> deepestItem = null;
while (!ancestors.isEmpty()) {
PathObject pathObject = ancestors.remove(0);
TreeItem<PathObject> found = null;
for (TreeItem<PathObject> treeItem : treeItems) {
if (treeItem.getValue() == pathObject) {
found = treeItem;
break;
}
}
// We can't get any deeper
if (found == null)
return;
deepestItem = found;
treeItems = found.getChildren();
}
if (deepestItem != null && deepestItem.getValue() == pathObjectSelected) {
TreeItem<PathObject> parent = deepestItem.getParent();
while (parent != null) {
parent.setExpanded(true);
parent = parent.getParent();
}
// deepestItem.setExpanded(true);
int row = treeView.getRow(deepestItem);
treeView.getSelectionModel().clearAndSelect(row);
treeView.scrollTo(row);
}
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class TestCompositeClassifier method test_classifyPathObjects.
@Test
public void test_classifyPathObjects() {
MeasurementList ml1 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList ml2 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList ml3 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
MeasurementList ml4 = MeasurementListFactory.createMeasurementList(16, MeasurementList.MeasurementListType.GENERAL);
// Adding measurement to list2 (all measurements)
ml2.addMeasurement("intensityMeasurement1", 0.0);
ml2.addMeasurement("intensityMeasurement2", 0.2);
ml2.addMeasurement("intensityMeasurement3", 0.6);
ml2.addMeasurement("intensityMeasurement4", -4.6);
// Adding measurement to list3 (missing intensityMeasurement3)
ml3.addMeasurement("intensityMeasurement1", -1.0);
ml3.addMeasurement("intensityMeasurement2", 0.9999);
ml3.addMeasurement("intensityMeasurement4", 0.999);
// Adding measurement to list4 (missing intensityMeasurement4)
ml4.addMeasurement("intensityMeasurement1", 0.2);
ml4.addMeasurement("intensityMeasurement2", 0.3);
ml4.addMeasurement("intensityMeasurement3", 0.5);
// Create annotation objects
PathObject obj1 = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0, 0, 5, 5, ImagePlane.getDefaultPlane()), null, ml1);
PathObject obj2 = PathObjects.createAnnotationObject(ROIs.createRectangleROI(10, 10, 5, 5, ImagePlane.getDefaultPlane()), pathClass1, ml2);
PathObject obj3 = PathObjects.createAnnotationObject(ROIs.createEllipseROI(100, 100, 5, 5, ImagePlane.getDefaultPlane()), pathClass2, ml3);
PathObject obj4 = PathObjects.createAnnotationObject(ROIs.createLineROI(0, 0, ImagePlane.getDefaultPlane()), pathClass3, ml4);
// Classify objects and check classification manually
var objs = Arrays.asList(obj1, obj2, obj3, obj4);
// Composite classifier 1
var classifiedObjs1 = cp1.classifyPathObjects(objs);
// Last classifier classified one object only (obj4)
assertEquals(1, classifiedObjs1);
// Unchanged
assertEquals(null, obj1.getPathClass());
assertEquals(PathClassFactory.getNegative(pathClass1), obj2.getPathClass());
assertEquals(PathClassFactory.getPositive(pathClass2), obj3.getPathClass());
assertEquals(PathClassFactory.getPositive(pathClass3), obj4.getPathClass());
// Composite classifier 2
var classifiedObjs2 = cp2.classifyPathObjects(objs);
assertEquals(4, classifiedObjs2);
// Missing measurement -> unchanged
assertEquals(null, obj1.getPathClass());
assertEquals(PathClassFactory.getNegative(pathClass1), obj2.getPathClass());
assertEquals(PathClassFactory.getPositive(pathClass2), obj3.getPathClass());
// Missing measurement -> reset
assertEquals(pathClass3, obj4.getPathClass());
// Invalid composite classifier
var classifiedObjs3 = cpInvalid.classifyPathObjects(objs);
assertEquals(0, classifiedObjs3);
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class SummaryMeasurementTableCommand method showTable.
/**
* Show a measurement table for the specified image data.
* @param imageData the image data
* @param type the object type to show
*/
public void showTable(ImageData<BufferedImage> imageData, Class<? extends PathObject> type) {
if (imageData == null) {
Dialogs.showNoImageError("Show measurement table");
return;
}
final PathObjectHierarchy hierarchy = imageData.getHierarchy();
ObservableMeasurementTableData model = new ObservableMeasurementTableData();
model.setImageData(imageData, imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type));
SplitPane splitPane = new SplitPane();
HistogramDisplay histogramDisplay = new HistogramDisplay(model, true);
// table.setTableMenuButtonVisible(true);
TableView<PathObject> table = new TableView<>();
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<PathObject>() {
@Override
public void onChanged(ListChangeListener.Change<? extends PathObject> c) {
synchronizeSelectionModelToTable(hierarchy, c, table);
}
});
StringProperty displayedName = new SimpleStringProperty(ServerTools.getDisplayableImageName(imageData.getServer()));
var title = Bindings.createStringBinding(() -> {
if (type == null)
return "Results " + displayedName.get();
else
return PathObjectTools.getSuitableName(type, false) + " results - " + displayedName.get();
}, displayedName);
// Handle double-click as a way to center on a ROI
// var enter = new KeyCodeCombination(KeyCode.ENTER);
table.setRowFactory(params -> {
var row = new TableRow<PathObject>();
row.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
maybeCenterROI(row.getItem());
}
});
// });
return row;
});
// Create columns according to the table model
// for (int i = 0; i < model.getColumnCount(); i++) {
// // Add string column
// if (model.getColumnClass(i).equals(String.class)) {
// TableColumn<PathObject, String> col = null;
// col = new TableColumn<>(model.getColumnName(i));
// col.setCellValueFactory(new Callback<CellDataFeatures<PathObject, String>, ObservableValue<String>>() {
// public ObservableValue<String> call(CellDataFeatures<PathObject, String> val) {
// return new SimpleStringProperty(val.getValue().getDisplayedName());
// }
// });
// col.setCellFactory(column -> new BasicTableCell<String>());
// table.getColumns().add(col);
// }
// }
boolean tmaCoreList = TMACoreObject.class.isAssignableFrom(type);
// Add TMA core columns, if suitable
if (tmaCoreList) {
TableColumn<PathObject, ROI> col = new TableColumn<>("Image");
col.setCellValueFactory(val -> new SimpleObjectProperty<>(val.getValue().getROI()));
double maxWidth = maxDimForTMACore;
double padding = 10;
col.setCellFactory(column -> new TMACoreTableCell(table, imageData.getServer(), maxWidth, padding));
col.widthProperty().addListener((v, o, n) -> table.refresh());
col.setMaxWidth(maxWidth + padding * 2);
table.getColumns().add(col);
// While here, make sure we have fewer bins - don't usually have all that many cores
histogramDisplay.setNumBins(10);
}
// Create numeric columns
for (String columnName : model.getAllNames()) {
// Add column
if (model.isStringMeasurement(columnName)) {
TableColumn<PathObject, String> col = new TableColumn<>(columnName);
col.setCellValueFactory(column -> model.createStringMeasurement(column.getValue(), column.getTableColumn().getText()));
col.setCellFactory(column -> new BasicTableCell<>());
table.getColumns().add(col);
} else {
TableColumn<PathObject, Number> col = new TableColumn<>(columnName);
col.setCellValueFactory(column -> model.createNumericMeasurement(column.getValue(), column.getTableColumn().getText()));
col.setCellFactory(column -> new NumericTableCell<PathObject>(histogramDisplay));
table.getColumns().add(col);
}
}
// Set the PathObjects - need to deal with sorting, since a FilteredList won't handle it directly
SortedList<PathObject> items = new SortedList<>(model.getItems());
items.comparatorProperty().bind(table.comparatorProperty());
table.setItems(items);
List<ButtonBase> buttons = new ArrayList<>();
ToggleButton btnHistogram = new ToggleButton("Show histograms");
btnHistogram.selectedProperty().addListener((v, o, n) -> {
if (n) {
Pane paneHistograms = histogramDisplay.getPane();
splitPane.getItems().add(paneHistograms);
} else if (histogramDisplay != null)
splitPane.getItems().remove(histogramDisplay.getPane());
});
buttons.add(btnHistogram);
// Button btnScatterplot = new Button("Show scatterplots");
// btnScatterplot.setOnAction(e -> {
// SwingUtilities.invokeLater(() -> {
// JDialog dialog = new ScatterplotDisplay(null, "Scatterplots: " + displayedName, model).getDialog();
// dialog.setLocationRelativeTo(null);
// dialog.setVisible(true);
// });
// });
// buttons.add(btnScatterplot);
Button btnCopy = new Button("Copy to clipboard");
btnCopy.setOnAction(e -> {
// TODO: Deal with repetition immediately below...
Set<String> excludeColumns = new HashSet<>();
for (TableColumn<?, ?> col : table.getColumns()) {
if (!col.isVisible())
excludeColumns.add(col.getText());
}
copyTableContentsToClipboard(model, excludeColumns);
});
buttons.add(btnCopy);
Button btnSave = new Button("Save");
btnSave.setOnAction(e -> {
Set<String> excludeColumns = new HashSet<>();
for (TableColumn<?, ?> col : table.getColumns()) {
if (!col.isVisible())
excludeColumns.add(col.getText());
}
File fileOutput = promptForOutputFile();
if (fileOutput == null)
return;
if (saveTableModel(model, fileOutput, excludeColumns)) {
WorkflowStep step;
String includeColumns;
if (excludeColumns.isEmpty())
includeColumns = "";
else {
List<String> includeColumnList = new ArrayList<>(model.getAllNames());
includeColumnList.removeAll(excludeColumns);
includeColumns = ", " + includeColumnList.stream().map(s -> "'" + s + "'").collect(Collectors.joining(", "));
}
String path = qupath.getProject() == null ? fileOutput.toURI().getPath() : fileOutput.getParentFile().toURI().getPath();
if (type == TMACoreObject.class) {
step = new DefaultScriptableWorkflowStep("Save TMA measurements", String.format("saveTMAMeasurements('%s'%s)", path, includeColumns));
} else if (type == PathAnnotationObject.class) {
step = new DefaultScriptableWorkflowStep("Save annotation measurements", String.format("saveAnnotationMeasurements('%s\'%s)", path, includeColumns));
} else if (type == PathDetectionObject.class) {
step = new DefaultScriptableWorkflowStep("Save detection measurements", String.format("saveDetectionMeasurements('%s'%s)", path, includeColumns));
} else {
step = new DefaultScriptableWorkflowStep("Save measurements", String.format("saveMeasurements('%s', %s%s)", path, type == null ? null : type.getName(), includeColumns));
}
imageData.getHistoryWorkflow().addStep(step);
}
});
buttons.add(btnSave);
Stage frame = new Stage();
frame.initOwner(qupath.getStage());
frame.titleProperty().bind(title);
BorderPane paneTable = new BorderPane();
paneTable.setCenter(table);
// Add text field to filter visible columns
TextField tfColumnFilter = new TextField();
GridPane paneFilter = new GridPane();
paneFilter.add(new Label("Column filter"), 0, 0);
paneFilter.add(tfColumnFilter, 1, 0);
GridPane.setHgrow(tfColumnFilter, Priority.ALWAYS);
paneFilter.setHgap(5);
if (tmaCoreList) {
CheckBox cbHideMissing = new CheckBox("Hide missing cores");
paneFilter.add(cbHideMissing, 2, 0);
cbHideMissing.selectedProperty().addListener((v, o, n) -> {
if (n) {
model.setPredicate(p -> (!(p instanceof TMACoreObject)) || !((TMACoreObject) p).isMissing());
} else
model.setPredicate(null);
});
cbHideMissing.setSelected(true);
}
paneFilter.setPadding(new Insets(2, 5, 2, 5));
paneTable.setBottom(paneFilter);
StringProperty columnFilter = tfColumnFilter.textProperty();
columnFilter.addListener((v, o, n) -> {
String val = n.toLowerCase().trim();
if (val.isEmpty()) {
for (TableColumn<?, ?> col : table.getColumns()) {
if (!col.isVisible())
col.setVisible(true);
}
return;
}
for (TableColumn<?, ?> col : table.getColumns()) {
col.setVisible(col.getText().toLowerCase().contains(val));
}
});
BorderPane pane = new BorderPane();
// pane.setCenter(table);
splitPane.getItems().add(paneTable);
pane.setCenter(splitPane);
GridPane panelButtons = PaneTools.createColumnGridControls(buttons.toArray(new ButtonBase[0]));
pane.setBottom(panelButtons);
PathObjectHierarchyListener listener = new PathObjectHierarchyListener() {
@Override
public void hierarchyChanged(PathObjectHierarchyEvent event) {
if (event.isChanging())
return;
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> hierarchyChanged(event));
return;
}
if (imageData != null)
displayedName.set(ServerTools.getDisplayableImageName(imageData.getServer()));
model.refreshEntries();
table.refresh();
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
}
};
QuPathViewer viewer = qupath.getViewer();
TableViewerListener tableViewerListener = new TableViewerListener(viewer, table);
frame.setOnShowing(e -> {
hierarchy.addPathObjectListener(listener);
viewer.addViewerListener(tableViewerListener);
});
frame.setOnHiding(e -> {
hierarchy.removePathObjectListener(listener);
viewer.removeViewerListener(tableViewerListener);
});
Scene scene = new Scene(pane, 600, 500);
frame.setScene(scene);
frame.show();
// Add ability to remove entries from table
ContextMenu menu = new ContextMenu();
Menu menuLimitClasses = new Menu("Show classes");
menu.setOnShowing(e -> {
Set<PathClass> representedClasses = model.getBackingListEntries().stream().map(p -> p.getPathClass() == null ? null : p.getPathClass().getBaseClass()).collect(Collectors.toCollection(() -> new HashSet<>()));
representedClasses.remove(null);
if (representedClasses.isEmpty()) {
menuLimitClasses.setVisible(false);
} else {
menuLimitClasses.setVisible(true);
}
menuLimitClasses.getItems().clear();
List<PathClass> sortedClasses = new ArrayList<>(representedClasses);
Collections.sort(sortedClasses);
MenuItem miClass = new MenuItem("All");
miClass.setOnAction(e2 -> {
model.setPredicate(null);
histogramDisplay.refreshHistogram();
});
menuLimitClasses.getItems().add(miClass);
for (PathClass pathClass : sortedClasses) {
miClass = new MenuItem(pathClass.getName());
miClass.setOnAction(e2 -> {
model.setPredicate(p -> pathClass.isAncestorOf(p.getPathClass()));
histogramDisplay.refreshHistogram();
});
menuLimitClasses.getItems().add(miClass);
}
});
if (type != TMACoreObject.class) {
menu.getItems().add(menuLimitClasses);
table.setContextMenu(menu);
}
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class SummaryMeasurementTableCommand method synchronizeSelectionModelToTable.
private void synchronizeSelectionModelToTable(final PathObjectHierarchy hierarchy, final ListChangeListener.Change<? extends PathObject> change, final TableView<PathObject> table) {
if (synchronizingTableToModel || hierarchy == null)
return;
PathObjectSelectionModel model = hierarchy.getSelectionModel();
if (model == null) {
return;
}
boolean wasSynchronizingToTree = synchronizingModelToTable;
try {
synchronizingModelToTable = true;
// Check - was anything removed?
boolean removed = false;
if (change != null) {
while (change.next()) removed = removed | change.wasRemoved();
}
MultipleSelectionModel<PathObject> treeModel = table.getSelectionModel();
List<PathObject> selectedItems = treeModel.getSelectedItems();
// If we just have no selected items, and something was removed, then clear the selection
if (selectedItems.isEmpty() && removed) {
model.clearSelection();
return;
}
// if (selectedItems.size() == 1 && removed) {
if (selectedItems.size() == 1) {
model.setSelectedObject(selectedItems.get(0), false);
return;
}
// If we have multiple selected items, we need to ensure that everything in the tree matches with everything in the selection model
Set<PathObject> toSelect = new HashSet<>(treeModel.getSelectedItems());
PathObject primary = treeModel.getSelectedItem();
model.setSelectedObjects(toSelect, primary);
} finally {
synchronizingModelToTable = wasSynchronizingToTree;
}
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class PathHierarchyImageServer method readTile.
@Override
protected BufferedImage readTile(TileRequest tileRequest) throws IOException {
RegionRequest request = tileRequest.getRegionRequest();
// long startTime = System.currentTimeMillis();
// Get connections
Object o = options.getShowConnections() ? imageData.getProperty(DefaultPathObjectConnectionGroup.KEY_OBJECT_CONNECTIONS) : null;
PathObjectConnections connections = (o instanceof PathObjectConnections) ? (PathObjectConnections) o : null;
List<PathObject> pathObjects = new ArrayList<>(getObjectsToPaint(request));
if (pathObjects == null || pathObjects.isEmpty()) {
// We can only return null if no connections - otherwise we might still need to draw something
if (connections == null) {
return null;
}
}
// Because levels *can* change, we need to extract them first to avoid breaking the contract for comparable
// in a multithreaded environment
var levels = pathObjects.stream().collect(Collectors.toMap(p -> p, p -> p.getLevel()));
var comparator = DefaultPathObjectComparator.getInstance().thenComparingInt(p -> levels.get(p));
Collections.sort(pathObjects, comparator);
// Collections.sort(pathObjects, new HierarchyOverlay.DetectionComparator());
double downsampleFactor = request.getDownsample();
int width = tileRequest.getTileWidth();
int height = tileRequest.getTileHeight();
BufferedImage img = createDefaultRGBImage(width, height);
Graphics2D g2d = img.createGraphics();
g2d.setClip(0, 0, width, height);
// g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double scale = 1.0 / downsampleFactor;
// g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.scale(scale, scale);
g2d.translate(-request.getX(), -request.getY());
// Note we don't want to pass a selection model, as selections shouldn't be included
if (pathObjects != null && !pathObjects.isEmpty())
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, AwtTools.getBounds(request), pathObjects, options, null, downsampleFactor);
// See if we have any connections to draw
if (connections != null) {
PathHierarchyPaintingHelper.paintConnections(connections, hierarchy, g2d, imageData.isFluorescence() ? ColorToolsAwt.TRANSLUCENT_WHITE : ColorToolsAwt.TRANSLUCENT_BLACK, downsampleFactor);
}
g2d.dispose();
// System.out.println("Single tile image creation time: " + (endTime - startTime)/1000.);
return img;
}
Aggregations