From 81900bfc724aadd37ead295557eccc0adf34a423 Mon Sep 17 00:00:00 2001
From: alaskowski <alaskowski@ethz.ch>
Date: Thu, 23 Nov 2023 17:53:25 +0100
Subject: [PATCH] BIS-713: Implementation of Python API

---
 .../python/{premise => imaging}/__init__.py   |   8 +-
 .../source/python/imaging/imaging.py          | 450 ++++++++++++++++++
 .../source/python/premise/imaging.py          | 293 ------------
 .../source/python/tests/test_imaging.py       | 115 +++++
 4 files changed, 569 insertions(+), 297 deletions(-)
 rename core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/{premise => imaging}/__init__.py (90%)
 create mode 100644 core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/imaging.py
 delete mode 100644 core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/imaging.py
 create mode 100644 core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/tests/test_imaging.py

diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/__init__.py b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/__init__.py
similarity index 90%
rename from core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/__init__.py
rename to core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/__init__.py
index c9d2424102a..ea4523e5b12 100644
--- a/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/__init__.py
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/__init__.py
@@ -12,11 +12,11 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 #
-name = "premise"
+from . import imaging
+
+name = "imaging"
 __author__ = "ID SIS • ETH Zürich"
 __email__ = "openbis-support@id.ethz.ch"
 __version__ = "0.0.0"
 
