use of qupath.lib.objects.PathObject in project qupath by qupath.
the class PathHierarchyPaintingHelper method paintObject.
/**
* Paint an object (or, more precisely, its ROI), optionally along with the ROIs of any child objects.
*
* This is subject to the OverlayOptions, and therefore may not actually end up painting anything
* (if the settings are such that objects of the class provided are not to be displayed)
*
* @param pathObject
* @param paintChildren
* @param g
* @param boundsDisplayed
* @param overlayOptions
* @param selectionModel
* @param downsample
* @return true if anything was painted, false otherwise
*/
public static boolean paintObject(PathObject pathObject, boolean paintChildren, Graphics2D g, Rectangle boundsDisplayed, OverlayOptions overlayOptions, PathObjectSelectionModel selectionModel, double downsample) {
if (pathObject == null)
return false;
// g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
// g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
// Always paint the selected object
// Note: this makes the assumption that child ROIs are completely contained within their parents;
// this probably should be the case, but isn't guaranteed
boolean isSelected = (selectionModel != null && selectionModel.isSelected(pathObject)) && (PathPrefs.useSelectedColorProperty().get() || !PathObjectTools.hasPointROI(pathObject));
boolean isDetectedObject = pathObject.isDetection() || (pathObject.isTile() && pathObject.hasMeasurements());
// Check if the PathClass isn't being shown
PathClass pathClass = pathObject.getPathClass();
if (!isSelected && overlayOptions != null && overlayOptions.isPathClassHidden(pathClass))
return false;
boolean painted = false;
// See if we need to check the children
ROI pathROI = pathObject.getROI();
if (pathROI != null) {
double roiBoundsX = pathROI.getBoundsX();
double roiBoundsY = pathROI.getBoundsY();
double roiBoundsWidth = pathROI.getBoundsWidth();
double roiBoundsHeight = pathROI.getBoundsHeight();
if (PathObjectTools.hasPointROI(pathObject) || boundsDisplayed == null || pathROI instanceof LineROI || boundsDisplayed.intersects(roiBoundsX, roiBoundsY, Math.max(roiBoundsWidth, 1), Math.max(roiBoundsHeight, 1))) {
// Paint the ROI, if necessary
if (isSelected || (overlayOptions.getShowDetections() && isDetectedObject) || (overlayOptions.getShowAnnotations() && pathObject.isAnnotation()) || (overlayOptions.getShowTMAGrid() && pathObject.isTMACore())) {
boolean doFill = overlayOptions.getFillDetections() || pathObject instanceof ParallelTileObject;
boolean doOutline = true;
Color color = null;
boolean useMapper = false;
double fillOpacity = .75;
if (isSelected && PathPrefs.useSelectedColorProperty().get() && PathPrefs.colorSelectedObjectProperty().getValue() != null)
color = ColorToolsAwt.getCachedColor(PathPrefs.colorSelectedObjectProperty().get());
else {
MeasurementMapper mapper = overlayOptions.getMeasurementMapper();
useMapper = mapper != null && mapper.isValid() && pathObject.isDetection();
if (useMapper) {
if (pathObject.hasMeasurements()) {
Integer rgb = mapper.getColorForObject(pathObject);
// If the mapper returns null, the object shouldn't be painted
if (rgb == null)
return false;
// , mapper.getColorMapper().hasAlpha());
color = ColorToolsAwt.getCachedColor(rgb);
} else
color = null;
// System.out.println(color + " - " + pathObject.getMeasurementList().getMeasurementValue(mapper.));
fillOpacity = 1.0;
// Outlines are not so helpful with the measurement mapper
if (doFill)
doOutline = doOutline && !pathObject.isTile();
} else {
Integer rgb = ColorToolsFX.getDisplayedColorARGB(pathObject);
color = ColorToolsAwt.getCachedColor(rgb);
}
// color = PathObjectHelpers.getDisplayedColor(pathObject);
}
// Check if we have only one or two pixels to draw - if so, we can be done quickly
if (isDetectedObject && downsample > 4 && roiBoundsWidth / downsample < 3 && roiBoundsHeight / downsample < 3) {
int x = (int) roiBoundsX;
int y = (int) roiBoundsY;
// Prefer rounding up, lest we lose a lot of regions unnecessarily
int w = (int) (roiBoundsWidth + .9);
int h = (int) (roiBoundsHeight + .9);
if (w > 0 && h > 0) {
g.setColor(color);
// g.setColor(DisplayHelpers.getMoreTranslucentColor(color));
// g.setStroke(getCachedStroke(overlayOptions.strokeThinThicknessProperty().get()));
g.fillRect(x, y, w, h);
}
painted = true;
} else {
Stroke stroke = null;
// Decide whether to fill or not
Color colorFill = doFill && (isDetectedObject || PathObjectTools.hasPointROI(pathObject)) ? color : null;
if (colorFill != null && fillOpacity != 1) {
if (pathObject instanceof ParallelTileObject)
colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
else if (pathObject instanceof PathCellObject && overlayOptions.getShowCellBoundaries() && overlayOptions.getShowCellNuclei()) {
// if (isSelected)
// colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
// else
colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
} else if (pathObject.getParent() instanceof PathDetectionObject) {
colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
} else if (pathObject instanceof PathTileObject && pathClass == null && color != null && color.getRGB() == PathPrefs.colorTileProperty().get()) {
// Don't fill in empty, unclassified tiles
// DisplayHelpers.getMoreTranslucentColor(colorFill);
colorFill = null;
}
}
// Color colorStroke = doOutline ? (colorFill == null ? color : (downsample > overlayOptions.strokeThinThicknessProperty().get() ? null : DisplayHelpers.darkenColor(color))) : null;
Color colorStroke = doOutline ? (colorFill == null ? color : ColorToolsAwt.darkenColor(color)) : null;
// For thick lines, antialiasing is very noticeable... less so for thin lines (of which there may be a huge number)
if (isDetectedObject) {
// Detections inside detections get half the line width
if (pathObject.getParent() instanceof PathDetectionObject)
stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get() / 2.0);
else
stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get());
} else {
double thicknessScale = downsample * (isSelected && !PathPrefs.useSelectedColorProperty().get() ? 1.6 : 1);
float thickness = (float) (PathPrefs.annotationStrokeThicknessProperty().get() * thicknessScale);
if (isSelected && pathObject.getParent() == null && PathPrefs.selectionModeProperty().get()) {
stroke = getCachedStrokeDashed(thickness);
} else {
stroke = getCachedStroke(thickness);
}
}
g.setStroke(stroke);
boolean paintSymbols = overlayOptions.getDetectionDisplayMode() == DetectionDisplayMode.CENTROIDS && pathObject.isDetection() && !pathObject.isTile();
if (paintSymbols) {
pathROI = PathObjectTools.getROI(pathObject, true);
double x = pathROI.getCentroidX();
double y = pathROI.getCentroidY();
double radius = PathPrefs.detectionStrokeThicknessProperty().get() * 2.0;
if (pathObject.getParent() instanceof PathDetectionObject)
radius /= 2.0;
Shape shape;
int nSubclasses = 0;
if (pathClass != null) {
nSubclasses = PathClassTools.splitNames(pathClass).size();
}
switch(nSubclasses) {
case 0:
var ellipse = localEllipse2D.get();
ellipse.setFrame(x - radius, y - radius, radius * 2, radius * 2);
shape = ellipse;
break;
case 1:
var rect = localRect2D.get();
rect.setFrame(x - radius, y - radius, radius * 2, radius * 2);
shape = rect;
break;
case 2:
var triangle = localPath2D.get();
double sqrt3 = Math.sqrt(3.0);
triangle.reset();
triangle.moveTo(x, y - radius * 2.0 / sqrt3);
triangle.lineTo(x - radius, y + radius / sqrt3);
triangle.lineTo(x + radius, y + radius / sqrt3);
triangle.closePath();
shape = triangle;
break;
case 3:
var plus = localPath2D.get();
plus.reset();
plus.moveTo(x, y - radius);
plus.lineTo(x, y + radius);
plus.moveTo(x - radius, y);
plus.lineTo(x + radius, y);
shape = plus;
break;
default:
var cross = localPath2D.get();
cross.reset();
radius /= Math.sqrt(2);
cross.moveTo(x - radius, y - radius);
cross.lineTo(x + radius, y + radius);
cross.moveTo(x + radius, y - radius);
cross.lineTo(x - radius, y + radius);
shape = cross;
break;
}
paintShape(shape, g, colorStroke, stroke, colorFill);
} else if (pathObject instanceof PathCellObject) {
PathCellObject cell = (PathCellObject) pathObject;
if (overlayOptions.getShowCellBoundaries())
paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
if (overlayOptions.getShowCellNuclei())
paintROI(cell.getNucleusROI(), g, colorStroke, stroke, colorFill, downsample);
painted = true;
} else {
if ((overlayOptions.getFillAnnotations() && pathObject.isAnnotation() && pathObject.getPathClass() != PathClassFactory.getPathClass(StandardPathClasses.REGION) && (pathObject.getPathClass() != null || !pathObject.hasChildren())) || (pathObject.isTMACore() && overlayOptions.getShowTMACoreLabels()))
paintROI(pathROI, g, colorStroke, stroke, ColorToolsAwt.getMoreTranslucentColor(colorStroke), downsample);
else
paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
painted = true;
}
}
}
}
}
// Paint the children, if necessary
if (paintChildren) {
for (PathObject childObject : pathObject.getChildObjectsAsArray()) {
// Only call the painting method if required
ROI childROI = childObject.getROI();
if ((childROI != null && boundsDisplayed.intersects(childROI.getBoundsX(), childROI.getBoundsY(), childROI.getBoundsWidth(), childROI.getBoundsHeight())) || childObject.hasChildren())
painted = paintObject(childObject, paintChildren, g, boundsDisplayed, overlayOptions, selectionModel, downsample) | painted;
}
}
return painted;
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class PathHierarchyPaintingHelper method paintConnections.
/**
* Paint connections between objects (e.g. from Delaunay triangulation).
*
* @param connections
* @param hierarchy
* @param g2d
* @param color
* @param downsampleFactor
*/
public static void paintConnections(final PathObjectConnections connections, final PathObjectHierarchy hierarchy, Graphics2D g2d, final Color color, final double downsampleFactor) {
if (hierarchy == null || connections == null || connections.isEmpty())
return;
float alpha = (float) (1f - downsampleFactor / 5);
alpha = Math.min(alpha, 0.25f);
float thickness = PathPrefs.detectionStrokeThicknessProperty().get();
if (alpha < .1f || thickness / downsampleFactor <= 0.5)
return;
g2d = (Graphics2D) g2d.create();
// Shape clipShape = g2d.getClip();
g2d.setStroke(getCachedStroke(thickness));
// g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha * .5f));
// g2d.setColor(ColorToolsAwt.getColorWithOpacity(getPreferredOverlayColor(), 1));
g2d.setColor(ColorToolsAwt.getColorWithOpacity(color.getRGB(), alpha));
// g2d.setColor(Color.BLACK);
Line2D line = new Line2D.Double();
// We can have trouble whenever two objects are outside the clip, but their connections would be inside it
// Here, we just enlarge the region (by quite a lot)
// It's not guaranteed to work, but it usually does... and avoids much expensive computations
Rectangle bounds = g2d.getClipBounds();
int factor = 1;
Rectangle bounds2 = factor > 0 ? new Rectangle(bounds.x - bounds.width * factor, bounds.y - bounds.height * factor, bounds.width * (factor * 2 + 1), bounds.height * (factor * 2 + 1)) : bounds;
ImageRegion imageRegion = AwtTools.getImageRegion(bounds2, 0, 0);
// ImageRegion imageRegion = AwtTools.getImageRegion(bounds, 0, 0);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
// g2d.draw(g2d.getClipBounds());
Collection<PathObject> pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, imageRegion, null);
// double threshold = downsampleFactor*downsampleFactor*4;
for (PathObject pathObject : pathObjects) {
ROI roi = PathObjectTools.getROI(pathObject, true);
double x1 = roi.getCentroidX();
double y1 = roi.getCentroidY();
for (PathObjectConnectionGroup dt : connections.getConnectionGroups()) {
for (PathObject siblingObject : dt.getConnectedObjects(pathObject)) {
ROI roi2 = PathObjectTools.getROI(siblingObject, true);
double x2 = roi2.getCentroidX();
double y2 = roi2.getCentroidY();
if (bounds.intersectsLine(x1, y1, x2, y2)) {
line.setLine(x1, y1, x2, y2);
g2d.draw(line);
}
}
}
}
g2d.dispose();
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class QuPathViewer method updateRoiEditor.
private void updateRoiEditor() {
PathObject pathObjectSelected = getSelectedObject();
ROI previousROI = roiEditor.getROI();
ROI newROI = pathObjectSelected != null && pathObjectSelected.isEditable() ? pathObjectSelected.getROI() : null;
if (previousROI == newROI)
roiEditor.ensureHandlesUpdated();
else
roiEditor.setROI(newROI);
repaint();
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class HierarchyOverlay method paintOverlay.
@Override
public void paintOverlay(final Graphics2D g2d, final ImageRegion imageRegion, final double downsampleFactor, final ImageData<BufferedImage> imageData, final boolean paintCompletely) {
if (this.imageData != imageData) {
this.imageData = imageData;
updateOverlayServer();
}
// Get the selection model, which can influence colours (TODO: this might not be the best way to do it!)
PathObjectHierarchy hierarchy = imageData == null ? null : imageData.getHierarchy();
if (hierarchy == null)
return;
if (!isVisible() && hierarchy.getSelectionModel().noSelection())
return;
// Default RenderingHints (may be temporarily changed in some places)
var defaultAntiAlias = RenderingHints.VALUE_ANTIALIAS_ON;
var defaultStroke = RenderingHints.VALUE_STROKE_PURE;
// Doesn't seem to help...?
// boolean fastRendering = true;
// if (fastRendering) {
// defaultAntiAlias = RenderingHints.VALUE_ANTIALIAS_OFF;
// defaultStroke = RenderingHints.VALUE_STROKE_DEFAULT;
// }
OverlayOptions overlayOptions = getOverlayOptions();
long timestamp = overlayOptions.lastChangeTimestamp().get();
int pointRadius = PathPrefs.pointRadiusProperty().get();
if (overlayOptionsTimestamp != timestamp || pointRadius != lastPointRadius) {
lastPointRadius = pointRadius;
overlayOptionsTimestamp = timestamp;
}
int t = imageRegion.getT();
int z = imageRegion.getZ();
Rectangle serverBounds = AwtTools.getBounds(imageRegion);
// Ensure antialias is on...?
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, defaultAntiAlias);
// Get the displayed clip bounds for fast checking if ROIs need to be drawn
Shape shapeRegion = g2d.getClip();
if (shapeRegion == null)
shapeRegion = AwtTools.getBounds(imageRegion);
var boundsDisplayed = shapeRegion.getBounds();
// Ensure the bounds do not extend beyond what the server actually contains
boundsDisplayed = boundsDisplayed.intersection(serverBounds);
if (boundsDisplayed.width <= 0 || boundsDisplayed.height <= 0)
return;
// Get the annotations & selected objects (which must be painted directly)
Collection<PathObject> selectedObjects = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
selectedObjects.removeIf(p -> !p.hasROI() || (p.getROI().getZ() != z || p.getROI().getT() != t));
ImageRegion region = AwtTools.getImageRegion(boundsDisplayed, z, t);
// Paint detection objects
long startTime = System.currentTimeMillis();
if (overlayOptions.getShowDetections() && !hierarchy.isEmpty()) {
// If we aren't downsampling by much, or we're upsampling, paint directly - making sure to paint the right number of times, and in the right order
if (overlayServer == null || regionStore == null || downsampleFactor < 1.0) {
Collection<PathObject> pathObjects;
try {
Set<PathObject> pathObjectsToPaint = new TreeSet<>(comparator);
pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, region, pathObjectsToPaint);
} catch (IllegalArgumentException e) {
// This can happen (rarely) in a multithreaded environment if the level of a detection changes.
// However, protecting against this fully by caching the level with integer boxing/unboxing would be expensive.
logger.debug("Exception requesting detections to paint: " + e.getLocalizedMessage(), e);
pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, region, null);
}
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, pathObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
if (overlayOptions.getShowConnections()) {
Object connections = imageData.getProperty(DefaultPathObjectConnectionGroup.KEY_OBJECT_CONNECTIONS);
if (connections instanceof PathObjectConnections)
PathHierarchyPaintingHelper.paintConnections((PathObjectConnections) connections, hierarchy, g2d, imageData.isFluorescence() ? ColorToolsAwt.TRANSLUCENT_WHITE : ColorToolsAwt.TRANSLUCENT_BLACK, downsampleFactor);
}
} else {
// On the other hand, if a large image has been updated then we may be browsing quickly - better to repaint quickly while tiles may still be loading
if (paintCompletely) {
regionStore.paintRegionCompletely(overlayServer, g2d, shapeRegion, z, t, downsampleFactor, null, null, 5000);
} else {
regionStore.paintRegion(overlayServer, g2d, shapeRegion, z, t, downsampleFactor, null, null, null);
}
}
}
long endTime = System.currentTimeMillis();
if (endTime - startTime > 500)
logger.debug("Painting time: {} seconds", GeneralTools.formatNumber((endTime - startTime) / 1000.0, 4));
// The setting below stops some weird 'jiggling' effects during zooming in/out, or poor rendering of shape ROIs
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, defaultAntiAlias);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, defaultStroke);
// Prepare to handle labels, if we need to
Collection<PathObject> objectsWithNames = new ArrayList<>();
Collection<PathObject> annotations = hierarchy.getObjectsForRegion(PathAnnotationObject.class, region, null);
for (var iterator = annotations.iterator(); iterator.hasNext(); ) {
var next = iterator.next();
if ((next.getName() != null && !next.getName().isBlank()))
objectsWithNames.add(next);
if (selectedObjects.contains(next))
iterator.remove();
}
// Paint the annotations
List<PathObject> pathObjectList = new ArrayList<>(annotations);
Collections.sort(pathObjectList, Comparator.comparingInt(PathObject::getLevel).reversed().thenComparing(Comparator.comparingDouble((PathObject p) -> -p.getROI().getArea())));
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, pathObjectList, overlayOptions, null, downsampleFactor);
// Ensure that selected objects are painted last, to make sure they aren't obscured
if (!selectedObjects.isEmpty()) {
Composite previousComposite = g2d.getComposite();
float opacity = overlayOptions.getOpacity();
if (opacity < 1) {
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, selectedObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
g2d.setComposite(previousComposite);
} else {
PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, selectedObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
}
}
// Paint labels
if (overlayOptions.getShowNames() && !objectsWithNames.isEmpty()) {
double requestedFontSize;
switch(PathPrefs.viewerFontSizeProperty().get()) {
case HUGE:
requestedFontSize = 24;
break;
case LARGE:
requestedFontSize = 18;
break;
case SMALL:
requestedFontSize = 10;
break;
case TINY:
requestedFontSize = 8;
break;
case MEDIUM:
default:
requestedFontSize = 14;
break;
}
float fontSize = (float) (requestedFontSize * downsampleFactor);
if (!GeneralTools.almostTheSame(font.getSize2D(), fontSize, 0.001))
font = font.deriveFont(fontSize);
g2d.setFont(font);
var metrics = g2d.getFontMetrics(font);
var rect = new Rectangle2D.Double();
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
for (var annotation : objectsWithNames) {
var name = annotation.getName();
var roi = annotation.getROI();
if (name != null && !name.isBlank() && roi != null && !overlayOptions.isPathClassHidden(annotation.getPathClass())) {
g2d.setColor(ColorToolsAwt.TRANSLUCENT_BLACK);
var bounds = metrics.getStringBounds(name, g2d);
double pad = 5.0 * downsampleFactor;
double x = roi.getCentroidX() - bounds.getWidth() / 2.0;
double y = roi.getCentroidY() + bounds.getY() + metrics.getAscent() + pad;
rect.setFrame(x + bounds.getX() - pad, y + bounds.getY() - pad, bounds.getWidth() + pad * 2, bounds.getHeight() + pad * 2);
g2d.fill(rect);
g2d.setColor(Color.WHITE);
g2d.drawString(name, (float) x, (float) y);
}
}
}
}
use of qupath.lib.objects.PathObject 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);
}
Aggregations