diff --git a/README.md b/README.md index 920b4d0..c6389a3 100644 --- a/README.md +++ b/README.md @@ -272,8 +272,13 @@ Pixel intensities can be retrieved from images: imageplusID = Ext.getImage(imageIds[0]); ``` -Images can also be cropped on import by specifying the ROI as a String of the form: -"x:xstart:xend,y:ystart:yend,c:cstart:cend,z:zstart:zend,t:tstart:tend". +Images can also be cropped on import by specifying the ROI in one of two ways: +1. using the ROI ID (as a String) from OMERO, for example: +``` +imageplusID = Ext.getImage(imageIds[0], Roi.getProperty("ROI_ID")); +``` + +2. as a String of the form: "x:xstart:xend,y:ystart:yend,c:cstart:cend,z:zstart:zend,t:tstart:tend". For example, cropping a 512x512x3x100x5 image starting at (0,0,0,50,3) and ending at (100,100,2,99,3) can be done with: ``` diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index a422736..e8025be 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -110,6 +110,7 @@ public class OMEROMacroExtension implements PlugIn, MacroExtension { newDescriptor("delete", this, ARG_STRING, ARG_NUMBER), newDescriptor("getName", this, ARG_STRING, ARG_NUMBER), newDescriptor("getImage", this, ARG_NUMBER, ARG_STRING + ARG_OPTIONAL), + newDescriptor("getImageFromROI", this, ARG_NUMBER, ARG_NUMBER), newDescriptor("getROIs", this, ARG_NUMBER, ARG_NUMBER + ARG_OPTIONAL, ARG_STRING + ARG_OPTIONAL), newDescriptor("saveROIs", this, ARG_NUMBER, ARG_STRING + ARG_OPTIONAL), newDescriptor("removeROIs", this, ARG_NUMBER, ARG_STRING + ARG_OPTIONAL), @@ -128,6 +129,26 @@ public class OMEROMacroExtension implements PlugIn, MacroExtension { private ExperimenterWrapper user = null; + /** + * Safely converts a String to a Long, returning null if it fails. + * + * @param s The string. + * + * @return The integer value represented by s, null if not applicable. + */ + private static Long safeParseLong(String s) { + Long l = null; + if (s != null) { + try { + l = Long.parseLong(s); + } catch (NumberFormatException ignored) { + // DO NOTHING + } + } + return l; + } + + /** * Converts a list of GenericObjectWrappers to a comma-delimited list of IDs. * @@ -1166,20 +1187,30 @@ public String getName(String type, long id) { * Opens an image with optional bounds. The bounds are in the form "x:min:max" with max included. Each of XYCZT is * optional, min and max are also optional: "x:0:100 y::200 z:5: t::" * - * @param id The image ID. - * @param bounds The XYCZT bounds + * @param id The image ID. + * @param roi The ROI ID or XYCZT bounds * * @return The image, as an {@link ImagePlus}. */ - public ImagePlus getImage(long id, CharSequence bounds) { + public ImagePlus getImage(long id, String roi) { ImagePlus imp = null; try { ImageWrapper image = client.getImage(id); - if (bounds == null) { + if (roi == null) { imp = image.toImagePlus(client); } else { - Bounds b = extractBounds(bounds); - imp = image.toImagePlus(client, b); + final Long roiId = safeParseLong(roi); + if (roiId != null) { + ROIWrapper oRoi = image.getROIs(client) + .stream() + .filter(r -> r.getId() == roiId) + .findFirst() + .orElseThrow(() -> new NoSuchElementException("ROI not found: " + roi)); + imp = image.toImagePlus(client, oRoi); + } else { + Bounds b = extractBounds(roi); + imp = image.toImagePlus(client, b); + } } } catch (ServiceException | AccessException | ExecutionException | NoSuchElementException e) { IJ.error("Could not retrieve image: " + e.getMessage()); @@ -1426,16 +1457,17 @@ public ExtensionDescriptor[] getExtensionFunctions() { @Override public String handleExtension(String name, Object[] args) { - long id; - long id1; - long id2; - String type; - String type1; - String type2; - String property; - String tableName; - String path; - String results = null; + long id; + long id1; + long id2; + String type; + String type1; + String type2; + String property; + String tableName; + String path; + String results = null; + ImagePlus imp; switch (name) { case "connectToOMERO": String host = ((String) args[0]); @@ -1573,7 +1605,7 @@ public String handleExtension(String name, Object[] args) { case "getImage": id = ((Double) args[0]).longValue(); - ImagePlus imp = getImage(id, (CharSequence) args[1]); + imp = getImage(id, (String) args[1]); if (imp != null) { imp.show(); results = String.valueOf(imp.getID()); diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java index 3ff27b1..ec9ebcf 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java @@ -145,6 +145,15 @@ void testGetImageError() { } + @Test + void testGetImageFromROIError() { + Object[] args = {1.0d, "-1"}; + ext.handleExtension("getImage", args); + String expected = "Could not retrieve image: ROI not found: -1"; + assertEquals(expected, outContent.toString().trim()); + } + + @Test void testListForUserError() { Object[] args = {"hello"}; diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java index bfaccdd..d3df337 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java @@ -213,7 +213,7 @@ void testCreateProject() { @ParameterizedTest @NullSource - @ValueSource(doubles = {1.0}) + @ValueSource(doubles = 1.0) void testCreateDataset(Double projectId) { Object[] args = {"toDelete", "toBeDeleted", projectId}; String result = ext.handleExtension("createDataset", args); @@ -326,10 +326,10 @@ void testGetImage() { @ParameterizedTest @ValueSource(strings = {"x:100:200 y:1:511", "x:50:150 y:2:512", "x:50:150 y:2:513"}) - void testGetImageTwoBounds(CharSequence bounds) { + void testGetImageTwoBounds(String bounds) { final int partSize = 100; final int fullSize = 510; - ImagePlus imp = ext.getImage(1L, bounds); + ImagePlus imp = ext.getImage(1L, bounds); assertEquals(partSize, imp.getWidth()); assertEquals(fullSize, imp.getHeight()); } @@ -337,7 +337,7 @@ void testGetImageTwoBounds(CharSequence bounds) { @ParameterizedTest @ValueSource(strings = {"x:100: y::412", "X:100: Y::412", "x::412 y:100:"}) - void testGetImageOneBound(CharSequence bounds) { + void testGetImageOneBound(String bounds) { final int size = 412; ImagePlus imp = ext.getImage(1L, bounds); assertEquals(size, imp.getWidth()); @@ -347,13 +347,13 @@ void testGetImageOneBound(CharSequence bounds) { @ParameterizedTest @ValueSource(strings = {"x:300:480 y:24:36 z:1:3 c:0:4 t:3:6", "X:300:480 Y:24:36 Z:1:3 C:0:4 T:3:6"}) - void testGetImageAllBounds(CharSequence bounds) { + void testGetImageAllBounds(String bounds) { final int sizeX = 180; final int sizeY = 12; final int sizeZ = 2; final int sizeC = 4; final int sizeT = 3; - ImagePlus imp = ext.getImage(1L, bounds); + ImagePlus imp = ext.getImage(1L, bounds); assertEquals(sizeX, imp.getWidth()); assertEquals(sizeY, imp.getHeight()); assertEquals(sizeZ, imp.getNSlices()); @@ -364,12 +364,12 @@ void testGetImageAllBounds(CharSequence bounds) { @ParameterizedTest @ValueSource(strings = {"", " ", "x: y:: ", "x::9999 y:0:9999 z:50:99 c::99 t::99", "^#azerty*$"}) - void testGetImageNoBounds(CharSequence bounds) { - final int size = 512; + void testGetImageNoBounds(String bounds) { + final int size = 512; final int sizeZ = 3; final int sizeC = 5; final int sizeT = 7; - ImagePlus imp = ext.getImage(1L, bounds); + ImagePlus imp = ext.getImage(1L, bounds); assertEquals(size, imp.getWidth()); assertEquals(size, imp.getHeight()); assertEquals(sizeZ, imp.getNSlices()); @@ -380,12 +380,12 @@ void testGetImageNoBounds(CharSequence bounds) { @ParameterizedTest @ValueSource(strings = {"z:0", "z:2", "Z:0", "z:0:1"}) - void testGetImageZ(CharSequence bounds) { - final int size = 512; + void testGetImageZ(String bounds) { + final int size = 512; final int sizeZ = 1; final int sizeC = 5; final int sizeT = 7; - ImagePlus imp = ext.getImage(1L, bounds); + ImagePlus imp = ext.getImage(1L, bounds); assertEquals(size, imp.getWidth()); assertEquals(size, imp.getHeight()); assertEquals(sizeZ, imp.getNSlices()); @@ -396,12 +396,12 @@ void testGetImageZ(CharSequence bounds) { @ParameterizedTest @ValueSource(strings = {"c:0", "c:2", "C:0", "c:0:1"}) - void testGetImageC(CharSequence bounds) { - final int size = 512; + void testGetImageC(String bounds) { + final int size = 512; final int sizeZ = 3; final int sizeC = 1; final int sizeT = 7; - ImagePlus imp = ext.getImage(1L, bounds); + ImagePlus imp = ext.getImage(1L, bounds); assertEquals(size, imp.getWidth()); assertEquals(size, imp.getHeight()); assertEquals(sizeZ, imp.getNSlices()); @@ -412,12 +412,12 @@ void testGetImageC(CharSequence bounds) { @ParameterizedTest @ValueSource(strings = {"t:0", "t:2", "T:0", "t:0:1"}) - void testGetImageT(CharSequence bounds) { - final int size = 512; + void testGetImageT(String bounds) { + final int size = 512; final int sizeZ = 3; final int sizeC = 5; final int sizeT = 1; - ImagePlus imp = ext.getImage(1L, bounds); + ImagePlus imp = ext.getImage(1L, bounds); assertEquals(size, imp.getWidth()); assertEquals(size, imp.getHeight()); assertEquals(sizeZ, imp.getNSlices()); @@ -426,6 +426,36 @@ void testGetImageT(CharSequence bounds) { } + @Test + void testGetImageFromROI() { + ImagePlus imp = ext.getImage(1L, null); + Overlay overlay = new Overlay(); + Roi roi = new Roi(25, 30, 70, 50); + roi.setImage(imp); + overlay.add(roi); + imp.setOverlay(overlay); + int savedROIs = ext.saveROIs(imp, 1L, ""); + overlay.clear(); + imp.setOverlay(null); + int loadedROIs = ext.getROIs(imp, 1L, true, ""); + + Roi retrievedRoi = imp.getOverlay().get(0); + String roiId = retrievedRoi.getProperty("ROI_ID"); + ImagePlus cropped = ext.getImage(1L, roiId); + + ext.removeROIs(1L); + int clearedROIs = ext.getROIs(imp, 1L, true, ""); + + assertEquals(1, savedROIs); + assertEquals(1, loadedROIs); + assertEquals(0, clearedROIs); + assertEquals(25, Integer.parseInt(cropped.getProp("IMAGE_POS_X"))); + assertEquals(30, Integer.parseInt(cropped.getProp("IMAGE_POS_Y"))); + assertEquals(70, cropped.getWidth()); + assertEquals(50, cropped.getHeight()); + } + + @Test void testSaveAndGetROIs() { ImagePlus imp = ext.getImage(1L, null); @@ -455,7 +485,7 @@ void testSaveAndGetROIs() { @CsvSource(delimiter = ';', value = {"image;1;null;testKey1\ttestValue1\ttestKey2\t20", "image;3;' ';testKey1 testValue1 testKey2 20", "image;2;&&;testKey1&&testValue2&&testKey2&&30", - "image;4;'';''"}, nullValues = {"null"}) + "image;4;'';''"}, nullValues = "null") void testGetKeyValuePairs(String type, Double id, String separator, String output) { Object[] args = {type, id, separator}; String result = ext.handleExtension("getKeyValuePairs", args); @@ -467,7 +497,7 @@ void testGetKeyValuePairs(String type, Double id, String separator, String outpu @CsvSource(delimiter = ';', value = {"image;1;testKey1;null;testValue1", "image;3;testKey2;null;20", "image;2;testKey2;null;30", - "image;2;notExist;default;default"}, nullValues = {"null"}) + "image;2;notExist;default;default"}, nullValues = "null") void testGetValue(String type, Double id, String key, String defaultValue, String output) { Object[] args = {type, id, key, defaultValue}; String result = ext.handleExtension("getValue", args);