-from . import imaging
-# from .pybis import DataSet
-# from .pybis import Openbis
\ No newline at end of file
+
diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/imaging.py b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/imaging.py
new file mode 100644
index 00000000000..74d5f93c058
--- /dev/null
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/imaging/imaging.py
@@ -0,0 +1,450 @@
+#   Copyright ETH 2023 Zürich, Scientific IT Services
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+
+from pybis import Openbis
+import abc
+import json
+import base64
+import requests
+import os
+from urllib.parse import urljoin
+
+
+DEFAULT_SERVICE_NAME = "imaging"
+IMAGING_CONFIG_PROP_NAME = "$IMAGING_DATA_CONFIG".lower()
+
+
+def get_instance(url="http://localhost:8888/openbis"):
+    base_url = url
+    # base_url = "http://localhost:8888/openbis"
+    # base_url = "https://alaskowski:8443/openbis"
+    # base_url = "https://openbis-sis-ci-sprint.ethz.ch/"
+    openbis_instance = Openbis(
+        url=base_url,
+        verify_certificates=False,
+        allow_http_but_do_not_use_this_in_production_and_only_within_safe_networks=True
+    )
+    token = openbis_instance.login('admin', 'changeit')
+    print(f'Connected to {base_url} -> token: {token}')
+    return openbis_instance
+
+
+class AbstractImagingClass(metaclass=abc.ABCMeta):
+    def to_json(self):
+        return json.dumps(self, default=lambda x: x.__dict__, sort_keys=True, indent=4)
+
+    def __str__(self):
+        return json.dumps(self.__dict__, default=lambda x: x.__dict__)
+
+    def __repr__(self):
+        return json.dumps(self.__dict__, default=lambda x: x.__dict__)
+
+
+class AbstractImagingRequest(AbstractImagingClass, metaclass=abc.ABCMeta):
+    @abc.abstractmethod
+    def _validate_data(self):
+        return
+
+
+class ImagingDataSetPreview(AbstractImagingRequest):
+    config: dict
+    format: str
+    bytes: str | None
+    show: bool
+    metadata: dict
+
+    def __init__(self, config, preview_format, metadata=None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetPreview"
+        self.bytes = None
+        self.format = preview_format
+        self.config = config if config is not None else dict()
+        self.metadata = metadata if metadata is not None else dict()
+        self._validate_data()
+
+    def _validate_data(self):
+        assert self.format is not None, "Format can not be null"
+
+    def save_to_file(self, file_path):
+        assert self.bytes is not None, "There is no image information!"
+        img_data = bytearray(self.bytes, encoding='utf-8')
+        with open(file_path, "wb") as fh:
+            fh.write(base64.decodebytes(img_data))
+
+    @classmethod
+    def from_dict(cls, data):
+        if data is None:
+            return None
+        preview = cls(None, None, None)
+        for prop in cls.__annotations__.keys():
+            attribute = data.get(prop)
+            preview.__dict__[prop] = attribute
+        return preview
+
+
+class ImagingDataSetExport(AbstractImagingRequest):
+    config: dict
+    metadata: dict
+
+    def __init__(self, config, metadata=None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetExport"
+        self.config = config if config is not None else dict()
+        self.metadata = metadata if metadata is not None else dict()
+        self._validate_data()
+
+    def _validate_data(self):
+        assert self.config is not None, "Config can not be null"
+        required_keys = {"include", "archive-format", "image-format", "resolution"}
+        for key in required_keys:
+            assert key in self.config and self.config[key] is not None, \
+                f"export->config->{key}: Must not be None!"
+
+
+class ImagingDataSetMultiExport(AbstractImagingRequest):
+    permId: str
+    index: int
+    config: dict
+    metadata: dict
+
+    def __init__(self, permId, index, config, metadata=None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetMultiExport"
+        self.permId = permId
+        self.index = index
+        self.config = config
+        self.metadata = metadata if metadata is not None else dict()
+        self._validate_data()
+
+    def _validate_data(self):
+        assert self.permId is not None, "PermId can not be null"
+        assert self.index is not None, "Index can not be null"
+        assert self.config is not None, "Config can not be null"
+        required_keys = {"include", "archive-format", "image-format", "resolution"}
+        for key in required_keys:
+            assert key in self.config and self.config[key] is not None, \
+                f"export->config->{key}: Must not be None!"
+
+
+class ImagingDataSetControlVisibility(AbstractImagingClass):
+    label: str
+    values: list[str]
+    range: list[str]
+    unit: str
+
+    def __init__(self, label: str, values: list[str], values_range: list[str], unit: str = None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetControlVisibility"
+        self.label = label
+        self.values = values
+        self.range = values_range
+        self.unit = unit
+
+    @classmethod
+    def from_dict(cls, data):
+        if data is None:
+            return None
+        control = cls(None, None, None, None)
+        for prop in cls.__annotations__.keys():
+            attribute = data.get(prop)
+            control.__dict__[prop] = attribute
+        return control
+
+
+class ImagingDataSetControl(AbstractImagingClass):
+    label: str
+    section: str
+    type: str
+    values: list[str]
+    unit: str
+    range: list[str]
+    multiselect: bool
+    playable: bool
+    speeds: list[int]
+    visibility: list[ImagingDataSetControlVisibility]
+    metaData: dict
+
+    def __init__(self, label: str, control_type: str, values: list[str] = None,
+                 values_range: list[str] = None, multiselect: bool = None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetControl"
+        self.label = label
+        self.type = control_type
+        if control_type.lower() in ["slider", "range"]:
+            self.range = values_range
+        elif control_type.lower() == "dropdown":
+            self.values = values
+            self.multiselect = multiselect
+
+    @classmethod
+    def from_dict(cls, data):
+        if data is None:
+            return None
+        control = cls(None, "", None, None)
+        for prop in cls.__annotations__.keys():
+            attribute = data.get(prop)
+            if prop == 'visibility' and attribute is not None:
+                attribute = [ImagingDataSetControlVisibility.from_dict(visibility) for visibility in attribute]
+            control.__dict__[prop] = attribute
+        return control
+
+
+class ImagingDataSetConfig(AbstractImagingClass):
+    adaptor: str
+    version: float
+    speeds: list[int]
+    resolutions: list[str]
+    playable: bool
+    exports: list[ImagingDataSetControl]
+    inputs: list[ImagingDataSetControl]
+    metadata: dict
+
+    def __init__(self, adaptor: str, version: float, resolutions: list[str], playable: bool,
+                 speeds: list[int] = None, exports: list[ImagingDataSetControl] = None,
+                 inputs: list[ImagingDataSetControl] = None, metadata: dict = None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetConfig"
+        self.adaptor = adaptor
+        self.version = version
+        self.resolutions = resolutions
+        self.playable = playable
+        if playable:
+            self.speeds = speeds
+        self.exports = exports
+        self.inputs = inputs
+        self.metadata = metadata
+
+    @classmethod
+    def from_dict(cls, data):
+        if data is None:
+            return None
+        config = cls(None, None, None, None)
+        for prop in cls.__annotations__.keys():
+            attribute = data.get(prop)
+            if prop in ['exports', 'inputs'] and attribute is not None:
+                attribute = [ImagingDataSetControl.from_dict(control) for control in attribute]
+            config.__dict__[prop] = attribute
+        return config
+
+
+class ImagingDataSetImage(AbstractImagingClass):
+    previews: list[ImagingDataSetPreview]
+    config: dict
+    metadata: dict
+
+    def __init__(self, config=None, previews=None, metadata=None):
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetImage"
+        self.config = config if config is not None else dict()
+        self.previews = previews if previews is not None else []
+        self.metadata = metadata if metadata is not None else dict()
+
+    def add_preview(self, preview):
+        self.previews += [preview]
+
+    @classmethod
+    def from_dict(cls, data):
+        if data is None:
+            return None
+        image = cls(None, None, None)
+        for prop in cls.__annotations__.keys():
+            attribute = data.get(prop)
+            if prop == 'images' and attribute is not None:
+                attribute = [ImagingDataSetPreview.from_dict(image) for image in attribute]
+            image.__dict__[prop] = attribute
+        return image
+
+
+class ImagingDataSetPropertyConfig(AbstractImagingClass):
+    config: ImagingDataSetConfig
+    images: list[ImagingDataSetImage]
+
+    def __init__(self, config: ImagingDataSetConfig, images: list[ImagingDataSetImage]):
+        assert config is not None, "Config must not be None!"
+        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetPropertyConfig"
+        self.config = config
+        self.images = images if images is not None else []
+
+    @classmethod
+    def from_dict(cls, data):
+        config = ImagingDataSetConfig.from_dict(data.get('config'))
+        attr = data.get('images')
+        images = [ImagingDataSetImage.from_dict(image) for image in attr] if attr is not None else None
+        return cls(config, images)
+
+    def add_image(self, image):
+        if self.images is None:
+            self.images = []
+        self.images += [image]
+
+
+class ImagingControl:
+
+    def __init__(self, openbis_instance, service_name=DEFAULT_SERVICE_NAME):
+        self._openbis = openbis_instance
+        self._service_name = service_name
+
+    def _execute_custom_dss_service(self, parameters):
+        service_id = {
+            "@type": "dss.dto.service.id.CustomDssServiceCode",
+            "permId": self._service_name
+        }
+        options = {
+            "@type": "dss.dto.service.CustomDSSServiceExecutionOptions",
+            "parameters": parameters
+        }
+        request = {
+            "method": "executeCustomDSSService",
+            "params": [
+                self._openbis.token,
+                service_id,
+                options
+            ],
+        }
+        full_url = urljoin(self._openbis._get_dss_url(), self._openbis.dss_v3)
+        return self._openbis._post_request_full_url(full_url, request)
+
+    def make_preview(self, perm_id: str, index: int, preview: ImagingDataSetPreview) -> ImagingDataSetPreview:
+        parameters = {
+            "type": "preview",
+            "permId": perm_id,
+            "index": index,
+            "error": None,
+            "preview": preview.__dict__
+        }
+        service_response = self._execute_custom_dss_service(parameters)
+        if service_response['error'] is None:
+            preview.__dict__ = service_response["preview"]
+            return preview
+        else:
+            raise ValueError(service_response['error'])
+
+    def get_export_url(self, perm_id: str, export: ImagingDataSetExport, image_index: int = 0) -> str:
+        parameters = {
+            "type": "export",
+            "permId": perm_id,
+            "index": image_index,
+            "error": None,
+            "url": None,
+            "export": export.__dict__
+        }
+        service_response = self._execute_custom_dss_service(parameters)
+        if service_response['error'] is None:
+            return service_response['url']
+        else:
+            raise ValueError(service_response['error'])
+
+    def get_multi_export_url(self, exports: list[ImagingDataSetMultiExport]) -> str:
+        parameters = {
+            "type": "multi-export",
+            "error": None,
+            "url": None,
+            "exports": [export.__dict__ for export in exports]
+        }
+        service_response = self._execute_custom_dss_service(parameters)
+        if service_response['error'] is None:
+            return service_response['url']
+        else:
+            raise ValueError(service_response['error'])
+
+    def single_export_download(self, perm_id: str, export: ImagingDataSetExport, image_index: int = 0, directory_path=""):
+        export_url = self.get_export_url(perm_id, export, image_index)
+        self._download(export_url, directory_path)
+
+    def multi_export_download(self, exports: list[ImagingDataSetMultiExport], directory_path=""):
+        export_url = self.get_multi_export_url(exports)
+        self._download(export_url, directory_path)
+
+    def _download(self, url, directory_path=""):
+        get_response = requests.get(url, stream=True)
+        file_name = url.split("/")[-1]
+        with open(os.path.join(directory_path, file_name), 'wb') as f:
+            for chunk in get_response.iter_content(chunk_size=1024):
+                if chunk:
+                    f.write(chunk)
+
+
+
+
+    def get_property_config(self, perm_id: str) -> ImagingDataSetPropertyConfig:
+        dataset = self._openbis.get_dataset(perm_id)
+        imaging_property = json.loads(dataset.props[IMAGING_CONFIG_PROP_NAME])
+        return ImagingDataSetPropertyConfig.from_dict(imaging_property)
+
+    def update_property_config(self, perm_id: str, config: ImagingDataSetPropertyConfig):
+        dataset = self._openbis.get_dataset(perm_id)
+        dataset.props[IMAGING_CONFIG_PROP_NAME] = config.to_json()
+        dataset.save()
+
+
+# o = get_instance()
+#
+#
+# # imaging_preview = ImagingDataSetPreview(preview_format="png", config=config_sxm)
+# # response = get_preview('20231110130838616-26', 0, imaging_preview)
+# # print(response)
+#
+# config_export = {
+#     "include": ['image', 'raw data'],
+#     "image-format": 'original',
+#     "archive-format": "zip",
+#     "resolution": "original"
+# }
+# # imaging_export = ImagingDataSetExport(config_export)
+# # export_response = get_export('20231110130838616-26', 0, imaging_export)
+# # print(export_response)
+#
+#
+# # imaging_export1 = ImagingDataSetMultiExport('20231110130838616-26', 0, config_export)
+# # imaging_export2 = ImagingDataSetMultiExport('20231110134813653-27', 0, config_export)
+# # multi_export_response = get_multi_export([imaging_export1, imaging_export2])
+# # print(multi_export_response)
+#
+#
+# # imaging_property_config = get_property_config('20231110134813653-27')
+# # print(imaging_property_config.to_json())
+#
+# ic = ImagingControl(o)
+# perm_id = '20231110130838616-26'
+# pc = ic.get_property_config(perm_id)
+#
+#
+#
+# config_sxm_preview = {
+#     "channel": "z", # usually one of these: ['z', 'I', 'dIdV', 'dIdV_Y']
+#     "x-axis": [1.2, 3.0], # file dependent
+#     "y-axis": [1.2, 3.0], # file dependent
+#     "color-scale": [-700.0, 700.0], # file dependend
+#     "colormap": "gray", # [gray, YlOrBr, viridis, cividis, inferno, rainbow, Spectral, RdBu, RdGy]
+#     "scaling": "linear", # ['linear', 'logarithmic']
+#     # "mode": 3 # uncomment this if you want to generate random pixel image generation
+# }
+#
+# # imaging_preview = ImagingDataSetPreview(preview_format="png", config=config_sxm_preview)
+# #
+# # preview = ic.make_preview(perm_id, 0, imaging_preview)
+# # pc.images[0].add_preview(preview)
+# # ic.update_property_config(perm_id, pc)
+# #
+# # print(ic.get_property_config(perm_id))
+#
+#
+#
+# config_export = {
+#     "include": ['image', 'raw data'],
+#     "image-format": 'original',
+#     "archive-format": "zip",
+#     "resolution": "original"
+# }
+# imaging_export = ImagingDataSetExport(config_export)
+# ic.single_export_download(perm_id, imaging_export, 0, '/home/alaskowski/PREMISE')
+
+
+
+
+
diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/imaging.py b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/imaging.py
deleted file mode 100644
index cd8501e4a93..00000000000
--- a/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/premise/imaging.py
+++ /dev/null
@@ -1,293 +0,0 @@
-#   Copyright ETH 2023 Zürich, Scientific IT Services
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#        http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-#
-
-from pybis import Openbis
-import abc
-import time
-import json
-import base64
-import numpy as np
-import sys
-from urllib.parse import urljoin, urlparse
-
-
-SERVICE_NAME = "imaging"
-
-def get_instance(url="http://localhost:8888/openbis"):
-    base_url = url
-    # base_url = "http://localhost:8888/openbis"
-    # base_url = "https://alaskowski:8443/openbis"
-    # base_url = "https://openbis-sis-ci-sprint.ethz.ch/"
-    openbis_instance = Openbis(
-        url=base_url,
-        verify_certificates=False,
-        allow_http_but_do_not_use_this_in_production_and_only_within_safe_networks=True
-    )
-    token = openbis_instance.login('admin', 'changeit')
-    print(f'Connected to {base_url} -> token: {token}')
-    return openbis_instance
-
-
-def execute_custom_dss_service(openbis, code, parameters):
-    service_id = {
-        "@type": "dss.dto.service.id.CustomDssServiceCode",
-        "permId": code
-    }
-    options = {
-        "@type": "dss.dto.service.CustomDSSServiceExecutionOptions",
-        "parameters": parameters
-    }
-    request = {
-        "method": "executeCustomDSSService",
-        "params": [
-            openbis.token,
-            service_id,
-            options
-        ],
-    }
-    full_url = urljoin(openbis._get_dss_url(), openbis.dss_v3)
-    return openbis._post_request_full_url(full_url, request)
-
-
-class AbstractImagingRequest(metaclass=abc.ABCMeta):
-
-    @abc.abstractmethod
-    def _validate_data(self):
-        return
-
-    def __str__(self):
-        return json.dumps(self.__dict__)
-
-
-class ImagingDataSetPreview(AbstractImagingRequest):
-    config: dict
-    format: str
-    bytes: str | None
-    show: bool
-    metadata: dict
-
-    def __init__(self, config, preview_format, metadata=None):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetPreview"
-        self.bytes = None
-        self.format = preview_format
-        self.config = config if config is not None else dict()
-        self.metadata = metadata if metadata is not None else dict()
-        self._validate_data()
-
-    def _validate_data(self):
-        assert self.format is not None, "Format can not be null"
-
-    def save_to_file(self, file_path):
-        assert self.bytes is not None, "There is no image information!"
-        img_data = bytearray(self.bytes, encoding='utf-8')
-        with open(file_path, "wb") as fh:
-            fh.write(base64.decodebytes(img_data))
-
-
-class ImagingDataSetExport(AbstractImagingRequest):
-    config: dict
-    metadata: dict
-
-    def __init__(self, config, metadata=None):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetExport"
-        self.config = config if config is not None else dict()
-        self.metadata = metadata if metadata is not None else dict()
-        self._validate_data()
-
-    def _validate_data(self):
-        assert self.config is not None, "Config can not be null"
-        required_keys = {"include", "archive-format", "image-format", "resolution"}
-        for key in required_keys:
-            assert key in self.config and self.config[key] is not None, \
-                f"export->config->{key}: Must not be None!"
-
-
-class ImagingDataSetMultiExport(AbstractImagingRequest):
-    permId: str
-    index: int
-    config: dict
-    metadata: dict
-
-    def __init__(self, permId, index, config, metadata=None):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetMultiExport"
-        self.permId = permId
-        self.index = index
-        self.config = config
-        self.metadata = metadata if metadata is not None else dict()
-        self._validate_data()
-
-    def _validate_data(self):
-        assert self.permId is not None, "PermId can not be null"
-        assert self.index is not None, "Index can not be null"
-        assert self.config is not None, "Config can not be null"
-        required_keys = {"include", "archive-format", "image-format", "resolution"}
-        for key in required_keys:
-            assert key in self.config and self.config[key] is not None, \
-                f"export->config->{key}: Must not be None!"
-
-
-class ImagingDataSetControlVisibility:
-    label: str
-    values: list[str]
-    range: list[str]
-    unit: str
-
-    def __init__(self):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetControlVisibility"
-
-
-class ImagingDataSetControl:
-    label: str
-    section: str
-    type: str
-    values: list[str]
-    unit: str
-    range: list[str]
-    multiselect: bool
-    playable: bool | None
-    speeds: list[int]
-    visibility: list[ImagingDataSetControlVisibility]
-    metaData: dict
-
-    def __init__(self):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetControl"
-
-
-class ImagingDataSetConfig:
-    adaptor: str
-    version: float
-    speeds: list[int]
-    resolutions: list[str]
-    playable: bool
-    exports: list[ImagingDataSetControl]
-    inputs: list[ImagingDataSetControl]
-    metadata: dict
-
-    def __init__(self):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetConfig"
-
-
-class ImagingDataSetImage:
-    previews: list[ImagingDataSetPreview]
-    config: dict
-    metadata: dict
-
-    def __init__(self, config=None, previews=None, metadata=None):
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetImage"
-        self.config = config if config is not None else dict()
-        self.previews = previews if previews is not None else []
-        self.metadata = metadata if metadata is not None else dict()
-
-    def add_preview(self, preview):
-        self.previews += [preview]
-
-
-class ImagingDataSetPropertyConfig:
-    config: ImagingDataSetConfig
-    images: list[ImagingDataSetImage]
-
-    def __init__(self, config: ImagingDataSetConfig, images: list[ImagingDataSetImage]):
-        assert config is not None, "Config must not be None!"
-        self.__dict__["@type"] = "dss.dto.imaging.ImagingDataSetPropertyConfig"
-        self.config = config
-        self.images = images if images is not None else []
-
-    def add_image(self, image):
-        self.images += [image]
-
-
-def get_preview(perm_id: str, index: int, preview: ImagingDataSetPreview) -> ImagingDataSetPreview:
-    parameters = {
-        "type": "preview",
-        "permId": perm_id,
-        "index": index,
-        "error": None,
-        "preview": preview.__dict__
-    }
-    service_response = execute_custom_dss_service(o, SERVICE_NAME, parameters)
-    if service_response['error'] is None:
-        preview.__dict__ = service_response["preview"]
-        return preview
-    else:
-        raise ValueError(service_response['error'])
-
-
-def get_export(perm_id: str, index: int, export: ImagingDataSetExport) -> str:
-    parameters = {
-        "type": "export",
-        "permId": perm_id,
-        "index": index,
-        "error": None,
-        "url": None,
-        "export": export.__dict__
-    }
-    service_response = execute_custom_dss_service(o, SERVICE_NAME, parameters)
-    if service_response['error'] is None:
-        return service_response['url']
-    else:
-        raise ValueError(service_response['error'])
-
-
-def get_multi_export(exports: list[ImagingDataSetMultiExport]) -> str:
-    parameters = {
-        "type": "multi-export",
-        "error": None,
-        "url": None,
-        "exports": [export.__dict__ for export in exports]
-    }
-    service_response = execute_custom_dss_service(o, SERVICE_NAME, parameters)
-    if service_response['error'] is None:
-        return service_response['url']
-    else:
-        raise ValueError(service_response['error'])
-
-
-o = get_instance()
-
-config_sxm_preview = {
-    "channel": "z", # usually one of these: ['z', 'I', 'dIdV', 'dIdV_Y']
-    "x-axis": [0.0, 3.0], # file dependent
-    "y-axis": [0.0, 3.0], # file dependent
-    "color-scale": [-700.0, 700.0], # file dependend
-    "colormap": "cividis", # [gray, YlOrBr, viridis, cividis, inferno, rainbow, Spectral, RdBu, RdGy]
-    "scaling": "linear", # ['linear', 'logarithmic']
-    # "mode": 3 # uncomment this if you want to generate random pixel image generation
-}
-
-# imaging_preview = ImagingDataSetPreview(preview_format="png", config=config_sxm)
-# response = get_preview('20231110130838616-26', 0, imaging_preview)
-# print(response)
-
-config_export = {
-    "include": ['image', 'raw data'],
-    "image-format": 'original',
-    "archive-format": "zip",
-    "resolution": "original"
-}
-# imaging_export = ImagingDataSetExport(config_export)
-# export_response = get_export('20231110130838616-26', 0, imaging_export)
-# print(export_response)
-
-
-# imaging_export1 = ImagingDataSetMultiExport('20231110130838616-26', 0, config_export)
-# imaging_export2 = ImagingDataSetMultiExport('20231110134813653-27', 0, config_export)
-# multi_export_response = get_multi_export([imaging_export1, imaging_export2])
-# print(multi_export_response)
-
-
-
-
-
-
diff --git a/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/tests/test_imaging.py b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/tests/test_imaging.py
new file mode 100644
index 00000000000..33050808a83
--- /dev/null
+++ b/core-plugin-openbis/dist/core-plugins/imaging/1/dss/custom-services/imaging/lib/premise-sources/source/python/tests/test_imaging.py
@@ -0,0 +1,115 @@
+#   Copyright ETH 2023 Zürich, Scientific IT Services
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+
+import unittest
+import imaging.imaging as imaging
+from unittest.mock import MagicMock
+import json
+
+IMAGING_CONFIG_PROP_NAME = "$IMAGING_DATA_CONFIG".lower()
+
+PROPER_CONFIG = {
+    "config": {
+        "adaptor": "some.adaptor.class.MyAdaptorClass",
+        "version": 1.0,
+        "speeds": [1, 2, 5],
+        "resolutions": ["150dpi", "300dpi"],
+        "playable": True,
+        "exports": [{
+            "label": "Archive",
+            "type": "Dropdown",
+            "values": ["zip", "tar.gz"],
+            "multiselect": False,
+            "metaData": {}
+        }],
+        "inputs": [{
+            "label": "Option 1",
+            "section": "section",
+            "type": "Dropdown",
+            "values": ["a", "b", "c"],
+            "multiselect": True,
+            "playable": False,
+            "metaData": {}
+        }, {
+            "label": "Option 2",
+            "section": "section",
+            "type": "Slider",
+            "unit": "cm",
+            "range": ["1", "2", "0.1"],
+            "multiselect": False,
+            "playable": True,
+            "speeds": [1, 2, 5],
+            "metaData": {}
+        }, {
+            "label": "Option 2",
+            "section": "section",
+            "type": "Range",
+            "multiselect": False,
+            "playable": False,
+            "visibility": [{
+                "label": "Option 1",
+                "values": ["a", "b"],
+                "range": ["1", "2", "0.1"],
+                "unit": "mm"
+            }, {
+                "label": "Option 1",
+                "values": ["c"],
+                "range": ["0", "100", "1"],
+                "unit": "px"
+            }],
+            "metaData": {}
+        }],
+        "metadata": {}
+    },
+    "images": [{
+        "previews": [{
+            "config": {},
+            "format": "png",
+            "bytes": "base64_encoded_bytes",
+            "show": False,
+            "metadata": {}
+        }],
+        "config": {},
+        "metadata": {}
+    }]
+}
+
+
+class ImagingTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.dataset_mock = MagicMock()
+        self.set_dataset_config(PROPER_CONFIG)
+
+        self.openbis_mock = MagicMock()
+        self.openbis_mock.get_dataset.return_value = self.dataset_mock
+
+        self.imaging_control = imaging.ImagingControl(self.openbis_mock)
+
+    def set_dataset_config(self, config):
+        json_config = json.dumps(config)
+        self.dataset_mock.props = {IMAGING_CONFIG_PROP_NAME: json_config}
+
+    def test_get_property_config(self):
+        self.imaging_control.get_property_config('some_perm_id')
+
+
+
+
+
+
+
+if __name__ == '__main__':
+    unittest.main()
-- 
GitLab