diff --git a/docs/source/dcp_client_installation.rst b/docs/source/dcp_client_installation.rst index b4a883b2..6b3c8251 100644 --- a/docs/source/dcp_client_installation.rst +++ b/docs/source/dcp_client_installation.rst @@ -11,12 +11,28 @@ The client of our data centric platform for microscopy imaging. Installation ------------- -Before starting make sure you have navigated to ``data-centric-platform/src/client``. All future steps expect you are in the client directory. This installation has been tested using a conda environment with python version 3.9 on a mac local machine. In your dedicated environment run: +For installing dcp-client you will first need to clone the repo: + +.. code-block:: bash + + git clone https://github.com/HelmholtzAI-Consultants-Munich/data-centric-platform.git + + +Then navigate to the client directory: + +.. code-block:: bash + + cd data-centric-platform/src/client + +In your dedicated environment run: .. code-block:: bash pip install -e . + +This installation has been thoroughly tested using a conda environment with python version 3.9, 3.10, 3.11 and 3.12 on a macOS local machine. + Running the client: A step-by-step guide! ------------------------------------------ @@ -25,13 +41,20 @@ Running the client: A step-by-step guide! DCP includes a client and server side for using our data centric platform. The client and server communicate via the `bentoml `_ library. There are currently two options available: running the server locally, or connecting to the running instance on the FZJ jusuf-cloud. - Before continuing, you need to make sure that DCP server is running, either locally or on the cloud. See :doc: `dcp_server_installation` for instructions on how to launch the server. **Note:** In order for this connection to succeed, you will need to have contacted the team developing DCP, so they can add your IP to the list of accepted requests. + Before continuing, you need to make sure that DCP server is running, either locally or on the cloud. See :ref:`DCP Client` for instructions on how to launch the server. **Note:** In order for this connection to succeed, you will need to have contacted the team developing DCP, so they can add your IP to the list of accepted requests. After you are certain the server is running, simply run: .. code-block:: bash - dcp-client --mode local/remote + dcp-client --mode local + + or + + .. code-block:: bash + + dcp-client --mode remote + Set the ``--mode`` argument to ``local`` or ``remote`` depending on which setup you have chosen for the server. @@ -60,12 +83,9 @@ Set the ``--mode`` argument to ``local`` or ``remote`` depending on which setup This folder is intended to contain images along with their final segmentations. **Only** move images here when the segmentation is complete and finalised, you won't be able to change them after they have been moved here. These are then used for training your model. -3. **Setting paths** -~~~~~~~~~~~~~~~~~~~~~ - After setting the paths for these three folders, you can click the **Start** button. If you have set the server configuration to the cloud, you will receive a message notifying you that your data will be uploaded to the cloud. Click **Ok** to continue. -4. **Data Overview** +3. **Data Overview** ~~~~~~~~~~~~~~~~~~~~ The main working window will appear next. This gives you an overview of the directories selected in the previous step along with three options: @@ -79,10 +99,10 @@ Set the ``--mode`` argument to ``local`` or ``remote`` depending on which setup :height: 200 :align: center -5. **The viewer** +4. **The viewer** ~~~~~~~~~~~~~~~~~~~~ - In DCP, we use [napari](https://napari.org/stable) for viewing our images and masks, adding, editing or removing labels. An example of the viewer can be seen below. After adding or removing any objects and editing existing objects wherever necessary, there are two options available: + In DCP, we use `napari `_ for viewing our images and masks, adding, editing or removing labels. The newest version of DCP comes with `napari-sam `_ integration, to enable even faster labelling! Check out the turorial on the napari-hub to find out how to use this tool. An example of the viewer can be seen below. After adding or removing any objects and editing existing objects wherever necessary, there are two options available: - Click the **Move to Curation in progress folder** if you are not 100% certain about the labels you have created. You can also click on the label in the labels layer and change the name. This will result in several label files being created in the *In progress folder*, which can be examined later on. **Note:** When changing the layer name in Napari, the user should rename it such that they add their initials or any other new info after _seg. E.g., if the labels of 1_seg.tiff have been changed in the Napari viewer, then the appropriate naming would for example be: 1_seg_CB.tiff and not 1_CB_seg.tiff. - Click the **Move to Curated dataset folder** if you are certain that the labels you are now viewing are final and require no more curation. These images and labels will later be used for training the machine learning model, so make sure that you select this option only if you are certain about the labels. If several labels are displayed (opened from the 'Curation in progress' step), make sure to **click** on the single label in the labels layer list you wish to be moved to the *Curated data folder*. The other images will then be automatically deleted from this folder. diff --git a/docs/source/dcp_server_installation.rst b/docs/source/dcp_server_installation.rst index 823f97af..15f81f5f 100644 --- a/docs/source/dcp_server_installation.rst +++ b/docs/source/dcp_server_installation.rst @@ -15,11 +15,28 @@ The client and server communicate via the `bentoml None: self.app = app self.setWindowTitle("napari viewer") self.setStyleSheet("background-color: #262930;") + #screen_size = QGuiApplication.primaryScreen().geometry() + #self.resize(int(screen_size.width()*0.9), int(screen_size.height()*0.8)) # Load image and get corresponding segmentation filenames img = self.app.load_image() @@ -39,6 +42,8 @@ def __init__(self, app: Application) -> None: # Set the viewer self.viewer = napari.Viewer(show=False) + self.viewer.window.add_plugin_dock_widget("napari-sam") + self.viewer.add_image(img, name=get_path_stem(self.app.cur_selected_img)) for seg_file in self.seg_files: self.viewer.add_labels( @@ -238,6 +243,7 @@ def axis_changed(self, event) -> None: """ self.active_mask_index = self.viewer.dims.current_step[0] masks = deepcopy(self.layer.data) + # if user has switched to the instance mask if self.active_mask_index == 0: class_mask_with_contours = Compute4Mask.add_contour(masks[1], masks[0]) @@ -247,6 +253,7 @@ def axis_changed(self, event) -> None: ): self.update_instance_mask(masks[0], masks[1]) self.switch_to_instance_mask() + # else if user has switched to the class mask elif self.active_mask_index == 1: if not check_equal_arrays( @@ -260,6 +267,8 @@ def switch_to_instance_mask(self) -> None: Switch the application to the active mask mode by enabling 'paint_button', 'erase_button' and 'fill_button'. """ + + self.original_class_mask[self.cur_selected_seg] = deepcopy(self.layer.data[1]) self.switch_controls("paint_button", True) self.switch_controls("erase_button", True) self.switch_controls("fill_button", True) @@ -268,6 +277,8 @@ def switch_to_labels_mask(self) -> None: """ Switch the application to non-active mask mode by enabling 'fill_button' and disabling 'paint_button' and 'erase_button'. """ + + self.original_instance_mask[self.cur_selected_seg] = deepcopy(self.layer.data[0]) if self.cur_selected_seg in [layer.name for layer in self.viewer.layers]: self.viewer.layers[self.cur_selected_seg].mode = "pan_zoom" info_message_paint = ( diff --git a/src/client/dcp_client/utils/bentoml_model.py b/src/client/dcp_client/utils/bentoml_model.py index 5f57b421..8eccf57a 100644 --- a/src/client/dcp_client/utils/bentoml_model.py +++ b/src/client/dcp_client/utils/bentoml_model.py @@ -1,6 +1,5 @@ -import asyncio from typing import Optional, List -from bentoml.client import Client as BentoClient +from bentoml.client import Client as BentoClient # Client is type SyncHTTPClient from bentoml.exceptions import BentoMLException import numpy as np @@ -44,7 +43,7 @@ def is_connected(self) -> bool: """ return bool(self.client) - async def _run_train(self, data_path: str) -> Optional[str]: + def _run_train(self, data_path: str) -> Optional[str]: """Runs the training task asynchronously. :param data_path: Path to the training data. @@ -53,7 +52,7 @@ async def _run_train(self, data_path: str) -> Optional[str]: :rtype: str, or None """ try: - response = await self.client.async_train(data_path) + response = self.client.train(data_path) # train is part of running server return response except BentoMLException: return None @@ -65,9 +64,9 @@ def run_train(self, data_path: str): :type data_path: str :return: Response from the server if successful, None otherwise. """ - return asyncio.run(self._run_train(data_path)) + return self._run_train(data_path) - async def _run_inference(self, data_path: str) -> Optional[np.ndarray]: + def _run_inference(self, data_path: str) -> Optional[np.ndarray]: """Runs the inference task asynchronously. :param data_path: Path to the data for inference. @@ -76,7 +75,7 @@ async def _run_inference(self, data_path: str) -> Optional[np.ndarray]: :rtype: np.ndarray, or None """ try: - response = await self.client.async_segment_image(data_path) + response = self.client.segment_image(data_path) # segment_image is part of running server return response except BentoMLException: return None @@ -88,5 +87,5 @@ def run_inference(self, data_path: str) -> List: :type data_path: str :return: List of files not supported by the server if unsuccessful, otherwise returns None. """ - list_of_files_not_suported = asyncio.run(self._run_inference(data_path)) + list_of_files_not_suported = self._run_inference(data_path) return list_of_files_not_suported diff --git a/src/client/dcp_client/utils/compute4mask.py b/src/client/dcp_client/utils/compute4mask.py index f14bff5d..e9e4b3ff 100644 --- a/src/client/dcp_client/utils/compute4mask.py +++ b/src/client/dcp_client/utils/compute4mask.py @@ -48,8 +48,8 @@ def get_contours( contour[:, 0], contour[:, 1], contour_mask.shape ) contour_mask[rr, cc] = instance_id - except: - print("Could not create contour for instance id", instance_id) + except Exception as error: + print("Could not create contour for instance id", instance_id, ". Error is :", error) return contour_mask @staticmethod diff --git a/src/client/pyproject.toml b/src/client/pyproject.toml index 93af7bd7..8fe2f47a 100644 --- a/src/client/pyproject.toml +++ b/src/client/pyproject.toml @@ -1,47 +1,3 @@ [build-system] -requires = ["setuptools>=61.0"] +requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" - -[tool.setuptools] -packages = ['dcp_client'] - -[tool.setuptools.dynamic] -dependencies = {file = ["requirements.txt"]} - -[project] -name = "data-centric-platform-client" -version = "0.1" -requires-python = ">=3.9" -description = "The client of the data centric platform for microscopy image segmentation" -keywords = [] -classifiers = [ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", -] -readme = "README.md" -dynamic = ["dependencies"] -authors = [ - {name="Christina Bukas", email="christina.bukas@helmholtz-munich.de"}, - {name="Helena Pelin", email="helena.pelin@helmholtz-munich.de"}, - {name="Mariia Koren", email="mariia.koren@helmholtz-munich.de"}, - {name="Marie Piraud", email="marie.piraud@helmholtz-munich.de"}, -] -maintainers = [ - {name="Christina Bukas", email="christina.bukas@helmholtz-munich.de"}, - {name="Helena Pelin", email="helena.pelin@helmholtz-munich.de"} -] - -[project.optional-dependencies] -dev = [ - "pytest>=7.4.3", - "pytest-qt>=4.2.0", - "sphinx", - "sphinx-rtd-theme" -] - -[project.urls] -repository = "https://github.com/HelmholtzAI-Consultants-Munich/data-centric-platform" -documentation = "https://readthedocs.org/projects/data-centric-platform" - -[project.scripts] -dcp-client = "dcp_client.main:main" diff --git a/src/client/readme_figs/client_napari_viewer.png b/src/client/readme_figs/client_napari_viewer.png index 414171f1..42fcb5a6 100644 Binary files a/src/client/readme_figs/client_napari_viewer.png and b/src/client/readme_figs/client_napari_viewer.png differ diff --git a/src/client/requirements.txt b/src/client/requirements.txt deleted file mode 100644 index e47ad839..00000000 --- a/src/client/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -napari[pyqt5]>=0.4.17 -bentoml[grpc]==1.0.16 \ No newline at end of file diff --git a/src/client/setup.py b/src/client/setup.py new file mode 100644 index 00000000..73032334 --- /dev/null +++ b/src/client/setup.py @@ -0,0 +1,52 @@ +from setuptools import setup, find_packages + +setup( + name="data-centric-platform-client", + version="0.1", + description="The client of the data centric platform for microscopy image segmentation", + author=["Christina Bukas", + "Helena Pelin", + "Mariia Koren", + "Marie Piraud"], + author_email= ["christina.bukas@helmholtz-munich.de", + "helena.pelin@helmholtz-munich.de", + "mariia.koren@helmholtz-munich.de", + "marie.piraud@helmholtz-munich.de"], + url="https://github.com/HelmholtzAI-Consultants-Munich/data-centric-platform", + packages=find_packages(), + install_requires=[ + "matplotlib >=3.3", + "napari[pyqt5]>=0.4.17", + "segment-anything @ git+https://github.com/facebookresearch/segment-anything.git@main", + "torch", + "torchvision", + "napari-sam @ git+https://github.com/christinab12/napari-sam.git@main", + "bentoml[grpc]>=1.2.5", + ], + extras_require={ + "dev": [ + "pytest>=7.4.3", + "pytest-qt>=4.2.0", + "sphinx", + "sphinx-rtd-theme", + ] + }, + entry_points={ + "console_scripts": [ + "dcp-client=dcp_client.main:main", + ] + }, + python_requires=">=3.9", + keywords=[], + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + ], + long_description=open("README.md").read(), + maintainer=["Christina Bukas", "Helena Pelin"], + maintainer_email=["christina.bukas@helmholtz-munich.de", "helena.pelin@helmholtz-munich.de"], + project_urls={ + "Repository": "https://github.com/HelmholtzAI-Consultants-Munich/data-centric-platform", + "Documentation": "https://readthedocs.org/projects/data-centric-platform", + } +) diff --git a/src/server/README.md b/src/server/README.md index 4c19dadf..24af07e6 100644 --- a/src/server/README.md +++ b/src/server/README.md @@ -9,19 +9,27 @@ The client and server communicate via the [bentoml](https://www.bentoml.com/?gcl ## How to use? -### Installation -This has been tested on Python versions 3.9, 3.10 and 3.11 on latest versions of Windows, Ubuntu and MacOS. In your dedicated environment run: +### Installation for developers + +For installing dcp-server you will need to have Python <3.12, the repo has been tested on Python versions 3.9, 3.10 and 3.11 on latest versions of Windows, Ubuntu and MacOS. + +First clone the repo: ``` -pip install dcp_server +git clone https://github.com/HelmholtzAI-Consultants-Munich/data-centric-platform.git ``` -### Installation for developers -Before starting make sure you have navigated to ```data-centric-platform/src/server```. All future steps expect you are in the server directory. In your dedicated environment run: +Then navigate to the server directory: +``` +cd data-centric-platform/src/server +``` + +In your dedicated environment run: ``` +pip install numpy pip install -e . ``` -#### Launch DCP server +### Launch DCP server Simply run: ``` python dcp_server/main.py diff --git a/src/server/dcp_server/config.yaml b/src/server/dcp_server/config.yaml index 5652469a..1facae5a 100644 --- a/src/server/dcp_server/config.yaml +++ b/src/server/dcp_server/config.yaml @@ -8,7 +8,8 @@ "runner_name": "bento_runner", "bento_model_path": "cells", "service_name": "data-centric-platform", - "port": 7010 + "port": 7010, + "timeout": 1000, }, "model": { diff --git a/src/server/dcp_server/main.py b/src/server/dcp_server/main.py index 9c149b5b..339c2be1 100644 --- a/src/server/dcp_server/main.py +++ b/src/server/dcp_server/main.py @@ -23,7 +23,7 @@ def main() -> None: "service", config_path=path.join(dir_name, "config.yaml") ) port = str(service_config["port"]) - + timeout = str(service_config["timeout"]) subprocess.run( [ "bentoml", @@ -33,6 +33,7 @@ def main() -> None: "service:svc", "--reload", "--port=" + port, + "--timeout=" + timeout ] ) diff --git a/src/server/dcp_server/service.py b/src/server/dcp_server/service.py index d464545b..16f7e3b0 100644 --- a/src/server/dcp_server/service.py +++ b/src/server/dcp_server/service.py @@ -1,23 +1,24 @@ from __future__ import annotations -import bentoml +import os import typing as t -from dcp_server.serviceclasses import CustomBentoService, CustomRunnable +import bentoml +from dcp_server.serviceclasses import CustomBentoService, CustomRunnable from dcp_server.utils.fsimagestorage import FilesystemImageStorage from dcp_server.utils.helpers import read_config -import sys, inspect models_module = __import__("models") segmentation_module = __import__("segmentationclasses") # Import configuration -service_config = read_config("service", config_path="config.yaml") -model_config = read_config("model", config_path="config.yaml") -data_config = read_config("data", config_path="config.yaml") -train_config = read_config("train", config_path="config.yaml") -eval_config = read_config("eval", config_path="config.yaml") -setup_config = read_config("setup", config_path="config.yaml") +rel_path = os.path.dirname(os.path.realpath(__file__)) +service_config = read_config("service", config_path=os.path.join(rel_path, "config.yaml")) +model_config = read_config("model", config_path=os.path.join(rel_path, "config.yaml")) +data_config = read_config("data", config_path=os.path.join(rel_path, "config.yaml")) +train_config = read_config("train", config_path=os.path.join(rel_path, "config.yaml")) +eval_config = read_config("eval", config_path=os.path.join(rel_path, "config.yaml")) +setup_config = read_config("setup", config_path=os.path.join(rel_path, "config.yaml")) # instantiate the model diff --git a/src/server/pyproject.toml b/src/server/pyproject.toml index 4acd006c..13c75d08 100644 --- a/src/server/pyproject.toml +++ b/src/server/pyproject.toml @@ -11,7 +11,7 @@ dependencies = {file = ["requirements.txt"]} [project] name = "data-centric-platform-server" version = "0.1" -requires-python = ">=3.9" +requires-python = ">=3.9, <3.12" description = "The server of the data centric platform for microscopy image segmentation" keywords = [] classifiers = [ diff --git a/src/server/requirements.txt b/src/server/requirements.txt index a42ba5eb..e0982e0d 100644 --- a/src/server/requirements.txt +++ b/src/server/requirements.txt @@ -1,10 +1,10 @@ wheel==0.42.0 cellpose==2.2.3 -bentoml==1.0.16 +bentoml>=1.2.5 scikit-image>=0.19.3 torchmetrics>=0.11.4 torch>=2.1.0 -numpy scikit-learn>=1.2.2 SimpleITK>=2.2.1 +numpy pyradiomics==3.0.1 \ No newline at end of file diff --git a/src/server/test/configs/test_config_MultiCellpose.yaml b/src/server/test/configs/test_config_MultiCellpose.yaml index 46b913d7..6cff5706 100644 --- a/src/server/test/configs/test_config_MultiCellpose.yaml +++ b/src/server/test/configs/test_config_MultiCellpose.yaml @@ -31,7 +31,7 @@ "train":{ "segmentor":{ - "n_epochs": 30, + "n_epochs": 50, "channels": [0,0], "min_train_masks": 1, "learning_rate":0.01