diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 856c5a57..1bc91cd6 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -14,6 +14,8 @@ [omero-insight]: https://github.com/ome/omero-insight """ +import os + from fr.igred.omero import Client from fr.igred.omero.annotations import ( MapAnnotationWrapper, @@ -27,14 +29,77 @@ from omero.gateway.model import TableData, TableDataColumn +def parse_url_string(url_str): + """Extract IDs from an OMERO URL. + + Info: URLs can only be of the same type (Dataset, Project, Images). + Returns tuple[empty list, ""] if input str (URL) is not an OMERO URL. + + Parameters + ---------- + url_str : str + OMERO URL, e.g. from the webclient. + + Returns + ------- + tuple[list[str], str] + - List of IDs extracted from the OMERO URL. + - Type of IDs (i.e. "project", "dataset", or "image") + """ + omero_types = ["dataset-", "project-", "image-"] + + for omero_type in omero_types: + omero_ids = extract_ids_from_string(url_str, omero_type) + if len(omero_ids) > 0: + return omero_ids, omero_type.strip("-") + # If no IDs identified + return [], "" + + +def extract_ids_from_string(omero_str, omero_type): + """Extract IDs from an OMERO URL for a given OMERO type. + + Parameters + ---------- + omero_str : str + OMERO URL, e.g. from the webclient. + omero_type : str + OMERO type, i.e. "project", "Dataset", or "Image" # FIXME + + Returns + ------- + list[str] + List of IDs extracted from the OMERO URL. + """ + omero_type = omero_type.lower().strip("-") + if omero_type not in ["project", "dataset", "image"]: + raise ValueError("Unknown OMERO type: {}".format(omero_type)) + + omero_ids = [] + omero_type = omero_type + "-" + if omero_type in omero_str: + # If there are multiple types + if "|" in omero_str: + parts = omero_str.split("|") + for part in parts: + if omero_type in part: + omero_id = part.split(omero_type)[1].split("/")[0] + omero_ids.append(omero_id) + else: + # If there is only one dataset + omero_id = omero_str.split(omero_type)[1].split("/")[0] + omero_ids.append(omero_id) + return omero_ids + + def parse_url(client, omero_str): """Parse an OMERO URL / image IDs into the respective ImageWrapper objects. Assemble a list of ImageWrapper objects from one of the following inputs: - - An OMERO URL (as retrieved e.g. from OMERO.web). + - An OMERO URL for Dataset(s) or Image(s) + (as retrieved e.g. from OMERO.web). - One or more OMERO image IDs. - - An OMERO Dataset ID. Parameters ---------- @@ -63,62 +128,31 @@ def parse_url(client, omero_str): # Sanitize the string omero_str = omero_str.strip() - image_ids = [] - dataset_ids = [] - image_wpr_list = [] + # Get a list of str IDs and their OMERO type + omero_ids, omero_type = parse_url_string(omero_str) - # Check if the url is a dataset - if "dataset-" in omero_str: - # If there are multiple datasets - if "|" in omero_str: - parts = omero_str.split("|") - for part in parts: - if "dataset-" in part: - image_ids.extend( - [ - image - for image in client.getDataset( - Long(part.split("dataset-")[1].split("/")[0]) - ).getImages() - ] - ) - dataset_id = Long(part.split("dataset-")[1].split("/")[0]) - dataset_ids.append(dataset_id) - else: - image_ids.extend( - [ - image - for image in client.getDataset( - Long(omero_str.split("dataset-")[1].split("/")[0]) - ).getImages() - ] - ) - # If there is only one dataset - dataset_id = Long(omero_str.split("dataset-")[1].split("/")[0]) - dataset_ids.append(dataset_id) + if omero_type == "project": + raise NotImplementedError( + "Getting Image IDs from Project(s) is not implemented." + ) - # Get the images from the dataset - for dataset_id in dataset_ids: - dataset_wpr = client.getDataset(dataset_id) + elif omero_type == "dataset": + image_wpr_list = [] + # Get the images from the dataset(s) + for omero_id in omero_ids: + dataset_wpr = client.getDataset(Long(omero_id)) image_wpr_list.extend(dataset_wpr.getImages()) - return image_wpr_list - # Check if the url is an image - elif "image-" in omero_str: - image_ids = omero_str.split("image-") - image_ids.pop(0) - image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] + elif omero_type == "image": + # omero_ids will be processed at the end + pass + else: - image_ids = ( - [s.split("%")[0].replace("|", "") for s in omero_str.split("image-")[1:]] - if "image-" in omero_str - else omero_str.split(",") - ) - # If it is a list of IDs separated by commas - image_ids = omero_str.split(",") + # (omero_type == "") supposedly a list of IDs separated by commas + omero_ids = omero_str.split(",") - return [client.getImage(Long(image_id)) for image_id in image_ids] + return [client.getImage(Long(image_id)) for image_id in omero_ids] def connect(host, port, username, password): @@ -190,6 +224,12 @@ def upload_image_to_omero(user_client, path, dataset_id): Long ID of the uploaded image """ + if not os.path.exists(path): + try: + raise FileNotFoundError("Could not find file: {}".format(path)) + except NameError: + raise IOError("Could not find file: {}".format(path)) + return user_client.getDataset(Long(dataset_id)).importImage(user_client, path)[0] @@ -201,7 +241,7 @@ def add_keyvalue_annotation(client, repository_wpr, annotations, header): client : fr.igred.omero.Client The client object used to connect to the OMERO server. repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper - Wrapper to the object for the anotation. + Wrapper to the object for the annotation. annotations : dict Dictionary with the annotation to add. header : str @@ -222,7 +262,7 @@ def delete_keyvalue_annotations(user_client, object_wrapper): user_client : fr.igred.omero.Client Client used for login to OMERO. object_wrapper : fr.igred.omero.repositor.GenericRepositoryObjectWrapper - Wrapper to the object for the anotation. + Wrapper to the object for the annotation. """ kv_pairs = object_wrapper.getMapAnnotations(user_client) diff --git a/tests/interactive-imagej/omerotools/parse_url/test_parse_url_01.py b/tests/interactive-imagej/omerotools/parse_url/test_parse_url_01.py new file mode 100644 index 00000000..327eeb6e --- /dev/null +++ b/tests/interactive-imagej/omerotools/parse_url/test_parse_url_01.py @@ -0,0 +1,58 @@ +# @ String (label="OMERO HOST") host +# @ Integer (label="OMERO PORT", value=4064) port +# @ String (label="OMERO User") user +# @ String (label="OMERO Password", style="password") pwd +# @ String (label="Provide OMERO Project URL") url_project +# @ String (label="Provide OMERO 'Dataset A' URL") url_dataset +# @ String (label="Provide OMERO URL for ALL images in 'Dataset A'") url_images +# Examples +# url_project: https://omero.biozentrum.unibas.ch/webclient/?show=project-25155 +# url_dataset: https://omero.biozentrum.unibas.ch/webclient/?show=dataset-82170 +# url_images: https://omero.biozentrum.unibas.ch/webclient/?show=image-3454870|image-3454871|image-3454872|image-3454873|image-3454874 + + +from imcflibs.imagej import omerotools + +# Parse the url_images to get the IDs +image_ids = [] +parts = url_images.split("|") +for part in parts: + if "image-" in part: + image_id = part.split("image-")[1].split("/")[0] + image_ids.append(image_id) +assert len(image_ids) > 0, "No image ids in image url!" + +# Test +try: + user_client = omerotools.connect(host, port, user, pwd) + + # Project URLs are not implemented + ids = None + try: + ids = omerotools.parse_url(user_client, url_project) + except NotImplementedError: + pass + assert ids is None + + # Single Dataset + ids = omerotools.parse_url(user_client, url_dataset) + assert len(ids) == len(image_ids) + + # Images url + ids = omerotools.parse_url(user_client, url_images) + assert len(ids) == len(image_ids) + for cur_id in ids: + cur_id = cur_id.getId() + assert str(cur_id) in image_ids + + # Image IDs as comma-separated string + ids = omerotools.parse_url(user_client, ",".join(image_ids)) + assert len(ids) == len(image_ids) + for cur_id in ids: + cur_id = cur_id.getId() + assert str(cur_id) in image_ids +finally: + if user_client is not user_client: + user_client.disconnect() + +print("Test completed!") diff --git a/tests/interactive-imagej/omerotools/upload_image_to_omero/test_upload_image_to_omero_01.py b/tests/interactive-imagej/omerotools/upload_image_to_omero/test_upload_image_to_omero_01.py new file mode 100644 index 00000000..f5735c11 --- /dev/null +++ b/tests/interactive-imagej/omerotools/upload_image_to_omero/test_upload_image_to_omero_01.py @@ -0,0 +1,19 @@ +from imcflibs.imagej import omerotools + + +error_type = "FileNotFoundError" +try: + FileNotFoundError +except NameError: + FileNotFoundError = IOError + error_type = "IOError" + +path = "/usr/fancy/path/pony-with-one-horn.tiff" + +try: + # No client needed. Only testing the exception + omerotools.upload_image_to_omero(None, path, None) +except FileNotFoundError: + print("Got the expected {}".format(error_type)) + +print("Test completed!") diff --git a/tests/test_omerotools.py b/tests/test_omerotools.py new file mode 100644 index 00000000..1b1dc7b5 --- /dev/null +++ b/tests/test_omerotools.py @@ -0,0 +1,110 @@ +"""Tests for the imcflibs.imagej.omerotools module.""" + +import pytest + +from imcflibs.imagej.omerotools import ( + extract_ids_from_string, + parse_url, + parse_url_string, + upload_image_to_omero, +) + +# Enusre Python 2.7 and 3.x compability +try: + FileNotFoundError +except NameError: + FileNotFoundError = IOError + + +def test_upload_image_to_omero(): + """Test `upload_image_to_omero()`. + + Tests only the case for non-existing files. + """ + path = "/usr/random/path/questionable/file.tiff" + with pytest.raises(FileNotFoundError, match=r"Could not find file"): + upload_image_to_omero(None, path, None) + + +def test_extract_ids_from_string(): + """Test `extract_ids_from_string()`.""" + + url_proj = "webclient/?show=project-25406" + # Expect error with wrong omero_type + with pytest.raises(ValueError, match=r"Unknown OMERO type"): + extract_ids_from_string(url_proj, "unicorn") + + assert len(extract_ids_from_string(url_proj, "Project")) == 1 + assert len(extract_ids_from_string(url_proj, "Project-")) == 1 + assert len(extract_ids_from_string(url_proj, "Dataset")) == 0 + + url_dataset = "webclient/?show=dataset-82753" + assert len(extract_ids_from_string(url_dataset, "dataset")) == 1 + + url_image = "webclient/?show=image-3598144" + assert len(extract_ids_from_string(url_image, "image")) == 1 + + url_multi_image = ( + "webclient/?show=image-3598144|image-3598145|image-3598146|image-3598147" + ) + assert len(extract_ids_from_string(url_multi_image, "image-")) == 4 + assert len(extract_ids_from_string(url_multi_image, "Project")) == 0 + + url_bad = "https://github.com/imcf/python-imcflibs" + assert len(extract_ids_from_string(url_bad, "image")) == 0 + + +def test_parse_url_string(): + """Test `parse_url_string()`.""" + url_proj = "webclient/?show=project-25406" + omero_ids, omero_type = parse_url_string(url_proj) + assert len(omero_ids) == 1 + assert omero_type == "project" + assert "25406" in omero_ids + + url_dataset = "webclient/?show=dataset-82753" + omero_ids, omero_type = parse_url_string(url_dataset) + assert len(omero_ids) == 1 + assert omero_type == "dataset" + assert "82753" in omero_ids + + url_image = "webclient/?show=image-3598144" + omero_ids, omero_type = parse_url_string(url_image) + assert len(omero_ids) == 1 + assert omero_type == "image" + assert "3598144" in omero_ids + + url_multi_image = ( + "webclient/?show=image-3598144|image-3598145|image-3598146|image-3598147" + ) + omero_ids, omero_type = parse_url_string(url_multi_image) + assert len(omero_ids) == 4 + assert omero_type == "image" + assert "3598144" in omero_ids + assert "3598145" in omero_ids + assert "3598146" in omero_ids + assert "3598147" in omero_ids + + url_bad = "https://github.com/imcf/python-imcflibs" + omero_ids, omero_type = parse_url_string(url_bad) + assert len(omero_ids) == 0 + assert omero_type == "" + + +def test_parse_url(): + """Test `parse_url()`. + + Only tests the first part of the function + (NotImplementedError on Project-URLs). + Since the reset of the function depends on OMERO libraries. + """ + url_empty = "" + with pytest.raises(ValueError, match=r"No OMERO link or image ID prov"): + parse_url(None, url_empty) + + url_proj = "webclient/?show=project-25406" + with pytest.raises( + NotImplementedError, + match=r"Getting Image IDs from Project", + ): + parse_url(None, url_proj)