use of qupath.lib.objects.hierarchy.PathObjectHierarchy 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.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class PathIO method writeImageDataSerialized.
private static void writeImageDataSerialized(final OutputStream stream, final ImageData<?> imageData) throws IOException {
try (OutputStream outputStream = new BufferedOutputStream(stream)) {
long startTime = System.currentTimeMillis();
ObjectOutputStream outStream = new ObjectOutputStream(outputStream);
// Write the identifier
outStream.writeUTF("Data file version " + DATA_FILE_VERSION);
// Try to write a backwards-compatible image path
var server = imageData.getServer();
// var uris = server.getURIs();
// String path;
// if (uris.size() == 1) {
// var uri = uris.iterator().next();
// var serverPath = GeneralTools.toPath(uri);
// if (serverPath != null && Files.exists(serverPath))
// path = serverPath.toFile().getAbsolutePath();
// else
// path = uri.toString();
// } else
// path = server.getPath();
// outStream.writeObject("Image path: " + path);
// Write JSON object including QuPath version and ServerBuilder
// Note that the builder may be null, in which case the server cannot be recreated
var builder = server.getBuilder();
if (builder == null)
logger.warn("Server {} does not provide a builder - it will not be possible to recover the ImageServer from this data file", server);
var wrapper = ServerBuilderWrapper.create(builder, server.getPath());
String json = GsonTools.getInstance().toJson(wrapper);
outStream.writeObject(json);
// Write the current locale
outStream.writeObject(Locale.getDefault(Category.FORMAT));
// Write the rest of the main image metadata
outStream.writeObject(imageData.getImageType());
outStream.writeObject(imageData.getColorDeconvolutionStains());
outStream.writeObject(imageData.getHistoryWorkflow());
// Write the rest of the main image metadata
PathObjectHierarchy hierarchy = imageData.getHierarchy();
logger.info(String.format("Writing object hierarchy with %d object(s)...", hierarchy.nObjects()));
outStream.writeObject(hierarchy);
// Write any remaining (serializable) properties
Map<String, Object> map = new HashMap<>();
for (Entry<String, Object> entry : imageData.getProperties().entrySet()) {
if (entry.getValue() instanceof Serializable)
map.put(entry.getKey(), entry.getValue());
else
logger.error("Property not serializable and will not be saved! Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
if (map != null)
outStream.writeObject(map);
// Write EOF marker
outStream.writeObject("EOF");
long endTime = System.currentTimeMillis();
logger.info(String.format("Image data written in %.2f seconds", (endTime - startTime) / 1000.));
}
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class PathIO method readImageDataSerialized.
@SuppressWarnings("unchecked")
private static <T> ImageData<T> readImageDataSerialized(final InputStream stream, ImageData<T> imageData, ImageServer<T> server, Class<T> cls) throws IOException {
long startTime = System.currentTimeMillis();
Locale locale = Locale.getDefault(Category.FORMAT);
boolean localeChanged = false;
try (ObjectInputStream inStream = new ObjectInputStream(new BufferedInputStream(stream))) {
ServerBuilder<T> serverBuilder = null;
PathObjectHierarchy hierarchy = null;
ImageData.ImageType imageType = null;
ColorDeconvolutionStains stains = null;
Workflow workflow = null;
Map<String, Object> propertyMap = null;
String firstLine = inStream.readUTF();
// int versionNumber = -1;
if (!firstLine.startsWith("Data file version")) {
logger.error("Input stream does not contain valid QuPath data!");
}
// else {
// // Could try to parse version number... although frankly, at this time, we don't really care...
// try {
// versionNumber = NumberFormat.getInstance(Locale.US).parse(firstLine.substring("Data file version".length()).trim()).intValue();
// } catch (Exception e) {
// logger.warn("Unable to parse version number from {}", firstLine);
// }
// }
String serverString = (String) inStream.readObject();
// Don't log warnings if we are provided with a server
serverBuilder = extractServerBuilder(serverString, server == null);
while (true) {
// logger.debug("Starting read: " + inStream.available());
try {
// Try to read a relevant object from the stream
Object input = inStream.readObject();
logger.debug("Read: {}", input);
// If we have a Locale, then set it
if (input instanceof Locale) {
if (input != locale) {
Locale.setDefault(Category.FORMAT, (Locale) input);
localeChanged = true;
}
} else if (input instanceof PathObjectHierarchy)
hierarchy = (PathObjectHierarchy) input;
else if (input instanceof ImageData.ImageType)
imageType = (ImageData.ImageType) input;
else if (input instanceof String && "EOF".equals(input)) {
// else if ("EOF".equals(input)) {
break;
// }
} else if (input instanceof ColorDeconvolutionStains)
stains = (ColorDeconvolutionStains) input;
else if (input instanceof Workflow)
workflow = (Workflow) input;
else if (input instanceof Map)
propertyMap = (Map<String, Object>) input;
else if (input == null) {
logger.debug("Null object will be skipped");
} else
logger.warn("Unsupported object of class {} will be skipped: {}", input.getClass().getName(), input);
} catch (ClassNotFoundException e) {
logger.error("Unable to find class: " + e.getLocalizedMessage(), e);
} catch (EOFException e) {
// Try to recover from EOFExceptions - we may already have enough info
logger.error("Reached end of file...");
if (hierarchy == null)
logger.error(e.getLocalizedMessage(), e);
break;
}
}
// Create an entirely new ImageData if necessary
var existingBuilder = imageData == null || imageData.getServer() == null ? null : imageData.getServer().getBuilder();
if (imageData == null || !Objects.equals(serverBuilder, existingBuilder)) {
// Create a new server if we need to
if (server == null) {
try {
server = serverBuilder.build();
} catch (Exception e) {
logger.error(e.getLocalizedMessage());
}
;
if (server == null) {
logger.error("Warning: Unable to build server with " + serverBuilder);
// throw new RuntimeException("Warning: Unable to create server for path " + serverPath);
}
}
// TODO: Make this less clumsy... but for now we need to ensure we have a fully-initialized hierarchy (which deserialization alone doesn't achieve)
PathObjectHierarchy hierarchy2 = new PathObjectHierarchy();
hierarchy2.setHierarchy(hierarchy);
hierarchy = hierarchy2;
imageData = new ImageData<>(server, hierarchy, imageType);
} else {
if (imageType != null)
imageData.setImageType(imageType);
// Set the new hierarchy
if (hierarchy != null)
imageData.getHierarchy().setHierarchy(hierarchy);
}
// Set the other properties we have just read
if (workflow != null) {
imageData.getHistoryWorkflow().clear();
imageData.getHistoryWorkflow().addSteps(workflow.getSteps());
}
if (stains != null) {
imageData.setColorDeconvolutionStains(stains);
}
if (propertyMap != null) {
for (Entry<String, Object> entry : propertyMap.entrySet()) imageData.setProperty(entry.getKey(), entry.getValue());
}
long endTime = System.currentTimeMillis();
// if (hierarchy == null) {
// logger.error(String.format("%s does not contain a valid QUPath object hierarchy!", file.getAbsolutePath()));
// return null;
// }
logger.debug(String.format("Hierarchy with %d object(s) read in %.2f seconds", hierarchy.nObjects(), (endTime - startTime) / 1000.));
} catch (ClassNotFoundException e1) {
logger.warn("Class not found reading image data", e1);
} finally {
if (localeChanged)
Locale.setDefault(Category.FORMAT, locale);
}
return imageData;
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class GuiTools method promptToClearAllSelectedObjects.
/**
* Prompt user to select all currently-selected objects (except TMA core objects).
*
* @param imageData
* @return
*/
public static boolean promptToClearAllSelectedObjects(final ImageData<?> imageData) {
// Get all non-TMA core objects
PathObjectHierarchy hierarchy = imageData.getHierarchy();
Collection<PathObject> selectedRaw = hierarchy.getSelectionModel().getSelectedObjects();
List<PathObject> selected = selectedRaw.stream().filter(p -> !(p instanceof TMACoreObject)).collect(Collectors.toList());
if (selected.isEmpty()) {
if (selectedRaw.size() > selected.size())
Dialogs.showErrorMessage("Delete selected objects", "No valid objects selected! \n\nNote: Individual TMA cores cannot be deleted with this method.");
else
Dialogs.showErrorMessage("Delete selected objects", "No objects selected!");
return false;
}
int n = selected.size();
String message;
if (n == 1)
message = "Delete selected object?";
else
message = "Delete " + n + " selected objects?";
if (Dialogs.showYesNoDialog("Delete objects", message)) {
// Check for descendants
List<PathObject> children = new ArrayList<>();
for (PathObject temp : selected) {
children.addAll(temp.getChildObjects());
}
children.removeAll(selected);
boolean keepChildren = true;
if (!children.isEmpty()) {
Dialogs.DialogButton response = Dialogs.showYesNoCancelDialog("Delete objects", "Keep descendant objects?");
if (response == Dialogs.DialogButton.CANCEL)
return false;
keepChildren = response == Dialogs.DialogButton.YES;
}
hierarchy.removeObjects(selected, keepChildren);
hierarchy.getSelectionModel().clearSelection();
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Delete selected objects", "clearSelectedObjects(" + keepChildren + ");"));
if (keepChildren)
logger.info(selected.size() + " object(s) deleted");
else
logger.info(selected.size() + " object(s) deleted with descendants");
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Delete selected objects", "clearSelectedObjects();"));
logger.info(selected.size() + " object(s) deleted");
return true;
} else
return false;
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class QuPathViewer method setImageData.
/**
* Set the current image for this viewer.
* @param imageDataNew
*/
public void setImageData(ImageData<BufferedImage> imageDataNew) {
if (this.imageDataProperty.get() == imageDataNew)
return;
imageDataChanging.set(true);
// Remove listeners for previous hierarchy
ImageData<BufferedImage> imageDataOld = this.imageDataProperty.get();
if (imageDataOld != null) {
imageDataOld.getHierarchy().removePathObjectListener(this);
imageDataOld.getHierarchy().getSelectionModel().removePathObjectSelectionListener(this);
}
// Determine if the server has remained the same, so we can avoid shifting the viewer
boolean sameServer = false;
if (imageDataOld != null && imageDataNew != null && imageDataOld.getServerPath().equals(imageDataNew.getServerPath()))
sameServer = true;
this.imageDataProperty.set(imageDataNew);
ImageServer<BufferedImage> server = imageDataNew == null ? null : imageDataNew.getServer();
PathObjectHierarchy hierarchy = imageDataNew == null ? null : imageDataNew.getHierarchy();
long startTime = System.currentTimeMillis();
if (imageDisplay != null) {
boolean keepDisplay = PathPrefs.keepDisplaySettingsProperty().get();
// This is a bit of a hack to avoid calling internal methods for ImageDisplay
// See https://github.com/qupath/qupath/issues/601
boolean displaySet = false;
if (imageDataNew != null && keepDisplay) {
if (imageDisplay.getImageData() != null && serversCompatible(imageDataNew.getServer(), imageDisplay.getImageData().getServer())) {
imageDisplay.setImageData(imageDataNew, keepDisplay);
displaySet = true;
} else {
for (var viewer : QuPathGUI.getInstance().getViewers()) {
if (this == viewer || viewer.getImageData() == null)
continue;
var tempServer = viewer.getServer();
var currentServer = imageDataNew.getServer();
if (serversCompatible(tempServer, currentServer)) {
var json = viewer.getImageDisplay().toJSON(false);
imageDataNew.setProperty(ImageDisplay.class.getName(), json);
imageDisplay.setImageData(imageDataNew, false);
displaySet = true;
break;
}
}
}
}
if (!displaySet)
imageDisplay.setImageData(imageDataNew, keepDisplay);
// See https://github.com/qupath/qupath/issues/843
if (server != null && !server.isRGB()) {
var colors = imageDisplay.availableChannels().stream().filter(c -> c instanceof DirectServerChannelInfo).map(c -> c.getColor()).collect(Collectors.toList());
if (server.nChannels() == colors.size())
updateServerChannels(server, colors);
}
}
long endTime = System.currentTimeMillis();
logger.debug("Setting ImageData time: {} ms", endTime - startTime);
initializeForServer(server);
if (!sameServer) {
setDownsampleFactorImpl(getZoomToFitDownsampleFactor(), -1, -1);
centerImage();
}
fireImageDataChanged(imageDataOld, imageDataNew);
if (imageDataNew != null) {
// hierarchyPainter = new PathHierarchyPainter(hierarchy);
hierarchy.addPathObjectListener(this);
hierarchy.getSelectionModel().addPathObjectSelectionListener(this);
}
setSelectedObject(null);
// TODO: Consider shifting, fixing magnification, repainting etc.
if (isShowing())
repaint();
logger.info("Image data set to {}", imageDataNew);
}
Aggregations