use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class SubcellularDetection method processObject.
/**
* Initial version of subcellular detection processing.
*
* @param pathObject
* @param params
* @param imageWrapper
* @return
* @throws InterruptedException
* @throws IOException
*/
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageWrapper imageWrapper) throws InterruptedException, IOException {
// Get the base classification for the object as it currently stands
PathClass baseClass = PathClassTools.getNonIntensityAncestorClass(pathObject.getPathClass());
// Variable to hold estimated spot count
double estimatedSpots;
// We assume that after this processing, any previous sub-cellular objects should be removed
pathObject.clearPathObjects();
// Ensure we have no existing subcellular detection measurements - if we do, remove them
String[] existingMeasurements = pathObject.getMeasurementList().getMeasurementNames().stream().filter(n -> n.startsWith("Subcellular:")).toArray(n -> new String[n]);
if (existingMeasurements.length > 0) {
pathObject.getMeasurementList().removeMeasurements(existingMeasurements);
pathObject.getMeasurementList().close();
}
// // If we're part of a TMA core, request the whole core...
// if (pathObject.getParent() instanceof TMACoreObject && pathObject.getParent().hasROI()) {
// regionStore.getImage(server, RegionRequest.createInstance(server.getPath(), 1, pathObject.getParent().getROI()), 25, true);
// }
ROI pathROI = pathObject.getROI();
if (pathROI == null || pathROI.isEmpty())
return false;
// double downsample = 0.5;
double downsample = 1;
// Determine spot size
ImageServer<BufferedImage> server = imageWrapper.getServer();
PixelCalibration cal = server.getPixelCalibration();
double minSpotArea, maxSpotArea, singleSpotArea;
double pixelWidth, pixelHeight;
if (cal.hasPixelSizeMicrons()) {
double spotSizeMicrons = params.getDoubleParameterValue("spotSizeMicrons");
double minSpotSizeMicrons = params.getDoubleParameterValue("minSpotSizeMicrons");
double maxSpotSizeMicrons = params.getDoubleParameterValue("maxSpotSizeMicrons");
pixelWidth = cal.getPixelWidthMicrons() * downsample;
pixelHeight = cal.getPixelHeightMicrons() * downsample;
singleSpotArea = spotSizeMicrons / (pixelWidth * pixelHeight);
minSpotArea = minSpotSizeMicrons / (pixelWidth * pixelHeight);
maxSpotArea = maxSpotSizeMicrons / (pixelWidth * pixelHeight);
} else {
singleSpotArea = params.getDoubleParameterValue("spotSizePixels");
minSpotArea = params.getDoubleParameterValue("minSpotSizePixels");
maxSpotArea = params.getDoubleParameterValue("maxSpotSizePixels");
pixelWidth = downsample;
pixelHeight = downsample;
}
boolean includeClusters = Boolean.TRUE.equals(params.getBooleanParameterValue("includeClusters"));
boolean doSmoothing = Boolean.TRUE.equals(params.getBooleanParameterValue("doSmoothing"));
boolean splitByIntensity = Boolean.TRUE.equals(params.getBooleanParameterValue("splitByIntensity"));
boolean splitByShape = Boolean.TRUE.equals(params.getBooleanParameterValue("splitByShape"));
// Get region to request - give a pixel as border
int xStart = (int) Math.max(0, pathROI.getBoundsX() - 1);
int yStart = (int) Math.max(0, pathROI.getBoundsY() - 1);
int width = (int) Math.min(server.getWidth() - 1, pathROI.getBoundsX() + pathROI.getBoundsWidth() + 1.5) - xStart;
int height = (int) Math.min(server.getHeight() - 1, pathROI.getBoundsY() + pathROI.getBoundsHeight() + 1.5) - yStart;
if (width <= 0 || height <= 0) {
logger.error("Negative ROI size for {}", pathROI);
pathObject.setPathClass(baseClass);
return false;
}
int z = pathROI.getZ();
int t = pathROI.getT();
// Don't associate with channel
int c = -1;
RegionRequest region = RegionRequest.createInstance(server.getPath(), 1.0, xStart, yStart, width, height, z, t);
// Mask to indicate pixels within the cell
byte[] cellMask = null;
for (String channelName : imageWrapper.getChannelNames(true, true)) {
double detectionThreshold = params.getDoubleParameterValue("detection[" + channelName + "]");
if (Double.isNaN(detectionThreshold) || detectionThreshold < 0)
continue;
// // TODO: Consider whether to use channel numbers for non-brightfield images
// if (!imageWrapper.imageData.isBrightfield())
// c++;
SimpleImage img = imageWrapper.getRegion(region, channelName);
// Get an ImageJ-friendly calibration for ROI conversion
Calibration calIJ = new Calibration();
calIJ.xOrigin = -xStart / downsample;
calIJ.yOrigin = -yStart / downsample;
// Create a cell mask
if (cellMask == null) {
BufferedImage imgMask = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
Graphics2D g2d = imgMask.createGraphics();
if (downsample != 1)
g2d.scale(1.0 / downsample, 1.0 / downsample);
g2d.translate(-xStart, -yStart);
Shape shape = RoiTools.getShape(pathROI);
g2d.setColor(Color.WHITE);
g2d.fill(shape);
g2d.dispose();
cellMask = (byte[]) ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData(0);
}
// Get a buffer containing the image pixels
int w = img.getWidth();
int h = img.getHeight();
// Identify (& try to separate) spots
// Mask out non-cell areas as we go
FloatProcessor fpDetection = new FloatProcessor(w, h);
if (doSmoothing) {
for (int i = 0; i < w * h; i++) fpDetection.setf(i, img.getValue(i % w, i / w));
fpDetection.smooth();
for (int i = 0; i < w * h; i++) {
if (cellMask[i] == (byte) 0)
fpDetection.setf(i, 0f);
}
} else {
for (int i = 0; i < w * h; i++) {
if (cellMask[i] == (byte) 0)
fpDetection.setf(i, 0f);
else
fpDetection.setf(i, img.getValue(i % w, i / w));
}
}
ByteProcessor bpSpots;
if (splitByIntensity)
bpSpots = new MaximumFinder().findMaxima(fpDetection, detectionThreshold / 10.0, detectionThreshold, MaximumFinder.SEGMENTED, false, false);
else
bpSpots = SimpleThresholding.thresholdAboveEquals(fpDetection, (float) detectionThreshold);
if (splitByShape) {
new EDM().toWatershed(bpSpots);
}
// Loop through spot ROIs & make a decision
bpSpots.setThreshold(1, ImageProcessor.NO_THRESHOLD, ImageProcessor.NO_LUT_UPDATE);
List<PolygonRoi> possibleSpotRois = RoiLabeling.getFilledPolygonROIs(bpSpots, Wand.FOUR_CONNECTED);
List<PathObject> spotObjects = new ArrayList<>();
List<PathObject> clusterObjects = new ArrayList<>();
estimatedSpots = 0;
for (PolygonRoi spotRoi : possibleSpotRois) {
fpDetection.setRoi(spotRoi);
ImageStatistics stats = fpDetection.getStatistics();
// In v0.2
// ImagePlane plane = ImagePlane.getPlaneWithChannel(spotRoi.getCPosition(), spotRoi.getZPosition(), spotRoi.getTPosition());
// In v0.3
ImagePlane plane = ImagePlane.getPlaneWithChannel(c, z, t);
PathObject spotOrCluster = null;
if (stats.pixelCount >= minSpotArea && stats.pixelCount <= maxSpotArea) {
ROI roi = IJTools.convertToROI(spotRoi, calIJ, downsample, plane);
// cluster = new SubcellularObject(roi, 1);
spotOrCluster = createSubcellularObject(roi, 1);
estimatedSpots += 1;
} else if (includeClusters && stats.pixelCount >= minSpotArea) {
// Add a cluster
ROI roi = IJTools.convertToROI(spotRoi, calIJ, downsample, plane);
double nSpots = stats.pixelCount / singleSpotArea;
estimatedSpots += nSpots;
// cluster = new SubcellularObject(roi, nSpots);
spotOrCluster = createSubcellularObject(roi, nSpots);
}
if (spotOrCluster != null) {
boolean isCluster = spotOrCluster.getMeasurementList().getMeasurementValue("Num spots") > 1;
int rgb = imageWrapper.getChannelColor(channelName);
rgb = isCluster ? ColorTools.makeScaledRGB(rgb, 0.5) : ColorTools.makeScaledRGB(rgb, 1.5);
PathClass pathClass = PathClassFactory.getDerivedPathClass(spotOrCluster.getPathClass(), channelName + " object", rgb);
spotOrCluster.setPathClass(pathClass);
spotOrCluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Area", stats.pixelCount * pixelWidth * pixelHeight);
spotOrCluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Mean channel intensity", stats.mean);
// cluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Max channel intensity", stats.max);
spotOrCluster.getMeasurementList().close();
if (isCluster)
clusterObjects.add(spotOrCluster);
else
spotObjects.add(spotOrCluster);
}
}
// Add measurements
MeasurementList measurementList = pathObject.getMeasurementList();
measurementList.putMeasurement("Subcellular: " + channelName + ": Num spots estimated", estimatedSpots);
measurementList.putMeasurement("Subcellular: " + channelName + ": Num single spots", spotObjects.size());
measurementList.putMeasurement("Subcellular: " + channelName + ": Num clusters", clusterObjects.size());
// Add spots
pathObject.addPathObjects(spotObjects);
pathObject.addPathObjects(clusterObjects);
}
return true;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class QuPath_Send_Overlay_to_QuPath method createObjectsFromROIs.
/**
* Turn an array of ImageJ ROIs into a list of QuPath PathObjects, optionally adding measurements as well.
*
* @param imp
* @param rois
* @param downsample
* @param asDetection
* @param includeMeasurements
* @param plane
* @return
* @since v0.4.0
*/
public static List<PathObject> createObjectsFromROIs(final ImagePlus imp, final Collection<? extends Roi> rois, final double downsample, final boolean asDetection, final boolean includeMeasurements, final ImagePlane plane) {
List<PathObject> pathObjects = new ArrayList<>();
ResultsTable rt = new ResultsTable();
Analyzer analyzer = imp == null ? null : new Analyzer(imp, Analyzer.getMeasurements(), rt);
String[] headings = null;
Calibration cal = imp == null ? null : imp.getCalibration();
var xOrigin = cal == null ? 0 : cal.xOrigin;
var yOrigin = cal == null ? 0 : cal.yOrigin;
for (Roi roi : rois) {
PathObject pathObject;
if (asDetection && !(roi instanceof PointRoi))
pathObject = IJTools.convertToDetection(roi, xOrigin, yOrigin, downsample, plane);
else
pathObject = IJTools.convertToAnnotation(roi, xOrigin, yOrigin, downsample, plane);
if (pathObject == null)
IJ.log("Sorry, I couldn't convert " + roi + " to a valid QuPath object");
else {
// Make measurements
if (includeMeasurements && imp != null) {
ImageProcessor ip = imp.getProcessor();
ip.setRoi(roi);
ImageStatistics stats = ImageStatistics.getStatistics(ip, Analyzer.getMeasurements(), imp.getCalibration());
analyzer.saveResults(stats, roi);
// Get measurements from table and append
if (headings == null)
headings = rt.getHeadings();
int row = rt.getCounter() - 1;
MeasurementList ml = pathObject.getMeasurementList();
for (String h : headings) {
if ("Label".equals(h))
continue;
ml.putMeasurement(h, rt.getValue(h, row));
}
ml.close();
}
pathObjects.add(pathObject);
}
}
return pathObjects;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class PixelClassificationMeasurementManager method updateMeasurements.
private synchronized MeasurementList updateMeasurements(Map<Integer, PathClass> classificationLabels, long[] counts, double pixelArea, String pixelAreaUnits) {
long total = counts == null ? 0L : GeneralTools.sum(counts);
Collection<PathClass> pathClasses = new LinkedHashSet<>(classificationLabels.values());
boolean addNames = measurementNames == null;
List<String> tempList = null;
int nMeasurements = pathClasses.size() * 2;
if (!isMulticlass)
nMeasurements += 2;
if (addNames) {
tempList = new ArrayList<>();
measurementNames = Collections.unmodifiableList(tempList);
} else
nMeasurements = measurementNames.size();
MeasurementList measurementList = MeasurementListFactory.createMeasurementList(nMeasurements, MeasurementListType.DOUBLE);
Set<PathClass> ignored = pathClasses.stream().filter(p -> p == null || PathClassTools.isIgnoredClass(p)).collect(Collectors.toSet());
// Calculate totals for all non-ignored classes
Map<PathClass, Long> pathClassTotals = new LinkedHashMap<>();
long totalWithoutIgnored = 0L;
if (counts != null) {
for (var entry : classificationLabels.entrySet()) {
PathClass pathClass = entry.getValue();
// Skip background channels
if (pathClass == null || ignored.contains(pathClass))
continue;
int c = entry.getKey();
long temp = counts == null || c >= counts.length ? 0L : counts[c];
totalWithoutIgnored += temp;
pathClassTotals.put(pathClass, pathClassTotals.getOrDefault(pathClass, 0L) + temp);
}
} else {
for (var pathClass : pathClasses) if (pathClass != null && !ignored.contains(pathClass))
pathClassTotals.put(pathClass, 0L);
}
// Add measurements for classes
for (var entry : pathClassTotals.entrySet()) {
var pathClass = entry.getKey();
String name = pathClass.toString();
String namePercentage = name + " %";
String nameArea = name + " area " + pixelAreaUnits;
if (tempList != null) {
if (pathClassTotals.size() > 1)
tempList.add(namePercentage);
tempList.add(nameArea);
}
if (counts != null) {
long count = entry.getValue();
if (pathClassTotals.size() > 1)
measurementList.putMeasurement(namePercentage, (double) count / totalWithoutIgnored * 100.0);
if (!Double.isNaN(pixelArea)) {
measurementList.putMeasurement(nameArea, count * pixelArea);
}
}
}
// Add total area (useful as a check)
String nameArea = "Total annotated area " + pixelAreaUnits;
String nameAreaWithoutIgnored = "Total quantified area " + pixelAreaUnits;
if (counts != null && !Double.isNaN(pixelArea)) {
if (tempList != null) {
tempList.add(nameArea);
tempList.add(nameAreaWithoutIgnored);
}
measurementList.putMeasurement(nameArea, totalWithoutIgnored * pixelArea);
measurementList.putMeasurement(nameAreaWithoutIgnored, total * pixelArea);
}
measurementList.close();
return measurementList;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class OpenCvClassifier method updateClassifier.
@Override
public boolean updateClassifier(final Map<PathClass, List<PathObject>> map, final List<String> measurements, Normalization normalization) {
// There is a chance we don't need to retrain... to find out, cache the most important current variables
boolean maybeSameClassifier = isValid() && this.normalization == normalization && !classifierOptionsChanged() && this.measurements.equals(measurements) && pathClasses.size() == map.size() && map.keySet().containsAll(pathClasses);
float[] arrayTrainingPrevious = arrayTraining;
int[] arrayResponsesPrevious = arrayResponses;
pathClasses = new ArrayList<>(map.keySet());
Collections.sort(pathClasses);
int n = 0;
for (Map.Entry<PathClass, List<PathObject>> entry : map.entrySet()) {
n += entry.getValue().size();
}
// Compute running statistics for normalization
HashMap<String, RunningStatistics> statsMap = new LinkedHashMap<>();
for (String m : measurements) statsMap.put(m, new RunningStatistics());
this.measurements.clear();
this.measurements.addAll(measurements);
int nMeasurements = measurements.size();
arrayTraining = new float[n * nMeasurements];
arrayResponses = new int[n];
int row = 0;
int nnan = 0;
for (PathClass pathClass : pathClasses) {
List<PathObject> list = map.get(pathClass);
int classIndex = pathClasses.indexOf(pathClass);
for (int i = 0; i < list.size(); i++) {
MeasurementList measurementList = list.get(i).getMeasurementList();
int col = 0;
for (String m : measurements) {
double value = measurementList.getMeasurementValue(m);
if (Double.isNaN(value))
nnan++;
else
statsMap.get(m).addValue(value);
arrayTraining[row * nMeasurements + col] = (float) value;
col++;
}
arrayResponses[row] = classIndex;
row++;
}
}
// Normalise, if required
if (normalization != null && normalization != Normalization.NONE) {
logger.debug("Training classifier with normalization: {}", normalization);
int numMeasurements = measurements.size();
normOffset = new double[numMeasurements];
normScale = new double[numMeasurements];
for (int i = 0; i < numMeasurements; i++) {
RunningStatistics stats = statsMap.get(measurements.get(i));
if (normalization == Normalization.MEAN_VARIANCE) {
normOffset[i] = -stats.getMean();
if (stats.getStdDev() > 0)
normScale[i] = 1.0 / stats.getStdDev();
} else if (normalization == Normalization.MIN_MAX) {
normOffset[i] = -stats.getMin();
if (stats.getRange() > 0)
normScale[i] = 1.0 / (stats.getMax() - stats.getMin());
else
normScale[i] = 1.0;
}
}
// Apply normalisation
for (int i = 0; i < arrayTraining.length; i++) {
int k = i % numMeasurements;
arrayTraining[i] = (float) ((arrayTraining[i] + normOffset[k]) * normScale[k]);
}
this.normalization = normalization;
} else {
logger.debug("Training classifier without normalization");
normScale = null;
normOffset = null;
this.normalization = Normalization.NONE;
}
// Record that we have NaNs
if (nnan > 0)
logger.debug("Number of NaNs in training set: " + nnan);
// Having got this far, check to see whether we really do need to retrain
if (maybeSameClassifier) {
if (Arrays.equals(arrayTrainingPrevious, arrayTraining) && Arrays.equals(arrayResponsesPrevious, arrayResponses)) {
logger.info("Classifier already trained with the same samples - existing classifier will be used");
return false;
}
}
createAndTrainClassifier();
timestamp = System.currentTimeMillis();
this.measurements = new ArrayList<>(measurements);
return true;
}
use of qupath.lib.measurements.MeasurementList in project qupath by qupath.
the class OpenCvClassifier method classifyPathObjects.
@Override
public int classifyPathObjects(Collection<PathObject> pathObjects) {
int counter = 0;
float[] array = new float[measurements.size()];
Mat samples = new Mat(1, array.length, CV_32FC1);
FloatBuffer bufferSamples = samples.createBuffer();
Mat results = new Mat();
for (PathObject pathObject : pathObjects) {
MeasurementList measurementList = pathObject.getMeasurementList();
int idx = 0;
for (String m : measurements) {
double value = measurementList.getMeasurementValue(m);
if (normScale != null && normOffset != null)
value = (value + normOffset[idx]) * normScale[idx];
array[idx] = (float) value;
idx++;
}
// FloatIndexer indexerSamples = samples.createIndexer();
// indexerSamples.put(0L, 0L, array);
bufferSamples.clear();
bufferSamples.put(array);
try {
setPredictedClass(classifier, pathClasses, samples, results, pathObject);
// float prediction = classifier.predict(samples);
//
// // float prediction2 = classifier.predict(samples, results, StatModel.RAW_OUTPUT);
// float prediction2 = classifier.predict(samples, results, StatModel.RAW_OUTPUT);
//
// pathObject.setPathClass(pathClasses.get((int)prediction), prediction2);
} catch (Exception e) {
pathObject.setPathClass(null);
logger.trace("Error with samples: {}", samples);
// e.printStackTrace();
}
// TODO: See if this can be created outside the loop & reused... appears to work, but docs say release should be called
// indexerSamples.release();
// }
counter++;
}
samples.close();
results.close();
return counter;
}
Aggregations