/*
* $Id$
*
* Copyright 2009 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.blitz.impl;
import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
import ome.api.IQuery;
import ome.api.IUpdate;
import ome.model.IObject;
import ome.model.core.OriginalFile;
import ome.parameters.Filter;
import ome.services.blitz.util.BlitzExecutor;
import ome.services.blitz.util.BlitzOnly;
import ome.services.blitz.util.ServiceFactoryAware;
import ome.services.roi.GeomTool;
import ome.services.throttling.Adapter;
import ome.services.util.Executor.SimpleWork;
import ome.system.ServiceFactory;
import ome.tools.hibernate.QueryBuilder;
import ome.util.SqlAction;
import omero.ServerError;
import omero.api.AMD_IRoi_findByImage;
import omero.api.AMD_IRoi_findByPlane;
import omero.api.AMD_IRoi_findByRoi;
import omero.api.AMD_IRoi_getMeasuredRois;
import omero.api.AMD_IRoi_getMeasuredRoisMap;
import omero.api.AMD_IRoi_getPoints;
import omero.api.AMD_IRoi_getRoiMeasurements;
import omero.api.AMD_IRoi_getRoiStats;
import omero.api.AMD_IRoi_getShapeStats;
import omero.api.AMD_IRoi_getShapeStatsList;
import omero.api.AMD_IRoi_getTable;
import omero.api.AMD_IRoi_uploadMask;
import omero.api.RoiOptions;
import omero.api.RoiResult;
import omero.api._IRoiOperations;
import omero.constants.namespaces.NSMEASUREMENT;
import omero.model.OriginalFileI;
import omero.model.Roi;
import omero.model.Shape;
import omero.util.IceMapper;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.transaction.annotation.Transactional;
import Ice.Current;
/**
* implementation of the IRoi service interface.
*
* @since Beta4.1
*/
public class RoiI extends AbstractAmdServant implements _IRoiOperations,
ServiceFactoryAware, BlitzOnly {
protected ServiceFactoryI factory;
protected final GeomTool geomTool;
protected final SqlAction sql;
public RoiI(BlitzExecutor be, GeomTool geomTool, SqlAction sql) {
super(null, be);
this.geomTool = geomTool;
this.sql = sql;
}
public void setServiceFactory(ServiceFactoryI sf) {
this.factory = sf;
}
// ~ Service methods
// =========================================================================
public void findByImage_async(AMD_IRoi_findByImage __cb,
final long imageId, final RoiOptions opts, Current __current)
throws ServerError {
final IceMapper mapper = new RoiResultMapper(opts);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"findByImage", imageId, opts) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
boolean ns = false;
if (opts != null) {
if(opts.namespace != null) {
ns = true;
}
}
if (ns)
{
final List<Map<String, Object>> mapList = sql.roiByImageAndNs(imageId,
opts.namespace.getValue());
final List<Long> idList = new ArrayList<Long>();
for(Map<String, Object> idMap : mapList) {
idList.add((Long)idMap.get("id"));
}
if (idList.size() == 0) return new ArrayList();
final RoiQueryBuilder qb = new RoiQueryBuilder(idList, opts);
return qb.query(session).list();
}
else
{
final Filter f = filter(opts);
final QueryBuilder qb = new QueryBuilder();
qb.select("distinct r").from("Roi", "r");
qb.join("r.image", "i", false, false);
qb.join("r.shapes", "shapes", false, true); // fetch
qb.where();
qb.and("i.id = :id");
qb.filter("r", f);
qb.filterNow();
qb.order("r.id", true); // ascending
qb.param("id", imageId);
return qb.queryWithoutFilter(session).list();
}
}
}));
}
public void findByRoi_async(AMD_IRoi_findByRoi __cb, final long roiId,
final RoiOptions opts, Current __current) throws ServerError {
final IceMapper mapper = new RoiResultMapper(opts);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"findByRoi", roiId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
RoiQueryBuilder qb = new RoiQueryBuilder(Arrays.asList(roiId), opts);
return qb.query(session).list();
}
}));
}
public void findByPlane_async(AMD_IRoi_findByPlane __cb,
final long imageId, final int z, final int t, final RoiOptions opts,
Current __current) throws ServerError {
final IceMapper mapper = new RoiResultMapper(opts);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"findByPlane", imageId, z, t) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
final Filter f = filter(opts);
final QueryBuilder qb = new QueryBuilder();
qb.select("distinct r").from("Roi", "r");
qb.join("r.shapes", "s", false, true); // fetch
qb.join("r.image", "i", false, false);
qb.where();
qb.and("i.id = :id");
qb.and(" ( s.theZ is null or s.theZ = :z ) ");
qb.and(" ( s.theT is null or s.theT = :t ) ");
qb.filter("r", f);
qb.filterNow();
qb.order("r.id", true); // ascending
qb.param("id", imageId);
qb.param("z", z);
qb.param("t", t);
return qb.queryWithoutFilter(session).list();
}
}));
}
public void getPoints_async(AMD_IRoi_getPoints __cb, final long shapeId,
Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.UNMAPPED);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getPoints", shapeId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
return geomTool.getPoints(shapeId, session);
}
}));
}
public void getShapeStats_async(AMD_IRoi_getShapeStats __cb,
final long shapeId, Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.UNMAPPED);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getShapeStats", shapeId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
return geomTool.getStats(Arrays.asList(shapeId)).perShape[0];
}
}));
}
public void getShapeStatsList_async(AMD_IRoi_getShapeStatsList __cb,
final List<Long> shapeIdList, Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.UNMAPPED);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getShapeStatsList", shapeIdList) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
return Arrays.asList(geomTool.getStats(shapeIdList).perShape);
}
}));
}
public void getRoiStats_async(AMD_IRoi_getRoiStats __cb, final long roiId,
Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.UNMAPPED);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getRoiStats", roiId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
List<Long> shapesInRoi = sql.getShapeIds(roiId);
return geomTool.getStats(shapesInRoi);
}
}));
}
// Measurement results.
// =========================================================================
public void getRoiMeasurements_async(AMD_IRoi_getRoiMeasurements __cb,
final long imageId, final RoiOptions opts, Current __current)
throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.FILTERABLE_COLLECTION);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getRoiMeasurements", imageId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
QueryBuilder qb = new QueryBuilder();
qb.select("distinct fa");
qb.from("Image", "i");
qb.append(", Roi roi ");
qb.join("roi.annotationLinks", "rlinks", false, false);
qb.join("rlinks.child", "rfa", false, false);
qb.join("i.wellSamples", "ws", false, false);
qb.join("ws.well", "well", false, false);
qb.join("well.plate", "plate", false, false);
qb.join("plate.annotationLinks", "links", false, false);
qb.join("links.child", "fa", false, false);
qb.where();
qb.and("fa.ns = '" + NSMEASUREMENT.value + "'");
qb.and("rfa.id = fa.id");
qb.and("i.id = :id");
qb.and("i.id = roi.image");
qb.param("id", imageId);
qb.filter("fa", filter(opts));
return qb.query(session).list();
}
}));
}
protected List<ome.model.roi.Roi> loadMeasuredRois(Session session,
long imageId, long annotationId) {
Query q = session
.createQuery("select distinct r from Roi r join r.image i "
+ "join fetch r.shapes join i.wellSamples ws join ws.well well "
+ "join well.plate plate join plate.annotationLinks links "
+ "join links.child a where a.id = :aid and i.id = :iid "
+ "order by r.id");
q.setParameter("iid", imageId);
q.setParameter("aid", annotationId);
return q.list();
}
public void getMeasuredRoisMap_async(AMD_IRoi_getMeasuredRoisMap __cb,
final long imageId, final List<Long> annotationIds,
RoiOptions opts, Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(new RoiResultMapReturnMapper(
opts));
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getMeasuredRoisMap", imageId, annotationIds) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
if (annotationIds == null) {
return null;
}
Map<Long, List<ome.model.roi.Roi>> rv = new HashMap<Long, List<ome.model.roi.Roi>>();
for (Long annotationId : annotationIds) {
rv.put(annotationId, loadMeasuredRois(session, imageId,
annotationId));
}
return rv;
}
}));
}
public void getMeasuredRois_async(AMD_IRoi_getMeasuredRois __cb,
final long imageId, final long annotationId, final RoiOptions opts,
Current __current) throws ServerError {
final IceMapper mapper = new RoiResultMapper(opts);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getMeasuredRois", imageId, annotationId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
return loadMeasuredRois(session, imageId, annotationId);
}
}));
}
public void getTable_async(AMD_IRoi_getTable __cb, final long annotationId,
final Current __current) throws ServerError {
final IceMapper mapper = new IceMapper(IceMapper.UNMAPPED);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"getTable", annotationId) {
@Transactional(readOnly = true)
public Object doWork(Session session, ServiceFactory sf) {
QueryBuilder qb = new QueryBuilder();
qb.select("f");
qb.from("FileAnnotation", "fa");
qb.join("fa.file", "f", false, false);
qb.where();
qb.and("fa.id = :id");
qb.param("id", annotationId);
OriginalFile file = (OriginalFile) qb.query(session)
.uniqueResult();
if (file == null) {
throw new ome.conditions.ApiUsageException("No such file annotation: " + annotationId);
}
try {
return factory.sharedResources(__current).openTable(
new OriginalFileI(file.getId(), false));
} catch (ServerError e) {
throw new RuntimeException(e);
}
}
}));
}
class MaskClass
{
Set<Point> points;
int colour;
Point min, max;
int width;
int height;
MaskClass(int value)
{
points = new HashSet<Point>();
colour = value;
}
public Color getColour()
{
return new Color(colour);
}
public byte[] asBytes() throws IOException
{
byte[] data = new byte[(int) Math.ceil((double)width*(double)height/8.0)];
int offset = 0;
for(int y = min.y ; y < max.y + 1 ; y++)
{
for(int x = min.x ; x < max.x + 1 ; x++)
{
if(points.contains(new Point(x,y)))
setBit(data, offset, 1);
else
setBit(data, offset, 0);
offset++;
}
}
return data;
}
public void add(Point p)
{
if(points.size()==0)
{
min = new Point(p);
max = new Point(p);
}
else
{
min.x = Math.min(p.x, min.x);
min.y = Math.min(p.y, min.y);
max.x = Math.max(p.x, max.x);
max.y = Math.max(p.y, max.y);
}
width = max.x-min.x+1;
height = max.y-min.y+1;
points.add(p);
}
public ome.model.roi.Mask asMaskI(int z, int t) throws IOException
{
ome.model.roi.Mask mask = new ome.model.roi.Mask();
mask.setX((double)min.x);
mask.setY((double)min.y);
mask.setWidth((double)width);
mask.setHeight((double)height);
mask.setLocked(true);
mask.setTheT(t);
mask.setTheZ(z);
byte[] theseBytes;
theseBytes = this.asBytes();
mask.setBytes(theseBytes);
return mask;
}
/**
* Set the bit value in a byte array at position bit to be the value
* value.
* @param data See above.
* @param bit See above.
* @param val See above.
*/
private void setBit(byte[] data, int bit, int val)
{
int bytePosition = bit/8;
int bitPosition = 7-bit%8;
data[bytePosition] = (byte) ((byte)(data[bytePosition]&
(~(byte)(0x1<<bitPosition)))|
(byte)(val<<bitPosition));
}
/**
* Set the bit value in a byte array at position bit to be the value
* value.
* @param data See above.
* @param bit See above.
* @param val See above.
*/
private byte getBit(byte[] data, int bit)
{
int bytePosition = bit/8;
int bitPosition = 7-bit%8;
return (byte) ((byte)(data[bytePosition] & (0x1<<bitPosition))!=0 ? (byte)1 : (byte)0);
}
}
@SuppressWarnings("unchecked")
private <T extends IObject> T safeReverse(Object o, IceMapper mapper) {
try {
return (T) mapper.reverse(o);
} catch (Exception e) {
throw new RuntimeException("Failed to safely reverse: " + o);
}
}
public void uploadMask_async(final AMD_IRoi_uploadMask __cb,
final long imageId, final int z, final int t, final byte[] bytes,
final Current __current) throws ServerError
{
final IceMapper mapper = new IceMapper(IceMapper.VOID);
runnableCall(__current, new Adapter(__cb, __current, mapper, factory
.getExecutor(), factory.principal, new SimpleWork(this,
"uploadMask", bytes)
{
@Transactional(readOnly = false)
public Object doWork(Session session, ServiceFactory sf)
{
IUpdate update = sf.getUpdateService();
ome.model.core.Image image;
ome.model.roi.Roi roi;
ByteArrayInputStream s = new ByteArrayInputStream(bytes);
IQuery query = sf.getQueryService();
IObject o = query.findByQuery("from Image as i left outer join " +
"fetch i.pixels as p where i.id = "+imageId, null);
try
{
image = (ome.model.core.Image) o;
BufferedImage inputImage = ImageIO.read(s);
Map<Integer, MaskClass> map = new HashMap<Integer, MaskClass>();
MaskClass mask;
int value;
for (int x = 0; x < inputImage.getWidth(); x++)
for (int y = 0; y < inputImage.getHeight(); y++)
{
value = inputImage.getRGB(x, y);
if(value==Color.black.getRGB())
continue;
if (!map.containsKey(value))
{
mask = new MaskClass(value);
map.put(value, mask);
}
else
mask = map.get(value);
mask.add(new Point(x, y));
}
Iterator<Integer> maskIterator = map.keySet().iterator();
while (maskIterator.hasNext())
{
int colour = maskIterator.next();
mask = map.get(colour);
roi = new ome.model.roi.Roi();
roi.setImage(image);
ome.model.roi.Mask toSaveMask = mask.asMaskI(z, t);
roi.addShape(toSaveMask);
ome.model.roi.Roi newROI = update.saveAndReturnObject(roi);
}
return null;
} catch (Exception e)
{
__cb.ice_exception(e);
}
return null;
}
}));
}
// Helpers
// =========================================================================
private static Filter filter(RoiOptions opts) {
Filter f = new Filter();
if (opts != null) {
if (opts.userId != null) {
f.owner(opts.userId.getValue());
}
if (opts.groupId != null) {
f.group(opts.groupId.getValue());
}
Integer offset = null;
Integer limit = null;
if (opts.offset != null) {
offset = opts.offset.getValue();
}
if (opts.limit != null) {
limit = opts.limit.getValue();
}
if (offset != null || limit != null) {
f.page(offset, limit);
}
}
return f;
}
private static class RoiQueryBuilder extends QueryBuilder {
final RoiOptions opts;
RoiQueryBuilder(List<Long> roiIds, RoiOptions opts) {
this.opts = opts;
this.paramList("ids", roiIds);
this.select("distinct r");
this.from("Roi", "r");
this.join("r.shapes", "s", false, true);
this.where();
}
@Override
public Query query(Session session) {
this.and("r.id in (:ids)");
Filter f = RoiI.filter(opts);
this.filter("r", f);
this.filterNow();
this.append("order by r.id");
return super.queryWithoutFilter(session);
}
}
public static class RoiResultMapper extends IceMapper {
public RoiResultMapper(RoiOptions opts) {
super(new RoiResultReturnMapper(opts));
}
}
public static class RoiResultReturnMapper implements
IceMapper.ReturnMapping {
private final RoiOptions opts;
public RoiResultReturnMapper(RoiOptions opts) {
this.opts = opts;
}
@SuppressWarnings("unchecked")
public Object mapReturnValue(IceMapper mapper, Object value)
throws Ice.UserException {
RoiResult result = new RoiResult();
result.opts = opts;
if (value == null) {
result.rois = Collections.emptyList();
result.byZ = Collections.emptyMap();
result.byT = Collections.emptyMap();
result.byG = Collections.emptyMap();
result.groups = Collections.emptyMap();
return result; // EARLY EXIT
}
List<Roi> rois = (List<Roi>) IceMapper.FILTERABLE_COLLECTION
.mapReturnValue(mapper, value);
result.rois = rois;
MultiMap byZ = new MultiValueMap();
MultiMap byT = new MultiValueMap();
MultiMap byG = new MultiValueMap();
for (Roi roi : rois) {
omero.model.RoiI roii = (omero.model.RoiI) roi;
Iterator<Shape> it = roii.iterateShapes();
while (it.hasNext()) {
Shape shape = it.next();
if (shape == null) {
continue;
}
if (shape.getTheT() != null) {
byT.put(shape.getTheT().getValue(), shape);
} else {
byT.put(-1, shape);
}
if (shape.getTheZ() != null) {
byZ.put(shape.getTheZ().getValue(), shape);
} else {
byZ.put(-1, shape);
}
if (shape.getG() != null) {
byG.put(shape.getG().getValue(), shape);
} else {
byG.put("", shape);
}
}
result.byG = byG;
result.byZ = byZ;
result.byT = byT;
}
return result;
}
}
public static class RoiResultMapReturnMapper implements
IceMapper.ReturnMapping {
private final RoiOptions opts;
public RoiResultMapReturnMapper(RoiOptions opts) {
this.opts = opts;
}
@SuppressWarnings("unchecked")
public Object mapReturnValue(IceMapper mapper, Object value)
throws Ice.UserException {
Map<Long, RoiResult> rv = new HashMap<Long, RoiResult>();
Map<Long, List<ome.model.roi.Roi>> iv = (Map<Long, List<ome.model.roi.Roi>>) value;
RoiResultMapper m = new RoiResultMapper(opts);
for (Map.Entry<Long, List<ome.model.roi.Roi>> entry : iv.entrySet()) {
rv.put(entry.getKey(), (RoiResult) m.mapReturnValue(entry
.getValue()));
}
return rv;
}
}
private class NamespaceKeywords
{
public String[] namespaces;
public String[][] keywords;
}
}