use of qupath.lib.gui.dialogs.ParameterPanelFX in project qupath by qupath.
the class EstimateStainVectorsCommand method showStainEditor.
@SuppressWarnings("unchecked")
public static ColorDeconvolutionStains showStainEditor(final BufferedImage img, final ColorDeconvolutionStains stains) {
//
int[] buf = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
// int[] rgb = buf;
int[] rgb = EstimateStainVectors.subsample(buf, 10000);
float[] red = ColorDeconvolutionHelper.getRedOpticalDensities(rgb, stains.getMaxRed(), null);
float[] green = ColorDeconvolutionHelper.getGreenOpticalDensities(rgb, stains.getMaxGreen(), null);
float[] blue = ColorDeconvolutionHelper.getBlueOpticalDensities(rgb, stains.getMaxBlue(), null);
// panelPlots.setBorder(BorderFactory.createTitledBorder(null, "Stain vector scatterplots", TitledBorder.CENTER, TitledBorder.TOP));
StainsWrapper stainsWrapper = new StainsWrapper(stains);
Node panelRedGreen = createScatterPanel(new ScatterPlot(red, green, null, rgb), stainsWrapper, AxisColor.RED, AxisColor.GREEN);
Node panelRedBlue = createScatterPanel(new ScatterPlot(red, blue, null, rgb), stainsWrapper, AxisColor.RED, AxisColor.BLUE);
Node panelGreenBlue = createScatterPanel(new ScatterPlot(green, blue, null, rgb), stainsWrapper, AxisColor.GREEN, AxisColor.BLUE);
// GridPane panelPlots = PanelToolsFX.createColumnGrid(panelRedGreen, panelRedBlue, panelGreenBlue);
GridPane panelPlots = new GridPane();
panelPlots.setHgap(10);
panelPlots.add(panelRedGreen, 0, 0);
panelPlots.add(panelRedBlue, 1, 0);
panelPlots.add(panelGreenBlue, 2, 0);
// panelPlots.getChildren().addAll(panelRedGreen, panelRedBlue, panelGreenBlue);
panelPlots.setPadding(new Insets(0, 0, 10, 0));
BorderPane panelSouth = new BorderPane();
TableView<Integer> table = new TableView<>();
table.getItems().setAll(1, 2, 3);
stainsWrapper.addStainListener(new StainChangeListener() {
@Override
public void stainChanged(StainsWrapper stainsWrapper) {
table.refresh();
}
});
TableColumn<Integer, String> colName = new TableColumn<>("Name");
colName.setCellValueFactory(v -> new SimpleStringProperty(stainsWrapper.getStains().getStain(v.getValue()).getName()));
TableColumn<Integer, String> colOrig = new TableColumn<>("Original");
colOrig.setCellValueFactory(v -> new SimpleStringProperty(stainArrayAsString(Locale.getDefault(Category.FORMAT), stainsWrapper.getOriginalStains().getStain(v.getValue()), " | ", 3)));
TableColumn<Integer, String> colCurrent = new TableColumn<>("Current");
colCurrent.setCellValueFactory(v -> new SimpleStringProperty(stainArrayAsString(Locale.getDefault(Category.FORMAT), stainsWrapper.getStains().getStain(v.getValue()), " | ", 3)));
TableColumn<Integer, String> colAngle = new TableColumn<>("Angle");
colAngle.setCellValueFactory(v -> {
return new SimpleStringProperty(GeneralTools.formatNumber(StainVector.computeAngle(stainsWrapper.getOriginalStains().getStain(v.getValue()), stainsWrapper.getStains().getStain(v.getValue())), 2));
});
// new SimpleStringProperty(stainsWrapper.getStains().getStain(v.getValue()).arrayAsString(", ", 3)));
table.getColumns().addAll(colName, colOrig, colCurrent, colAngle);
table.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
table.setPrefHeight(120);
// // Fix first & preferred column sizes
// int widthName = 0, widthStain = 0;
// for (int row = 0; row < table.getRowCount(); row++) {
// TableCellRenderer renderer = table.getCellRenderer(row, 0);
// Component comp = table.prepareRenderer(renderer, row, 0);
// widthName = Math.max(comp.getPreferredSize().width, widthName);
//
// renderer = table.getCellRenderer(row, 1);
// comp = table.prepareRenderer(renderer, row, 1);
// widthStain = Math.max(comp.getPreferredSize().width, widthStain);
// renderer = table.getCellRenderer(row, 2);
// comp = table.prepareRenderer(renderer, row, 2);
// widthStain = Math.max(comp.getPreferredSize().width, widthStain);
// }
// table.getColumnModel().getColumn(0).setMaxWidth(widthName + 10);
// table.getColumnModel().getColumn(0).setPreferredWidth(widthName + 10);
// table.getColumnModel().getColumn(1).setPreferredWidth(widthStain + 20);
// table.getColumnModel().getColumn(2).setPreferredWidth(widthStain + 20);
// Create auto detection parameters
ParameterList params = new ParameterList().addDoubleParameter("minStainOD", "Min channel OD", 0.05, "", "Minimum staining OD - pixels with a lower OD in any channel (RGB) are ignored (default = 0.05)").addDoubleParameter("maxStainOD", "Max total OD", 1., "", "Maximum staining OD - more densely stained pixels are ignored (default = 1)").addDoubleParameter("ignorePercentage", "Ignore extrema", 1., "%", "Percentage of extreme pixels to ignore, to improve robustness in the presence of noise/other artefacts (default = 1)").addBooleanParameter("checkColors", "Exclude unrecognised colors (H&E only)", false, "Exclude unexpected colors (e.g. green) that are likely to be caused by artefacts and not true staining");
// .addDoubleParameter("ignorePercentage", "Ignore extrema", 1., "%", 0, 20, "Percentage of extreme pixels to ignore, to improve robustness in the presence of noise/other artefacts");
Button btnAuto = new Button("Auto");
btnAuto.setOnAction(e -> {
double minOD = params.getDoubleParameterValue("minStainOD");
double maxOD = params.getDoubleParameterValue("maxStainOD");
double ignore = params.getDoubleParameterValue("ignorePercentage");
// Only accept if H&E
boolean checkColors = params.getBooleanParameterValue("checkColors") && stainsWrapper.getOriginalStains().isH_E();
ignore = Math.max(0, Math.min(ignore, 100));
// ColorDeconvolutionStains stains = estimateStains(imgFinal, stainsWrapper.getStains(), minOD, maxOD, ignore);
try {
ColorDeconvolutionStains stainsNew = EstimateStainVectors.estimateStains(img, stainsWrapper.getStains(), minOD, maxOD, ignore, checkColors);
stainsWrapper.setStains(stainsNew);
} catch (Exception e2) {
Dialogs.showErrorMessage("Estimate stain vectors", e2);
}
});
ParameterPanelFX panelParams = new ParameterPanelFX(params);
// panelParams.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
BorderPane panelAuto = new BorderPane();
// panelAuto.setBorder(BorderFactory.createTitledBorder("Auto detect"));
panelAuto.setCenter(panelParams.getPane());
panelAuto.setBottom(btnAuto);
// JScrollPane scrollPane = new JScrollPane(table);
// JPanel panelTable = new JPanel(new BorderLayout());
// panelTable.add(scrollPane, BorderLayout.CENTER);
// // JTextArea textInstructions = new JTextArea();
// // textInstructions.setWrapStyleWord(true);
// // textInstructions.setLineWrap(true);
// // textInstructions.setText(
// // "Viewer for manually and automatically adjusting stain vectors used for stain separation.\n\n" +
// // "Each stain vector is 3 values describing the red, green and blue components that define the colour of each " +
// // "stain (e.g. hematoxylin, DAB, eosin). The scatterplots show how these relate to pixel colours for each " +
// // "combination of red, green and blue.\n\n" +
// // "'Good' stain vectors should point along the edges of the scattered points, ignoring any artefacts resulting from " +
// // "pixels that don't belong to normal staining patterns."
// // );
// // panelTable.add(new JScrollPane(textInstructions), BorderLayout.SOUTH);
// panelTable.setBorder(BorderFactory.createTitledBorder("Stain vectors"));
panelSouth.setCenter(new TitledPane("Stain vectors", table));
panelSouth.setBottom(new TitledPane("Auto detect", panelAuto));
BorderPane panelMain = new BorderPane();
panelMain.setCenter(panelPlots);
panelMain.setBottom(panelSouth);
if (Dialogs.showConfirmDialog("Visual Stain Editor", panelMain)) {
return stainsWrapper.getStains();
} else {
stainsWrapper.resetStains();
return stainsWrapper.getStains();
}
}
use of qupath.lib.gui.dialogs.ParameterPanelFX in project qupath by qupath.
the class KaplanMeierDisplay method generatePlot.
@SuppressWarnings("unchecked")
private void generatePlot() {
KaplanMeierDisplay.ScoreData newScoreData = scoreData;
// If we have a hierarchy, update the scores with the most recent data
if (hierarchy != null) {
List<TMACoreObject> cores = PathObjectTools.getTMACoreObjects(hierarchy, false);
double[] survival = new double[cores.size()];
boolean[] censored = new boolean[cores.size()];
double[] scores = new double[cores.size()];
// scoreColumn = "RoughScore";
for (int i = 0; i < cores.size(); i++) {
TMACoreObject core = cores.get(i);
MeasurementList ml = core.getMeasurementList();
survival[i] = core.getMeasurementList().getMeasurementValue(survivalColumn);
double censoredValue = core.getMeasurementList().getMeasurementValue(censoredColumn);
boolean hasCensoredValue = !Double.isNaN(censoredValue) && (censoredValue == 0 || censoredValue == 1);
censored[i] = censoredValue != 0;
if (!hasCensoredValue) {
// If we don't have a censored value, ensure we mask out everything else
scores[i] = Double.NaN;
survival[i] = Double.NaN;
} else if (ml.containsNamedMeasurement(scoreColumn))
// Get the score if we can
scores[i] = ml.getMeasurementValue(scoreColumn);
else {
// // Try to compute score if we need to
// Map<String, Number> map = ROIMeaningfulMeasurements.getPathClassSummaryMeasurements(core.getChildObjects(), true);
// Number value = map.get(scoreColumn);
// if (value == null)
scores[i] = Double.NaN;
// else
// scores[i] = value.doubleValue();
}
}
// Mask out any scores that don't have associated survival data
for (int i = 0; i < survival.length; i++) {
if (Double.isNaN(survival[i]))
scores[i] = Double.NaN;
}
newScoreData = new ScoreData(scores, survival, censored);
}
if (newScoreData == null || newScoreData.scores.length == 0)
return;
// KaplanMeier kmHigh = new KaplanMeier("Above threshold");
// KaplanMeier kmLow = new KaplanMeier("Below threshold");
double[] quartiles = StatisticsHelper.getQuartiles(newScoreData.scores);
double q1 = quartiles[0];
double median = quartiles[1];
double q3 = quartiles[2];
double[] thresholds;
if (params != null) {
Object thresholdMethod = params.getChoiceParameterValue("scoreThresholdMethod");
if (thresholdMethod.equals("Median")) {
// panelParams.setNumericParameterValue("scoreThreshold", median);
// ((DoubleParameter)params.getParameters().get("scoreThreshold")).setValue(median); // TODO: UPDATE DIALOG!
thresholds = new double[] { median };
} else if (thresholdMethod.equals("Tertiles")) {
// ((DoubleParameter)params.getParameters().get("scoreThreshold")).setValue(median); // TODO: UPDATE DIALOG!
thresholds = StatisticsHelper.getTertiles(newScoreData.scores);
} else if (thresholdMethod.equals("Quartiles")) {
// ((DoubleParameter)params.getParameters().get("scoreThreshold")).setValue(median); // TODO: UPDATE DIALOG!
thresholds = new double[] { q1, median, q3 };
} else if (thresholdMethod.equals("Manual (1)")) {
thresholds = new double[] { params.getDoubleParameterValue("threshold1") };
} else if (thresholdMethod.equals("Manual (2)")) {
thresholds = new double[] { params.getDoubleParameterValue("threshold1"), params.getDoubleParameterValue("threshold2") };
} else
// if (thresholdMethod.equals("Manual (3)")) {
thresholds = new double[] { params.getDoubleParameterValue("threshold1"), params.getDoubleParameterValue("threshold2"), params.getDoubleParameterValue("threshold3") };
} else
thresholds = new double[] { median };
double minVal = Double.POSITIVE_INFINITY;
double maxVal = Double.NEGATIVE_INFINITY;
int numNonNaN = 0;
for (double d : newScoreData.scores) {
if (Double.isNaN(d))
continue;
if (d < minVal)
minVal = d;
if (d > maxVal)
maxVal = d;
numNonNaN++;
}
// If not this, we don't have valid scores that we can work with
boolean scoresValid = maxVal > minVal;
double maxTimePoint = 0;
for (double d : newScoreData.survival) {
if (Double.isNaN(d))
continue;
if (d > maxTimePoint)
maxTimePoint = d;
}
if (panelParams != null && maxTimePoint > ((IntParameter) params.getParameters().get("censorTimePoints")).getUpperBound()) {
panelParams.setNumericParameterValueRange("censorTimePoints", 0, Math.ceil(maxTimePoint));
}
// Optionally censor at specified time
double censorThreshold = params == null ? maxTimePoint : params.getIntParameterValue("censorTimePoints");
// Compute log-rank p-values for *all* possible thresholds
// Simultaneously determine the threshold that yields the lowest p-value,
// resolving ties in favour of a more even split between high/low numbers of events
boolean pValuesChanged = false;
if (calculateAllPValues) {
if (!(pValues != null && pValueThresholds != null && newScoreData.equals(scoreData) && censorThreshold == lastPValueCensorThreshold)) {
Map<Double, Double> mapLogRank = new TreeMap<>();
Set<Double> setObserved = new HashSet<>();
for (int i = 0; i < newScoreData.scores.length; i++) {
Double d = newScoreData.scores[i];
boolean observed = !newScoreData.censored[i] && newScoreData.survival[i] < censorThreshold;
if (observed)
setObserved.add(d);
if (mapLogRank.containsKey(d))
continue;
List<KaplanMeierData> kmsTemp = splitByThresholds(newScoreData, new double[] { d }, censorThreshold, false);
// if (kmsTemp.get(1).nObserved() == 0 || kmsTemp.get(1).nObserved() == 0)
// continue;
LogRankResult test = LogRankTest.computeLogRankTest(kmsTemp.get(0), kmsTemp.get(1));
double pValue = test.getPValue();
// double pValue = test.hazardRatio < 1 ? test.hazardRatio : 1.0/test.hazardRatio; // Checking usefulness of Hazard ratios...
if (!Double.isFinite(pValue))
continue;
// if (!Double.isFinite(test.getHazardRatio())) {
// // continue;
// pValue = Double.NaN;
// }
mapLogRank.put(d, pValue);
}
pValueThresholds = new double[mapLogRank.size()];
pValues = new double[mapLogRank.size()];
pValueThresholdsObserved = new boolean[mapLogRank.size()];
int count = 0;
for (Entry<Double, Double> entry : mapLogRank.entrySet()) {
pValueThresholds[count] = entry.getKey();
pValues[count] = entry.getValue();
if (setObserved.contains(entry.getKey()))
pValueThresholdsObserved[count] = true;
count++;
}
// Find the longest 'significant' stretch
int maxSigCount = 0;
int maxSigInd = -1;
int sigCurrent = 0;
int[] sigCount = new int[pValues.length];
for (int i = 0; i < pValues.length; i++) {
if (pValues[i] < 0.05) {
sigCurrent++;
sigCount[i] = sigCurrent;
if (sigCurrent > maxSigCount) {
maxSigCount = sigCurrent;
maxSigInd = i;
}
} else
sigCurrent = 0;
}
if (maxSigCount == 0) {
logger.info("No p-values < 0.05");
} else {
double minThresh = maxSigInd - maxSigCount < 0 ? pValueThresholds[0] - 0.0000001 : pValueThresholds[maxSigInd - maxSigCount];
double maxThresh = pValueThresholds[maxSigInd];
int nBetween = 0;
int nBetweenObserved = 0;
for (int i = 0; i < newScoreData.scores.length; i++) {
if (newScoreData.scores[i] > minThresh && newScoreData.scores[i] <= maxThresh) {
nBetween++;
if (newScoreData.survival[i] < censorThreshold && !newScoreData.censored[i])
nBetweenObserved++;
}
}
logger.info("Longest stretch of p-values < 0.05: {} - {} ({} entries, {} observed)", minThresh, maxThresh, nBetween, nBetweenObserved);
}
pValuesSmoothed = new double[pValues.length];
Arrays.fill(pValuesSmoothed, Double.NaN);
int n = (pValues.length / 20) * 2 + 1;
logger.info("Smoothing log-rank test p-values by " + n);
for (int i = n / 2; i < pValues.length - n / 2; i++) {
double sum = 0;
for (int k = i - n / 2; k < i - n / 2 + n; k++) {
sum += pValues[k];
}
pValuesSmoothed[i] = sum / n;
}
// for (int i = 0; i < pValues.length; i++) {
// double sum = 0;
// for (int k = Math.max(0, i-n/2); k < Math.min(pValues.length, i-n/2+n); k++) {
// sum += pValues[k];
// }
// pValuesSmoothed[i] = sum/n;
// }
// pValues = pValuesSmoothed;
lastPValueCensorThreshold = censorThreshold;
pValuesChanged = true;
}
} else {
lastPValueCensorThreshold = Double.NaN;
pValueThresholds = null;
pValues = null;
}
// if (params != null && !Double.isNaN(bestThreshold) && (params.getChoiceParameterValue("scoreThresholdMethod").equals("Lowest p-value")))
if (params != null && (params.getChoiceParameterValue("scoreThresholdMethod").equals("Lowest p-value"))) {
int bestIdx = -1;
double bestPValue = Double.POSITIVE_INFINITY;
for (int i = pValueThresholds.length / 10; i < pValueThresholds.length * 9 / 10; i++) {
if (pValues[i] < bestPValue) {
bestIdx = i;
bestPValue = pValues[i];
}
}
thresholds = bestIdx >= 0 ? new double[] { pValueThresholds[bestIdx] } : new double[0];
} else if (params != null && (params.getChoiceParameterValue("scoreThresholdMethod").equals("Lowest smoothed p-value"))) {
int bestIdx = -1;
double bestPValue = Double.POSITIVE_INFINITY;
for (int i = pValueThresholds.length / 10; i < pValueThresholds.length * 9 / 10; i++) {
if (pValuesSmoothed[i] < bestPValue) {
bestIdx = i;
bestPValue = pValuesSmoothed[i];
}
}
thresholds = bestIdx >= 0 ? new double[] { pValueThresholds[bestIdx] } : new double[0];
}
// Split into different curves using the provided thresholds
List<KaplanMeierData> kms = splitByThresholds(newScoreData, thresholds, censorThreshold, params != null && "Quartiles".equals(params.getChoiceParameterValue("scoreThresholdMethod")));
if (plotter == null) {
plotter = new KaplanMeierChartWrapper(survivalColumn + " time");
// plotter.setBorder(BorderFactory.createTitledBorder("Survival plot"));
// plotter.getCanvas().setWidth(300);
// plotter.getCanvas().setHeight(300);
}
KaplanMeierData[] kmArray = new KaplanMeierData[kms.size()];
plotter.setKaplanMeierCurves(survivalColumn + " time", kms.toArray(kmArray));
tableModel.setSurvivalCurves(thresholds, params != null && params.getChoiceParameterValue("scoreThresholdMethod").equals("Lowest p-value"), kmArray);
// Bar width determined using 'Freedman and Diaconis' rule' (but overridden if this gives < 16 bins...)
double barWidth = (2 * q3 - q1) * Math.pow(numNonNaN, -1.0 / 3.0);
int nBins = 100;
if (!Double.isNaN(barWidth))
barWidth = (int) Math.max(16, Math.ceil((maxVal - minVal) / barWidth));
Histogram histogram = scoresValid ? new Histogram(newScoreData.scores, nBins) : null;
if (histogramPanel == null) {
GridPane paneHistogram = new GridPane();
histogramPanel = new HistogramPanelFX();
histogramPanel.getChart().setAnimated(false);
histogramWrapper = new ThresholdedChartWrapper(histogramPanel.getChart());
for (ObservableNumberValue val : threshProperties) histogramWrapper.addThreshold(val, ColorToolsFX.getCachedColor(240, 0, 0, 128));
histogramWrapper.getPane().setPrefHeight(150);
paneHistogram.add(histogramWrapper.getPane(), 0, 0);
Tooltip.install(histogramPanel.getChart(), new Tooltip("Distribution of scores"));
GridPane.setHgrow(histogramWrapper.getPane(), Priority.ALWAYS);
GridPane.setVgrow(histogramWrapper.getPane(), Priority.ALWAYS);
NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Score threshold");
NumberAxis yAxis = new NumberAxis();
yAxis.setLowerBound(0);
yAxis.setUpperBound(1);
yAxis.setTickUnit(0.1);
yAxis.setAutoRanging(false);
yAxis.setLabel("P-value");
chartPValues = new LineChart<>(xAxis, yAxis);
chartPValues.setAnimated(false);
chartPValues.setLegendVisible(false);
// Make chart so it can be navigated
ChartTools.makeChartInteractive(chartPValues, xAxis, yAxis);
pValuesChanged = true;
Tooltip.install(chartPValues, new Tooltip("Distribution of p-values (log-rank test) comparing low vs. high for all possible score thresholds"));
// chartPValues.getYAxis().setAutoRanging(false);
pValuesWrapper = new ThresholdedChartWrapper(chartPValues);
for (ObservableNumberValue val : threshProperties) pValuesWrapper.addThreshold(val, ColorToolsFX.getCachedColor(240, 0, 0, 128));
pValuesWrapper.getPane().setPrefHeight(150);
paneHistogram.add(pValuesWrapper.getPane(), 0, 1);
GridPane.setHgrow(pValuesWrapper.getPane(), Priority.ALWAYS);
GridPane.setVgrow(pValuesWrapper.getPane(), Priority.ALWAYS);
ContextMenu popup = new ContextMenu();
ChartTools.addChartExportMenu(chartPValues, popup);
RadioMenuItem miZoomY1 = new RadioMenuItem("0-1");
miZoomY1.setOnAction(e -> {
yAxis.setAutoRanging(false);
yAxis.setUpperBound(1);
yAxis.setTickUnit(0.2);
});
RadioMenuItem miZoomY05 = new RadioMenuItem("0-0.5");
miZoomY05.setOnAction(e -> {
yAxis.setAutoRanging(false);
yAxis.setUpperBound(0.5);
yAxis.setTickUnit(0.1);
});
RadioMenuItem miZoomY02 = new RadioMenuItem("0-0.2");
miZoomY02.setOnAction(e -> {
yAxis.setAutoRanging(false);
yAxis.setUpperBound(0.2);
yAxis.setTickUnit(0.05);
});
RadioMenuItem miZoomY01 = new RadioMenuItem("0-0.1");
miZoomY01.setOnAction(e -> {
yAxis.setAutoRanging(false);
yAxis.setUpperBound(0.1);
yAxis.setTickUnit(0.05);
});
RadioMenuItem miZoomY005 = new RadioMenuItem("0-0.05");
miZoomY005.setOnAction(e -> {
yAxis.setAutoRanging(false);
yAxis.setUpperBound(0.05);
yAxis.setTickUnit(0.01);
});
RadioMenuItem miZoomY001 = new RadioMenuItem("0-0.01");
miZoomY001.setOnAction(e -> {
yAxis.setAutoRanging(false);
yAxis.setUpperBound(0.01);
yAxis.setTickUnit(0.005);
});
ToggleGroup tgZoom = new ToggleGroup();
miZoomY1.setToggleGroup(tgZoom);
miZoomY05.setToggleGroup(tgZoom);
miZoomY02.setToggleGroup(tgZoom);
miZoomY01.setToggleGroup(tgZoom);
miZoomY005.setToggleGroup(tgZoom);
miZoomY001.setToggleGroup(tgZoom);
Menu menuZoomY = new Menu("Set y-axis range");
menuZoomY.getItems().addAll(miZoomY1, miZoomY05, miZoomY02, miZoomY01, miZoomY005, miZoomY001);
MenuItem miCopyData = new MenuItem("Copy chart data");
miCopyData.setOnAction(e -> {
String dataString = ChartTools.getChartDataAsString(chartPValues);
ClipboardContent content = new ClipboardContent();
content.putString(dataString);
Clipboard.getSystemClipboard().setContent(content);
});
popup.getItems().addAll(miCopyData, menuZoomY);
chartPValues.setOnContextMenuRequested(e -> {
popup.show(chartPValues, e.getScreenX(), e.getScreenY());
});
for (int col = 0; col < tableModel.getColumnCount(); col++) {
TableColumn<Integer, String> column = new TableColumn<>(tableModel.getColumnName(col));
int colNumber = col;
column.setCellValueFactory(new Callback<CellDataFeatures<Integer, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(CellDataFeatures<Integer, String> p) {
return new SimpleStringProperty((String) tableModel.getValueAt(p.getValue(), colNumber));
}
});
column.setCellFactory(new Callback<TableColumn<Integer, String>, TableCell<Integer, String>>() {
@Override
public TableCell<Integer, String> call(TableColumn<Integer, String> param) {
TableCell<Integer, String> cell = new TableCell<Integer, String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
setTooltip(new Tooltip(item));
}
};
return cell;
}
});
table.getColumns().add(column);
}
table.setPrefHeight(250);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.maxHeightProperty().bind(table.prefHeightProperty());
params = new ParameterList();
// maxTimePoint = 0;
// for (TMACoreObject core : hierarchy.getTMAGrid().getTMACoreList()) {
// double os = core.getMeasurementList().getMeasurementValue(TMACoreObject.KEY_OVERALL_SURVIVAL);
// double rfs = core.getMeasurementList().getMeasurementValue(TMACoreObject.KEY_RECURRENCE_FREE_SURVIVAL);
// if (os > maxTimePoint)
// maxTimePoint = os;
// if (rfs > maxTimePoint)
// maxTimePoint = rfs;
// }
params.addIntParameter("censorTimePoints", "Max censored time", (int) (censorThreshold + 0.5), null, 0, (int) Math.ceil(maxTimePoint), "Latest time point beyond which data will be censored");
// params.addChoiceParameter("scoreThresholdMethod", "Threshold method", "Manual", Arrays.asList("Manual", "Median", "Log-rank test"));
if (calculateAllPValues)
// Don't include "Lowest smoothed p-value" - it's not an established method and open to misinterpretation...
params.addChoiceParameter("scoreThresholdMethod", "Threshold method", "Median", Arrays.asList("Manual (1)", "Manual (2)", "Manual (3)", "Median", "Tertiles", "Quartiles", "Lowest p-value"));
else
// params.addChoiceParameter("scoreThresholdMethod", "Threshold method", "Median", Arrays.asList("Manual (1)", "Manual (2)", "Manual (3)", "Median", "Tertiles", "Quartiles", "Lowest p-value", "Lowest smoothed p-value"));
params.addChoiceParameter("scoreThresholdMethod", "Threshold method", "Median", Arrays.asList("Manual (1)", "Manual (2)", "Manual (3)", "Median", "Tertiles", "Quartiles"));
params.addDoubleParameter("threshold1", "Threshold 1", thresholds.length > 0 ? thresholds[0] : (minVal + maxVal) / 2, null, "Threshold to distinguish between patient groups");
params.addDoubleParameter("threshold2", "Threshold 2", thresholds.length > 1 ? thresholds[1] : (minVal + maxVal) / 2, null, "Threshold to distinguish between patient groups");
params.addDoubleParameter("threshold3", "Threshold 3", thresholds.length > 2 ? thresholds[2] : (minVal + maxVal) / 2, null, "Threshold to distinguish between patient groups");
params.addBooleanParameter("showAtRisk", "Show at risk", plotter.getShowAtRisk(), "Show number of patients at risk below the plot");
params.addBooleanParameter("showTicks", "Show censored ticks", plotter.getShowCensoredTicks(), "Show ticks to indicate censored data");
params.addBooleanParameter("showKey", "Show key", plotter.getShowKey(), "Show key indicating display of each curve");
// Hide threshold parameters if threshold can't be used
if (!scoresValid) {
// params.setHiddenParameters(true, "scoreThresholdMethod", "scoreThreshold");
histogramPanel.getChart().setVisible(false);
}
panelParams = new ParameterPanelFX(params);
panelParams.addParameterChangeListener(this);
updateThresholdsEnabled();
for (int i = 0; i < threshProperties.length; i++) {
String p = "threshold" + (i + 1);
threshProperties[i].addListener((v, o, n) -> {
if (interactiveThresholds()) {
// Need to do a decent double check with tolerance to text field value changing while typing
if (!GeneralTools.almostTheSame(params.getDoubleParameterValue(p), n.doubleValue(), 0.0001))
panelParams.setNumericParameterValue(p, n);
}
});
}
BorderPane paneBottom = new BorderPane();
TitledPane paneOptions = new TitledPane("Options", panelParams.getPane());
// paneOptions.setCollapsible(false);
Pane paneCanvas = new StackPane();
paneCanvas.getChildren().add(plotter.getCanvas());
GridPane paneLeft = new GridPane();
paneLeft.add(paneOptions, 0, 0);
paneLeft.add(table, 0, 1);
GridPane.setHgrow(paneOptions, Priority.ALWAYS);
GridPane.setHgrow(table, Priority.ALWAYS);
paneBottom.setLeft(paneLeft);
paneBottom.setCenter(paneHistogram);
paneMain.setCenter(paneCanvas);
paneMain.setBottom(paneBottom);
paneMain.setPadding(new Insets(10, 10, 10, 10));
} else if (thresholds.length > 0) {
// Ensure the sliders/text fields are set sensibly
if (!GeneralTools.almostTheSame(thresholds[0], params.getDoubleParameterValue("threshold1"), 0.0001)) {
panelParams.setNumericParameterValue("threshold1", thresholds[0]);
}
if (thresholds.length > 1 && !GeneralTools.almostTheSame(thresholds[1], params.getDoubleParameterValue("threshold2"), 0.0001)) {
panelParams.setNumericParameterValue("threshold2", thresholds[1]);
}
if (thresholds.length > 2 && !GeneralTools.almostTheSame(thresholds[2], params.getDoubleParameterValue("threshold3"), 0.0001)) {
panelParams.setNumericParameterValue("threshold3", thresholds[2]);
}
}
if (histogram != null) {
histogramPanel.getHistogramData().setAll(HistogramPanelFX.createHistogramData(histogram, false, (Color) null));
histogramPanel.getChart().getXAxis().setLabel(scoreColumn);
histogramPanel.getChart().getYAxis().setLabel("Count");
ChartTools.addChartExportMenu(histogramPanel.getChart(), null);
// histogramWrapper.setVerticalLines(thresholds, ColorToolsFX.getCachedColor(240, 0, 0, 128));
// Deal with threshold adjustment
// histogramWrapper.getThresholds().addListener((Observable o) -> generatePlot());
}
if (pValues != null) {
// TODO: Raise earlier where p-value calculation is
if (pValuesChanged) {
ObservableList<XYChart.Data<Number, Number>> data = FXCollections.observableArrayList();
for (int i = 0; i < pValueThresholds.length; i++) {
double pValue = pValues[i];
if (Double.isNaN(pValue))
continue;
data.add(new XYChart.Data<>(pValueThresholds[i], pValue, pValueThresholdsObserved[i]));
}
ObservableList<XYChart.Data<Number, Number>> dataSmoothed = null;
if (pValuesSmoothed != null) {
dataSmoothed = FXCollections.observableArrayList();
for (int i = 0; i < pValueThresholds.length; i++) {
double pValueSmoothed = pValuesSmoothed[i];
if (Double.isNaN(pValueSmoothed))
continue;
dataSmoothed.add(new XYChart.Data<>(pValueThresholds[i], pValueSmoothed));
}
}
// Don't bother showing the smoothed data... it tends to get in the way...
// if (dataSmoothed != null)
// chartPValues.getData().setAll(new XYChart.Series<>("P-values", data), new XYChart.Series<>("Smoothed P-values", dataSmoothed));
// else
chartPValues.getData().setAll(new XYChart.Series<>("P-values", data));
// Add line to show 0.05 significance threshold
if (pValueThresholds.length > 1) {
Data<Number, Number> sigData1 = new Data<>(pValueThresholds[0], 0.05);
Data<Number, Number> sigData2 = new Data<>(pValueThresholds[pValueThresholds.length - 1], 0.05);
XYChart.Series<Number, Number> dataSignificant = new XYChart.Series<>("Significance 0.05", FXCollections.observableArrayList(sigData1, sigData2));
chartPValues.getData().add(dataSignificant);
sigData1.getNode().setVisible(false);
sigData2.getNode().setVisible(false);
}
// pValuesWrapper.clearThresholds();
for (XYChart.Data<Number, Number> dataPoint : data) {
if (!Boolean.TRUE.equals(dataPoint.getExtraValue()))
dataPoint.getNode().setVisible(false);
}
// if (dataSmoothed != null) {
// for (XYChart.Data<Number, Number> dataPoint : dataSmoothed) {
// dataPoint.getNode().setVisible(false);
// }
// chartPValues.getData().get(1).getNode().setOpacity(0.5);
// }
// int count = 0;
// for (int i = 0; i < pValueThresholds.length; i++) {
// double pValue = pValues[i];
// if (Double.isNaN(pValue))
// continue;
// boolean observed = pValueThresholdsObserved[i];
// // if (observed)
// // pValuesWrapper.addThreshold(new ReadOnlyDoubleWrapper(pValueThresholds[i]), Color.rgb(0, 0, 0, 0.05));
//
// if (!observed) {
// // StackPane pane = (StackPane)data.get(count).getNode();
// // pane.setEffect(new DropShadow());
// data.get(count).getNode().setVisible(false);
// }
// count++;
// }
}
for (int i = 0; i < threshProperties.length; i++) {
if (i < thresholds.length)
threshProperties[i].set(thresholds[i]);
else
threshProperties[i].set(Double.NaN);
}
boolean isInteractive = interactiveThresholds();
histogramWrapper.setIsInteractive(isInteractive);
pValuesWrapper.setIsInteractive(isInteractive);
chartPValues.setVisible(true);
}
// else
// chartPValues.setVisible(false);
// Store values for next time
scoreData = newScoreData;
}
use of qupath.lib.gui.dialogs.ParameterPanelFX in project qupath by qupath.
the class ImageJMacroRunner method runPlugin.
@Override
public boolean runPlugin(final PluginRunner<BufferedImage> runner, final String arg) {
if (!parseArgument(runner.getImageData(), arg))
return false;
if (dialog == null) {
dialog = new Stage();
dialog.initOwner(qupath.getStage());
dialog.setTitle("ImageJ macro runner");
BorderPane pane = new BorderPane();
if (arg != null)
macroText = arg;
// Create text area
final TextArea textArea = new TextArea();
textArea.setPrefRowCount(12);
textArea.setPrefSize(400, 400);
textArea.setWrapText(true);
textArea.setFont(Font.font("Courier"));
if (macroText != null)
textArea.setText(macroText);
BorderPane panelMacro = new BorderPane();
// panelMacro.setBorder(BorderFactory.createTitledBorder("Macro"));
panelMacro.setCenter(textArea);
ParameterPanelFX parameterPanel = new ParameterPanelFX(getParameterList(runner.getImageData()));
panelMacro.setBottom(parameterPanel.getPane());
// Create button panel
Button btnRun = new Button("Run");
btnRun.setOnAction(e -> {
macroText = textArea.getText().trim();
if (macroText.length() == 0)
return;
PathObjectHierarchy hierarchy = getHierarchy(runner);
PathObject pathObject = hierarchy.getSelectionModel().singleSelection() ? hierarchy.getSelectionModel().getSelectedObject() : null;
if (pathObject instanceof PathAnnotationObject || pathObject instanceof TMACoreObject) {
SwingUtilities.invokeLater(() -> {
runMacro(params, qupath.getViewer().getImageData(), qupath.getViewer().getImageDisplay(), pathObject, macroText);
});
} else {
// DisplayHelpers.showErrorMessage(getClass().getSimpleName(), "Sorry, ImageJ macros can only be run for single selected images");
// logger.warn("ImageJ macro being run in current thread");
// runPlugin(runner, arg); // TODO: Consider running in a background thread?
// Run in a background thread
Collection<? extends PathObject> parents = getParentObjects(runner);
if (parents.isEmpty()) {
Dialogs.showErrorMessage("ImageJ macro runner", "No annotation or TMA core objects selected!");
return;
}
List<Runnable> tasks = new ArrayList<>();
for (PathObject parent : parents) addRunnableTasks(qupath.getViewer().getImageData(), parent, tasks);
qupath.submitShortTask(() -> runner.runTasks(tasks, true));
// runner.runTasks(tasks);
// Runnable r = new Runnable() {
// public void run() {
// runPlugin(runner, arg);
// }
// };
// new Thread(r).start();
}
});
Button btnClose = new Button("Close");
btnClose.setOnAction(e -> dialog.hide());
GridPane panelButtons = PaneTools.createRowGridControls(btnRun, btnClose);
pane.setCenter(panelMacro);
pane.setBottom(panelButtons);
panelButtons.setPadding(new Insets(5, 0, 0, 0));
pane.setPadding(new Insets(10, 10, 10, 10));
dialog.setScene(new Scene(pane));
}
dialog.show();
return true;
}
use of qupath.lib.gui.dialogs.ParameterPanelFX in project qupath by qupath.
the class ImageDetailsPane method editStainVector.
void editStainVector(Object value) {
if (!(value instanceof StainVector || value instanceof double[]))
return;
// JOptionPane.showMessageDialog(null, "Modifying stain vectors not yet implemented...");
ImageData<BufferedImage> imageData = qupath.getImageData();
if (imageData == null)
return;
ColorDeconvolutionStains stains = imageData.getColorDeconvolutionStains();
// Default to background values
int num = -1;
String name = null;
String message = null;
if (value instanceof StainVector) {
StainVector stainVector = (StainVector) value;
if (stainVector.isResidual() && imageData.getImageType() != ImageType.BRIGHTFIELD_OTHER) {
logger.warn("Cannot set residual stain vector - this is computed from the known vectors");
return;
}
num = stains.getStainNumber(stainVector);
if (num <= 0) {
logger.error("Could not identify stain vector " + stainVector + " inside " + stains);
return;
}
name = stainVector.getName();
message = "Set stain vector from ROI?";
} else
message = "Set color deconvolution background values from ROI?";
ROI pathROI = imageData.getHierarchy().getSelectionModel().getSelectedROI();
boolean wasChanged = false;
String warningMessage = null;
boolean editableName = imageData.getImageType() == ImageType.BRIGHTFIELD_OTHER;
if (pathROI != null) {
if ((pathROI instanceof RectangleROI) && !pathROI.isEmpty() && ((RectangleROI) pathROI).getArea() < 500 * 500) {
if (Dialogs.showYesNoDialog("Color deconvolution stains", message)) {
ImageServer<BufferedImage> server = imageData.getServer();
BufferedImage img = null;
try {
img = server.readBufferedImage(RegionRequest.createInstance(server.getPath(), 1, pathROI));
} catch (IOException e) {
Dialogs.showErrorMessage("Set stain vector", "Unable to read image region");
logger.error("Unable to read region", e);
}
int rgb = ColorDeconvolutionHelper.getMedianRGB(img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth()));
if (num >= 0) {
StainVector vectorValue = ColorDeconvolutionHelper.generateMedianStainVectorFromPixels(name, img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth()), stains.getMaxRed(), stains.getMaxGreen(), stains.getMaxBlue());
if (!Double.isFinite(vectorValue.getRed() + vectorValue.getGreen() + vectorValue.getBlue())) {
Dialogs.showErrorMessage("Set stain vector", "Cannot set stains for the current ROI!\n" + "It might be too close to the background color.");
return;
}
value = vectorValue;
} else {
// Update the background
value = new double[] { ColorTools.red(rgb), ColorTools.green(rgb), ColorTools.blue(rgb) };
}
wasChanged = true;
}
} else {
warningMessage = "Note: To set stain values from an image region, draw a small, rectangular ROI first";
}
}
// Prompt to set the name / verify stains
ParameterList params = new ParameterList();
String title;
String nameBefore = null;
String valuesBefore = null;
String collectiveNameBefore = stains.getName();
String suggestedName;
if (collectiveNameBefore.endsWith("default"))
suggestedName = collectiveNameBefore.substring(0, collectiveNameBefore.lastIndexOf("default")) + "modified";
else
suggestedName = collectiveNameBefore;
params.addStringParameter("collectiveName", "Collective name", suggestedName, "Enter collective name for all 3 stains (e.g. H-DAB Scanner A, H&E Scanner B)");
if (value instanceof StainVector) {
nameBefore = ((StainVector) value).getName();
valuesBefore = ((StainVector) value).arrayAsString(Locale.getDefault(Category.FORMAT));
params.addStringParameter("name", "Name", nameBefore, "Enter stain name").addStringParameter("values", "Values", valuesBefore, "Enter 3 values (red, green, blue) defining color deconvolution stain vector, separated by spaces");
title = "Set stain vector";
} else {
nameBefore = "Background";
valuesBefore = GeneralTools.arrayToString(Locale.getDefault(Category.FORMAT), (double[]) value, 2);
params.addStringParameter("name", "Stain name", nameBefore);
params.addStringParameter("values", "Stain values", valuesBefore, "Enter 3 values (red, green, blue) defining background, separated by spaces");
params.setHiddenParameters(true, "name");
title = "Set background";
}
if (warningMessage != null)
params.addEmptyParameter(warningMessage);
// Disable editing the name if it should be fixed
ParameterPanelFX parameterPanel = new ParameterPanelFX(params);
parameterPanel.setParameterEnabled("name", editableName);
;
if (!Dialogs.showConfirmDialog(title, parameterPanel.getPane()))
return;
// Check if anything changed
String collectiveName = params.getStringParameterValue("collectiveName");
String nameAfter = params.getStringParameterValue("name");
String valuesAfter = params.getStringParameterValue("values");
if (collectiveName.equals(collectiveNameBefore) && nameAfter.equals(nameBefore) && valuesAfter.equals(valuesBefore) && !wasChanged)
return;
double[] valuesParsed = ColorDeconvolutionStains.parseStainValues(Locale.getDefault(Category.FORMAT), valuesAfter);
if (valuesParsed == null) {
logger.error("Input for setting color deconvolution information invalid! Cannot parse 3 numbers from {}", valuesAfter);
return;
}
if (num >= 0) {
try {
stains = stains.changeStain(StainVector.createStainVector(nameAfter, valuesParsed[0], valuesParsed[1], valuesParsed[2]), num);
} catch (Exception e) {
logger.error("Error setting stain vectors", e);
Dialogs.showErrorMessage("Set stain vectors", "Requested stain vectors are not valid!\nAre two stains equal?");
}
} else {
// Update the background
stains = stains.changeMaxValues(valuesParsed[0], valuesParsed[1], valuesParsed[2]);
}
// Set the collective name
stains = stains.changeName(collectiveName);
imageData.setColorDeconvolutionStains(stains);
qupath.getViewer().repaintEntireImage();
}
use of qupath.lib.gui.dialogs.ParameterPanelFX in project qupath by qupath.
the class ParameterDialogWrapper method createDialog.
private Stage createDialog(final PathInteractivePlugin<T> plugin, final ParameterList params, final PluginRunner<T> pluginRunner) {
panel = new ParameterPanelFX(params);
panel.getPane().setPadding(new Insets(5, 5, 5, 5));
// panel.addParameterChangeListener(new ParameterChangeListener() {
//
// @Override
// public void parameterChanged(ParameterList parameterList, String key, boolean isAdjusting) {
//
// if (!plugin.requestLiveUpdate())
// return;
//
// PathObjectHierarchy hierarchy = pluginRunner.getHierarchy();
// if (hierarchy == null)
// return;
//
// Collection<Class<? extends PathObject>> supportedParents = plugin.getSupportedParentObjectClasses();
//
// PathObject selectedObject = pluginRunner.getSelectedObject();
// if (selectedObject == null) {
// if (supportedParents.contains(PathRootObject.class))
// Collections.singleton(hierarchy.getRootObject());
// } else if (supportedParents.contains(selectedObject.getClass()))
// Collections.singleton(selectedObject);
// }
//
// });
// final Button btnRun = new Button("Run " + plugin.getName());
final Button btnRun = new Button("Run");
btnRun.textProperty().bind(Bindings.createStringBinding(() -> {
if (btnRun.isDisabled())
return "Please wait...";
else
return "Run";
}, btnRun.disabledProperty()));
final Stage dialog = new Stage();
QuPathGUI qupath = QuPathGUI.getInstance();
if (qupath != null)
dialog.initOwner(qupath.getStage());
dialog.setTitle(plugin.getName());
final String emptyLabel = " \n";
final Label label = new Label(emptyLabel);
label.setStyle("-fx-font-weight: bold;");
label.setPadding(new Insets(5, 5, 5, 5));
label.setAlignment(Pos.CENTER);
label.setTextAlignment(TextAlignment.CENTER);
btnRun.setOnAction(e -> {
// Check if we have the parent objects available to make this worthwhile
if (plugin instanceof PathInteractivePlugin) {
// // Strip off any of our extra parameters
// params.removeParameter(KEY_REGIONS);
boolean alwaysPrompt = plugin.alwaysPromptForObjects();
ImageData<?> imageData = pluginRunner.getImageData();
Collection<PathObject> selected = imageData == null ? Collections.emptyList() : imageData.getHierarchy().getSelectionModel().getSelectedObjects();
Collection<? extends PathObject> parents = PathObjectTools.getSupportedObjects(selected, plugin.getSupportedParentObjectClasses());
if (alwaysPrompt || parents == null || parents.isEmpty()) {
if (!ParameterDialogWrapper.promptForParentObjects(pluginRunner, plugin, alwaysPrompt && !parents.isEmpty()))
return;
}
// promptForParentObjects
}
dialog.getScene().setCursor(Cursor.WAIT);
btnRun.setDisable(true);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
WorkflowStep lastStep = pluginRunner.getImageData().getHistoryWorkflow().getLastStep();
boolean success = plugin.runPlugin(pluginRunner, ParameterList.getParameterListJSON(params, " "));
WorkflowStep lastStepNew = pluginRunner.getImageData().getHistoryWorkflow().getLastStep();
if (success && lastStep != lastStepNew)
lastWorkflowStep = lastStepNew;
else
lastWorkflowStep = null;
} catch (Exception e) {
Dialogs.showErrorMessage("Plugin error", e);
} catch (OutOfMemoryError e) {
// This doesn't actually work...
Dialogs.showErrorMessage("Out of memory error", "Out of memory - try to close other applications, or decrease the number of parallel processors in the QuPath preferences");
} finally {
Platform.runLater(() -> {
QuPathGUI.getInstance().pluginRunningProperty().set(false);
dialog.getScene().setCursor(Cursor.DEFAULT);
label.setText(plugin.getLastResultsDescription());
btnRun.setDisable(false);
});
}
}
};
Thread t = new Thread(runnable, "Plugin thread");
QuPathGUI.getInstance().pluginRunningProperty().set(true);
t.start();
});
BorderPane pane = new BorderPane();
ScrollPane scrollPane = new ScrollPane();
label.setMaxWidth(Double.MAX_VALUE);
scrollPane.setContent(panel.getPane());
scrollPane.setFitToWidth(true);
pane.setCenter(scrollPane);
btnRun.setMaxWidth(Double.MAX_VALUE);
btnRun.setPadding(new Insets(5, 5, 5, 5));
pane.setBottom(btnRun);
Scene scene = new Scene(pane);
dialog.setScene(scene);
// Request focus, to make it easier to run from the keyboard
btnRun.requestFocus();
dialog.sizeToScene();
return dialog;
}
Aggregations