use of qupath.lib.gui.measure.ObservableMeasurementTableData 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();
}
}
}
use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.
the class QPEx method saveMeasurements.
/**
* Save measurements for the specified image for objects of a fixed type.
* @param imageData the image data
* @param type the type of objects to measure
* @param path file path describing where to write the results
* @param includeColumns specific columns to include, or empty to indicate that all measurements should be exported
*/
public static void saveMeasurements(final ImageData<?> imageData, final Class<? extends PathObject> type, final String path, final String... includeColumns) {
File fileOutput = new File(resolvePath(path));
if (fileOutput.isDirectory()) {
String ext = ",".equals(PathPrefs.tableDelimiterProperty().get()) ? ".csv" : ".txt";
fileOutput = new File(fileOutput, ServerTools.getDisplayableImageName(imageData.getServer()) + " " + PathObjectTools.getSuitableName(type, true) + ext);
}
ObservableMeasurementTableData model = new ObservableMeasurementTableData();
model.setImageData(imageData, imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type));
try (PrintWriter writer = new PrintWriter(fileOutput, StandardCharsets.UTF_8)) {
Collection<String> excludeColumns;
if (includeColumns.length == 0) {
excludeColumns = Collections.emptyList();
} else {
excludeColumns = new LinkedHashSet<>(model.getAllNames());
excludeColumns.removeAll(Arrays.asList(includeColumns));
}
for (String row : SummaryMeasurementTableCommand.getTableModelStrings(model, PathPrefs.tableDelimiterProperty().get(), excludeColumns)) writer.println(row);
writer.close();
} catch (IOException e) {
logger.error("Error writing file to " + fileOutput, e);
}
}
use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.
the class TMASummaryViewer method getEntriesForTMAData.
private static List<TMAEntry> getEntriesForTMAData(final ImageData<BufferedImage> imageData) {
List<TMAEntry> entriesNew = new ArrayList<>();
if (imageData.getHierarchy().getTMAGrid() == null)
return entriesNew;
ObservableMeasurementTableData data = new ObservableMeasurementTableData();
data.setImageData(imageData, imageData.getHierarchy().getTMAGrid().getTMACoreList());
for (TMACoreObject core : imageData.getHierarchy().getTMAGrid().getTMACoreList()) {
entriesNew.add(TMAEntries.createTMAObjectEntry(imageData, data, core));
}
return entriesNew;
}
use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.
the class MeasurementExporter method exportMeasurements.
/**
* Exports the measurements of one or more entries in the project.
* This function first opens all the images in the project to store
* all the column names and values of the measurements.
* Then, it loops through the maps containing the values to write
* them to the given output stream.
* @param stream
*/
public void exportMeasurements(OutputStream stream) {
long startTime = System.currentTimeMillis();
Map<ProjectImageEntry<?>, String[]> imageCols = new HashMap<>();
Map<ProjectImageEntry<?>, Integer> nImageEntries = new HashMap<>();
List<String> allColumns = new ArrayList<>();
Multimap<String, String> valueMap = LinkedListMultimap.create();
String pattern = "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
for (ProjectImageEntry<?> entry : imageList) {
try {
ImageData<?> imageData = entry.readImageData();
ObservableMeasurementTableData model = new ObservableMeasurementTableData();
Collection<PathObject> pathObjects = imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type);
if (filter != null)
pathObjects = pathObjects.stream().filter(filter).collect(Collectors.toList());
model.setImageData(imageData, pathObjects);
List<String> data = SummaryMeasurementTableCommand.getTableModelStrings(model, separator, excludeColumns);
// Get header
String[] header;
String headerString = data.get(0);
if (headerString.chars().filter(e -> e == '"').count() > 1)
header = headerString.split(separator.equals("\t") ? "\\" + separator : separator + pattern, -1);
else
header = headerString.split(separator);
imageCols.put(entry, header);
nImageEntries.put(entry, data.size() - 1);
for (String col : header) {
if (!allColumns.contains(col) && !excludeColumns.contains(col))
allColumns.add(col);
}
// To keep the same column order, just delete non-relevant columns
if (!includeOnlyColumns.isEmpty())
allColumns.removeIf(n -> !includeOnlyColumns.contains(n));
for (int i = 1; i < data.size(); i++) {
String[] row;
String rowString = data.get(i);
// Check if some values in the row are escaped
if (rowString.chars().filter(e -> e == '"').count() > 1)
row = rowString.split(separator.equals("\t") ? "\\" + separator : separator + pattern, -1);
else
row = rowString.split(separator);
// Put value in map
for (int elem = 0; elem < row.length; elem++) {
if (allColumns.contains(header[elem]))
valueMap.put(header[elem], row[elem]);
}
}
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e);
}
}
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8))) {
writer.write(String.join(separator, allColumns));
writer.write(System.lineSeparator());
Iterator[] its = new Iterator[allColumns.size()];
for (int col = 0; col < allColumns.size(); col++) {
its[col] = valueMap.get(allColumns.get(col)).iterator();
}
for (ProjectImageEntry<?> entry : imageList) {
for (int nObject = 0; nObject < nImageEntries.get(entry); nObject++) {
for (int nCol = 0; nCol < allColumns.size(); nCol++) {
if (Arrays.stream(imageCols.get(entry)).anyMatch(allColumns.get(nCol)::equals)) {
String val = (String) its[nCol].next();
// NaN values -> blank
if (val.equals("NaN"))
val = "";
writer.write(val);
}
if (nCol < allColumns.size() - 1)
writer.write(separator);
}
writer.write(System.lineSeparator());
}
}
} catch (Exception e) {
logger.error("Error writing to file: " + e.getLocalizedMessage(), e);
}
long endTime = System.currentTimeMillis();
long timeMillis = endTime - startTime;
String time = null;
if (timeMillis > 1000 * 60)
time = String.format("Total processing time: %.2f minutes", timeMillis / (1000.0 * 60.0));
else if (timeMillis > 1000)
time = String.format("Total processing time: %.2f seconds", timeMillis / (1000.0));
else
time = String.format("Total processing time: %d milliseconds", timeMillis);
logger.info("Processed {} images", imageList.size());
logger.info(time);
}
use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.
the class ObservableMeasurementTableDataTest method test.
@SuppressWarnings("javadoc")
@Test
public void test() {
// See https://github.com/locationtech/jts/issues/571
for (int counter = 0; counter < 50; counter++) {
ImageData<BufferedImage> imageData = new ImageData<>(null);
PathClass tumorClass = PathClassFactory.getPathClass(StandardPathClasses.TUMOR);
PathClass stromaClass = PathClassFactory.getPathClass(StandardPathClasses.STROMA);
// PathClass otherClass = PathClassFactory.getDefaultPathClass(PathClasses.OTHER);
PathClass artefactClass = PathClassFactory.getPathClass("Artefact");
PathObjectHierarchy hierarchy = imageData.getHierarchy();
// Add a parent annotation
PathObject parent = PathObjects.createAnnotationObject(ROIs.createRectangleROI(500, 500, 1000, 1000, ImagePlane.getDefaultPlane()));
// Create 100 tumor detections
// ROI emptyROI = ROIs.createEmptyROI();
ROI smallROI = ROIs.createRectangleROI(500, 500, 1, 1, ImagePlane.getDefaultPlane());
for (int i = 0; i < 100; i++) {
if (i < 25)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getNegative(tumorClass)));
else if (i < 50)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getOnePlus(tumorClass)));
else if (i < 75)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getTwoPlus(tumorClass)));
else if (i < 100)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getThreePlus(tumorClass)));
}
// Create 100 stroma detections
for (int i = 0; i < 100; i++) {
if (i < 50)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getNegative(stromaClass)));
else if (i < 60)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getOnePlus(stromaClass)));
else if (i < 70)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getTwoPlus(stromaClass)));
else if (i < 100)
parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getThreePlus(stromaClass)));
}
// Create 50 artefact detections
for (int i = 0; i < 50; i++) {
parent.addPathObject(PathObjects.createDetectionObject(smallROI, artefactClass));
}
hierarchy.addPathObject(parent);
ObservableMeasurementTableData model = new ObservableMeasurementTableData();
model.setImageData(imageData, Collections.singletonList(parent));
// Check tumor counts
assertEquals(100, model.getNumericValue(parent, "Num Tumor (base)"), EPSILON);
assertEquals(25, model.getNumericValue(parent, "Num Tumor: Negative"), EPSILON);
assertEquals(25, model.getNumericValue(parent, "Num Tumor: 1+"), EPSILON);
assertEquals(25, model.getNumericValue(parent, "Num Tumor: 2+"), EPSILON);
assertEquals(25, model.getNumericValue(parent, "Num Tumor: 3+"), EPSILON);
assertTrue(Double.isNaN(model.getNumericValue(parent, "Num Tumor: 4+")));
// Check tumor H-score, Allred score & positive %
assertEquals(150, model.getNumericValue(parent, "Tumor: H-score"), EPSILON);
assertEquals(75, model.getNumericValue(parent, "Tumor: Positive %"), EPSILON);
assertEquals(2, model.getNumericValue(parent, "Tumor: Allred intensity"), EPSILON);
assertEquals(5, model.getNumericValue(parent, "Tumor: Allred proportion"), EPSILON);
assertEquals(7, model.getNumericValue(parent, "Tumor: Allred score"), EPSILON);
// Check tumor H-score unaffected when tumor detections added without intensity classification
for (int i = 0; i < 10; i++) parent.addPathObject(PathObjects.createDetectionObject(smallROI, tumorClass));
hierarchy.fireHierarchyChangedEvent(this);
model.refreshEntries();
// model.setImageData(imageData, Collections.singletonList(parent));
assertEquals(100, model.getNumericValue(parent, "Num Stroma (base)"), EPSILON);
assertEquals(50, model.getNumericValue(parent, "Num Stroma: Negative"), EPSILON);
assertEquals(150, model.getNumericValue(parent, "Tumor: H-score"), EPSILON);
assertEquals(75, model.getNumericValue(parent, "Tumor: Positive %"), EPSILON);
// Check stroma scores
assertEquals(100, model.getNumericValue(parent, "Num Stroma (base)"), EPSILON);
assertEquals(120, model.getNumericValue(parent, "Stroma: H-score"), EPSILON);
// Check complete scores
assertEquals(135, model.getNumericValue(parent, "Stroma + Tumor: H-score"), EPSILON);
// Add a new parent that completely contains the current object, and confirm complete scores agree
PathObject parentNew = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0, 0, 2000, 2000, ImagePlane.getDefaultPlane()));
hierarchy.addPathObject(parentNew);
model.refreshEntries();
assertEquals(135, model.getNumericValue(parent, "Stroma + Tumor: H-score"), EPSILON);
assertEquals(135, model.getNumericValue(parentNew, "Stroma + Tumor: H-score"), EPSILON);
// Create a new object and demonstrate Allred dependence on a single cell
PathObject parentAllred = PathObjects.createAnnotationObject(ROIs.createRectangleROI(4000, 4000, 1000, 1000, ImagePlane.getDefaultPlane()));
ROI newROI = ROIs.createEllipseROI(4500, 4500, 10, 10, ImagePlane.getDefaultPlane());
for (int i = 0; i < 100; i++) parentAllred.addPathObject(PathObjects.createDetectionObject(newROI, PathClassFactory.getNegative(tumorClass)));
hierarchy.addPathObject(parentAllred);
model.refreshEntries();
assertEquals(0, model.getNumericValue(parentAllred, "Tumor: Allred score"), EPSILON);
parentAllred.addPathObject(PathObjects.createDetectionObject(newROI, PathClassFactory.getThreePlus(tumorClass)));
hierarchy.fireHierarchyChangedEvent(parentAllred);
model.refreshEntries();
assertEquals(4, model.getNumericValue(parentAllred, "Tumor: Allred score"), EPSILON);
}
}
Aggregations