imaging-server-kit 0.0.3__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/Dockerfile +2 -1
  2. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/PKG-INFO +20 -12
  3. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/README.md +15 -9
  4. imaging_server_kit-0.0.5/build.sh +6 -0
  5. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/main.py +4 -0
  6. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/pyproject.toml +4 -2
  7. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/__init__.py +2 -0
  8. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/_version.py +9 -4
  9. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/client.py +6 -3
  10. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/errors.py +14 -4
  11. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/geometry.py +76 -26
  12. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/registry.py +1 -1
  13. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/serialization.py +17 -0
  14. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/server.py +46 -10
  15. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/templates/info.html +1 -2
  16. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/PKG-INFO +20 -12
  17. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/SOURCES.txt +0 -1
  18. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/requires.txt +2 -0
  19. imaging_server_kit-0.0.5/start_registry.py +8 -0
  20. imaging_server_kit-0.0.3/Dockerfile-registry +0 -5
  21. imaging_server_kit-0.0.3/build.sh +0 -14
  22. imaging_server_kit-0.0.3/start_registry.py +0 -4
  23. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.dockerignore +0 -0
  24. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.gitattributes +0 -0
  25. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.github/workflows/publish.yml +0 -0
  26. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/.gitignore +0 -0
  27. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/Dockerfile-GPU +0 -0
  28. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/LICENSE +0 -0
  29. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/MANIFEST.in +0 -0
  30. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/examples/.gitkeep +0 -0
  31. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/setup.cfg +0 -0
  32. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/assets/style.css +0 -0
  33. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/encoding.py +0 -0
  34. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/static/index.css +0 -0
  35. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/static/info.css +0 -0
  36. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/templates/index.html +0 -0
  37. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit/web_demo.py +0 -0
  38. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/dependency_links.txt +0 -0
  39. {imaging_server_kit-0.0.3 → imaging_server_kit-0.0.5}/src/imaging_server_kit.egg-info/top_level.txt +0 -0
@@ -12,4 +12,5 @@ RUN python -m pip install -e .
12
12
 
13
13
  EXPOSE 8000
14
14
 
15
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
15
+ CMD ["python3", "main.py"]
16
+ # CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: imaging-server-kit
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Deploy image processing algorithms in FastAPI servers.
5
5
  Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
6
6
  License: Copyright (c) 2024, EPFL
@@ -31,8 +31,8 @@ License: Copyright (c) 2024, EPFL
31
31
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
32
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
 
34
- Project-URL: homepage, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
35
- Project-URL: repository, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
34
+ Project-URL: homepage, https://github.com/Imaging-Server-Kit/imaging-server-kit
35
+ Project-URL: repository, https://github.com/Imaging-Server-Kit/imaging-server-kit
36
36
  Classifier: Development Status :: 2 - Pre-Alpha
37
37
  Classifier: Intended Audience :: Developers
38
38
  Classifier: License :: OSI Approved :: BSD License
@@ -53,6 +53,8 @@ Requires-Dist: python-multipart
53
53
  Requires-Dist: tifffile
54
54
  Requires-Dist: numpy
55
55
  Requires-Dist: scikit-image
56
+ Requires-Dist: opencv-contrib-python-headless
57
+ Requires-Dist: imantics
56
58
  Requires-Dist: geojson
57
59
  Requires-Dist: jinja2
58
60
  Requires-Dist: pyyaml
