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) {