/* * Copyright 2009 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.services.roi; import static omero.rtypes.rdouble; import static omero.rtypes.rint; import static omero.rtypes.rlong; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import ome.conditions.ApiUsageException; import ome.io.nio.PixelBuffer; import ome.model.IObject; import ome.model.core.Pixels; import ome.services.util.Executor; import ome.tools.hibernate.SessionFactory; import ome.util.Filterable; import ome.util.SqlAction; import omero.api.RoiStats; import omero.api.ShapePoints; import omero.api.ShapeStats; import omero.model.Ellipse; import omero.model.Line; import omero.model.Point; import omero.model.Rectangle; import omero.model.Shape; import omero.model.SmartEllipseI; import omero.model.SmartLineI; import omero.model.SmartPointI; import omero.model.SmartRectI; import omero.model.SmartShape; import omero.util.IceMapper; import omero.util.ObjectFactoryRegistry.ObjectFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.hibernate.Query; import org.hibernate.Session; /** * Strategy for handling the conversion between {@link Shape shapes} and * database-specific geometries. * @since Beta4.1 */ public class GeomTool { protected Logger log = LoggerFactory.getLogger(GeomTool.class); protected final AtomicBoolean hasShapes = new AtomicBoolean(true); protected final SqlAction sql; protected final SessionFactory factory; protected final PixelData data; protected final Executor ex; protected final String uuid; public GeomTool(PixelData data, SqlAction sql, SessionFactory factory) { this(data, sql, factory, null, null); } public GeomTool(PixelData data, SqlAction sql, SessionFactory factory, Executor ex, String uuid) { this.data = data; this.sql = sql; this.factory = factory; this.ex = ex; this.uuid = uuid; } /** * Loads just the shape and no other relationships. This * * @param shapeId * @param session * @return See above. */ private Shape justShapeById(long shapeId, Session session) { Query q = session.createQuery("select s from Shape s where s.id = :id"); q.setParameter("id", shapeId); ome.model.roi.Shape shape = (ome.model.roi.Shape) q.uniqueResult(); return (Shape) new ShapeMapper().map(shape); } // // Factory methods // public List<Shape> random(int count) { if (count < 1 || count > 100000) { throw new RuntimeException("Count out of bounds: " + count); } Map<String, ObjectFactory> map = new RoiTypes.RoiTypesObjectFactoryRegistry().createFactories(null); List<String> types = new ArrayList<String>(map.keySet()); List<Shape> shapes = new ArrayList<Shape>(); Random r = new Random(); try { while (shapes.size() < count) { int which = r.nextInt(map.size()); String typeName = types.get(which); ObjectFactory of = map.get(typeName); SmartShape s = (SmartShape) of.create(""); Method m = s.getClass().getMethod("randomize", Random.class); m.invoke(s, r); shapes.add((Shape) s); } } catch (Exception e) { throw new RuntimeException("Failure on creating shape " + shapes.size(), e); } return shapes; } public Line ln(double x1, double y1, double x2, double y2) { SmartLineI rect = new SmartLineI(); rect.setX1(rdouble(x1)); rect.setY1(rdouble(y1)); rect.setX2(rdouble(x2)); rect.setY2(rdouble(y2)); return rect; } public Rectangle rect(double x, double y, double w, double h) { SmartRectI rect = new SmartRectI(); rect.setX(rdouble(x)); rect.setY(rdouble(y)); rect.setWidth(rdouble(w)); rect.setHeight(rdouble(h)); return rect; } public Point pt(double x, double y) { SmartPointI pt = new SmartPointI(); pt.setCx(rdouble(x)); pt.setCy(rdouble(y)); return pt; } public Ellipse ellipse(double cx, double cy, double rx, double ry) { SmartEllipseI ellipse = new SmartEllipseI(); ellipse.setCx(rdouble(cx)); ellipse.setCy(rdouble(cy)); ellipse.setRx(rdouble(rx)); ellipse.setRy(rdouble(ry)); return ellipse; } public Ellipse ellipse(double cx, double cy, double rx, double ry, int t, int z) { Ellipse ellipse = ellipse(cx, cy, rx, ry); ellipse.setTheT(rint(t)); ellipse.setTheZ(rint(z)); return ellipse; } // // Conversion methods // private static final String ORIGIN = "'(0,0)'"; public String dbPath(Shape shape) { if (shape == null) { // ticket:2045 doing anything we can to prevent null returns values log.warn("Shape is null"); return ORIGIN; } SmartShape ss = assertSmart(shape); List<Point> points = ss.asPoints(); // ticket:1652 - to prevent the objects from being repeatedly // checked, they must be set to something. Here we are using // the top-left point as a default (like SmartText) if (points == null) { return ORIGIN; } StringBuilder sb = new StringBuilder(); sb.append("'("); for (int i = 0; i < points.size(); i++) { if (i > 0) { sb.append(","); } SmartShape.Util.appendDbPoint(sb, points.get(i)); } sb.append(")'"); return sb.toString(); } // // Database access methods // public ShapePoints getPoints(long shapeId, Session session) { Shape shape = justShapeById(shapeId, session); SmartShape smart = assertSmart(shape); final List<Integer> xs = new ArrayList<Integer>(); // This is not good final List<Integer> ys = new ArrayList<Integer>(); // nor is this. smart.areaPoints(new SmartShape.PointCallback() { public void handle(int x, int y) { xs.add(x); ys.add(y); } }); ShapePoints sp = new ShapePoints(); sp.x = new int[xs.size()]; sp.y = new int[ys.size()]; for (int i = 0; i < sp.x.length; i++) { sp.x[i] = xs.get(i); sp.y[i] = ys.get(i); } return sp; } public RoiStats getStats(List<Long> shapeIds) { if (shapeIds == null) { return null; // EARLY EXIT } final Session session = factory.getSession(); final RoiStats rs = new RoiStats(); rs.perShape = new ShapeStats[shapeIds.size()]; for (int i = 0; i < shapeIds.size(); i++) { final long shapeId = shapeIds.get(i); final ome.model.roi.Shape shape = (ome.model.roi.Shape) session .createQuery( "select s from Shape s " + "left outer join fetch s.channels selected " // optional + "join fetch s.roi r join fetch r.image i " + "join fetch i.pixels p join fetch p.channels c " + "join fetch c.logicalChannel lc " + "where s.id = :id").setParameter("id", shapeId).uniqueResult(); final SmartShape smartShape = (SmartShape) new ShapeMapper() .map(shape); final ome.model.roi.Roi roi = shape.getRoi(); final ome.model.core.Image img = roi.getImage(); final ome.model.core.Pixels pix = img.getPrimaryPixels(); final long roiId = roi.getId(); final long imgId = img.getId(); final long pixId = pix.getId(); final int maxZ = pix.getSizeZ(); final int maxT = pix.getSizeT(); // We only take the values for the first Shape. If this call is // being made with different shapes, then the user will know as // much. if (rs.combined == null) { rs.roiId = roiId; rs.imageId = imgId; rs.pixelsId = pixId; int ch = pix.sizeOfChannels(); rs.combined = makeStats(ch); rs.combined.shapeId = -1; rs.combined.channelIds = new long[ch]; for (int w = 0; w < ch; w++) { rs.combined.channelIds[w] = pix.getChannel(w) .getLogicalChannel().getId(); } } final ShapeStats stats = makeStats(pix, shape); stats.shapeId = shape.getId(); final int ch = stats.channelIds.length; final double[] sumOfSquares = new double[ch]; final Integer theZ = shape.getTheZ(); // May be null final Integer theT = shape.getTheT(); // May be null final int startZ = (theZ == null) ? 0 : theZ.intValue(); final int startT = (theT == null) ? 0 : theT.intValue(); final int endZ = (theZ == null) ? (maxZ - 1) : theZ.intValue(); final int endT = (theT == null) ? (maxT - 1) : theT.intValue(); final PixelBuffer buf = data.getBuffer(pixId); try { SmartShape.PointCallback cb = new SmartShape.PointCallback() { public void handle(int x, int y) { for (int w = 0; w < ch; w++) { for (int z = startZ; z <= endZ; z++) { for (int t = startT; t <= endT; t++) { // WHAT TO DO ABOUT THE CHANNELS IN AGGREGATION? stats.pointsCount[w]++; double value = data.get(buf, x, y, z, w, t); stats.min[w] = Math.min(value, stats.min[w]); stats.max[w] = Math.max(value, stats.max[w]); stats.sum[w] += value; sumOfSquares[w] += value * value; } } } } }; smartShape.areaPoints(cb); } finally { try { buf.close(); } catch (IOException e) { log.error("Error closing " + buf, e); } } for (int w = 0; w < ch; w++) { stats.mean[w] = stats.sum[w] / stats.pointsCount[w]; if (stats.pointsCount[w] > 1) { double sigmaSquare = (sumOfSquares[w] - stats.sum[w] * stats.sum[w] / stats.pointsCount[w]) / (stats.pointsCount[w] - 1); if (sigmaSquare > 0) { stats.stdDev[w] = Math.sqrt(sigmaSquare); } } } rs.perShape[i] = stats; } return rs; } /** * Maps from multiple possible user-provided names of shapes (e.g. * "::omero::model::Text", "Text", "TextI", "omero.model.TextI", * "ome.model.roi.Text", ...) to the definitive database discriminator. * * @param string The string to check. * @return See above. */ public Object discriminator(String string) { if (string == null || string.length() == 0) { throw new ApiUsageException("Empty string"); } String[] s = string.split("[.:]"); string = s[s.length - 1]; string = string.toLowerCase(); if (string.endsWith("i")) { string = string.substring(0, string.length() - 1); } return string; } // // helpers // private ShapeStats makeStats(int ch) { ShapeStats stats = new ShapeStats(); stats.channelIds = new long[ch]; stats.min = new double[ch]; stats.max = new double[ch]; stats.sum = new double[ch]; stats.mean = new double[ch]; stats.stdDev = new double[ch]; stats.pointsCount = new long[ch]; Arrays.fill(stats.min, 0, ch, Double.MAX_VALUE); return stats; } private ShapeStats makeStats(Pixels pix, ome.model.roi.Shape shape) { // If the shape does not explicitly list any channels, then we will // need to take all the available channels from the pixels. Integer theC = shape.getTheC(); int sizeC = pix.sizeOfChannels(); if (theC != null) { sizeC = 1; } ShapeStats stats = makeStats(sizeC); for (int w = 0; w < sizeC; w++) { if (theC != null) { stats.channelIds[w] = pix.getChannel(theC).getLogicalChannel() .getId(); } else { stats.channelIds[w] = pix.getChannel(w).getLogicalChannel() .getId(); } } return stats; } private SmartShape assertSmart(Shape shape) { if (!SmartShape.class.isAssignableFrom(shape.getClass())) { throw new RuntimeException( "Internally only SmartShapes should be used! not " + shape.getClass()); } SmartShape ss = (SmartShape) shape; return ss; } private static class ShapeMapper extends IceMapper { boolean called = false; /** * Overrides {@link IceMapper#filter(String, Filterable)} in order to * only allow descending one level deep. */ @Override public Filterable filter(String fieldId, Filterable source) { if (!called) { called = true; return super.filter(fieldId, source); } Object o = findTarget(source); if (o instanceof omero.model.IObject) { IObject iobj = (IObject) source; omero.model.IObject robj = (omero.model.IObject) o; robj.setId(rlong(iobj.getId())); robj.unload(); } return source; } } }