Search in sources :

Example 6 with TMACoreObject

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();
                }
            }
        }
    });
}
Also used : PathObject(qupath.lib.objects.PathObject) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) TMACoreObject(qupath.lib.objects.TMACoreObject) ActionEvent(javafx.event.ActionEvent)

Example 7 with TMACoreObject

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;
}
Also used : TMACoreObject(qupath.lib.objects.TMACoreObject)

Example 8 with TMACoreObject

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);
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) TMACoreObject(qupath.lib.objects.TMACoreObject) MeasurementList(qupath.lib.measurements.MeasurementList) TMAEntry(qupath.lib.gui.tma.TMAEntries.TMAEntry) ArrayList(java.util.ArrayList) DefaultTMAGrid(qupath.lib.objects.hierarchy.DefaultTMAGrid) TMAGrid(qupath.lib.objects.hierarchy.TMAGrid) ObservableList(javafx.collections.ObservableList) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) SortedList(javafx.collections.transformation.SortedList) FilteredList(javafx.collections.transformation.FilteredList) List(java.util.List)

Example 9 with TMACoreObject

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;
}
Also used : Histogram(qupath.lib.analysis.stats.Histogram) CellDataFeatures(javafx.scene.control.TableColumn.CellDataFeatures) ObservableValue(javafx.beans.value.ObservableValue) ThresholdedChartWrapper(qupath.lib.gui.charts.HistogramPanelFX.ThresholdedChartWrapper) StackPane(javafx.scene.layout.StackPane) HashSet(java.util.HashSet) ObservableNumberValue(javafx.beans.value.ObservableNumberValue) GridPane(javafx.scene.layout.GridPane) TMACoreObject(qupath.lib.objects.TMACoreObject) Tooltip(javafx.scene.control.Tooltip) Color(javafx.scene.paint.Color) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) TableColumn(javafx.scene.control.TableColumn) StackPane(javafx.scene.layout.StackPane) Pane(javafx.scene.layout.Pane) BorderPane(javafx.scene.layout.BorderPane) GridPane(javafx.scene.layout.GridPane) TitledPane(javafx.scene.control.TitledPane) ParameterList(qupath.lib.plugins.parameters.ParameterList) XYChart(javafx.scene.chart.XYChart) TMACoreObject(qupath.lib.objects.TMACoreObject) HistogramPanelFX(qupath.lib.gui.charts.HistogramPanelFX) LogRankResult(qupath.lib.analysis.stats.survival.LogRankTest.LogRankResult) IntParameter(qupath.lib.plugins.parameters.IntParameter) BorderPane(javafx.scene.layout.BorderPane) NumberAxis(javafx.scene.chart.NumberAxis) Insets(javafx.geometry.Insets) ClipboardContent(javafx.scene.input.ClipboardContent) MeasurementList(qupath.lib.measurements.MeasurementList) ContextMenu(javafx.scene.control.ContextMenu) RadioMenuItem(javafx.scene.control.RadioMenuItem) KaplanMeierData(qupath.lib.analysis.stats.survival.KaplanMeierData) ParameterPanelFX(qupath.lib.gui.dialogs.ParameterPanelFX) TableCell(javafx.scene.control.TableCell) ContextMenu(javafx.scene.control.ContextMenu) Menu(javafx.scene.control.Menu) TitledPane(javafx.scene.control.TitledPane) MenuItem(javafx.scene.control.MenuItem) RadioMenuItem(javafx.scene.control.RadioMenuItem) KaplanMeierData(qupath.lib.analysis.stats.survival.KaplanMeierData) Data(javafx.scene.chart.XYChart.Data) TreeMap(java.util.TreeMap) ToggleGroup(javafx.scene.control.ToggleGroup)

Example 10 with TMACoreObject

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 (&gt;= 1).  If &lt;= 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();
        }
    }
}
Also used : TMACoreObject(qupath.lib.objects.TMACoreObject) DefaultTMAGrid(qupath.lib.objects.hierarchy.DefaultTMAGrid) TMAGrid(qupath.lib.objects.hierarchy.TMAGrid) IOException(java.io.IOException) ROI(qupath.lib.roi.interfaces.ROI) RenderedImageServer(qupath.lib.gui.images.servers.RenderedImageServer) BufferedImage(java.awt.image.BufferedImage) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException) HierarchyOverlay(qupath.lib.gui.viewer.overlays.HierarchyOverlay) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) File(java.io.File) RegionRequest(qupath.lib.regions.RegionRequest) TMAGridOverlay(qupath.lib.gui.viewer.overlays.TMAGridOverlay) PrintWriter(java.io.PrintWriter)

Aggregations

TMACoreObject (qupath.lib.objects.TMACoreObject)35 PathObject (qupath.lib.objects.PathObject)20 ArrayList (java.util.ArrayList)17 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)15 TMAGrid (qupath.lib.objects.hierarchy.TMAGrid)13 BufferedImage (java.awt.image.BufferedImage)12 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)12 ROI (qupath.lib.roi.interfaces.ROI)12 IOException (java.io.IOException)9 List (java.util.List)9 RegionRequest (qupath.lib.regions.RegionRequest)9 File (java.io.File)6 ImageServer (qupath.lib.images.servers.ImageServer)6 PathDetectionObject (qupath.lib.objects.PathDetectionObject)6 Collection (java.util.Collection)5 LinkedHashMap (java.util.LinkedHashMap)5 Collectors (java.util.stream.Collectors)5 HashSet (java.util.HashSet)4 Insets (javafx.geometry.Insets)4 Scene (javafx.scene.Scene)4