use of qupath.lib.objects.TMACoreObject in project qupath by qupath.
the class QuPathGUI method setupViewer.
void setupViewer(final QuPathViewerPlus viewer) {
viewer.getView().setFocusTraversable(true);
// Update active viewer as required
viewer.getView().focusedProperty().addListener((e, f, nowFocussed) -> {
if (nowFocussed) {
viewerManager.setActiveViewer(viewer);
}
});
viewer.getView().addEventFilter(MouseEvent.MOUSE_PRESSED, e -> viewer.getView().requestFocus());
viewer.zoomToFitProperty().bind(zoomToFit);
// Create popup menu
setViewerPopupMenu(viewer);
viewer.getView().widthProperty().addListener((e, f, g) -> {
if (viewer.getZoomToFit())
updateMagnificationString();
});
viewer.getView().heightProperty().addListener((e, f, g) -> {
if (viewer.getZoomToFit())
updateMagnificationString();
});
// Enable drag and drop
dragAndDrop.setupTarget(viewer.getView());
// Listen to the scroll wheel
viewer.getView().setOnScroll(e -> {
if (viewer == viewerManager.getActiveViewer() || !viewerManager.getSynchronizeViewers()) {
double scrollUnits = e.getDeltaY() * PathPrefs.getScaledScrollSpeed();
// Use shift down to adjust opacity
if (e.isShortcutDown()) {
OverlayOptions options = viewer.getOverlayOptions();
options.setOpacity((float) (options.getOpacity() + scrollUnits * 0.001));
return;
}
// Avoid zooming at the end of a gesture when using touchscreens
if (e.isInertia())
return;
if (PathPrefs.invertScrollingProperty().get())
scrollUnits = -scrollUnits;
double newDownsampleFactor = viewer.getDownsampleFactor() * Math.pow(viewer.getDefaultZoomFactor(), scrollUnits);
newDownsampleFactor = Math.min(viewer.getMaxDownsample(), Math.max(newDownsampleFactor, viewer.getMinDownsample()));
viewer.setDownsampleFactor(newDownsampleFactor, e.getX(), e.getY());
}
});
viewer.getView().addEventFilter(RotateEvent.ANY, e -> {
if (!PathPrefs.useRotateGesturesProperty().get())
return;
// logger.debug("Rotating: " + e.getAngle());
viewer.setRotation(viewer.getRotation() + Math.toRadians(e.getAngle()));
e.consume();
});
viewer.getView().addEventFilter(ZoomEvent.ANY, e -> {
if (!PathPrefs.useZoomGesturesProperty().get())
return;
double zoomFactor = e.getZoomFactor();
if (Double.isNaN(zoomFactor))
return;
logger.debug("Zooming: " + e.getZoomFactor() + " (" + e.getTotalZoomFactor() + ")");
viewer.setDownsampleFactor(viewer.getDownsampleFactor() / zoomFactor, e.getX(), e.getY());
e.consume();
});
viewer.getView().addEventFilter(ScrollEvent.ANY, new ScrollEventPanningFilter(viewer));
viewer.getView().addEventHandler(KeyEvent.KEY_PRESSED, e -> {
PathObject pathObject = viewer.getSelectedObject();
if (!e.isConsumed() && pathObject != null) {
if (pathObject.isTMACore()) {
TMACoreObject core = (TMACoreObject) pathObject;
if (e.getCode() == KeyCode.ENTER) {
defaultActions.TMA_ADD_NOTE.handle(new ActionEvent(e.getSource(), e.getTarget()));
e.consume();
} else if (e.getCode() == KeyCode.BACK_SPACE) {
core.setMissing(!core.isMissing());
viewer.getHierarchy().fireObjectsChangedEvent(this, Collections.singleton(core));
e.consume();
}
} else if (pathObject.isAnnotation()) {
if (e.getCode() == KeyCode.ENTER) {
GuiTools.promptToSetActiveAnnotationProperties(viewer.getHierarchy());
e.consume();
}
}
}
});
}
use of qupath.lib.objects.TMACoreObject in project qupath by qupath.
the class DefaultTMAGrid method getCoreIndex.
int getCoreIndex(String coreName) {
int ind = 0;
for (TMACoreObject core : cores) {
String name = core.getName();
if (coreName == null) {
if (name == null)
return ind;
} else if (coreName.equals(name))
return ind;
ind++;
}
return -1;
}
use of qupath.lib.objects.TMACoreObject in project qupath by qupath.
the class TMASummaryViewer method updateSurvivalCurves.
private void updateSurvivalCurves() {
String colID = null;
String colScore = null;
colCensored = null;
for (String nameOrig : model.getAllNames()) {
if (nameOrig.equals(TMACoreObject.KEY_UNIQUE_ID))
colID = nameOrig;
else // else if (nameOrig.equals(TMACoreObject.KEY_CENSORED))
// colCensored = nameOrig;
// else if (!Number.class.isAssignableFrom())
// continue;
{
if (nameOrig.trim().length() == 0 || !model.getMeasurementNames().contains(nameOrig))
continue;
String name = nameOrig.toLowerCase();
if (name.equals("h-score"))
colScore = nameOrig;
else if (name.equals("positive %") && colScore == null)
colScore = nameOrig;
}
}
// Check for a column with the exact requested name
String colCensoredRequested = null;
String colSurvival = getSurvivalColumn();
if (colSurvival != null) {
colCensoredRequested = getRequestedSurvivalCensoredColumn(colSurvival);
if (model.getAllNames().contains(colCensoredRequested))
colCensored = colCensoredRequested;
else // Check for a general 'censored' column... less secure since it doesn't specify OS or RFS (but helps with backwards-compatibility)
if (model.getAllNames().contains("Censored")) {
logger.warn("Correct censored column for \"{}\" unavailable - should be \"{}\", but using \"Censored\" column instead", colSurvival, colCensoredRequested);
colCensored = "Censored";
}
}
if (colCensored == null && colSurvival != null) {
logger.warn("Unable to find censored column - survival data will be uncensored");
} else
logger.info("Survival column: {}, Censored column: {}", colSurvival, colCensored);
colScore = comboMainMeasurement.getSelectionModel().getSelectedItem();
if (colID == null || colSurvival == null || colCensored == null) {
// Adjust priority depending on whether we have any data at all..
if (!model.getItems().isEmpty())
logger.warn("No survival data found!");
else
logger.trace("No entries or survival data available");
return;
}
// Generate a pseudo TMA core hierarchy
Map<String, List<TMAEntry>> scoreMap = createScoresMap(model.getItems(), colScore, colID);
// System.err.println("Score map size: " + scoreMap.size() + "\tEntries: " + model.getEntries().size());
List<TMACoreObject> cores = new ArrayList<>(scoreMap.size());
double[] scores = new double[15];
for (Entry<String, List<TMAEntry>> entry : scoreMap.entrySet()) {
TMACoreObject core = new TMACoreObject();
core.setName("ID: " + entry.getKey());
MeasurementList ml = core.getMeasurementList();
Arrays.fill(scores, Double.POSITIVE_INFINITY);
List<TMAEntry> list = entry.getValue();
// Increase array size, if needed
if (list.size() > scores.length)
scores = new double[list.size()];
for (int i = 0; i < list.size(); i++) {
scores[i] = model.getNumericValue(list.get(i), colScore);
// scores[i] = list.get(i).getMeasurement(colScore).doubleValue();
}
Arrays.sort(scores);
int n = list.size();
double score;
if (n % 2 == 1)
score = scores[n / 2];
else
score = (scores[n / 2 - 1] + scores[n / 2]) / 2;
core.putMetadataValue(TMACoreObject.KEY_UNIQUE_ID, entry.getKey());
// System.err.println("Putting: " + list.get(0).getMeasurement(colSurvival).doubleValue() + " LIST: " + list.size());
ml.putMeasurement(colSurvival, list.get(0).getMeasurementAsDouble(colSurvival));
ml.putMeasurement(colCensoredRequested, list.get(0).getMeasurementAsDouble(colCensored));
if (colScore != null)
ml.putMeasurement(colScore, score);
cores.add(core);
// logger.info(entry.getKey() + "\t" + score);
}
TMAGrid grid = DefaultTMAGrid.create(cores, 1);
PathObjectHierarchy hierarchy = new PathObjectHierarchy();
hierarchy.setTMAGrid(grid);
kmDisplay.setHierarchy(hierarchy, colSurvival, colCensoredRequested);
kmDisplay.setScoreColumn(comboMainMeasurement.getSelectionModel().getSelectedItem());
// new KaplanMeierPlotTMA.KaplanMeierDisplay(hierarchy, colScore).show(frame, colScore);
}
use of qupath.lib.objects.TMACoreObject 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.objects.TMACoreObject in project qupath by qupath.
the class TMADataIO method writeTMAData.
/**
* Write TMA data in a human-readable (and viewable) way, with JPEGs and TXT/CSV files.
*
* @param file
* @param imageData
* @param overlayOptions
* @param downsampleFactor The downsample factor used for the TMA cores. If NaN, an automatic downsample value will be selected (>= 1). If <= 0, no cores are exported.
*/
public static void writeTMAData(File file, final ImageData<BufferedImage> imageData, OverlayOptions overlayOptions, final double downsampleFactor) {
if (imageData == null || imageData.getHierarchy() == null || imageData.getHierarchy().getTMAGrid() == null) {
logger.error("No TMA data available to save!");
return;
}
final ImageServer<BufferedImage> server = imageData.getServer();
String coreExt = imageData.getServer().isRGB() ? ".jpg" : ".tif";
if (file == null) {
file = Dialogs.promptToSaveFile("Save TMA data", null, ServerTools.getDisplayableImageName(server), "TMA data", "qptma");
if (file == null)
return;
} else if (file.isDirectory() || (!file.exists() && file.getAbsolutePath().endsWith(File.pathSeparator))) {
// Put inside the specified directory
file = new File(file, ServerTools.getDisplayableImageName(server) + TMA_DEARRAYING_DATA_EXTENSION);
if (!file.getParentFile().exists())
file.getParentFile().mkdirs();
}
final File dirData = new File(file + ".data");
if (!dirData.exists())
dirData.mkdir();
// Write basic file info
String delimiter = "\t";
TMAGrid tmaGrid = imageData.getHierarchy().getTMAGrid();
try {
PrintWriter writer = new PrintWriter(file);
writer.println(server.getPath());
writer.println(ServerTools.getDisplayableImageName(server));
writer.println();
writer.println("TMA grid width: " + tmaGrid.getGridWidth());
writer.println("TMA grid height: " + tmaGrid.getGridHeight());
writer.println("Core name" + delimiter + "X" + delimiter + "Y" + delimiter + "Width" + delimiter + "Height" + delimiter + "Present" + delimiter + TMACoreObject.KEY_UNIQUE_ID);
for (int row = 0; row < tmaGrid.getGridHeight(); row++) {
for (int col = 0; col < tmaGrid.getGridWidth(); col++) {
TMACoreObject core = tmaGrid.getTMACore(row, col);
if (!core.hasROI()) {
writer.println(core.getName() + delimiter + delimiter + delimiter + delimiter);
continue;
}
ROI pathROI = core.getROI();
int x = (int) pathROI.getBoundsX();
int y = (int) pathROI.getBoundsY();
int w = (int) Math.ceil(pathROI.getBoundsWidth());
int h = (int) Math.ceil(pathROI.getBoundsHeight());
String id = core.getUniqueID() == null ? "" : core.getUniqueID();
writer.println(core.getName() + delimiter + x + delimiter + y + delimiter + w + delimiter + h + delimiter + !core.isMissing() + delimiter + id);
}
}
writer.close();
} catch (Exception e) {
logger.error("Error writing TMA data: " + e.getLocalizedMessage(), e);
return;
}
// Save the summary results
ObservableMeasurementTableData tableData = new ObservableMeasurementTableData();
tableData.setImageData(imageData, tmaGrid.getTMACoreList());
SummaryMeasurementTableCommand.saveTableModel(tableData, new File(dirData, "TMA results - " + ServerTools.getDisplayableImageName(server) + ".txt"), Collections.emptyList());
boolean outputCoreImages = Double.isNaN(downsampleFactor) || downsampleFactor > 0;
if (outputCoreImages) {
// Create new overlay options, if we don't have some already
if (overlayOptions == null) {
overlayOptions = new OverlayOptions();
overlayOptions.setFillDetections(true);
}
final OverlayOptions options = overlayOptions;
// Write an overall TMA map (for quickly checking if the dearraying is ok)
File fileTMAMap = new File(dirData, "TMA map - " + ServerTools.getDisplayableImageName(server) + ".jpg");
double downsampleThumbnail = Math.max(1, (double) Math.max(server.getWidth(), server.getHeight()) / 1024);
RegionRequest request = RegionRequest.createInstance(server.getPath(), downsampleThumbnail, 0, 0, server.getWidth(), server.getHeight());
OverlayOptions optionsThumbnail = new OverlayOptions();
optionsThumbnail.setShowTMAGrid(true);
optionsThumbnail.setShowGrid(false);
optionsThumbnail.setShowAnnotations(false);
optionsThumbnail.setShowDetections(false);
try {
var renderedServer = new RenderedImageServer.Builder(imageData).layers(new TMAGridOverlay(overlayOptions)).downsamples(downsampleThumbnail).build();
ImageWriterTools.writeImageRegion(renderedServer, request, fileTMAMap.getAbsolutePath());
// ImageWriters.writeImageRegionWithOverlay(imageData.getServer(), Collections.singletonList(new TMAGridOverlay(overlayOptions, imageData)), request, fileTMAMap.getAbsolutePath());
} catch (IOException e) {
logger.warn("Unable to write image overview: " + e.getLocalizedMessage(), e);
}
final double downsample = Double.isNaN(downsampleFactor) ? (server.getPixelCalibration().hasPixelSizeMicrons() ? ServerTools.getDownsampleFactor(server, preferredExportPixelSizeMicrons) : 1) : downsampleFactor;
// Creating a plugin makes it possible to parallelize & show progress easily
var renderedImageServer = new RenderedImageServer.Builder(imageData).layers(new HierarchyOverlay(null, options, imageData)).downsamples(downsample).build();
ExportCoresPlugin plugin = new ExportCoresPlugin(dirData, renderedImageServer, downsample, coreExt);
PluginRunner<BufferedImage> runner;
var qupath = QuPathGUI.getInstance();
if (qupath == null || qupath.getImageData() != imageData) {
runner = new CommandLinePluginRunner<>(imageData);
plugin.runPlugin(runner, null);
} else {
try {
qupath.runPlugin(plugin, null, false);
} catch (Exception e) {
logger.error("Error writing TMA data: " + e.getLocalizedMessage(), e);
}
// new Thread(() -> qupath.runPlugin(plugin, null, false)).start();
// runner = new PluginRunnerFX(QuPathGUI.getInstance());
// new Thread(() -> plugin.runPlugin(runner, null)).start();
}
}
}
Aggregations