use of qupath.lib.gui.tma.TMAEntries.TMAEntry 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.gui.tma.TMAEntries.TMAEntry in project qupath by qupath.
the class TMASummaryViewer method setTMAEntries.
void setTMAEntries(final Collection<TMAEntry> newEntries) {
// Turn off use-selected - can be crashy when replacing entries
if (!newEntries.equals(entriesBase)) {
useSelectedProperty.set(false);
// Reset the cache
imageCache.clear();
// Try to load small images in a background thread
List<TMAEntry> duplicateEntries = new ArrayList<>(newEntries);
ExecutorService service = Executors.newSingleThreadExecutor();
service.submit(() -> {
duplicateEntries.parallelStream().forEach(entry -> {
imageCache.getImage(entry, maxSmallWidth.get());
imageCache.getOverlay(entry, maxSmallWidth.get());
});
});
service.shutdown();
}
this.entriesBase.setAll(newEntries);
// Store the names of any currently hidden columns
lastHiddenColumns = table.getColumns().stream().filter(c -> !c.isVisible()).map(c -> c.getText()).collect(Collectors.toSet());
// this.table.getColumns().clear();
// // Useful for a paper, but not generally...
// int count = 0;
// int nCells = 0;
// int nTumor = 0;
// for (TMAEntry entry : entriesBase) {
// if (!entry.isMissing() && (predicate.get() == null || predicate.get().test(entry))) {
// count++;
// nCells += (int)(entry.getMeasurement("Num Tumor").doubleValue() + entry.getMeasurement("Num Stroma").doubleValue());
// nTumor += (int)(entry.getMeasurement("Num Tumor").doubleValue());
// }
// }
// System.err.println(String.format("Num entries:\t%d\tNum tumor:\t%d\tNum cells:\t%d", count, nTumor, nCells));
// Update measurement names
Set<String> namesMeasurements = new LinkedHashSet<>();
Set<String> namesMetadata = new LinkedHashSet<>();
// boolean containsSummaries = false;
for (TMAEntry entry : newEntries) {
namesMeasurements.addAll(entry.getMeasurementNames());
namesMetadata.addAll(entry.getMetadataNames());
// containsSummaries = containsSummaries || entry instanceof TMASummaryEntry;
}
// Get the available survival columns
String currentSurvival = getSurvivalColumn();
survivalColumns.clear();
if (namesMeasurements.contains(TMACoreObject.KEY_OVERALL_SURVIVAL))
survivalColumns.add(TMACoreObject.KEY_OVERALL_SURVIVAL);
if (namesMeasurements.contains(TMACoreObject.KEY_RECURRENCE_FREE_SURVIVAL))
survivalColumns.add(TMACoreObject.KEY_RECURRENCE_FREE_SURVIVAL);
if (currentSurvival != null && survivalColumns.contains(currentSurvival))
comboSurvival.getSelectionModel().select(currentSurvival);
else if (!survivalColumns.isEmpty())
comboSurvival.getSelectionModel().select(survivalColumns.get(0));
// // Add the count of non-missing cores if we are working with summaries
// if (containsSummaries)
namesMeasurements.add("Available cores");
// Make sure there are no nulls or other unusable values
namesMeasurements.remove(null);
namesMeasurements.remove("");
// measurementNames.clear();
String selectedMainMeasurement = comboMainMeasurement.getSelectionModel().getSelectedItem();
measurementNames.setAll(namesMeasurements);
if (namesMeasurements.contains(selectedMainMeasurement))
comboMainMeasurement.getSelectionModel().select(selectedMainMeasurement);
else {
namesMeasurements.remove(TMACoreObject.KEY_UNIQUE_ID);
namesMeasurements.remove(TMACoreObject.KEY_OVERALL_SURVIVAL);
namesMeasurements.remove(TMACoreObject.KEY_RECURRENCE_FREE_SURVIVAL);
namesMeasurements.remove(TMACoreObject.KEY_OS_CENSORED);
namesMeasurements.remove(TMACoreObject.KEY_RFS_CENSORED);
// For historical reasons when there was only one censored column supported...
namesMeasurements.remove("Censored");
if (!namesMeasurements.isEmpty())
comboMainMeasurement.getSelectionModel().select(0);
}
metadataNames.clear();
metadataNames.addAll(namesMetadata);
refreshTableData();
// The next time the table is empty, show a different placeholder
// from the original (which is for loading/import)
table.setPlaceholder(new Text("No data"));
}
use of qupath.lib.gui.tma.TMAEntries.TMAEntry in project qupath by qupath.
the class TMASummaryViewer method parseInputFile.
private void parseInputFile(File file, List<TMAEntry> entries) {
int nEntries = entries.size();
String serverPath = null;
try {
Scanner scanner = new Scanner(file);
serverPath = scanner.nextLine().trim();
scanner.close();
} catch (Exception e) {
logger.error("Error parsing input file", e);
}
if (serverPath == null) {
// || !(new File(serverPath).exists())) {
logger.error("Unable to find a server with path " + serverPath + " - cannot parse " + file.getAbsolutePath());
return;
}
File dirData = new File(file.getAbsolutePath() + ".data");
try {
File fileResults = getTMAResultsFile(dirData);
if (fileResults == null) {
logger.error("No results file found for {}", dirData.getAbsolutePath());
return;
}
Map<String, List<String>> csvData = TMAScoreImporter.readCSV(fileResults);
if (csvData.isEmpty()) {
logger.warn("Results file empty: {}", fileResults.getAbsolutePath());
return;
}
// Identify metadata and numeric columns
Map<String, List<String>> metadataColumns = new LinkedHashMap<>();
Map<String, double[]> measurementColumns = new LinkedHashMap<>();
List<String> idColumn = csvData.remove(TMACoreObject.KEY_UNIQUE_ID);
if (idColumn != null) {
metadataColumns.put(TMACoreObject.KEY_UNIQUE_ID, idColumn);
// Make sure IDs are trimmed
if (trimUniqueIDs) {
for (int i = 0; i < idColumn.size(); i++) idColumn.set(i, idColumn.get(i) == null ? null : idColumn.get(i).trim());
}
}
List<String> nameColumn = csvData.remove("Name");
if (nameColumn == null)
nameColumn = csvData.remove("Object");
// Handle 'missing-ness' separately from general metadata
List<String> missingColumn = csvData.remove(MISSING_COLUMN);
// csvData.values().iterator().next().size();
int n = idColumn == null ? 0 : idColumn.size();
for (Entry<String, List<String>> entry : csvData.entrySet()) {
List<String> list = entry.getValue();
n = list.size();
double[] values = TMAScoreImporter.parseNumeric(list, true);
if (values == null || GeneralTools.numNaNs(values) == list.size())
metadataColumns.put(entry.getKey(), list);
else
measurementColumns.put(entry.getKey(), values);
}
for (int i = 0; i < n; i++) {
// Don't permit 'NaN' as an ID
if (idColumn != null && "NaN".equals(idColumn.get(i)))
continue;
String name = nameColumn == null ? idColumn.get(i) : nameColumn.get(i);
boolean missing = missingColumn != null && "True".equals(missingColumn.get(i));
File fileImage = new File(dirData, name + ".jpg");
File fileOverlayImage = new File(dirData, name + "-overlay.jpg");
if (!fileOverlayImage.exists())
fileOverlayImage = new File(dirData, name + "-overlay.png");
TMAEntry entry = TMAEntries.createDefaultTMAEntry(serverPath, fileImage.getAbsolutePath(), fileOverlayImage.getAbsolutePath(), name, missing);
for (Entry<String, List<String>> temp : metadataColumns.entrySet()) {
entry.putMetadata(temp.getKey(), temp.getValue().get(i));
}
for (Entry<String, double[]> temp : measurementColumns.entrySet()) {
entry.putMeasurement(temp.getKey(), temp.getValue()[i]);
}
entries.add(entry);
}
} catch (Exception e) {
logger.error("Error parsing input file " + file, e);
}
logger.info("Parsed " + (entries.size() - nEntries) + " from " + file.getName() + " (" + entries.size() + " total)");
}
use of qupath.lib.gui.tma.TMAEntries.TMAEntry in project qupath by qupath.
the class TMASummaryViewer method initialize.
private void initialize() {
model = new TMATableModel();
groupByIDProperty.addListener((v, o, n) -> refreshTableData());
MenuBar menuBar = new MenuBar();
Menu menuFile = new Menu("File");
MenuItem miOpen = new MenuItem("Open...");
miOpen.setAccelerator(new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN));
miOpen.setOnAction(e -> {
File file = Dialogs.getChooser(stage).promptForFile(null, null, "TMA data files", new String[] { "qptma" });
if (file == null)
return;
setInputFile(file);
});
MenuItem miSave = new MenuItem("Save As...");
miSave.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
miSave.setOnAction(e -> SummaryMeasurementTableCommand.saveTableModel(model, null, Collections.emptyList()));
MenuItem miImportFromImage = new MenuItem("Import from current image...");
miImportFromImage.setAccelerator(new KeyCodeCombination(KeyCode.I, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
miImportFromImage.setOnAction(e -> setTMAEntriesFromOpenImage());
MenuItem miImportFromProject = new MenuItem("Import from current project... (experimental)");
miImportFromProject.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
miImportFromProject.setOnAction(e -> setTMAEntriesFromOpenProject());
MenuItem miImportClipboard = new MenuItem("Import from clipboard...");
miImportClipboard.setOnAction(e -> {
String text = Clipboard.getSystemClipboard().getString();
if (text == null) {
Dialogs.showErrorMessage("Import scores", "Clipboard is empty!");
return;
}
int n = importScores(text);
if (n > 0) {
setTMAEntries(new ArrayList<>(entriesBase));
}
Dialogs.showMessageDialog("Import scores", "Number of scores imported: " + n);
});
Menu menuEdit = new Menu("Edit");
MenuItem miCopy = new MenuItem("Copy table to clipboard");
miCopy.setOnAction(e -> {
SummaryMeasurementTableCommand.copyTableContentsToClipboard(model, Collections.emptyList());
});
combinedPredicate.addListener((v, o, n) -> {
// We want any other changes triggered by this to have happened,
// so that the data has already been updated
Platform.runLater(() -> handleTableContentChange());
});
// Reset the scores for missing cores - this ensures they will be NaN and not influence subsequent results
MenuItem miResetMissingScores = new MenuItem("Reset scores for missing cores");
miResetMissingScores.setOnAction(e -> {
int changes = 0;
for (TMAEntry entry : entriesBase) {
if (!entry.isMissing())
continue;
boolean changed = false;
for (String m : entry.getMeasurementNames().toArray(new String[0])) {
if (!TMASummaryEntry.isSurvivalColumn(m) && !Double.isNaN(entry.getMeasurementAsDouble(m))) {
entry.putMeasurement(m, null);
changed = true;
}
}
if (changed)
changes++;
}
if (changes == 0) {
logger.info("No changes made when resetting scores for missing cores!");
return;
}
logger.info("{} change(s) made when resetting scores for missing cores!", changes);
table.refresh();
updateSurvivalCurves();
if (scatterPane != null)
scatterPane.updateChart();
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
});
menuEdit.getItems().add(miResetMissingScores);
MenuTools.addMenuItems(menuFile, miOpen, miSave, null, miImportClipboard, null, miImportFromImage, miImportFromProject);
menuBar.getMenus().add(menuFile);
menuEdit.getItems().add(miCopy);
menuBar.getMenus().add(menuEdit);
menuFile.setOnShowing(e -> {
boolean imageDataAvailable = QuPathGUI.getInstance() != null && QuPathGUI.getInstance().getImageData() != null && QuPathGUI.getInstance().getImageData().getHierarchy().getTMAGrid() != null;
miImportFromImage.setDisable(!imageDataAvailable);
boolean projectAvailable = QuPathGUI.getInstance() != null && QuPathGUI.getInstance().getProject() != null && !QuPathGUI.getInstance().getProject().getImageList().isEmpty();
miImportFromProject.setDisable(!projectAvailable);
});
// Double-clicking previously used for comments... but conflicts with tree table expansion
// table.setOnMouseClicked(e -> {
// if (!e.isPopupTrigger() && e.getClickCount() > 1)
// promptForComment();
// });
table.setPlaceholder(new Text("Drag TMA data folder onto window, or choose File -> Open"));
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
BorderPane pane = new BorderPane();
pane.setTop(menuBar);
menuBar.useSystemMenuBarProperty().bindBidirectional(PathPrefs.useSystemMenubarProperty());
// menuBar.setUseSystemMenuBar(true);
// Create options
ToolBar toolbar = new ToolBar();
Label labelMeasurementMethod = new Label("Combination method");
labelMeasurementMethod.setLabelFor(comboMeasurementMethod);
labelMeasurementMethod.setTooltip(new Tooltip("Method whereby measurements for multiple cores with the same " + TMACoreObject.KEY_UNIQUE_ID + " will be combined"));
CheckBox cbHidePane = new CheckBox("Hide pane");
cbHidePane.setSelected(hidePaneProperty.get());
cbHidePane.selectedProperty().bindBidirectional(hidePaneProperty);
CheckBox cbGroupByID = new CheckBox("Group by ID");
entriesBase.addListener((Change<? extends TMAEntry> event) -> {
if (!event.getList().stream().anyMatch(e -> e.getMetadataValue(TMACoreObject.KEY_UNIQUE_ID) != null)) {
cbGroupByID.setSelected(false);
cbGroupByID.setDisable(true);
} else {
cbGroupByID.setDisable(false);
}
});
cbGroupByID.setSelected(groupByIDProperty.get());
cbGroupByID.selectedProperty().bindBidirectional(groupByIDProperty);
CheckBox cbUseSelected = new CheckBox("Use selection only");
cbUseSelected.selectedProperty().bindBidirectional(useSelectedProperty);
CheckBox cbSkipMissing = new CheckBox("Hide missing cores");
cbSkipMissing.selectedProperty().bindBidirectional(skipMissingCoresProperty);
skipMissingCoresProperty.addListener((v, o, n) -> {
table.refresh();
updateSurvivalCurves();
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
updateSurvivalCurves();
if (scatterPane != null)
scatterPane.updateChart();
});
toolbar.getItems().addAll(labelMeasurementMethod, comboMeasurementMethod, new Separator(Orientation.VERTICAL), cbHidePane, new Separator(Orientation.VERTICAL), cbGroupByID, new Separator(Orientation.VERTICAL), cbUseSelected, new Separator(Orientation.VERTICAL), cbSkipMissing);
comboMeasurementMethod.getItems().addAll(TMAEntries.MeasurementCombinationMethod.values());
comboMeasurementMethod.getSelectionModel().select(TMAEntries.MeasurementCombinationMethod.MEDIAN);
selectedMeasurementCombinationProperty.addListener((v, o, n) -> table.refresh());
ContextMenu popup = new ContextMenu();
MenuItem miSetMissing = new MenuItem("Set missing");
miSetMissing.setOnAction(e -> setSelectedMissingStatus(true));
MenuItem miSetAvailable = new MenuItem("Set available");
miSetAvailable.setOnAction(e -> setSelectedMissingStatus(false));
MenuItem miExpand = new MenuItem("Expand all");
miExpand.setOnAction(e -> {
if (table.getRoot() == null)
return;
for (TreeItem<?> item : table.getRoot().getChildren()) {
item.setExpanded(true);
}
});
MenuItem miCollapse = new MenuItem("Collapse all");
miCollapse.setOnAction(e -> {
if (table.getRoot() == null)
return;
for (TreeItem<?> item : table.getRoot().getChildren()) {
item.setExpanded(false);
}
});
popup.getItems().addAll(miSetMissing, miSetAvailable, new SeparatorMenuItem(), miExpand, miCollapse);
table.setContextMenu(popup);
table.setRowFactory(e -> {
TreeTableRow<TMAEntry> row = new TreeTableRow<>();
// // Make rows invisible if they don't pass the predicate
// row.visibleProperty().bind(Bindings.createBooleanBinding(() -> {
// TMAEntry entry = row.getItem();
// if (entry == null || (entry.isMissing() && skipMissingCoresProperty.get()))
// return false;
// return entries.getPredicate() == null || entries.getPredicate().test(entry);
// },
// skipMissingCoresProperty,
// entries.predicateProperty()));
// Style rows according to what they contain
row.styleProperty().bind(Bindings.createStringBinding(() -> {
if (row.isSelected())
return "";
TMAEntry entry = row.getItem();
if (entry == null || entry instanceof TMASummaryEntry)
return "";
else if (entry.isMissing())
return "-fx-background-color:rgb(225,225,232)";
else
return "-fx-background-color:rgb(240,240,245)";
}, row.itemProperty(), row.selectedProperty()));
// });
return row;
});
BorderPane paneTable = new BorderPane();
paneTable.setTop(toolbar);
paneTable.setCenter(table);
MasterDetailPane mdTablePane = new MasterDetailPane(Side.RIGHT, paneTable, createSidePane(), true);
mdTablePane.showDetailNodeProperty().bind(Bindings.createBooleanBinding(() -> !hidePaneProperty.get() && !entriesBase.isEmpty(), hidePaneProperty, entriesBase));
mdTablePane.setDividerPosition(2.0 / 3.0);
pane.setCenter(mdTablePane);
model.getItems().addListener(new ListChangeListener<TMAEntry>() {
@Override
public void onChanged(ListChangeListener.Change<? extends TMAEntry> c) {
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
updateSurvivalCurves();
if (scatterPane != null)
scatterPane.updateChart();
}
});
Label labelPredicate = new Label();
labelPredicate.setPadding(new Insets(5, 5, 5, 5));
labelPredicate.setAlignment(Pos.CENTER);
// labelPredicate.setStyle("-fx-background-color: rgba(20, 120, 20, 0.15);");
labelPredicate.setStyle("-fx-background-color: rgba(120, 20, 20, 0.15);");
labelPredicate.textProperty().addListener((v, o, n) -> {
if (n.trim().length() > 0)
pane.setBottom(labelPredicate);
else
pane.setBottom(null);
});
labelPredicate.setMaxWidth(Double.MAX_VALUE);
labelPredicate.setMaxHeight(labelPredicate.getPrefHeight());
labelPredicate.setTextAlignment(TextAlignment.CENTER);
predicateMeasurements.addListener((v, o, n) -> {
if (n == null)
labelPredicate.setText("");
else if (n instanceof TablePredicate) {
TablePredicate tp = (TablePredicate) n;
if (tp.getOriginalCommand().trim().isEmpty())
labelPredicate.setText("");
else
labelPredicate.setText("Predicate: " + tp.getOriginalCommand());
} else
labelPredicate.setText("Predicate: " + n.toString());
});
// predicate.set(new TablePredicate("\"Tumor\" > 100"));
scene = new Scene(pane);
scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
KeyCode code = e.getCode();
if ((code == KeyCode.SPACE || code == KeyCode.ENTER) && entrySelected != null) {
promptForComment();
return;
}
});
}
use of qupath.lib.gui.tma.TMAEntries.TMAEntry in project qupath by qupath.
the class TMAImageCache method getLargeCachedImage.
private Image getLargeCachedImage(final TMAEntry entry, final boolean isOverlay) {
Map<TMAEntry, SoftReference<Image>> cache = isOverlay ? overlayLarge : imageLarge;
SoftReference<Image> ref = cache.get(entry);
Image img = ref == null ? null : ref.get();
if (img == null) {
img = isOverlay ? entry.getOverlay(-1) : entry.getImage(-1);
if (img != null) {
cache.put(entry, new SoftReference<>(img));
}
}
return img;
}
Aggregations