From cee5eff99cfd4ef9bc2741923021afcb49ef6d8a Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 20 Sep 2024 14:13:15 +0200 Subject: [PATCH 01/13] Bump version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 123bd05..4356086 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ pom-scijava org.scijava - 37.0.0 + 38.0.1 fr.igred @@ -113,7 +113,7 @@ fr.igred simple-omero-client - 5.18.0 + 5.19.0 From 9b61eb5b368dfac74ecf07e953d7d35ce54a45e1 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 20 Sep 2024 14:38:04 +0200 Subject: [PATCH 02/13] Do not use a map fo retrieve key/value pairs --- src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index 54d8ca8..1c26755 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -1292,14 +1292,14 @@ public int saveROIs(ImagePlus imp, long id, String property) { * @return The concatenated string of all key-value pairs for the specified repository object. */ public String getKeyValuePairs(String type, long id, String separator) { - Map keyValuePairs = new TreeMap<>(); + List> keyValuePairs = new ArrayList<>(0); String sep = separator == null ? "\t" : separator; GenericRepositoryObjectWrapper object = getRepositoryObject(type, id); try { if (object != null) { - keyValuePairs = new TreeMap<>(object.getKeyValuePairs(client)); + keyValuePairs = object.getKeyValuePairsAsList(client); } } catch (ServiceException | AccessException | ExecutionException e) { IJ.error("Could not retrieve object: " + e.getMessage()); @@ -1308,7 +1308,7 @@ public String getKeyValuePairs(String type, long id, String separator) { int size = 10 * keyValuePairs.size(); StringBuilder concatenation = new StringBuilder(size); - for (Map.Entry entry : keyValuePairs.entrySet()) { + for (Map.Entry entry : keyValuePairs) { concatenation.append(entry.getKey()) .append(sep) .append(entry.getValue()) From e7e085bf7ec3eb13aa665a0ec1b020ceee5773e3 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 20 Sep 2024 17:30:28 +0200 Subject: [PATCH 03/13] Improve tests and code coverage --- .../igred/ij/plugin/OMEROMacroExtension.java | 15 ++++---- .../ij/plugin/OMEROExtensionErrorTest.java | 35 +++++++++++++++++++ .../igred/ij/plugin/OMEROExtensionTest.java | 8 +++-- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index 1c26755..a422736 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -60,7 +60,6 @@ import java.util.Collection; import java.util.Comparator; import java.util.HashMap; -import java.util.TreeMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -712,11 +711,15 @@ public void addToTable(String tableName, ResultsTable results, Long imageId, Lis public void saveTableAsFile(String tableName, String path, CharSequence delimiter) { TableWrapper table = tables.get(tableName); - char sep = delimiter == null || delimiter.length() != 1 ? DEFAULT_DELIMITER : delimiter.charAt(0); - try { - table.saveAs(path, sep); - } catch (FileNotFoundException | UnsupportedEncodingException e) { - IJ.error("Could not create table file: ", e.getMessage()); + if (table != null) { + char sep = delimiter == null || delimiter.length() != 1 ? DEFAULT_DELIMITER : delimiter.charAt(0); + try { + table.saveAs(path, sep); + } catch (FileNotFoundException | UnsupportedEncodingException e) { + IJ.error("Could not create table file: ", e.getMessage()); + } + } else { + IJ.error("Table does not exist: " + tableName); } } diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java index 2ae669d..3ff27b1 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java @@ -16,6 +16,7 @@ package fr.igred.ij.plugin; +import ij.measure.ResultsTable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,6 +31,7 @@ import java.io.InputStream; import java.io.PrintStream; import java.nio.file.Files; +import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -228,4 +230,37 @@ void testKeyNotExist() { assertEquals(expected, outContent.toString().trim()); } + + @Test + void testClearTable() throws Exception { + long imageId = 1L; + String label1 = "test"; + String label2 = "test2"; + double size1 = 25.023579d; + double size2 = 50.0d; + + ResultsTable rt1 = new ResultsTable(); + rt1.incrementCounter(); + rt1.setLabel(label1, 0); + rt1.setValue("Size", 0, size1); + + ResultsTable rt2 = new ResultsTable(); + rt2.incrementCounter(); + rt2.setLabel(label2, 0); + rt2.setValue("Size", 0, size2); + + ext.addToTable("test_table", rt1, imageId, new ArrayList<>(0), null); + ext.addToTable("test_table", rt2, imageId, new ArrayList<>(0), null); + + Object[] args2 = {"test_table"}; + ext.handleExtension("clearTable", args2); + + File textFile = new File("test.txt"); + Object[] args3 = {"test_table", textFile.getCanonicalPath(), null}; + ext.handleExtension("saveTableAsFile", args3); + + String expected = "Table does not exist: test_table"; + assertEquals(expected, outContent.toString().trim()); + } + } diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java index a1227f8..bfaccdd 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java @@ -434,12 +434,15 @@ void testSaveAndGetROIs() { roi.setImage(imp); overlay.add(roi); imp.setOverlay(overlay); + int savedROIs = ext.saveROIs(imp, 1L, ""); overlay.clear(); int loadedROIs = ext.getROIs(imp, 1L, true, ""); - ext.removeROIs(1L); - int clearedROIs = ext.getROIs(imp, 1L, true, ""); + Object[] args = {1.0d}; + ext.handleExtension("removeROIs", args); + + int clearedROIs = ext.getROIs(imp, 1L, true, ""); assertEquals(1, savedROIs); assertEquals(1, loadedROIs); @@ -585,6 +588,7 @@ void testTable() throws Exception { client.connect(HOSTNAME, (int) PORT, USERNAME, PASSWORD.toCharArray()); List tables = client.getDataset(1L).getTables(client); client.disconnect(); + assertEquals(1, tables.size()); assertEquals(2, tables.get(0).getRowCount()); assertEquals(size1, tables.get(0).getData(0, 2)); From f41d46741cf632633995611e1c08e6fb3df54c7e Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Thu, 3 Oct 2024 14:55:56 +0200 Subject: [PATCH 04/13] Add option to crop image from ROI ID --- README.md | 9 ++- .../igred/ij/plugin/OMEROMacroExtension.java | 66 ++++++++++++----- .../ij/plugin/OMEROExtensionErrorTest.java | 9 +++ .../igred/ij/plugin/OMEROExtensionTest.java | 70 +++++++++++++------ 4 files changed, 115 insertions(+), 39 deletions(-) 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); From adb94932c3681be88901ed3bfa6bc4214eb7f0b4 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Thu, 3 Oct 2024 16:44:02 +0200 Subject: [PATCH 05/13] Rollback useless variable declaration --- .../igred/ij/plugin/OMEROMacroExtension.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index e8025be..c051a72 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -1457,17 +1457,16 @@ 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; - ImagePlus imp; + long id; + long id1; + long id2; + String type; + String type1; + String type2; + String property; + String tableName; + String path; + String results = null; switch (name) { case "connectToOMERO": String host = ((String) args[0]); @@ -1605,7 +1604,7 @@ public String handleExtension(String name, Object[] args) { case "getImage": id = ((Double) args[0]).longValue(); - imp = getImage(id, (String) args[1]); + ImagePlus imp = getImage(id, (String) args[1]); if (imp != null) { imp.show(); results = String.valueOf(imp.getID()); From e7947383ba8ca47b0b0cc947974571e7fbe1e750 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Tue, 8 Oct 2024 15:23:26 +0200 Subject: [PATCH 06/13] Simplify code --- .../igred/ij/plugin/OMEROMacroExtension.java | 120 ++++++++++-------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index c051a72..7411d9f 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -32,7 +32,6 @@ import fr.igred.omero.repository.PlateWrapper; import fr.igred.omero.repository.ProjectWrapper; import fr.igred.omero.repository.ScreenWrapper; -import fr.igred.omero.repository.WellSampleWrapper; import fr.igred.omero.repository.WellWrapper; import fr.igred.omero.roi.ROIWrapper; import ij.IJ; @@ -58,7 +57,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -305,7 +303,7 @@ private GenericObjectWrapper getObject(String type, long id) { String singularType = singularType(type); GenericObjectWrapper object = null; - if (singularType.equals(TAG)) { + if (TAG.equals(singularType)) { try { object = client.getTag(id); } catch (OMEROServerError | ServiceException e) { @@ -475,31 +473,17 @@ private List> listForScreen(String type, long String singularType = singularType(type); List> objects = new ArrayList<>(0); - ScreenWrapper screen = client.getScreen(id); - List plates = screen.getPlates(); + + ScreenWrapper screen = client.getScreen(id); switch (singularType) { case PLATE: - objects = plates; + objects = screen.getPlates(); break; case WELL: - List wells = new ArrayList<>(); - for (PlateWrapper plate : plates) { - wells.addAll(plate.getWells(client)); - } - wells.sort(Comparator.comparing(GenericObjectWrapper::getId)); - objects = wells; + objects = screen.getWells(client); break; case IMAGE: - List images = new ArrayList<>(); - for (PlateWrapper plate : plates) { - for (WellWrapper well : plate.getWells(client)) { - images.addAll(well.getWellSamples().stream() - .map(WellSampleWrapper::getImage) - .collect(Collectors.toList())); - } - } - images.sort(Comparator.comparing(GenericObjectWrapper::getId)); - objects = images; + objects = screen.getImages(client); break; case TAG: objects = screen.getTags(client); @@ -524,17 +508,14 @@ private List> listForPlate(String type, long i String singularType = singularType(type); List> objects = new ArrayList<>(0); - PlateWrapper plate = client.getPlate(id); + + PlateWrapper plate = client.getPlate(id); switch (singularType) { case WELL: objects = plate.getWells(client); break; case IMAGE: - objects = plate.getWells(client) - .stream() - .flatMap(w -> w.getWellSamples().stream()) - .map(WellSampleWrapper::getImage) - .collect(Collectors.toList()); + objects = plate.getImages(client); break; case TAG: objects = plate.getTags(client); @@ -546,6 +527,60 @@ private List> listForPlate(String type, long i } + /** + * Lists the objects of the specified type inside a well. + * + * @param type The object type. + * @param id The well id. + * + * @return A list of GenericObjectWrappers. + */ + private List> listForWell(String type, long id) + throws AccessException, ServiceException, ExecutionException { + String singularType = singularType(type); + + List> objects = new ArrayList<>(0); + + WellWrapper well = client.getWell(id); + switch (singularType) { + case IMAGE: + objects = well.getImages(); + break; + case TAG: + objects = well.getTags(client); + break; + default: + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "images or tags.")); + break; + } + return objects; + } + + + /** + * Lists the objects of the specified type linked to an image. + * + * @param type The object type. + * @param id The image id. + * + * @return A list of GenericObjectWrappers. + */ + private List> listForImage(String type, long id) + throws AccessException, ServiceException, ExecutionException { + String singularType = singularType(type); + + List> objects = new ArrayList<>(0); + + ImageWrapper image = client.getImage(id); + if (TAG.equals(singularType)) { + objects = image.getTags(client); + } else { + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "tags.")); + } + return objects; + } + + /** * Connects the client to OMERO. * @@ -605,8 +640,7 @@ public String downloadImage(long imageId, String path) { List files = new ArrayList<>(0); try { files = client.getImage(imageId).download(client, path); - } catch (ServiceException | AccessException | OMEROServerError | ExecutionException | - NoSuchElementException e) { + } catch (ServiceException | AccessException | OMEROServerError | ExecutionException | NoSuchElementException e) { IJ.error("Could not download image: " + e.getMessage()); } return files.stream().map(File::toString).collect(Collectors.joining(",")); @@ -987,7 +1021,6 @@ public String list(String type, String name) { */ public String list(String type, String parent, long id) { String singularParent = singularType(parent); - String singularType = singularType(type); Collection> objects = new ArrayList<>(0); try { @@ -1008,23 +1041,10 @@ public String list(String type, String parent, long id) { objects = listForPlate(type, id); break; case WELL: - WellWrapper well = client.getWell(id); - if (IMAGE.equals(singularType)) { - objects = well.getWellSamples().stream() - .map(WellSampleWrapper::getImage) - .collect(Collectors.toList()); - } else if (TAG.equals(singularType)) { - objects = well.getTags(client); - } else { - IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "images or tags.")); - } + objects = listForWell(type, id); break; case IMAGE: - if (TAG.equals(singularType)) { - objects = client.getImage(id).getTags(client); - } else { - IJ.error("Invalid type: " + type + ". Only possible value is: tags."); - } + objects = listForImage(type, id); break; default: String msg = String.format(ERROR_POSSIBLE_VALUES, INVALID, parent, @@ -1090,8 +1110,8 @@ public void link(String type1, long id1, String type2, long id2) { try { // Link tag to repository object - if (t1.equals(TAG) ^ t2.equals(TAG)) { - String obj = t1.equals(TAG) ? t2 : t1; + if (TAG.equals(t1) ^ TAG.equals(t2)) { + String obj = TAG.equals(t1) ? t2 : t1; GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); if (object != null) { @@ -1136,8 +1156,8 @@ public void unlink(String type1, long id1, String type2, long id2) { try { // Unlink tag from repository object - if (t1.equals(TAG) ^ t2.equals(TAG)) { - String obj = t1.equals(TAG) ? t2 : t1; + if (TAG.equals(t1) ^ TAG.equals(t2)) { + String obj = TAG.equals(t1) ? t2 : t1; GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); if (object != null) { From 679558cd5752d38a3d30dd702d6463900834ef96 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Tue, 22 Oct 2024 19:23:07 +0200 Subject: [PATCH 07/13] Add code to retrieve kv-pairs --- .omeroci/test-data | 8 ++ .../igred/ij/plugin/OMEROMacroExtension.java | 112 ++++++++++++++---- .../ij/plugin/OMEROExtensionErrorTest.java | 1 + .../igred/ij/plugin/OMEROExtensionTest.java | 28 ++++- 4 files changed, 122 insertions(+), 27 deletions(-) diff --git a/.omeroci/test-data b/.omeroci/test-data index 0bbb4fc..32fb842 100755 --- a/.omeroci/test-data +++ b/.omeroci/test-data @@ -126,3 +126,11 @@ MAP2=$(omero obj new MapAnnotation) omero obj map-set "$MAP2" mapValue testKey1 testValue2 omero obj map-set "$MAP2" mapValue testKey2 30 omero obj new ImageAnnotationLink parent="$IMAGE2" child="$MAP2" + +MAP3=$(omero obj new MapAnnotation) +omero obj map-set "$MAP3" mapValue key value +omero obj new ProjectAnnotationLink parent="$PROJECT2" child="$MAP3" +omero obj new DatasetAnnotationLink parent="$DATASET3" child="$MAP3" +omero obj new ScreenAnnotationLink parent="$SCREEN1" child="$MAP3" +omero obj new PlateAnnotationLink parent="$PLATE1" child="$MAP3" +omero obj new WellAnnotationLink parent="Well:1" child="$MAP3" diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index 7411d9f..37a8d6b 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -18,6 +18,8 @@ import fr.igred.omero.Client; import fr.igred.omero.GenericObjectWrapper; +import fr.igred.omero.annotations.GenericAnnotationWrapper; +import fr.igred.omero.annotations.MapAnnotationWrapper; import fr.igred.omero.annotations.TableWrapper; import fr.igred.omero.annotations.TagAnnotationWrapper; import fr.igred.omero.exception.AccessException; @@ -82,6 +84,7 @@ public class OMEROMacroExtension implements PlugIn, MacroExtension { private static final String PLATE = "plate"; private static final String WELL = "well"; private static final String TAG = "tag"; + private static final String MAP = "kv-pair"; private static final String INVALID = "Invalid type"; private static final String ERROR_POSSIBLE_VALUES = "%s: %s. Possible values are: %s"; @@ -309,6 +312,12 @@ private GenericObjectWrapper getObject(String type, long id) { } catch (OMEROServerError | ServiceException e) { IJ.error("Could not retrieve tag: " + e.getMessage()); } + } else if (MAP.equals(singularType)) { + try { + object = client.getMapAnnotation(id); + } catch (ServiceException | ExecutionException | AccessException e) { + IJ.error("Could not retrieve tag: " + e.getMessage()); + } } else { object = getRepositoryObject(type, id); } @@ -361,35 +370,35 @@ private GenericRepositoryObjectWrapper getRepositoryObject(String type, long /** * Lists the objects of the specified type linked to a tag. * - * @param type The object type. - * @param id The tag id. + * @param type The object type. + * @param annotation The annotation. * * @return A list of GenericObjectWrappers. */ - private List> listForTag(String type, long id) + private List> listForAnnotation(String type, + GenericAnnotationWrapper annotation) throws ServiceException, OMEROServerError, AccessException, ExecutionException { String singularType = singularType(type); List> objects = new ArrayList<>(0); - TagAnnotationWrapper tag = client.getTag(id); switch (singularType) { case PROJECT: - objects = tag.getProjects(client); + objects = annotation.getProjects(client); break; case DATASET: - objects = tag.getDatasets(client); + objects = annotation.getDatasets(client); break; case IMAGE: - objects = tag.getImages(client); + objects = annotation.getImages(client); break; case SCREEN: - objects = tag.getScreens(client); + objects = annotation.getScreens(client); break; case PLATE: - objects = tag.getPlates(client); + objects = annotation.getPlates(client); break; case WELL: - objects = tag.getWells(client); + objects = annotation.getWells(client); break; default: String msg = String.format(ERROR_POSSIBLE_VALUES, @@ -425,8 +434,11 @@ private List> listForProject(String type, long case TAG: objects = project.getTags(client); break; + case MAP: + objects = project.getMapAnnotations(client); + break; default: - IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "datasets, images or tags.")); + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "datasets, images, tags or kv-pairs.")); } return objects; } @@ -453,8 +465,11 @@ private List> listForDataset(String type, long case TAG: objects = dataset.getTags(client); break; + case MAP: + objects = dataset.getMapAnnotations(client); + break; default: - IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "images or tags.")); + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "images, tags or kv-pairs.")); } return objects; } @@ -488,8 +503,11 @@ private List> listForScreen(String type, long case TAG: objects = screen.getTags(client); break; + case MAP: + objects = screen.getMapAnnotations(client); + break; default: - IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "plates, wells, images or tags.")); + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "plates, wells, images, tags or kv-pairs.")); } return objects; } @@ -520,8 +538,11 @@ private List> listForPlate(String type, long i case TAG: objects = plate.getTags(client); break; + case MAP: + objects = plate.getMapAnnotations(client); + break; default: - IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "wells, images or tags.")); + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "wells, images, tags or kv-pairs.")); } return objects; } @@ -549,8 +570,11 @@ private List> listForWell(String type, long id case TAG: objects = well.getTags(client); break; + case MAP: + objects = well.getMapAnnotations(client); + break; default: - IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "images or tags.")); + IJ.error(String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "images, tags or kv-pairs.")); break; } return objects; @@ -941,6 +965,10 @@ public String list(String type) { List tags = client.getTags(); results = listToIDs(filterUser(tags)); break; + case MAP: + List maps = client.getMapAnnotations(); + results = listToIDs(filterUser(maps)); + break; default: String msg = String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "projects, datasets, images, screens, plates, wells or tags."); @@ -998,6 +1026,10 @@ public String list(String type, String name) { List tags = client.getTags(name); results = listToIDs(filterUser(tags)); break; + case MAP: + List maps = client.getMapAnnotations(name); + results = listToIDs(filterUser(maps)); + break; default: String msg = String.format(ERROR_POSSIBLE_VALUES, INVALID, type, "projects, datasets, images, screens, plates, wells or tags."); @@ -1031,8 +1063,13 @@ public String list(String type, String parent, long id) { case DATASET: objects = listForDataset(type, id); break; + case MAP: + MapAnnotationWrapper map = client.getMapAnnotation(id); + objects = listForAnnotation(type, map); + break; case TAG: - objects = listForTag(type, id); + TagAnnotationWrapper tag = client.getTag(id); + objects = listForAnnotation(type, tag); break; case SCREEN: objects = listForScreen(type, id); @@ -1107,9 +1144,14 @@ public void link(String type1, long id1, String type2, long id2) { Long datasetId = map.get(DATASET); Long imageId = map.get(IMAGE); Long tagId = map.get(TAG); + Long mapId = map.get(MAP); + + if (tagId != null && mapId != null) { + IJ.error(String.format("Cannot link %s and %s", type1, type2)); + } try { - // Link tag to repository object + // Link annotation to repository object if (TAG.equals(t1) ^ TAG.equals(t2)) { String obj = TAG.equals(t1) ? t2 : t1; @@ -1117,6 +1159,13 @@ public void link(String type1, long id1, String type2, long id2) { if (object != null) { object.addTag(client, tagId); } + } else if (MAP.equals(t1) ^ MAP.equals(t2)) { + String obj = MAP.equals(t1) ? t2 : t1; + + GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); + if (object != null) { + object.link(client, client.getMapAnnotation(mapId)); + } } else if (datasetId == null || (projectId == null && imageId == null)) { IJ.error(String.format("Cannot link %s and %s", type1, type2)); } else { // Or link dataset to image or project @@ -1153,25 +1202,38 @@ public void unlink(String type1, long id1, String type2, long id2) { Long datasetId = map.get(DATASET); Long imageId = map.get(IMAGE); Long tagId = map.get(TAG); + Long mapId = map.get(MAP); + + boolean linkAnnotations = tagId != null && mapId != null; + boolean tagObject = TAG.equals(t1) ^ TAG.equals(t2) && !linkAnnotations; + boolean mapObject = MAP.equals(t1) ^ MAP.equals(t2) && !linkAnnotations; + boolean linkDataset = datasetId != null && (projectId != null || imageId != null); try { - // Unlink tag from repository object - if (TAG.equals(t1) ^ TAG.equals(t2)) { + // Unlink annotation from repository object + if (tagObject) { String obj = TAG.equals(t1) ? t2 : t1; GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); if (object != null) { object.unlink(client, client.getTag(tagId)); } - } else if (datasetId == null || (projectId == null && imageId == null)) { - IJ.error(String.format("Cannot unlink %s and %s", type1, type2)); - } else { // Or unlink dataset from image or project + } else if (mapObject) { + String obj = MAP.equals(t1) ? t2 : t1; + + GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); + if (object != null) { + object.unlink(client, client.getMapAnnotation(mapId)); + } + } else if (linkDataset) { // Or unlink dataset from image or project DatasetWrapper dataset = client.getDataset(datasetId); if (projectId != null) { client.getProject(projectId).removeDataset(client, dataset); } else { dataset.removeImage(client, client.getImage(imageId)); } + } else { + IJ.error(String.format("Cannot unlink %s and %s", type1, type2)); } } catch (ServiceException | AccessException | ExecutionException | OMEROServerError e) { IJ.error(String.format("Cannot unlink %s and %s: %s", type1, type2, e.getMessage())); @@ -1198,6 +1260,12 @@ public String getName(String type, long id) { name = ((GenericRepositoryObjectWrapper) object).getName(); } else if (object instanceof TagAnnotationWrapper) { name = ((TagAnnotationWrapper) object).getName(); + } else if (object instanceof MapAnnotationWrapper) { + MapAnnotationWrapper map = (MapAnnotationWrapper) object; + name = map.getContentAsEntryList() + .stream() + .map(e -> e.getKey() + "\t" + e.getValue()) + .collect(Collectors.joining(String.format("%n"))); } return name; } diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java index ec9ebcf..e0b3702 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java @@ -199,6 +199,7 @@ void testListInvalidArgs() { "hello;plate;2", "hello;well;2", "hello;tag;2", + "hello;kv-pair;2", "hello;TestDatasetImport;", "hello;;",}) void testListInvalidType(String type1, String type2, Double id) { diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java index d3df337..4c96e16 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java @@ -117,7 +117,9 @@ void testListForUser(String user, double output) { "list;plates;1,2,3", "list;wells;1,2,3,4,5", "list;tags;1,2,3", - "list;tag;1,2,3",}) + "list;tag;1,2,3", + "list;kv-pairs;4,5,6", + "list;kv-pair;4,5,6",}) void testListAll(String extension, String type, String output) { Object[] args = {type, null, null}; String result = ext.handleExtension(extension, args); @@ -135,7 +137,9 @@ void testListAll(String extension, String type, String output) { "list;screen;TestScreen;1", "list;plate;Plate Name 0;1,2", "list;tags;tag2;2", - "list;tag;tag2;2",}) + "list;tag;tag2;2", + "list;kv-pairs;testKey1;4,5", + "list;kv-pair;testKey1;4,5",}) void testListByName(String extension, String type, String name, String output) { Object[] args = {type, name, null}; String result = ext.handleExtension(extension, args); @@ -161,9 +165,16 @@ void testListByName(String extension, String type, String name, String output) { "list;screens;tag;1.0;1", "list;plates;tag;1.0;1", "list;wells;tag;1.0;1", + "list;tags;project;2.0;1", + "list;tags;dataset;3.0;1", "list;tags;screen;1.0;1", "list;tags;plate;1.0;1", "list;tags;well;1.0;1", + "list;kv-pairs;project;2.0;6", + "list;kv-pairs;dataset;3.0;6", + "list;kv-pairs;screen;1.0;6", + "list;kv-pairs;plate;1.0;6", + "list;kv-pairs;well;1.0;6", "list;plates;screen;2.0;2,3", "list;wells;screen;2.0;2,3,4,5", "list;images;screen;1.0;5,6", @@ -174,7 +185,8 @@ void testListFrom(String extension, String type, String parent, double id, Strin Object[] args = {type, parent, id}; String result = ext.handleExtension(extension, args); - String sortedIds = Arrays.stream(result.split(",")).map(Long::parseLong).sorted() + String sortedIds = Arrays.stream(result.split(",")) + .map(Long::parseLong).sorted() .map(String::valueOf) .collect(Collectors.joining(",")); @@ -192,7 +204,8 @@ void testListFrom(String extension, String type, String parent, double id, Strin "getName;screen;1.0;TestScreen", "getName;plate;2.0;Plate Name 0", "getName;well;1.0;Well A-1", - "getName;tag;1.0;tag1",}) + "getName;tag;1.0;tag1", + "getName;kv-pair;6.0;key\tvalue",}) void testGetName(String extension, String type, double id, String output) { Object[] args = {type, id}; String result = ext.handleExtension(extension, args); @@ -239,11 +252,16 @@ void testCreateAndLinkTag() { @ParameterizedTest - @CsvSource(delimiter = ';', value = {"tag;1.0;project;2.0", + @CsvSource(delimiter = ';', value = {"project;2.0;tag;1.0", "tag;1.0;dataset;3.0", "tag;1.0;screen;1.0", "tag;1.0;plate;1.0", "tag;1.0;well;1.0", + "project;2.0;kv-pair;6.0", + "kv-pair;6.0;dataset;3.0", + "kv-pair;6.0;screen;1.0", + "kv-pair;6.0;plate;1.0", + "kv-pair;6.0;well;1.0", "dataset;3.0;project;2.0", "image;1.0;dataset;1.0",}) void testUnlinkThenLink(String type1, double id1, String type2, double id2) { From 703b84aac34294b431827c05ff25dd4be6116a52 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Thu, 24 Oct 2024 14:44:16 +0200 Subject: [PATCH 08/13] Add private method to retrieve annotations --- .../igred/ij/plugin/OMEROMacroExtension.java | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index 37a8d6b..e9a810d 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -305,19 +305,9 @@ private > List filterUser(List list) { private GenericObjectWrapper getObject(String type, long id) { String singularType = singularType(type); - GenericObjectWrapper object = null; - if (TAG.equals(singularType)) { - try { - object = client.getTag(id); - } catch (OMEROServerError | ServiceException e) { - IJ.error("Could not retrieve tag: " + e.getMessage()); - } - } else if (MAP.equals(singularType)) { - try { - object = client.getMapAnnotation(id); - } catch (ServiceException | ExecutionException | AccessException e) { - IJ.error("Could not retrieve tag: " + e.getMessage()); - } + GenericObjectWrapper object; + if (TAG.equals(singularType) || MAP.equals(singularType)) { + object = getAnnotation(singularType, id); } else { object = getRepositoryObject(type, id); } @@ -325,6 +315,36 @@ private GenericObjectWrapper getObject(String type, long id) { } + /** + * Retrieves the annotation of the specified type with the specified ID. + * + * @param type The type of annotation. + * @param id The object ID. + * + * @return The object. + */ + private GenericAnnotationWrapper getAnnotation(String type, long id) { + String singularType = singularType(type); + + GenericAnnotationWrapper annotation = null; + try { + switch (singularType) { + case TAG: + annotation = client.getTag(id); + break; + case MAP: + annotation = client.getMapAnnotation(id); + break; + default: + IJ.error(INVALID + ": " + type + "."); + } + } catch (OMEROServerError | ServiceException | ExecutionException | AccessException e) { + IJ.error(String.format("Could not retrieve %s: %s", singularType, e.getMessage())); + } + return annotation; + } + + /** * Retrieves the repository object of the specified type with the specified ID. * @@ -361,7 +381,7 @@ private GenericRepositoryObjectWrapper getRepositoryObject(String type, long IJ.error(INVALID + ": " + type + "."); } } catch (ServiceException | AccessException | ExecutionException e) { - IJ.error("Could not retrieve object: " + e.getMessage()); + IJ.error(String.format("Could not retrieve %s: %s", singularType, e.getMessage())); } return object; } From c556360440197b688bf527d6ae49165886d8ece5 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 15 Nov 2024 00:10:01 +0100 Subject: [PATCH 09/13] Refactor link/unlink methods --- .../igred/ij/plugin/OMEROMacroExtension.java | 129 ++++++++++++------ .../ij/plugin/OMEROExtensionErrorTest.java | 2 + 2 files changed, 88 insertions(+), 43 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index e9a810d..b7433db 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -275,6 +275,52 @@ private static Bounds extractBounds(CharSequence bounds) { } + /** + * Determines if the link between the referenced objects and annotations is invalid. + * + * @param objects The objects map (associating types and ids). + * @param annotations The annotations map (associating types and ids). + * + * @return True if the link is invalid, false otherwise. + */ + private boolean isInvalidLink(Map objects, + Map annotations) { + Long projectId = objects.get(PROJECT); + Long imageId = objects.get(IMAGE); + Long screenId = objects.get(SCREEN); + Long plateId = objects.get(PLATE); + Long wellId = objects.get(WELL); + + Map tree = new HashMap<>(2); + tree.computeIfAbsent(PROJECT, objects::get); + tree.computeIfAbsent(DATASET, objects::get); + + Map hcs = new HashMap<>(3); + hcs.computeIfAbsent(SCREEN, objects::get); + hcs.computeIfAbsent(PLATE, objects::get); + hcs.computeIfAbsent(WELL, objects::get); + + int nObjects = objects.values().size(); + int nAnnotations = annotations.values().size(); + int nTree = tree.values().size(); + int nHCS = hcs.values().size(); + + boolean linkNotTwo = nObjects + nAnnotations != 2; + boolean linkAnnotations = nAnnotations == 2; + boolean linkTreeHCS = nTree == 1 && nHCS == 1; + + boolean linkScreenWell = screenId != null && wellId != null; + boolean linkImageBad = imageId != null && + (projectId != null || screenId != null || plateId != null); + + return linkNotTwo || + linkAnnotations || + linkImageBad || + linkScreenWell || + linkTreeHCS; + } + + /** * Filters the objects list to only keep objects from the set user. * @@ -1160,35 +1206,34 @@ public void link(String type1, long id1, String type2, long id2) { map.put(t1, id1); map.put(t2, id2); - Long projectId = map.get(PROJECT); - Long datasetId = map.get(DATASET); - Long imageId = map.get(IMAGE); - Long tagId = map.get(TAG); - Long mapId = map.get(MAP); + Map annMap = new HashMap<>(1); + annMap.computeIfAbsent(TAG, map::get); + annMap.computeIfAbsent(MAP, map::get); - if (tagId != null && mapId != null) { - IJ.error(String.format("Cannot link %s and %s", type1, type2)); - } + Map objMap = new HashMap<>(2); + objMap.computeIfAbsent(PROJECT, map::get); + objMap.computeIfAbsent(DATASET, map::get); + objMap.computeIfAbsent(IMAGE, map::get); + objMap.computeIfAbsent(WELL, map::get); + objMap.computeIfAbsent(PLATE, map::get); + objMap.computeIfAbsent(SCREEN, map::get); try { - // Link annotation to repository object - if (TAG.equals(t1) ^ TAG.equals(t2)) { - String obj = TAG.equals(t1) ? t2 : t1; - - GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); - if (object != null) { - object.addTag(client, tagId); - } - } else if (MAP.equals(t1) ^ MAP.equals(t2)) { - String obj = MAP.equals(t1) ? t2 : t1; + if (isInvalidLink(objMap, annMap)) { + IJ.error(String.format("Cannot link %s and %s", type1, type2)); + } else if (annMap.size() == 1) { // Link annotation to repository object + String ann = annMap.keySet().iterator().next(); + String obj = ann.equals(t1) ? t2 : t1; GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); if (object != null) { - object.link(client, client.getMapAnnotation(mapId)); + object.link(client, getAnnotation(ann, annMap.get(ann))); } - } else if (datasetId == null || (projectId == null && imageId == null)) { - IJ.error(String.format("Cannot link %s and %s", type1, type2)); } else { // Or link dataset to image or project + Long projectId = map.get(PROJECT); + Long datasetId = map.get(DATASET); + Long imageId = map.get(IMAGE); + DatasetWrapper dataset = client.getDataset(datasetId); if (projectId != null) { client.getProject(projectId).addDataset(client, dataset); @@ -1218,42 +1263,40 @@ public void unlink(String type1, long id1, String type2, long id2) { map.put(t1, id1); map.put(t2, id2); - Long projectId = map.get(PROJECT); - Long datasetId = map.get(DATASET); - Long imageId = map.get(IMAGE); - Long tagId = map.get(TAG); - Long mapId = map.get(MAP); + Map objMap = new HashMap<>(2); + objMap.computeIfAbsent(PROJECT, map::get); + objMap.computeIfAbsent(DATASET, map::get); + objMap.computeIfAbsent(IMAGE, map::get); + objMap.computeIfAbsent(WELL, map::get); + objMap.computeIfAbsent(PLATE, map::get); + objMap.computeIfAbsent(SCREEN, map::get); - boolean linkAnnotations = tagId != null && mapId != null; - boolean tagObject = TAG.equals(t1) ^ TAG.equals(t2) && !linkAnnotations; - boolean mapObject = MAP.equals(t1) ^ MAP.equals(t2) && !linkAnnotations; - boolean linkDataset = datasetId != null && (projectId != null || imageId != null); + Map annMap = new HashMap<>(1); + annMap.computeIfAbsent(TAG, map::get); + annMap.computeIfAbsent(MAP, map::get); try { - // Unlink annotation from repository object - if (tagObject) { - String obj = TAG.equals(t1) ? t2 : t1; + if (isInvalidLink(objMap, annMap)) { + IJ.error(String.format("Cannot unlink %s and %s", type1, type2)); + } else if (annMap.size() == 1) { // Unlink annotation from repository object + String ann = annMap.keySet().iterator().next(); + String obj = ann.equals(t1) ? t2 : t1; GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); if (object != null) { - object.unlink(client, client.getTag(tagId)); + object.unlink(client, getAnnotation(ann, annMap.get(ann))); } - } else if (mapObject) { - String obj = MAP.equals(t1) ? t2 : t1; + } else { // Or unlink dataset from image or project + Long projectId = map.get(PROJECT); + Long datasetId = map.get(DATASET); + Long imageId = map.get(IMAGE); - GenericRepositoryObjectWrapper object = getRepositoryObject(obj, map.get(obj)); - if (object != null) { - object.unlink(client, client.getMapAnnotation(mapId)); - } - } else if (linkDataset) { // Or unlink dataset from image or project DatasetWrapper dataset = client.getDataset(datasetId); if (projectId != null) { client.getProject(projectId).removeDataset(client, dataset); } else { dataset.removeImage(client, client.getImage(imageId)); } - } else { - IJ.error(String.format("Cannot unlink %s and %s", type1, type2)); } } catch (ServiceException | AccessException | ExecutionException | OMEROServerError e) { IJ.error(String.format("Cannot unlink %s and %s: %s", type1, type2, e.getMessage())); diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java index e0b3702..eec6c4c 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java @@ -19,6 +19,7 @@ import ij.measure.ResultsTable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -213,6 +214,7 @@ void testListInvalidType(String type1, String type2, Double id) { @ParameterizedTest @ValueSource(strings = {"link", "unlink"}) + @Disabled("Methods no longer try to link or unlink invalid types currently") void testLinkUnlinkInvalidType(String function) { Object[] args = {"tag", 1.0, "hello", 1.0}; ext.handleExtension(function, args); From c18bf620b716c9df90e948e4ba2e3c18e9d4ace3 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 15 Nov 2024 11:04:08 +0100 Subject: [PATCH 10/13] Add method to create kv-pair --- .../igred/ij/plugin/OMEROMacroExtension.java | 28 ++++++++++++++++++- .../igred/ij/plugin/OMEROExtensionTest.java | 14 ++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index b7433db..f3536d9 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -98,6 +98,7 @@ public class OMEROMacroExtension implements PlugIn, MacroExtension { newDescriptor("createDataset", this, ARG_STRING, ARG_STRING, ARG_NUMBER + ARG_OPTIONAL), newDescriptor("createProject", this, ARG_STRING, ARG_STRING), newDescriptor("createTag", this, ARG_STRING, ARG_STRING), + newDescriptor("createKeyValuePair", this, ARG_STRING, ARG_STRING), newDescriptor("link", this, ARG_STRING, ARG_NUMBER, ARG_STRING, ARG_NUMBER), newDescriptor("unlink", this, ARG_STRING, ARG_NUMBER, ARG_STRING, ARG_NUMBER), newDescriptor("addFile", this, ARG_STRING, ARG_NUMBER, ARG_STRING), @@ -922,6 +923,27 @@ public long createTag(String name, String description) { } + /** + * Creates a key-value pair on OMERO. + * + * @param key The key. + * @param value The value. + * + * @return The kv-pair ID. + */ + public long createKeyValuePair(String key, String value) { + long id = -1; + try { + MapAnnotationWrapper pair = new MapAnnotationWrapper(key, value); + pair.saveAndUpdate(client); + id = pair.getId(); + } catch (ServiceException | AccessException | ExecutionException e) { + IJ.error("Could not create kv-pair: " + e.getMessage()); + } + return id; + } + + /** * Creates a project on OMERO. * @@ -1546,7 +1568,6 @@ public String getValue(String type, long id, String key, String defaultValue) { * @return The number of ROIs that were deleted. */ public int removeROIs(long id) { - int removed = 0; try { ImageWrapper image = client.getImage(id); @@ -1678,6 +1699,11 @@ public String handleExtension(String name, Object[] args) { results = String.valueOf(tagId); break; + case "createKeyValuePair": + long pairId = createKeyValuePair((String) args[0], (String) args[1]); + results = String.valueOf(pairId); + break; + case "addToTable": tableName = (String) args[0]; String resultsName = (String) args[1]; diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java index 4c96e16..7bf2418 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionTest.java @@ -251,6 +251,20 @@ void testCreateAndLinkTag() { } + @Test + void testCreateAndLinkKVPair() { + final double projectId = 2; + Object[] args = {"TestKey", "TestValue"}; + String result = ext.handleExtension("createKeyValuePair", args); + Double id = Double.parseDouble(result); + Object[] args2 = {"kv-pair", id, "project", projectId}; + ext.handleExtension("link", args2); + Object[] args3 = {"kv-pair", id}; + ext.handleExtension("delete", args3); + assertNotNull(id); + } + + @ParameterizedTest @CsvSource(delimiter = ';', value = {"project;2.0;tag;1.0", "tag;1.0;dataset;3.0", From d08bda83170a0017ec784c0dc4a3d768b8d64887 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 15 Nov 2024 11:07:06 +0100 Subject: [PATCH 11/13] Update documentation --- README.md | 49 ++++++++++++++++++++++++++++++++---- src/main/resources/helper.md | 17 +++++++++++-- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c6389a3..40abc56 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ A plugin for ImageJ to provide macro extensions to access OMERO. 1. Install the [OMERO.insight plugin](https://omero-guides.readthedocs.io/en/latest/fiji/docs/installation.html) (if you haven't already). -2. Download the JAR file for this [library](https://github.com/GReD-Clermont/simple-omero-client/releases/tag/5.16.0/). -3. Download the JAR file ([for this plugin](https://github.com/GReD-Clermont/omero_macro-extensions/releases/tag/1.3.3/)). +2. Download the JAR file for this [library](https://github.com/GReD-Clermont/simple-omero-client/releases/tag/5.19.0/). +3. Download the JAR file ([for this plugin](https://github.com/GReD-Clermont/omero_macro-extensions/releases/tag/1.4.0/)). 4. Place these JAR files in your plugins folder. ## How to use @@ -121,6 +121,16 @@ tagName = Ext.getName("tag", tagIds[0]); print(tagName); ``` +* Key-Value pairs: + +``` +kvpairs = Ext.list("kv-pairs"); +print(kvpairs); +kvIds = split(kvpairs,","); +kvName = Ext.getName("kvName", kvIds[0]); +print(kvName); +``` + ### Listing objects with a given name It is also possible to list objects with a specific name: @@ -185,6 +195,26 @@ tagName = Ext.getName("tag", tagIds[0]); print(tagName); ``` +Key-Value pairs can also be listed for a given object: + +``` +pairs = Ext.list("kv-pairs", "image", imageIds[0]); +print(pairs); +pairIds = split(pairs,","); +kvs = Ext.getName("kv-pair", pairIds[0]); +print(kvs); +``` +or: +``` +kvpairs = Ext.getKeyValuePairs("image", imageIds[0]); +print(kvpairs); +``` +or for a given key: +``` +values = Ext.getValues("image", imageIds[0], "key", defaultValue); +print(values); +``` + Similarly, a dataset ID can be used to retrieve its images: ``` @@ -222,7 +252,7 @@ imageName = Ext.getName("image", imageIds[0]); print(imageName); ``` -### Creating projects, datasets and tags +### Creating projects, datasets, tags and key-value pairs Projects can be created with *Ext.createProject*: @@ -242,6 +272,12 @@ Tags can be created with *Ext.createTag*: tagId = Ext.createTag(name, description); ``` +Key-value pairs can be created with *Ext.createKeyValuePair*: + +``` +pairId = Ext.createKeyValuePair(key, value); +``` + ### Linking/unlinking objects Objects can be linked with *Ext.link*, e.g.: @@ -253,7 +289,7 @@ Ext.link("dataset", datasetId, "tag", tagId); They can also be unlinked with *Ext.unlink*, e.g.: ``` -Ext.unlink("dataset", datasetId, "tag", tagId); +Ext.unlink("dataset", datasetId, "kv-pair", pairId); ``` ### Deleting objects @@ -272,7 +308,10 @@ Pixel intensities can be retrieved from images: imageplusID = Ext.getImage(imageIds[0]); ``` -Images can also be cropped on import by specifying the ROI in one of two ways: +Images can also be cropped on import. In this case, image properties containing the position of the crop in the original +image can be retrieved (IMAGE_POS_X, IMAGE_POS_Y, etc.). + +This crop can be done 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")); diff --git a/src/main/resources/helper.md b/src/main/resources/helper.md index 81daa86..f2c9686 100644 --- a/src/main/resources/helper.md +++ b/src/main/resources/helper.md @@ -47,6 +47,7 @@ Ext.list(type, parentType, parentId) Ext.getName(type, id) > Gets the name of the specified object, given its `type` and `id`. +> In the case of key-value pairs, it returns keys and values separated by tabs, one pair per line. Ext.getImage(id) > Opens the image with the given `id`. @@ -54,9 +55,17 @@ Ext.getImage(id) Ext.getImage(id, region) > Opens a subregion of the image with the given `id`. -> The region is specified as a string in the format "x:start:end,y:start:end,...". +> The region is specified in one of two ways: +> 1. using a ROI ID in OMERO +> 2. as a string in the format "x:start:end,y:start:end,...". > Returns the image ID in ImageJ. +Ext.getKeyValuePairs(type, id) +> Returns the key-value pairs attached to the object with the given `type` and `id`. + +Ext.getValue(type, id, key, defaultValue) +> Returns the value for the given key attached to the object with the given `type` and `id`. + Ext.getROIs(imageId, toOverlay, property) > Retrieves the ROIs for the image with the given `imageId`. > These are added to the ROI manager by default. @@ -74,6 +83,10 @@ Ext.getROIs(imageId, toOverlay, property) ### Saves data ### +Ext.createKeyValuePair(key, value) +> Creates a new key-value pair with the given `key` and `value`. +> Returns the new pair ID. + Ext.createTag(name, description) > Creates a new tag with the given `name` and `description`. > Returns the new tag ID. @@ -110,7 +123,7 @@ Ext.link(type1, id1, type2, id2) > Possible types are: > * Project and Dataset > * Dataset and Image -> * Tag and Project, Dataset or Image +> * Tag or KV-pair and Project, Dataset, Screen, Plate, Well or Image ### Removes data on OMERO ### From 8dac9afc43864bbcb8966c95e3e9f4caee30ff29 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 15 Nov 2024 14:27:21 +0100 Subject: [PATCH 12/13] Adjust valid links and related test --- .../igred/ij/plugin/OMEROMacroExtension.java | 19 ++++--------------- .../ij/plugin/OMEROExtensionErrorTest.java | 13 +++++++++---- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index f3536d9..d7b47c2 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -288,13 +288,6 @@ private boolean isInvalidLink(Map objects, Map annotations) { Long projectId = objects.get(PROJECT); Long imageId = objects.get(IMAGE); - Long screenId = objects.get(SCREEN); - Long plateId = objects.get(PLATE); - Long wellId = objects.get(WELL); - - Map tree = new HashMap<>(2); - tree.computeIfAbsent(PROJECT, objects::get); - tree.computeIfAbsent(DATASET, objects::get); Map hcs = new HashMap<>(3); hcs.computeIfAbsent(SCREEN, objects::get); @@ -303,22 +296,18 @@ private boolean isInvalidLink(Map objects, int nObjects = objects.values().size(); int nAnnotations = annotations.values().size(); - int nTree = tree.values().size(); int nHCS = hcs.values().size(); boolean linkNotTwo = nObjects + nAnnotations != 2; boolean linkAnnotations = nAnnotations == 2; - boolean linkTreeHCS = nTree == 1 && nHCS == 1; + boolean linkObjectHCS = nHCS >= 1 && nAnnotations == 0; - boolean linkScreenWell = screenId != null && wellId != null; - boolean linkImageBad = imageId != null && - (projectId != null || screenId != null || plateId != null); + boolean linkProjectImage = imageId != null && projectId != null; return linkNotTwo || linkAnnotations || - linkImageBad || - linkScreenWell || - linkTreeHCS; + linkObjectHCS || + linkProjectImage; } diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java index eec6c4c..402de8e 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java @@ -224,11 +224,16 @@ void testLinkUnlinkInvalidType(String function) { @ParameterizedTest - @ValueSource(strings = {"link", "unlink"}) - void testCannotLinkOrUnlink(String function) { - Object[] args = {"hello", 1.0, "world", 1.0}; + @CsvSource(delimiter = ';', value = {"link;hello;1.0;world;1.0", + "unlink;hello;1.0;world;1.0", + "link;image;image", + "link;image;project", + "link;image;screen", + "link;tag;kv-pair"}) + void testCannotLinkOrUnlink(String function, String type1, String type2) { + Object[] args = {type1, 1.0, type2, 1.0}; ext.handleExtension(function, args); - String expected = String.format("Cannot %s hello and world", function); + String expected = String.format("Cannot %s %s and %s", function, type1, type2); assertEquals(expected, outContent.toString().trim()); } From 7d41795723a3d0c8ce269863695f8d94b80b3761 Mon Sep 17 00:00:00 2001 From: Pierre Pouchin Date: Fri, 15 Nov 2024 14:58:19 +0100 Subject: [PATCH 13/13] Add getName test with invalid type (and handle it) --- .../igred/ij/plugin/OMEROMacroExtension.java | 24 ++++++++++--------- .../ij/plugin/OMEROExtensionErrorTest.java | 16 +++++++++++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java index d7b47c2..d6c7770 100644 --- a/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java +++ b/src/main/java/fr/igred/ij/plugin/OMEROMacroExtension.java @@ -1327,19 +1327,21 @@ public void unlink(String type1, long id1, String type2, long id2) { * @return The object name. */ public String getName(String type, long id) { - String name = null; + String name = ""; GenericObjectWrapper object = getObject(type, id); - if (object instanceof GenericRepositoryObjectWrapper) { - name = ((GenericRepositoryObjectWrapper) object).getName(); - } else if (object instanceof TagAnnotationWrapper) { - name = ((TagAnnotationWrapper) object).getName(); - } else if (object instanceof MapAnnotationWrapper) { - MapAnnotationWrapper map = (MapAnnotationWrapper) object; - name = map.getContentAsEntryList() - .stream() - .map(e -> e.getKey() + "\t" + e.getValue()) - .collect(Collectors.joining(String.format("%n"))); + if (object != null) { + if (object instanceof GenericRepositoryObjectWrapper) { + name = ((GenericRepositoryObjectWrapper) object).getName(); + } else if (object instanceof TagAnnotationWrapper) { + name = ((TagAnnotationWrapper) object).getName(); + } else if (object instanceof MapAnnotationWrapper) { + MapAnnotationWrapper map = (MapAnnotationWrapper) object; + name = map.getContentAsEntryList() + .stream() + .map(e -> e.getKey() + "\t" + e.getValue()) + .collect(Collectors.joining(String.format("%n"))); + } } return name; } diff --git a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java index 402de8e..0ea5174 100644 --- a/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java +++ b/src/test/java/fr/igred/ij/plugin/OMEROExtensionErrorTest.java @@ -212,6 +212,17 @@ void testListInvalidType(String type1, String type2, Double id) { } + @Test + void testGetNameInvalidType() { + String type = "hello"; + Object[] args = {type, 1.0}; + String output = ext.handleExtension("getName", args); + String error = outContent.toString().trim(); + assertTrue(output.isEmpty()); + assertTrue(error.startsWith("Invalid type: hello.")); + } + + @ParameterizedTest @ValueSource(strings = {"link", "unlink"}) @Disabled("Methods no longer try to link or unlink invalid types currently") @@ -237,11 +248,12 @@ void testCannotLinkOrUnlink(String function, String type1, String type2) { assertEquals(expected, outContent.toString().trim()); } + @Test void testKeyNotExist() { - final String key = "notExist"; + final String key = "notExist"; final double imageId = 2; - Object[] args = {"image", imageId, key, null}; + Object[] args = {"image", imageId, key, null}; ext.handleExtension("getValue", args); String expected = "Could not retrieve value: Key \"" + key + "\" not found"; assertEquals(expected, outContent.toString().trim());