use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class DensityMapDialog method buildAllObjectsPane.
private Pane buildAllObjectsPane(ObservableDensityMapBuilder params) {
ComboBox<DensityMapObjects> comboObjectType = new ComboBox<>();
comboObjectType.getItems().setAll(DensityMapObjects.values());
comboObjectType.getSelectionModel().select(DensityMapObjects.DETECTIONS);
params.allObjectTypes.bind(comboObjectType.getSelectionModel().selectedItemProperty());
ComboBox<PathClass> comboAllObjects = new ComboBox<>(createObservablePathClassList(DensityMapUI.ANY_CLASS));
comboAllObjects.setButtonCell(GuiTools.createCustomListCell(p -> classificationText(p)));
comboAllObjects.setCellFactory(c -> GuiTools.createCustomListCell(p -> classificationText(p)));
params.allObjectClass.bind(comboAllObjects.getSelectionModel().selectedItemProperty());
comboAllObjects.getSelectionModel().selectFirst();
ComboBox<PathClass> comboPrimary = new ComboBox<>(createObservablePathClassList(DensityMapUI.ANY_CLASS, DensityMapUI.ANY_POSITIVE_CLASS));
comboPrimary.setButtonCell(GuiTools.createCustomListCell(p -> classificationText(p)));
comboPrimary.setCellFactory(c -> GuiTools.createCustomListCell(p -> classificationText(p)));
params.densityObjectClass.bind(comboPrimary.getSelectionModel().selectedItemProperty());
comboPrimary.getSelectionModel().selectFirst();
ComboBox<DensityMapType> comboDensityType = new ComboBox<>();
comboDensityType.getItems().setAll(DensityMapType.values());
comboDensityType.getSelectionModel().select(DensityMapType.SUM);
params.densityType.bind(comboDensityType.getSelectionModel().selectedItemProperty());
var pane = createGridPane();
int row = 0;
var labelObjects = createTitleLabel("Choose all objects to include");
PaneTools.addGridRow(pane, row++, 0, null, labelObjects, labelObjects, labelObjects);
PaneTools.addGridRow(pane, row++, 0, "Select objects used to generate the density map.\n" + "Use 'All detections' to include all detection objects (including cells and tiles).\n" + "Use 'All cells' to include cell objects only.\n" + "Use 'Point annotations' to use annotated points rather than detections.", new Label("Object type"), comboObjectType, comboObjectType);
PaneTools.addGridRow(pane, row++, 0, "Select object classifications to include.\n" + "Use this to filter out detections that should not contribute to the density map at all.\n" + "For example, this can be used to selectively consider tumor cells and ignore everything else.\n" + "If used in combination with 'Secondary class' and 'Density type: Objects %', the 'Secondary class' defines the numerator and the 'Main class' defines the denominator.", new Label("Main class"), comboAllObjects, comboAllObjects);
var labelDensities = createTitleLabel("Define density map");
PaneTools.addGridRow(pane, row++, 0, null, labelDensities);
PaneTools.addGridRow(pane, row++, 0, "Calculate the density of objects containing a specified classification.\n" + "If used in combination with 'Main class' and 'Density type: Objects %', the 'Secondary class' defines the numerator and the 'Main class' defines the denominator.\n" + "For example, choose 'Main class: Tumor', 'Secondary class: Positive' and 'Density type: Objects %' to define density as the proportion of tumor cells that are positive.", new Label("Secondary class"), comboPrimary, comboPrimary);
PaneTools.addGridRow(pane, row++, 0, "Select method of normalizing densities.\n" + "Choose whether to show raw counts, or normalize densities by area or the number of objects locally.\n" + "This can be used to distinguish between the total number of objects in an area with a given classification, " + "and the proportion of objects within the area with that classification.\n" + "Gaussian weighting gives a smoother result, but it can be harder to interpret.", new Label("Density type"), comboDensityType, comboDensityType);
var sliderRadius = new Slider(0, 1000, params.radius.get());
sliderRadius.valueProperty().bindBidirectional(params.radius);
initializeSliderSnapping(sliderRadius, 50, 1, 0.1);
var tfRadius = createTextField();
boolean expandSliderLimits = true;
GuiTools.bindSliderAndTextField(sliderRadius, tfRadius, expandSliderLimits, 2);
GuiTools.installRangePrompt(sliderRadius);
PaneTools.addGridRow(pane, row++, 0, "Select smoothing radius used to calculate densities.\n" + "This is defined in calibrated pixel units (e.g. " + GeneralTools.micrometerSymbol() + " if available).", new Label("Density radius"), sliderRadius, tfRadius);
PaneTools.setToExpandGridPaneWidth(comboObjectType, comboPrimary, comboAllObjects, comboDensityType, sliderRadius);
return pane;
}
use of qupath.lib.objects.classes.PathClass 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.objects.classes.PathClass in project qupath by qupath.
the class TileExporter method writeTiles.
/**
* Export the image tiles to the specified directory.
* @param dirOutput full path to the export directory
* @throws IOException if an error occurs during export
*/
public void writeTiles(String dirOutput) throws IOException {
if (!new File(dirOutput).isDirectory())
throw new IOException("Output directory " + dirOutput + " does not exist!");
// Make sure we have any required subdirectories
if (imageSubDir != null)
new File(dirOutput, imageSubDir).mkdirs();
if (labelSubDir != null)
new File(dirOutput, labelSubDir).mkdirs();
if (serverLabeled != null) {
if (extLabeled == null)
extLabeled = serverLabeled.getMetadata().getChannelType() == ChannelType.CLASSIFICATION ? ".png" : ".tif";
}
// Work out which RegionRequests to use
Collection<RegionRequestWrapper> requests = createRequests();
if (requests.isEmpty()) {
logger.warn("No regions to export!");
return;
}
if (requests.size() > 1)
logger.info("Exporting {} tiles", requests.size());
var pool = Executors.newFixedThreadPool(ThreadTools.getParallelism(), ThreadTools.createThreadFactory("tile-exporter", true));
String imageName = GeneralTools.stripInvalidFilenameChars(GeneralTools.getNameWithoutExtension(server.getMetadata().getName()));
// Create something we can input as the image path for export
String imagePathName = null;
var uris = server.getURIs();
if (uris.isEmpty())
imagePathName = imageName;
else if (uris.size() == 1)
imagePathName = uris.iterator().next().toString();
else
imagePathName = "[" + uris.stream().map(u -> u.toString()).collect(Collectors.joining("|")) + "]";
// // If we have pixel calibration information, use it in the export
// PixelCalibration pixelSize = server.getPixelCalibration();
// if (pixelSize.equals(PixelCalibration.getDefaultInstance()))
// pixelSize = null;
// else
// pixelSize = pixelSize.createScaledInstance(downsample, downsample);
int tileWidth = this.tileWidth;
int tileHeight = this.tileHeight;
// int tileWidth = includePartialTiles || (parentObjects != null && useParentRoiBounds) ? -1 : this.tileWidth;
// int tileHeight = includePartialTiles || (parentObjects != null && useParentRoiBounds) ? -1 : this.tileHeight;
// Maintain a record of what we exported
List<TileExportEntry> exportImages = new ArrayList<>();
for (var r : requests) {
boolean ensureSize = !r.partialTile;
String baseName = String.format("%s [%s]", imageName, getRegionString(r.request));
String exportImageName = baseName + ext;
if (imageSubDir != null)
exportImageName = Paths.get(imageSubDir, exportImageName).toString();
String pathImageOutput = Paths.get(dirOutput, exportImageName).toAbsolutePath().toString();
ExportTask taskImage = new ExportTask(server, r.request, pathImageOutput, tileWidth, tileHeight, ensureSize);
String exportLabelName = null;
ExportTask taskLabels = null;
if (serverLabeled != null) {
String labelName = baseName;
if ((labelSubDir == null || labelSubDir.equals(imageSubDir)) && labelId == null && ext.equals(extLabeled)) {
labelName = baseName + "-labelled";
} else if (labelId != null)
labelName = baseName + labelId;
exportLabelName = labelName + extLabeled;
if (labelSubDir != null)
exportLabelName = Paths.get(labelSubDir, exportLabelName).toString();
String pathLabelsOutput = Paths.get(dirOutput, exportLabelName).toAbsolutePath().toString();
taskLabels = new ExportTask(serverLabeled, r.request.updatePath(serverLabeled.getPath()), pathLabelsOutput, tileWidth, tileHeight, ensureSize);
}
exportImages.add(new TileExportEntry(r.request.updatePath(imagePathName), // pixelSize,
exportImageName, exportLabelName));
if (taskImage != null)
pool.submit(taskImage);
if (taskLabels != null) {
pool.submit(taskLabels);
}
}
// Write JSON, if we need to
if (exportJson) {
var gson = GsonTools.getInstance(true).newBuilder().disableHtmlEscaping().create();
var data = new TileExportData(dirOutput, exportImages);
if (serverLabeled instanceof LabeledImageServer) {
var labels = ((LabeledImageServer) serverLabeled).getLabels();
var boundaryLabels = ((LabeledImageServer) serverLabeled).getBoundaryLabels();
List<TileExportLabel> labelList = new ArrayList<>();
Set<PathClass> existingLabels = new HashSet<>();
for (var entry : labels.entrySet()) {
var pathClass = entry.getKey();
var label = new TileExportLabel(pathClass.toString(), entry.getValue(), boundaryLabels.getOrDefault(pathClass, null));
labelList.add(label);
}
for (var entry : boundaryLabels.entrySet()) {
var pathClass = entry.getKey();
if (!existingLabels.contains(pathClass)) {
var label = new TileExportLabel(pathClass.toString(), null, boundaryLabels.getOrDefault(pathClass, null));
labelList.add(label);
}
}
data.labels = labelList;
}
var pathJson = Paths.get(dirOutput, imageName + "-tiles.json");
if (Files.exists(pathJson)) {
logger.warn("Overwriting existing JSON file {}", pathJson);
}
try (var writer = Files.newBufferedWriter(pathJson, StandardCharsets.UTF_8)) {
gson.toJson(data, writer);
}
}
pool.shutdown();
try {
pool.awaitTermination(24, TimeUnit.HOURS);
} catch (InterruptedException e) {
pool.shutdownNow();
logger.error("Tile export interrupted: {}", e);
logger.error("", e);
}
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class LabeledImageServer method createIndexedColorTile.
private BufferedImage createIndexedColorTile(TileRequest tileRequest, Collection<PathObject> pathObjects) {
RegionRequest request = tileRequest.getRegionRequest();
double downsampleFactor = request.getDownsample();
// Fill in the background color
int width = tileRequest.getTileWidth();
int height = tileRequest.getTileHeight();
boolean doRGB = maxLabel > 255;
// If we have > 255 labels, we can only use Graphics2D if we pretend to have an RGB image
BufferedImage img = doRGB ? new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) : new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
WritableRaster raster = img.getRaster();
Graphics2D g2d = img.createGraphics();
int bgLabel = params.labels.get(params.unannotatedClass);
Color color = getColorForLabel(bgLabel, doRGB);
g2d.setColor(color);
g2d.fillRect(0, 0, width, height);
if (!pathObjects.isEmpty()) {
g2d.setClip(0, 0, width, height);
double scale = 1.0 / downsampleFactor;
g2d.scale(scale, scale);
g2d.translate(-request.getX(), -request.getY());
BasicStroke stroke = new BasicStroke((float) (params.lineThickness * tileRequest.getDownsample()));
g2d.setStroke(stroke);
// We want to order consistently to avoid confusing overlaps
for (var entry : params.labels.entrySet()) {
var pathClass = getPathClass(entry.getKey());
int c = entry.getValue();
color = getColorForLabel(c, doRGB);
List<PathObject> toDraw;
if (instanceClassMapInverse != null) {
var temp = instanceClassMapInverse.get(c);
if (temp == null)
continue;
toDraw = Collections.singletonList(temp);
} else
toDraw = pathObjects.stream().filter(p -> getPathClass(p) == pathClass).collect(Collectors.toList());
for (var pathObject : toDraw) {
var roi = params.roiFunction.apply(pathObject);
g2d.setColor(color);
if (roi.isArea())
g2d.fill(roi.getShape());
else if (roi.isLine())
g2d.draw(roi.getShape());
else if (roi.isPoint()) {
for (var p : roi.getAllPoints()) {
int x = (int) ((p.getX() - request.getX()) / downsampleFactor);
int y = (int) ((p.getY() - request.getY()) / downsampleFactor);
if (x >= 0 && x < width && y >= 0 && y < height) {
if (doRGB)
img.setRGB(x, y, color.getRGB());
else
raster.setSample(x, y, 0, c);
}
}
}
}
}
for (var entry : params.boundaryLabels.entrySet()) {
int c = entry.getValue();
color = getColorForLabel(c, doRGB);
for (var pathObject : pathObjects) {
// if (pathObject.getPathClass() == pathClass) {
var pathClass = getPathClass(pathObject);
if (params.labels.containsKey(pathClass)) {
// && !PathClassTools.isIgnoredClass(pathObject.getPathClass())) {
var roi = params.roiFunction.apply(pathObject);
if (roi.isArea()) {
g2d.setColor(color);
g2d.draw(roi.getShape());
}
}
}
}
}
g2d.dispose();
if (doRGB) {
// Resort to RGB if we have to
if (maxLabel >= 65536)
return img;
// Convert to unsigned short if we can
var shortRaster = WritableRaster.createBandedRaster(DataBuffer.TYPE_USHORT, width, height, 1, null);
int[] samples = img.getRGB(0, 0, width, height, null, 0, width);
shortRaster.setSamples(0, 0, width, height, 0, samples);
// System.err.println("Before: " + Arrays.stream(samples).summaryStatistics());
raster = shortRaster;
// samples = raster.getSamples(0, 0, width, height, 0, (int[])null);
// System.err.println("After: " + Arrays.stream(samples).summaryStatistics());
}
return new BufferedImage((IndexColorModel) colorModel, raster, false, null);
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class DefaultPathObjectComparator method compare.
@Override
public int compare(PathObject o1, PathObject o2) {
Objects.nonNull(o1);
Objects.nonNull(o2);
// Quick check...
if (o1 == o2)
return 0;
// // Handle nulls
// if (o1 == null) {
// if (o2 == null)
// return 0;
// return 1;
// } else if (o2 == null)
// return -1;
// Handle class order
int temp = -Boolean.compare(o1.isRootObject(), o2.isRootObject());
if (temp != 0)
return temp;
temp = -Boolean.compare(o1.isTMACore(), o2.isTMACore());
if (temp != 0)
return temp;
temp = -Boolean.compare(o1.isAnnotation(), o2.isAnnotation());
if (temp != 0)
return temp;
temp = -Boolean.compare(o1.isDetection(), o2.isDetection());
if (temp != 0)
return temp;
// Try object class again
temp = o1.getClass().getName().compareTo(o2.getClass().getName());
if (temp != 0)
return temp;
// Handle ROI location
temp = DefaultROIComparator.getInstance().compare(o1.getROI(), o2.getROI());
if (temp != 0)
return temp;
// Try classifications
PathClass pc1 = o1.getPathClass();
PathClass pc2 = o2.getPathClass();
if (pc1 != null && pc2 != null)
return pc1.compareTo(pc2);
if (pc1 == null)
return -1;
if (pc2 != null)
return 1;
// Shouldn't end up here much...
return 0;
}
Aggregations