use of org.bytedeco.opencv.opencv_ml.ANN_MLP in project qupath by qupath.
the class PixelClassifierPane method initialize.
private void initialize() {
var imageData = qupath.getImageData();
int row = 0;
// Classifier
pane = new GridPane();
var labelClassifier = new Label("Classifier");
var comboClassifier = new ComboBox<OpenCVStatModel>();
labelClassifier.setLabelFor(comboClassifier);
selectedClassifier = comboClassifier.getSelectionModel().selectedItemProperty();
selectedClassifier.addListener((v, o, n) -> updateClassifier());
var btnEditClassifier = new Button("Edit");
btnEditClassifier.setOnAction(e -> editClassifierParameters());
btnEditClassifier.disableProperty().bind(selectedClassifier.isNull());
PaneTools.addGridRow(pane, row++, 0, "Choose classifier type (RTrees or ANN_MLP are generally good choices)", labelClassifier, comboClassifier, comboClassifier, btnEditClassifier);
// Image resolution
var labelResolution = new Label("Resolution");
labelResolution.setLabelFor(comboResolutions);
var btnResolution = new Button("Add");
btnResolution.setOnAction(e -> addResolution());
selectedResolution = comboResolutions.getSelectionModel().selectedItemProperty();
PaneTools.addGridRow(pane, row++, 0, "Choose the base image resolution based upon required detail in the classification (see preview on the right)", labelResolution, comboResolutions, comboResolutions, btnResolution);
// Features
var labelFeatures = new Label("Features");
var comboFeatures = new ComboBox<ImageDataTransformerBuilder>();
comboFeatures.getItems().add(new ImageDataTransformerBuilder.DefaultFeatureCalculatorBuilder(imageData));
// comboFeatures.getItems().add(new FeatureCalculatorBuilder.ExtractNeighborsFeatureCalculatorBuilder(viewer.getImageData()));
labelFeatures.setLabelFor(comboFeatures);
selectedFeatureCalculatorBuilder = comboFeatures.getSelectionModel().selectedItemProperty();
// var labelFeaturesSummary = new Label("No features selected");
var btnShowFeatures = new Button("Show");
btnShowFeatures.setOnAction(e -> showFeatures());
var btnCustomizeFeatures = new Button("Edit");
btnCustomizeFeatures.disableProperty().bind(Bindings.createBooleanBinding(() -> {
var calc = selectedFeatureCalculatorBuilder.get();
return calc == null || !calc.canCustomize(imageData);
}, selectedFeatureCalculatorBuilder));
btnCustomizeFeatures.setOnAction(e -> {
if (selectedFeatureCalculatorBuilder.get().doCustomize(imageData)) {
updateFeatureCalculator();
}
});
comboFeatures.getItems().addAll(defaultFeatureCalculatorBuilders);
comboFeatures.getSelectionModel().select(0);
comboFeatures.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> updateFeatureCalculator());
// btnCustomizeFeatures.setOnAction(e -> showFeatures());
PaneTools.addGridRow(pane, row++, 0, "Select features for the classifier", labelFeatures, comboFeatures, btnCustomizeFeatures, btnShowFeatures);
// Output
var labelOutput = new Label("Output");
var comboOutput = new ComboBox<ImageServerMetadata.ChannelType>();
comboOutput.getItems().addAll(ImageServerMetadata.ChannelType.CLASSIFICATION, ImageServerMetadata.ChannelType.PROBABILITY);
selectedOutputType = comboOutput.getSelectionModel().selectedItemProperty();
selectedOutputType.addListener((v, o, n) -> {
updateClassifier();
});
comboOutput.getSelectionModel().clearAndSelect(0);
var btnShowOutput = new Button("Show");
btnShowOutput.setOnAction(e -> showOutput());
PaneTools.addGridRow(pane, row++, 0, "Choose whether to output classifications only, or estimated probabilities per class (not all classifiers support probabilities, which also require more memory)", labelOutput, comboOutput, comboOutput, btnShowOutput);
// Region
var labelRegion = new Label("Region");
var comboRegionFilter = PixelClassifierUI.createRegionFilterCombo(qupath.getOverlayOptions());
// var nodeLimit = PixelClassifierTools.createLimitToAnnotationsControl(qupath.getOverlayOptions());
PaneTools.addGridRow(pane, row++, 0, "Control where the pixel classification is applied during preview", labelRegion, comboRegionFilter, comboRegionFilter, comboRegionFilter);
// Live predict
var btnAdvancedOptions = new Button("Advanced options");
btnAdvancedOptions.setTooltip(new Tooltip("Advanced options to customize preprocessing and classifier behavior"));
btnAdvancedOptions.setOnAction(e -> {
if (showAdvancedOptions())
updateClassifier();
});
// Live predict
var btnProject = new Button("Load training");
btnProject.setTooltip(new Tooltip("Train using annotations from more images in the current project"));
btnProject.setOnAction(e -> {
if (promptToLoadTrainingImages()) {
updateClassifier();
int n = trainingEntries.size();
if (n > 0)
btnProject.setText("Load training (" + n + ")");
else
btnProject.setText("Load training");
}
});
btnProject.disableProperty().bind(qupath.projectProperty().isNull());
var btnLive = new ToggleButton("Live prediction");
btnLive.selectedProperty().bindBidirectional(livePrediction);
btnLive.setTooltip(new Tooltip("Toggle whether to calculate classification 'live' while viewing the image"));
livePrediction.addListener((v, o, n) -> {
if (overlay == null) {
if (n) {
updateClassifier(n);
return;
}
} else {
overlay.setLivePrediction(n);
}
if (featureOverlay != null)
featureOverlay.setLivePrediction(n);
});
var panePredict = PaneTools.createColumnGridControls(btnProject, btnAdvancedOptions);
pane.add(panePredict, 0, row++, pane.getColumnCount(), 1);
// addGridRow(pane, row++, 0, btnPredict, btnPredict, btnPredict);
// var btnUpdate = new Button("Update classifier");
// btnUpdate.setMaxWidth(Double.MAX_VALUE);
// btnUpdate.setOnAction(e -> updateClassifier(true));
// btnUpdate.disableProperty().bind(qupath.imageDataProperty().isNull().or(btnLive.selectedProperty()));
pane.add(btnLive, 0, row++, pane.getColumnCount(), 1);
pieChart = new PieChart();
// var hierarchy = viewer.getHierarchy();
// Map<PathClass, List<PathObject>> map = hierarchy == null ? Collections.emptyMap() : PathClassificationLabellingHelper.getClassificationMap(hierarchy, false);
pieChart.setLabelsVisible(false);
pieChart.setLegendVisible(true);
pieChart.setMinSize(40, 40);
pieChart.setPrefSize(120, 120);
// pieChart.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
pieChart.setLegendSide(Side.RIGHT);
// GridPane.setVgrow(pieChart, Priority.ALWAYS);
// Tooltip.install(pieChart, new Tooltip("View training classes by proportion"));
var paneChart = new BorderPane(pieChart);
// paneChart.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
// PaneTools.addGridRow(pane, row++, 0,
// // null,
// "View information about the current classifier training",
// paneChart, paneChart, paneChart);
PaneTools.setFillWidth(Boolean.TRUE, paneChart);
PaneTools.setFillHeight(Boolean.TRUE, paneChart);
PaneTools.setVGrowPriority(Priority.ALWAYS, paneChart);
PaneTools.setHGrowPriority(Priority.ALWAYS, paneChart);
pane.add(paneChart, 0, row++, pane.getColumnCount(), 1);
// Label showing cursor location
var labelCursor = new Label();
labelCursor.textProperty().bindBidirectional(cursorLocation);
labelCursor.setAlignment(Pos.CENTER);
labelCursor.setTextAlignment(TextAlignment.CENTER);
labelCursor.setContentDisplay(ContentDisplay.CENTER);
labelCursor.setWrapText(true);
labelCursor.setMaxHeight(Double.MAX_VALUE);
labelCursor.setMinWidth(100);
labelCursor.setPrefWidth(390);
labelCursor.setMaxWidth(390);
labelCursor.setTooltip(new Tooltip("Prediction for current cursor location"));
paneChart.setBottom(labelCursor);
// This tends to make it harder to read the proportions as tooltips when putting the mouse over the pie chart
// Tooltip.install(paneChart, new Tooltip("Relative proportion of training samples"));
paneChart.setMaxWidth(400);
// PaneTools.addGridRow(pane, row++, 0,
// "Prediction for current cursor location",
// labelCursor, labelCursor, labelCursor);
comboClassifier.getItems().addAll(OpenCVClassifiers.createStatModel(RTrees.class), OpenCVClassifiers.createStatModel(ANN_MLP.class), OpenCVClassifiers.createStatModel(LogisticRegression.class), OpenCVClassifiers.createStatModel(KNearest.class));
comboClassifier.getSelectionModel().clearAndSelect(1);
PaneTools.setHGrowPriority(Priority.ALWAYS, comboResolutions, comboClassifier, comboFeatures);
PaneTools.setFillWidth(Boolean.TRUE, comboResolutions, comboClassifier, comboFeatures);
miniViewer = new MiniViewers.MiniViewerManager(qupath.getViewer(), 0);
var viewerPane = miniViewer.getPane();
Tooltip.install(viewerPane, new Tooltip("View image at classification resolution"));
updateAvailableResolutions(imageData);
selectedResolution.addListener((v, o, n) -> {
updateResolution(n);
updateClassifier();
ensureOverlaySet();
});
if (!comboResolutions.getItems().isEmpty())
comboResolutions.getSelectionModel().clearAndSelect(resolutions.size() / 2);
pane.setHgap(5);
pane.setVgap(6);
var classifierName = new SimpleStringProperty(null);
var panePostProcess = PaneTools.createRowGrid(PixelClassifierUI.createSavePixelClassifierPane(qupath.projectProperty(), currentClassifier, classifierName), PixelClassifierUI.createPixelClassifierButtons(qupath.imageDataProperty(), currentClassifier, classifierName));
panePostProcess.setVgap(5);
// var panePostProcess = PixelClassifierUI.createPixelClassifierButtons(qupath.imageDataProperty(), currentClassifier);
pane.add(panePostProcess, 0, row++, pane.getColumnCount(), 1);
PaneTools.setMaxWidth(Double.MAX_VALUE, pane.getChildren().stream().filter(p -> p instanceof Region).toArray(Region[]::new));
var viewerBorderPane = new BorderPane(viewerPane);
comboDisplayFeatures.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> ensureOverlaySet());
comboDisplayFeatures.setMaxWidth(Double.MAX_VALUE);
spinFeatureMin.setPrefWidth(100);
spinFeatureMax.setPrefWidth(100);
spinFeatureMin.valueProperty().addListener((v, o, n) -> updateFeatureDisplayRange());
spinFeatureMax.valueProperty().addListener((v, o, n) -> updateFeatureDisplayRange());
sliderFeatureOpacity.valueProperty().addListener((v, o, n) -> {
if (featureOverlay != null) {
featureOverlay.setOpacity(n.doubleValue());
}
if (overlay != null)
overlay.setOpacity(n.doubleValue());
qupath.repaintViewers();
});
var btnFeatureAuto = new Button("Auto");
btnFeatureAuto.setOnAction(e -> autoFeatureContrast());
comboDisplayFeatures.getItems().setAll(DEFAULT_CLASSIFICATION_OVERLAY);
comboDisplayFeatures.getSelectionModel().select(DEFAULT_CLASSIFICATION_OVERLAY);
var featureDisableBinding = comboDisplayFeatures.valueProperty().isEqualTo(DEFAULT_CLASSIFICATION_OVERLAY).or(comboDisplayFeatures.valueProperty().isNull());
btnFeatureAuto.disableProperty().bind(featureDisableBinding);
btnFeatureAuto.setMaxHeight(Double.MAX_VALUE);
spinFeatureMin.disableProperty().bind(featureDisableBinding);
spinFeatureMin.setEditable(true);
GuiTools.restrictTextFieldInputToNumber(spinFeatureMin.getEditor(), true);
spinFeatureMax.disableProperty().bind(featureDisableBinding);
spinFeatureMax.setEditable(true);
GuiTools.restrictTextFieldInputToNumber(spinFeatureMax.getEditor(), true);
var paneFeatures = new GridPane();
comboDisplayFeatures.setTooltip(new Tooltip("Choose classification result or feature overlay to display (Warning: This requires a lot of memory & computation!)"));
spinFeatureMin.setTooltip(new Tooltip("Min display value for feature overlay"));
spinFeatureMax.setTooltip(new Tooltip("Max display value for feature overlay"));
sliderFeatureOpacity.setTooltip(new Tooltip("Adjust classification/feature overlay opacity"));
PaneTools.addGridRow(paneFeatures, 0, 0, null, comboDisplayFeatures, comboDisplayFeatures, comboDisplayFeatures, comboDisplayFeatures);
PaneTools.addGridRow(paneFeatures, 1, 0, null, sliderFeatureOpacity, spinFeatureMin, spinFeatureMax, btnFeatureAuto);
var factory = new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> param) {
var listCell = new ListCell<String>() {
@Override
public void updateItem(String value, boolean empty) {
super.updateItem(value, empty);
if (value == null || empty)
setText(null);
else
setText(value);
}
};
listCell.setTextOverrun(OverrunStyle.ELLIPSIS);
return listCell;
}
};
comboDisplayFeatures.setCellFactory(factory);
comboDisplayFeatures.setButtonCell(factory.call(null));
PaneTools.setMaxWidth(Double.MAX_VALUE, comboDisplayFeatures, sliderFeatureOpacity);
PaneTools.setFillWidth(Boolean.TRUE, comboDisplayFeatures, sliderFeatureOpacity);
PaneTools.setHGrowPriority(Priority.ALWAYS, comboDisplayFeatures, sliderFeatureOpacity);
paneFeatures.setHgap(5);
paneFeatures.setVgap(5);
paneFeatures.setPadding(new Insets(5));
paneFeatures.prefWidthProperty().bind(viewerBorderPane.prefWidthProperty());
viewerBorderPane.setBottom(paneFeatures);
var splitPane = new BorderPane(viewerBorderPane);
splitPane.setLeft(pane);
pane.setMinWidth(400);
// pane.setPrefWidth(400);
// pane.setMaxWidth(400);
// new StackPane(splitPane);
var fullPane = splitPane;
pane.setPadding(new Insets(5));
stage = new Stage();
stage.setScene(new Scene(fullPane));
stage.setMinHeight(400);
stage.setMinWidth(600);
stage.sizeToScene();
stage.initOwner(QuPathGUI.getInstance().getStage());
// stage.getScene().getRoot().disableProperty().bind(
// QuPathGUI.getInstance().viewerProperty().isNotEqualTo(viewer)
// );
updateTitle();
updateFeatureCalculator();
PaneTools.setMinWidth(Region.USE_PREF_SIZE, PaneTools.getContentsOfType(stage.getScene().getRoot(), Region.class, true).toArray(Region[]::new));
stage.show();
stage.setOnCloseRequest(e -> destroy());
qupath.getStage().addEventFilter(MouseEvent.MOUSE_MOVED, mouseListener);
qupath.imageDataProperty().addListener(imageDataListener);
if (qupath.getImageData() != null)
qupath.getImageData().getHierarchy().addPathObjectListener(hierarchyListener);
stage.focusedProperty().addListener((v, o, n) -> {
if (n) {
for (var viewer : qupath.getViewers()) {
var currentOverlay = viewer.getCustomPixelLayerOverlay();
if (currentOverlay != this.featureOverlay && currentOverlay != this.overlay) {
ensureOverlaySet();
break;
}
}
}
});
nThreads.addListener((v, o, n) -> {
if (n == null)
return;
if (overlay != null)
overlay.setMaxThreads(n.intValue());
if (featureOverlay != null)
featureOverlay.setMaxThreads(n.intValue());
});
}
Aggregations