@@ -64,7 +66,12 @@ Requires-Dist: pytest
64
66
  ![EPFL Center for Imaging logo](https://imaging.epfl.ch/resources/logo-for-gitlab.svg)
65
67
  # 🪐 Imaging Server Kit
66
68
 
67
- Deploy image processing algorithms in FastAPI servers and easily run them in Napari, QuPath, and more.
69
+ Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
70
+
71
+ The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
72
+ - `/process`: Runs the algorithm
73
+ - `/info`: Displays documentation about the algorithm usage
74
+ - `/sample_images`: Provides one or multiple sample images
68
75
 
69
76
  ## Usage
70
77
 
@@ -72,22 +79,23 @@ Deploy image processing algorithms in FastAPI servers and easily run them in Nap
72
79
 
73
80
  Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
74
81
 
75
- - [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist)
76
- - [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose)
77
- - [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow)
78
- - [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg)
79
- - [Scikit-image LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG)
80
- - [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy)
82
+ - [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
83
+ - [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
84
+ - [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
85
+ - [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
86
+ - [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
87
+ - [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
81
88
  <!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
82
89
 
83
- To do that, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
90
+ All of these algorithm servers can be installed locally or built and run with docker. To use docker and serve multiple algorithms, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
84
91
 
85
92
  ```{docker-compose.yml}
86
93
  services:
87
94
  servers_registry:
88
- image: mallorywittwerepfl/imaging-server-kit:registry
95
+ image: mallorywittwerepfl/imaging-server-kit:latest
89
96
  ports:
90
97
  - "8000:8000"
98
+ command: python3 start_registry.py
91
99
  rembg:
92
100
  image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
93
101
  depends_on:
@@ -1,7 +1,12 @@
1
1
  ![EPFL Center for Imaging logo](https://imaging.epfl.ch/resources/logo-for-gitlab.svg)
2
2
  # 🪐 Imaging Server Kit
3
3
 
4
- Deploy image processing algorithms in FastAPI servers and easily run them in Napari, QuPath, and more.
4
+ Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
5
+
6
+ The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
7
+ - `/process`: Runs the algorithm
8
+ - `/info`: Displays documentation about the algorithm usage
9
+ - `/sample_images`: Provides one or multiple sample images
5
10
 
6
11
  ## Usage
7
12
 
@@ -9,22 +14,23 @@ Deploy image processing algorithms in FastAPI servers and easily run them in Nap
9
14
 
10
15
  Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
11
16
 
12
- - [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist)
13
- - [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose)
14
- - [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow)
15
- - [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg)
16
- - [Scikit-image LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG)
17
- - [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy)
17
+ - [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
18
+ - [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
19
+ - [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
20
+ - [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
21
+ - [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
22
+ - [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
18
23
  <!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
19
24
 
20
- To do that, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
25
+ All of these algorithm servers can be installed locally or built and run with docker. To use docker and serve multiple algorithms, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
21
26
 
22
27
  ```{docker-compose.yml}
23
28
  services:
24
29
  servers_registry:
25
- image: mallorywittwerepfl/imaging-server-kit:registry
30
+ image: mallorywittwerepfl/imaging-server-kit:latest
26
31
  ports:
27
32
  - "8000:8000"
33
+ command: python3 start_registry.py
28
34
  rembg:
29
35
  image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
30
36
  depends_on:
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ docker build --build-arg PYTHON_VERSION=3.9 -t imaging-server-kit .
4
+
5
+ # echo "Building image for Python 3.9, GPU-compatible..."
6
+ # docker build -t imaging-server-kit:gpu --file Dockerfile-GPU .
@@ -1,4 +1,8 @@
1
+ import uvicorn
1
2
  import imaging_server_kit as serverkit
2
3
 
3
4
  server = serverkit.Server(algorithm_name="foo", parameters_model=serverkit.Parameters)
4
5
  app = server.app
6
+
7
+ if __name__=='__main__':
8
+ uvicorn.run("main:app", host="0.0.0.0", port=8000)
@@ -28,6 +28,8 @@ dependencies = [
28
28
  "tifffile",
29
29
  "numpy",
30
30
  "scikit-image",
31
+ "opencv-contrib-python-headless",
32
+ "imantics",
31
33
  "geojson",
32
34
  "jinja2", # For the /info route
33
35
  "pyyaml", # For the /info route
@@ -38,8 +40,8 @@ dependencies = [
38
40
  ]
39
41
 
40
42
  [project.urls]
41
- homepage = "https://gitlab.com/epfl-center-for-imaging/imaging-server-kit"
42
- repository = "https://gitlab.com/epfl-center-for-imaging/imaging-server-kit"
43
+ homepage = "https://github.com/Imaging-Server-Kit/imaging-server-kit"
44
+ repository = "https://github.com/Imaging-Server-Kit/imaging-server-kit"
43
45
 
44
46
  [build-system]
45
47
  requires = ["setuptools>=42.0.0", "wheel", "setuptools_scm"]
@@ -14,7 +14,9 @@ from imaging_server_kit.serialization import (
14
14
  )
15
15
  from imaging_server_kit.geometry import (
16
16
  mask2features,
17
+ instance_mask2features,
17
18
  features2mask,
19
+ features2instance_mask,
18
20
  boxes2features,
19
21
  features2boxes,
20
22
  points2features,
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.0.3'
16
- __version_tuple__ = version_tuple = (0, 0, 3)
20
+ __version__ = version = '0.0.5'
21
+ __version_tuple__ = version_tuple = (0, 0, 5)
@@ -7,6 +7,7 @@ from imaging_server_kit.errors import (
7
7
  AlgorithmServerError,
8
8
  InvalidAlgorithmParametersError,
9
9
  ServerRequestError,
10
+ AlgorithmTimeoutError,
10
11
  )
11
12
 
12
13
 
@@ -29,7 +30,7 @@ class Client:
29
30
  services = response.json().get("services")
30
31
  self.algorithms = services
31
32
  else:
32
- raise AlgorithmServerError(response.status_code, response.json())
33
+ raise AlgorithmServerError(response.status_code, response.text)
33
34
 
34
35
  @property
35
36
  def server_url(self) -> str:
@@ -68,8 +69,10 @@ class Client:
68
69
  return serverkit.deserialize_result_tuple(response.json())
69
70
  elif response.status_code == 422:
70
71
  raise InvalidAlgorithmParametersError(response.status_code, response.json())
72
+ elif response.status_code == 504:
73
+ raise AlgorithmTimeoutError(response.status_code, response.text)
71
74
  else:
72
- raise AlgorithmServerError(response.status_code, response.json())
75
+ raise AlgorithmServerError(response.status_code, response.text)
73
76
 
74
77
  def get_algorithm_parameters(self, algorithm=None) -> Dict:
75
78
  algorithm = self._validate_algorithm(algorithm)
@@ -84,7 +87,7 @@ class Client:
84
87
  if response.status_code == 200:
85
88
  return response.json()
86
89
  else:
87
- raise AlgorithmServerError(response.status_code, response.json())
90
+ raise AlgorithmServerError(response.status_code, response.text)
88
91
 
89
92
  def get_sample_images(
90
93
  self, algorithm=None, first_only: bool = False
@@ -13,17 +13,27 @@ class AlgorithmServerError(Exception):
13
13
  def __init__(self, status_code, response_body, message="Algorithm server returned an unexpected status_code"):
14
14
  self.status_code = status_code
15
15
  self.response_body = response_body
16
- self.message = f"{message}: {status_code}\nResponse body: {response_body}"
16
+ self.message = f"{message}: {status_code}\nResponse: {response_body}"
17
+ super().__init__(self.message)
18
+
19
+
20
+ class AlgorithmTimeoutError(Exception):
21
+ """Exception raised when a request to an algorithm server exceeds the time limit."""
22
+
23
+ def __init__(self, status_code, response_text, message="Algorithm server returned an unexpected status_code"):
24
+ self.status_code = status_code
25
+ self.response_text = response_text
26
+ self.message = f"{message}: {status_code}\nResponse: {response_text}"
17
27
  super().__init__(self.message)
18
28
 
19
29
 
20
30
  class InvalidAlgorithmParametersError(Exception):
21
31
  """Exception raised when a request to an algorithm server is made with invalid algorithm parameters."""
22
32
 
23
- def __init__(self, status_code, response_body, message="Algorithm parameters were invalidated by the server"):
33
+ def __init__(self, status_code, response_text, message="Algorithm parameters were invalidated by the server"):
24
34
  self.status_code = status_code
25
- self.response_body = response_body
26
- pydantic_details = response_body.get("detail")[0]
35
+ self.response_text = response_text
36
+ pydantic_details = response_text.get("detail")[0]
27
37
  pydantic_validation_msg = pydantic_details.get("msg")
28
38
  pydantic_failing_param = pydantic_details.get("loc")[1]
29
39
  pydantic_failing_param_value = pydantic_details.get("input")
@@ -1,53 +1,103 @@
1
1
  from typing import List
2
- from skimage.measure import find_contours
3
2
  from skimage.draw import polygon2mask
4
3
  from geojson import Feature, Polygon, Point, LineString
5
4
  import numpy as np
5
+ from imantics import Mask
6
6
 
7
7
 
8
- def mask2features(segmentation_mask: np.ndarray) -> List[Feature]:
8
+ def instance_mask2features(segmentation_mask: np.ndarray) -> List[Feature]:
9
9
  """
10
10
  Args:
11
11
  segm_mask: Segmentation mask with the background pixels set to zero and the pixels assigned to a segmented
12
- object set to an int value
12
+ object instance set to an int value
13
13
 
14
14
  Returns:
15
15
  A list containing the contours of each object as a geojson.Feature
16
16
  """
17
17
  features = []
18
18
  indices = np.unique(segmentation_mask)
19
- indices = np.delete(indices, indices == 0) # remove background
20
- if indices.size > 0:
21
- for i in indices.tolist():
22
- mask = np.array(segmentation_mask == i)
23
- mask = np.pad(mask > 0, 1)
24
- contours_find = find_contours(mask, 0.5)
25
- if len(contours_find) == 1:
26
- index = 0
27
- else:
28
- # TODO: Change this to handle panoptic segmentation cases
29
- # if more than one contour is found, the largest one is returned
30
- n_pixels = []
31
- for _, item in enumerate(contours_find):
32
- n_pixels.append(len(item))
33
- index = np.argmax(n_pixels)
34
- contour = contours_find[index]
35
- contour -= 1 # reset padding
36
- contour_as_numpy = contour[:, np.argsort([1, 0])] # Fix XY
37
- geom = Polygon([contour_as_numpy.tolist()])
38
- features.append(Feature(geometry=geom, properties={"Detection ID": i}))
19
+ indices = indices[indices != 0] # remove background
20
+
21
+ if indices.size == 0:
22
+ return features
23
+
24
+ for detection_id in indices:
25
+ mask = segmentation_mask == int(detection_id)
26
+ polygons = Mask(mask).polygons()
27
+ for contour in polygons.points:
28
+ coords = np.array(contour)
29
+ coords = np.vstack([coords, coords[0]]) # Close the polygon for QuPath
30
+ geom = Polygon(coordinates=[coords.tolist()])
31
+ feature = Feature(
32
+ geometry=geom,
33
+ properties={
34
+ "Detection ID": int(detection_id),
35
+ "Class": 1
36
+ }
37
+ )
38
+ features.append(feature)
39
+
40
+ return features
41
+
42
+
43
+ def mask2features(segmentation_mask: np.ndarray) -> List[Feature]:
44
+ """
45
+ Args:
46
+ segm_mask: Segmentation mask with the background pixels set to zero and the pixels assigned to a segmented
47
+ class set to an int value
48
+
49
+ Returns:
50
+ A list containing the contours of each object as a geojson.Feature
51
+ """
52
+ features = []
53
+ indices = np.unique(segmentation_mask)
54
+ indices = indices[indices != 0] # remove background
55
+
56
+ if indices.size == 0:
57
+ return features
58
+
59
+ for pixel_class in indices:
60
+ mask = segmentation_mask == int(pixel_class)
61
+ polygons = Mask(mask).polygons()
62
+ for detection_id, contour in enumerate(polygons.points, start=1):
63
+ coords = np.array(contour)
64
+ coords = np.vstack([coords, coords[0]]) # Close the polygon for QuPath
65
+ geom = Polygon(coordinates=[coords.tolist()])
66
+ feature = Feature(
67
+ geometry=geom,
68
+ properties={
69
+ "Detection ID": detection_id,
70
+ "Class": pixel_class
71
+ }
72
+ )
73
+ features.append(feature)
74
+
39
75
  return features
40
76
 
41
77
 
42
78
  def features2mask(features, image_shape):
43
79
  segmentation_mask = np.zeros(image_shape, dtype=np.uint16)
44
- for idx, feature in enumerate(features):
45
- label_idx = idx+1 # Labels start at 1
80
+ for feature in features:
81
+ feature_coordinates = np.array(feature["geometry"]["coordinates"])
82
+ feature_coordinates = np.squeeze(feature_coordinates)
83
+ feature_coordinates = feature_coordinates[:, ::-1] # Invert XY
84
+ feature_mask = polygon2mask(image_shape, feature_coordinates)
85
+ feature_properites = feature.get("properties")
86
+ feature_class = feature_properites.get("Class")
87
+ segmentation_mask[feature_mask] = feature_class
88
+ return segmentation_mask
89
+
90
+
91
+ def features2instance_mask(features, image_shape):
92
+ segmentation_mask = np.zeros(image_shape, dtype=np.uint16)
93
+ for feature in features:
46
94
  feature_coordinates = np.array(feature["geometry"]["coordinates"])
47
95
  feature_coordinates = np.squeeze(feature_coordinates)
48
96
  feature_coordinates = feature_coordinates[:, ::-1] # Invert XY
49
97
  feature_mask = polygon2mask(image_shape, feature_coordinates)
50
- segmentation_mask[feature_mask] = label_idx
98
+ feature_properites = feature.get("properties")
99
+ feature_id = feature_properites.get("Detection ID")
100
+ segmentation_mask[feature_mask] = feature_id
51
101
  return segmentation_mask
52
102
 
53
103
 
@@ -74,7 +74,7 @@ class Registry:
74
74
  # response = requests.get(f"{self.services.get(algorithm)}/demo")
75
75
  # return HTMLResponse(content=response.text, status_code=response.status_code)
76
76
 
77
- @self.app.get("/{algorithm}")
77
+ @self.app.get("/{algorithm}/")
78
78
  async def get_algorithm(request: Request, algorithm: str):
79
79
  algo_url = self.services.get(algorithm)
80
80
  response = requests.get(f"{algo_url}/")
@@ -69,8 +69,13 @@ def serialize_result_tuple(result_data_tuple: List[Tuple]) -> List[Dict]:
69
69
  if data_type == "image":
70
70
  features = serverkit.encode_contents(data.astype(np.float32))
71
71
  elif data_type == "labels":
72
+ data = data.astype(np.uint16)
72
73
  features = serverkit.mask2features(data)
73
74
  data_params['image_shape'] = data.shape
75
+ elif data_type == "instance_mask":
76
+ data = data.astype(np.uint16)
77
+ features = serverkit.instance_mask2features(data)
78
+ data_params['image_shape'] = data.shape
74
79
  elif data_type == "labels3d":
75
80
  features = serverkit.encode_contents(data.astype(np.uint16))
76
81
  elif data_type == "points":
@@ -84,8 +89,13 @@ def serialize_result_tuple(result_data_tuple: List[Tuple]) -> List[Dict]:
84
89
  features = serverkit.vectors2features(data)
85
90
  elif data_type == "tracks":
86
91
  features = serverkit.encode_contents(data.astype(np.float32))
92
+ elif data_type == "class":
93
+ features = data # A simple string
94
+ elif data_type == "text":
95
+ features = data # A text string
87
96
  else:
88
97
  print(f"Unknown data_type: {data_type}")
98
+ features = None
89
99
 
90
100
  serialized_results.append(
91
101
  {
@@ -113,6 +123,9 @@ def deserialize_result_tuple(serialized_results: List[Dict]) -> List[Tuple]:
113
123
  elif data_type == "labels":
114
124
  image_shape = data_params.pop('image_shape')
115
125
  data = serverkit.features2mask(features, image_shape)
126
+ elif data_type == "instance_mask":
127
+ image_shape = data_params.pop('image_shape')
128
+ data = serverkit.features2instance_mask(features, image_shape)
116
129
  elif data_type == "labels3d":
117
130
  data = serverkit.decode_contents(features).astype(int)
118
131
  elif data_type == "points":
@@ -125,6 +138,10 @@ def deserialize_result_tuple(serialized_results: List[Dict]) -> List[Tuple]:
125
138
  data = serverkit.features2vectors(features)
126
139
  elif data_type == "tracks":
127
140
  data = serverkit.decode_contents(features).astype(float)
141
+ elif data_type == "class":
142
+ data = features # A simple string
143
+ elif data_type == "text":
144
+ data = features # A text string
128
145
  else:
129
146
  print(f"Unknown data_type: {data_type}")
130
147
 
@@ -5,15 +5,15 @@ from typing import List, Tuple, Type
5
5
  import imaging_server_kit as serverkit
6
6
  import requests
7
7
  import yaml
8
- from fastapi import FastAPI, Request, status
8
+ from fastapi import FastAPI, Request, status, HTTPException
9
9
  from fastapi.responses import HTMLResponse
10
10
  from fastapi.staticfiles import StaticFiles
11
11
  from fastapi.templating import Jinja2Templates
12
12
  from imaging_server_kit.web_demo import generate_dash_app
13
13
  from pydantic import BaseModel, ConfigDict
14
14
  from a2wsgi import WSGIMiddleware
15
- # from starlette.middleware.wsgi import WSGIMiddleware
16
15
  from contextlib import asynccontextmanager
16
+ import asyncio
17
17
 
18
18
  templates_dir = importlib.resources.files("imaging_server_kit").joinpath("templates")
19
19
  static_dir = importlib.resources.files("imaging_server_kit").joinpath("static")
@@ -45,10 +45,20 @@ class Parameters(BaseModel):
45
45
 
46
46
 
47
47
  class Server:
48
- def __init__(self, algorithm_name: str, parameters_model: Type[BaseModel]):
48
+ def __init__(
49
+ self,
50
+ algorithm_name: str,
51
+ parameters_model: Type[BaseModel],
52
+ metadata_file: str = None,
53
+ ):
49
54
  self.algorithm_name = algorithm_name
50
55
  self.parameters_model = parameters_model
51
56
 
57
+ if metadata_file is None:
58
+ self.metadata_file = "metadata.yaml"
59
+ else:
60
+ self.metadata_file = metadata_file
61
+
52
62
  self.app = FastAPI(title=algorithm_name, lifespan=self.lifespan)
53
63
  self.app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
54
64
 
@@ -65,9 +75,6 @@ class Server:
65
75
 
66
76
  self.app.mount(f"/{algorithm_name}/demo", WSGIMiddleware(dash_app.server))
67
77
 
68
- # self.app.on_event("startup")(self.register_with_registry)
69
- # self.app.on_event("shutdown")(self.deregister_from_registry)
70
-
71
78
  self.register_routes()
72
79
 
73
80
  self.services = [self.algorithm_name]
@@ -120,7 +127,7 @@ class Server:
120
127
 
121
128
  @self.app.get(f"/{self.algorithm_name}/info", response_class=HTMLResponse)
122
129
  def info(request: Request):
123
- algo_info = load_from_yaml("metadata.yaml")
130
+ algo_info = load_from_yaml(self.metadata_file)
124
131
  algo_params_schema = get_algo_params()
125
132
  algo_params = parse_algo_params_schema(algo_params_schema)
126
133
  return templates.TemplateResponse(
@@ -132,10 +139,29 @@ class Server:
132
139
  },
133
140
  )
134
141
 
135
- @self.app.post(f"/{self.algorithm_name}/process", status_code=status.HTTP_201_CREATED)
142
+ @self.app.post(
143
+ f"/{self.algorithm_name}/process", status_code=status.HTTP_201_CREATED
144
+ )
136
145
  async def run_algo(algo_params: self.parameters_model):
137
- result_data_tuple = self.run_algorithm(**algo_params.dict())
138
- serialized_results = serverkit.serialize_result_tuple(result_data_tuple)
146
+ try:
147
+ result_data_tuple = await asyncio.wait_for(
148
+ self._run_algorithm(**algo_params.dict()), timeout=600
149
+ )
150
+ except (
151
+ asyncio.TimeoutError
152
+ ): # This works but doesn't actually kill the process... we need something else to terminate it
153
+ raise HTTPException(
154
+ status_code=504, detail="Request timeout during processing."
155
+ )
156
+ try:
157
+ serialized_results = await asyncio.wait_for(
158
+ self._serialize_result_tuple(result_data_tuple), timeout=600
159
+ )
160
+ except asyncio.TimeoutError:
161
+ raise HTTPException(
162
+ status_code=504, detail="Request timeout during serialization."
163
+ )
164
+
139
165
  return serialized_results
140
166
 
141
167
  @self.app.get(f"/{self.algorithm_name}/parameters", response_model=dict)
@@ -150,6 +176,16 @@ class Server:
150
176
  ]
151
177
  return {"sample_images": encoded_images}
152
178
 
179
+ async def _serialize_result_tuple(self, result_data_tuple):
180
+ serialized_results = await asyncio.to_thread(
181
+ serverkit.serialize_result_tuple, result_data_tuple
182
+ )
183
+ return serialized_results
184
+
185
+ async def _run_algorithm(self, **algo_params):
186
+ result_data_tuple = await asyncio.to_thread(self.run_algorithm, **algo_params)
187
+ return result_data_tuple
188
+
153
189
  def load_sample_images(self) -> List["np.ndarray"]:
154
190
  raise NotImplementedError("Subclasses should implement this method")
155
191
 
@@ -38,7 +38,7 @@
38
38
  <p>Server Kit Repository</p>
39
39
  </div>
40
40
  </a>
41
- <a href="/demo">
41
+ <a href="demo">
42
42
  <div class="card-category">
43
43
  <i class="fa-solid fa-rocket"></i>
44
44
  <p>Web demo</p>
@@ -46,7 +46,6 @@
46
46
  </a>
47
47
  </div>
48
48
 
49
- <!-- <h3><strong>Parameters</strong></h3> -->
50
49
  <table>
51
50
  <thead>
52
51
  <tr>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: imaging-server-kit
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Deploy image processing algorithms in FastAPI servers.
5
5
  Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
6
6
  License: Copyright (c) 2024, EPFL
@@ -31,8 +31,8 @@ License: Copyright (c) 2024, EPFL
31
31
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
32
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
 
34
- Project-URL: homepage, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
35
- Project-URL: repository, https://gitlab.com/epfl-center-for-imaging/imaging-server-kit
34
+ Project-URL: homepage, https://github.com/Imaging-Server-Kit/imaging-server-kit
35
+ Project-URL: repository, https://github.com/Imaging-Server-Kit/imaging-server-kit
36
36
  Classifier: Development Status :: 2 - Pre-Alpha
37
37
  Classifier: Intended Audience :: Developers
38
38
  Classifier: License :: OSI Approved :: BSD License
@@ -53,6 +53,8 @@ Requires-Dist: python-multipart
53
53
  Requires-Dist: tifffile
54
54
  Requires-Dist: numpy
55
55
  Requires-Dist: scikit-image
56
+ Requires-Dist: opencv-contrib-python-headless
57
+ Requires-Dist: imantics
56
58
  Requires-Dist: geojson
57
59
  Requires-Dist: jinja2
58
60
  Requires-Dist: pyyaml
@@ -64,7 +66,12 @@ Requires-Dist: pytest
64
66
  ![EPFL Center for Imaging logo](https://imaging.epfl.ch/resources/logo-for-gitlab.svg)
65
67
  # 🪐 Imaging Server Kit
66
68
 
67
- Deploy image processing algorithms in FastAPI servers and easily run them in Napari, QuPath, and more.
69
+ Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
70
+
71
+ The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
72
+ - `/process`: Runs the algorithm
73
+ - `/info`: Displays documentation about the algorithm usage
74
+ - `/sample_images`: Provides one or multiple sample images
68
75
 
69
76
  ## Usage
70
77
 
@@ -72,22 +79,23 @@ Deploy image processing algorithms in FastAPI servers and easily run them in Nap
72
79
 
73
80
  Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
74
81
 
75
- - [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist)
76
- - [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose)
77
- - [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow)
78
- - [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg)
79
- - [Scikit-image LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG)
80
- - [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy)
82
+ - [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
83
+ - [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
84
+ - [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
85
+ - [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
86
+ - [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
87
+ - [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
81
88
  <!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
82
89
 
83
- To do that, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
90
+ All of these algorithm servers can be installed locally or built and run with docker. To use docker and serve multiple algorithms, the recommended way is to edit a `docker-compose.yml` file and pull server images from `registry.rcp.epfl.ch` (EPFL users only):
84
91
 
85
92
  ```{docker-compose.yml}
86
93
  services:
87
94
  servers_registry:
88
- image: mallorywittwerepfl/imaging-server-kit:registry
95
+ image: mallorywittwerepfl/imaging-server-kit:latest
89
96
  ports:
90
97
  - "8000:8000"
98
+ command: python3 start_registry.py
91
99
  rembg:
92
100
  image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
93
101
  depends_on:
@@ -3,7 +3,6 @@
3
3
  .gitignore
4
4
  Dockerfile
5
5
  Dockerfile-GPU
6
- Dockerfile-registry
7
6
  LICENSE
8
7
  MANIFEST.in
9
8
  README.md
@@ -4,6 +4,8 @@ python-multipart
4
4
  tifffile
5
5
  numpy
6
6
  scikit-image
7
+ opencv-contrib-python-headless
8
+ imantics
7
9
  geojson
8
10
  jinja2
9
11
  pyyaml
@@ -0,0 +1,8 @@
1
+ import uvicorn
2
+ import imaging_server_kit as serverkit
3
+
4
+ server = serverkit.Registry()
5
+ app = server.app
6
+
7
+ if __name__=='__main__':
8
+ uvicorn.run("start_registry:app", host="0.0.0.0", port=8000)
@@ -1,5 +0,0 @@
1
- FROM mallorywittwerepfl/imaging-server-kit:3.9
2
-
3
- COPY start_registry.py .
4
-
5
- CMD ["uvicorn", "start_registry:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -1,14 +0,0 @@
1
- #!/bin/bash
2
-
3
- PYTHON_VERSIONS=("3.9" "3.10" "3.11")
4
-
5
- for version in "${PYTHON_VERSIONS[@]}"; do
6
- echo "Building image for Python $version..."
7
- docker build --build-arg PYTHON_VERSION=$version -t imaging-server-kit:$version .
8
- done
9
-
10
- # echo "Building image for Python 3.9, GPU-compatible..."
11
- # docker build -t imaging-server-kit:gpu --file Dockerfile-GPU .
12
-
13
- echo "Building image for Python 3.9, serverkit registry..."
14
- docker build -t imaging-server-kit:registry --file Dockerfile-registry .
@@ -1,4 +0,0 @@
1
- import imaging_server_kit as serverkit
2
-
3
- server = serverkit.Registry()
4
- app = server.app