imaging-server-kit 0.1.0__tar.gz → 0.1.2__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 (108) hide show
  1. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/PKG-INFO +3 -2
  2. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/04_multialgo.ipynb +7 -1
  3. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/05_streams.ipynb +3 -3
  4. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/pyproject.toml +3 -2
  5. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/_version.py +3 -3
  6. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/client.py +4 -1
  7. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/results.py +1 -1
  8. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/demo/examples.py +1 -1
  9. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/template/cookiecutter.json +1 -1
  10. imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/main.py → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/main.py +7 -7
  11. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_bool.py +6 -4
  12. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_boxes.py +13 -6
  13. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_choice.py +6 -4
  14. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_float.py +6 -4
  15. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_image.py +6 -3
  16. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_integer.py +6 -4
  17. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_mask.py +8 -4
  18. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_notification.py +6 -4
  19. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_null.py +2 -2
  20. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_paths.py +6 -3
  21. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_points.py +8 -2
  22. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_string.py +6 -4
  23. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_tracks.py +6 -3
  24. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/_vectors.py +8 -3
  25. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit.egg-info/PKG-INFO +3 -2
  26. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit.egg-info/SOURCES.txt +8 -8
  27. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit.egg-info/requires.txt +2 -1
  28. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/.dockerignore +0 -0
  29. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/.gitattributes +0 -0
  30. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/.github/workflows/deploy.yml +0 -0
  31. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/.github/workflows/publish.yml +0 -0
  32. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/.gitignore +0 -0
  33. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/CITATION.cff +0 -0
  34. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/Dockerfile +0 -0
  35. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/Dockerfile-GPU +0 -0
  36. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/LICENSE +0 -0
  37. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/MANIFEST.in +0 -0
  38. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/README.md +0 -0
  39. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/build.sh +0 -0
  40. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_config.yml +0 -0
  41. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/oripy_threshold.mp4 +0 -0
  42. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/progressive_threshold.mp4 +0 -0
  43. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/server_napari.mp4 +0 -0
  44. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/stardist_blobs.mp4 +0 -0
  45. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/tiles.mp4 +0 -0
  46. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/tiling2.mp4 +0 -0
  47. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_static/yolo-stream.mp4 +0 -0
  48. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/_toc.yml +0 -0
  49. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/demo_napari_screenshot.png +0 -0
  50. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/gaussian_result.png +0 -0
  51. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/meta_sc.png +0 -0
  52. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/samples_sc.png +0 -0
  53. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/screenshot_napari_threshold.png +0 -0
  54. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/screenshot_server.png +0 -0
  55. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/server_running.png +0 -0
  56. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/assets/threshold_qupath.png +0 -0
  57. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/index.md +0 -0
  58. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/logo.ico +0 -0
  59. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/00_getting_started.md +0 -0
  60. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/01_algorithm.ipynb +0 -0
  61. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/02_samples.ipynb +0 -0
  62. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/03_metadata.ipynb +0 -0
  63. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/06_tiled.ipynb +0 -0
  64. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/07_server.md +0 -0
  65. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/08_python.ipynb +0 -0
  66. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/09_docker.md +0 -0
  67. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/10_types.md +0 -0
  68. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/docs/sections/11_examples.md +0 -0
  69. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/examples/readme.md +0 -0
  70. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/setup.cfg +0 -0
  71. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/__init__.py +0 -0
  72. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/cli.py +0 -0
  73. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/__init__.py +0 -0
  74. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/_etc.py +0 -0
  75. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/algorithm.py +0 -0
  76. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/app.py +0 -0
  77. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/encoding.py +0 -0
  78. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/errors.py +0 -0
  79. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/multialgo.py +0 -0
  80. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/runner.py +0 -0
  81. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/serialization.py +0 -0
  82. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/static/css/index.css +0 -0
  83. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/static/css/info.css +0 -0
  84. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/templates/index.html +0 -0
  85. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/templates/info.html +0 -0
  86. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/core/tiling.py +0 -0
  87. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/demo/__init__.py +0 -0
  88. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/demo/sample_images/blobs.tif +0 -0
  89. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/demo/sample_images/tracks.tif +0 -0
  90. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/.dockerignore +0 -0
  91. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/.gitignore +0 -0
  92. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/Dockerfile +0 -0
  93. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/README.md +0 -0
  94. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/docker-compose.yml +0 -0
  95. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/requirements.txt +0 -0
  96. {imaging_server_kit-0.1.0/src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }} → imaging_server_kit-0.1.2/src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}}/samples/blobs.tif +0 -0
  97. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/__init__.py +0 -0
  98. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/common.py +0 -0
  99. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit/types/data_layer.py +0 -0
  100. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit.egg-info/dependency_links.txt +0 -0
  101. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit.egg-info/entry_points.txt +0 -0
  102. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/src/imaging_server_kit.egg-info/top_level.txt +0 -0
  103. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/tests/sample_images/blobs.tif +0 -0
  104. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/tests/test_basic.py +0 -0
  105. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/tests/test_multialgo.py +0 -0
  106. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/tests/test_results.py +0 -0
  107. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/tests/test_streaming.py +0 -0
  108. {imaging_server_kit-0.1.0 → imaging_server_kit-0.1.2}/tests/test_tiling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: imaging-server-kit
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Convert image processing functions to GUIs, FastAPI servers, and more.
5
5
  Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
6
6
  License: Copyright (c) 2024, EPFL
@@ -52,10 +52,11 @@ Requires-Dist: numpy
52
52
  Requires-Dist: tifffile
53
53
  Requires-Dist: tqdm
54
54
  Requires-Dist: pydantic>=2.0
55
- Requires-Dist: scikit-image
55
+ Requires-Dist: scikit-image[optional]
56
56
  Requires-Dist: jinja2
57
57
  Requires-Dist: PyYAML
58
58
  Requires-Dist: cookiecutter
59
+ Requires-Dist: opencv-contrib-python-headless
59
60
  Requires-Dist: imantics
60
61
  Requires-Dist: geojson
61
62
  Requires-Dist: fastapi
@@ -80,8 +80,14 @@
80
80
  "\n",
81
81
  "## Next steps\n",
82
82
  "\n",
83
- "In the next section, we will explore the concept of streamed inference in Imaging Server Kit."
83
+ "In the next section, we will explore the concept of *live updates* in Imaging Server Kit."
84
84
  ]
85
+ },
86
+ {
87
+ "cell_type": "markdown",
88
+ "id": "57909c69",
89
+ "metadata": {},
90
+ "source": []
85
91
  }
86
92
  ],
87
93
  "metadata": {
@@ -5,9 +5,9 @@
5
5
  "id": "9ec31672",
6
6
  "metadata": {},
7
7
  "source": [
8
- "# Streamed inference\n",
8
+ "# Live updates\n",
9
9
  "\n",
10
- "Streamed inference allows an algorithm to send **intermediate outputs** to the client application before it has finished running. This makes it possible for user interfaces, such as the Napari Viewer, to update in real time.\n",
10
+ "Algorithms can send **intermediate outputs** to the client application before they have finished running. This makes it possible for user interfaces, such as the Napari Viewer, to update in real time.\n",
11
11
  "\n",
12
12
  "For example, you might \"stream\":\n",
13
13
  "\n",
@@ -56,7 +56,7 @@
56
56
  "id": "c5f4b3d1",
57
57
  "metadata": {},
58
58
  "source": [
59
- "Running this algorithm in Napari results in a \"streamed output\" showcasing a segmentation mask progressively updating as the threshold value increases.\n",
59
+ "When running this algorithm in Napari, the displayed segmentation mask progressively gets updated as the threshold value increases.\n",
60
60
  "\n",
61
61
  "<video width=512 controls loop autoplay>\n",
62
62
  " <source src=\"../_static/progressive_threshold.mp4\" type=\"video/mp4\">\n",
@@ -27,11 +27,12 @@ dependencies = [
27
27
  "tifffile", # To encode/decode
28
28
  "tqdm",
29
29
  "pydantic>=2.0",
30
- "scikit-image", # To imread samples and run demo examples
30
+ "scikit-image[optional]", # To imread samples and run demo examples
31
31
  "jinja2", # To template the info endpoint
32
32
  "PyYAML", # To load the metadata yaml file
33
33
  "cookiecutter", # To start from a template
34
- "imantics", # To convert masks to features for QuPath
34
+ "opencv-contrib-python-headless", # We need this instead of opencv-python to be able to run servers in docker.
35
+ "imantics", # To convert masks to features for QuPath. Comes with opencv-python.
35
36
  "geojson", # To convert masks and points to features for QuPath
36
37
  # Used in app
37
38
  "fastapi",
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.0'
32
- __version_tuple__ = version_tuple = (0, 1, 0)
31
+ __version__ = version = '0.1.2'
32
+ __version_tuple__ = version_tuple = (0, 1, 2)
33
33
 
34
- __commit_id__ = commit_id = 'g9cf654ced'
34
+ __commit_id__ = commit_id = 'g7132691fd'
@@ -22,6 +22,9 @@ from imaging_server_kit.core.results import Results
22
22
  from imaging_server_kit.core.serialization import deserialize_results
23
23
 
24
24
 
25
+ TIMEOUT_SEC = 3600 # Timeout for the /process route (in seconds)
26
+
27
+
25
28
  class Client(AlgorithmRunner):
26
29
  """Client to connect to and interact with algorithm servers.
27
30
 
@@ -181,7 +184,7 @@ class Client(AlgorithmRunner):
181
184
 
182
185
  def _run(self, algorithm: str, param_results: Results) -> Results:
183
186
  endpoint = f"{self.server_url}/{algorithm}/process"
184
- with httpx.Client(base_url=self.server_url) as client:
187
+ with httpx.Client(base_url=self.server_url, timeout=TIMEOUT_SEC) as client:
185
188
  try:
186
189
  response = client.post(
187
190
  endpoint,
@@ -234,7 +234,7 @@ class Results(LayerStackBase):
234
234
  return layer
235
235
 
236
236
  def update(
237
- self, layer_name: str, updated_data: np.ndarray, updated_meta: Dict
237
+ self, layer_name: str, updated_data: Any, updated_meta: Dict
238
238
  ) -> Optional[DataLayer]:
239
239
  """Update the data and meta attributes of a layer."""
240
240
  layer = self.read(layer_name)
@@ -50,7 +50,7 @@ def threshold_algo(image, threshold, dark_background):
50
50
  mask = image > thresh_rel
51
51
  else:
52
52
  mask = image <= thresh_rel
53
- return sk.Mask(mask, name="Binary mask (manual)")
53
+ return sk.Mask(mask, name="Binarized image")
54
54
 
55
55
 
56
56
  ## Automatic threshold
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "Threshold",
2
+ "name": "threshold",
3
3
  "project_url": "https://github.com/scikit-image/scikit-image",
4
4
  "python_version": "3.9"
5
5
  }
@@ -15,20 +15,20 @@ import imaging_server_kit as sk
15
15
  project_url="{{ cookiecutter.project_url }}",
16
16
  tags=["Segmentation"],
17
17
  parameters={
18
- "image": sk.Image(dimensionality=[2, 3]),
18
+ "image": sk.Image(),
19
19
  "threshold": sk.Float(
20
20
  name="Threshold",
21
- default=0.5,
22
- min=0.0,
23
- max=1.0,
24
- step=0.1,
25
- auto_call=True,
21
+ default=128,
22
+ min=0,
23
+ max=255,
24
+ step=1,
25
+ auto_call=False,
26
26
  ),
27
27
  },
28
28
  samples=[
29
29
  {
30
30
  "image": Path(__file__).parent / "samples" / "blobs.tif",
31
- "threshold": 0.7,
31
+ "threshold": 140,
32
32
  }
33
33
  ],
34
34
  )
@@ -36,9 +36,11 @@ class Bool(DataLayer):
36
36
  self.validate_data(data, self.meta, self.constraints)
37
37
 
38
38
  @classmethod
39
- def serialize(cls, data, client_origin):
40
- return bool(data)
39
+ def serialize(cls, data: Optional[bool], client_origin: str):
40
+ if data is not None:
41
+ return bool(data)
41
42
 
42
43
  @classmethod
43
- def deserialize(cls, serialized_data, client_origin):
44
- return bool(serialized_data)
44
+ def deserialize(cls, serialized_data: Optional[str], client_origin: str):
45
+ if serialized_data is not None:
46
+ return bool(serialized_data)
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Optional, Tuple
1
+ from typing import Any, Dict, List, Optional, Tuple
2
2
  import numpy as np
3
3
  from geojson import Feature, Polygon
4
4
 
@@ -133,9 +133,12 @@ class Boxes(DataLayer):
133
133
  self.meta = merged_boxes_meta
134
134
 
135
135
  @classmethod
136
- def serialize(cls, data, client_origin):
136
+ def serialize(cls, boxes: Optional[np.ndarray], client_origin: str) -> Optional[List[Feature]]:
137
+ if boxes is None:
138
+ return None
139
+
137
140
  features = []
138
- for i, box in enumerate(data):
141
+ for i, box in enumerate(boxes):
139
142
  coords = np.array(box)[:, ::-1] # Invert XY
140
143
  coords = coords.tolist()
141
144
  coords.append(coords[0]) # Close the Polygon
@@ -149,15 +152,18 @@ class Boxes(DataLayer):
149
152
  return features
150
153
 
151
154
  @classmethod
152
- def deserialize(cls, serialized_data, client_origin):
155
+ def deserialize(cls, serialized_data: Optional[List[Dict[str, Any]]], client_origin: str) -> Optional[np.ndarray]:
156
+ if serialized_data is None:
157
+ return None
153
158
  boxes = np.array(
154
159
  [feature["geometry"]["coordinates"] for feature in serialized_data]
155
160
  )
156
161
  boxes = np.array(
157
162
  [box[0] for box in boxes]
158
163
  ) # We get back a shape of (N, 1, 5, 2) - so we remove dim. 1
159
- boxes = boxes[:, :-1] # Remove the last element
160
- boxes = boxes[:, :, ::-1] # Invert XY
164
+ if len(boxes) > 0:
165
+ boxes = boxes[:, :-1] # Remove the last element
166
+ boxes = boxes[:, :, ::-1] # Invert XY
161
167
  return boxes
162
168
 
163
169
  @classmethod
@@ -175,4 +181,5 @@ class Boxes(DataLayer):
175
181
  assert isinstance(
176
182
  data, np.ndarray
177
183
  ), f"Boxes data ({type(data)}) is not a Numpy array"
184
+
178
185
  assert len(data.shape) == 3, "Boxes data should have shape (N, 4, D)"
@@ -51,9 +51,11 @@ class Choice(DataLayer):
51
51
  self.validate_data(data, self.meta, self.constraints)
52
52
 
53
53
  @classmethod
54
- def serialize(cls, data, client_origin):
55
- return str(data)
54
+ def serialize(cls, data: Optional[str], client_origin: str):
55
+ if data is not None:
56
+ return str(data)
56
57
 
57
58
  @classmethod
58
- def deserialize(cls, serialized_data, client_origin):
59
- return str(serialized_data)
59
+ def deserialize(cls, serialized_data: Optional[str], client_origin: str):
60
+ if serialized_data is not None:
61
+ return str(serialized_data)
@@ -49,9 +49,11 @@ class Float(DataLayer):
49
49
  self.validate_data(data, self.meta, self.constraints)
50
50
 
51
51
  @classmethod
52
- def serialize(cls, data, client_origin):
53
- return float(data)
52
+ def serialize(cls, data: Optional[float], client_origin: str):
53
+ if data is not None:
54
+ return float(data)
54
55
 
55
56
  @classmethod
56
- def deserialize(cls, serialized_data, client_origin):
57
- return float(serialized_data)
57
+ def deserialize(cls, serialized_data: Optional[float], client_origin: str):
58
+ if serialized_data is not None:
59
+ return float(serialized_data)
@@ -94,11 +94,14 @@ class Image(DataLayer):
94
94
  # so that we can compute a running average.
95
95
 
96
96
  @classmethod
97
- def serialize(cls, data, client_origin):
98
- return encode_contents(data.astype(np.float32))
97
+ def serialize(cls, data: Optional[np.ndarray], client_origin: str):
98
+ if data is not None:
99
+ return encode_contents(data.astype(np.float32))
99
100
 
100
101
  @classmethod
101
- def deserialize(cls, serialized_data: Union[np.ndarray, str], client_origin):
102
+ def deserialize(cls, serialized_data: Optional[Union[np.ndarray, str]], client_origin:str):
103
+ if serialized_data is None:
104
+ return None
102
105
  if isinstance(serialized_data, str):
103
106
  serialized_data = decode_contents(serialized_data)
104
107
  return serialized_data.astype(float)
@@ -50,9 +50,11 @@ class Integer(DataLayer):
50
50
  self.validate_data(data, self.meta, self.constraints)
51
51
 
52
52
  @classmethod
53
- def serialize(cls, data, client_origin):
54
- return int(data)
53
+ def serialize(cls, data: Optional[int], client_origin: str):
54
+ if data is not None:
55
+ return int(data)
55
56
 
56
57
  @classmethod
57
- def deserialize(cls, serialized_data, client_origin):
58
- return int(serialized_data)
58
+ def deserialize(cls, serialized_data: Optional[str], client_origin: str):
59
+ if serialized_data is not None:
60
+ return int(serialized_data)
@@ -242,17 +242,21 @@ class Mask(DataLayer):
242
242
  self.data[tile_slices] = mask_tile
243
243
 
244
244
  @classmethod
245
- def serialize(cls, data, client_origin):
245
+ def serialize(cls, mask: Optional[np.ndarray], client_origin: str) -> Optional[Union[List[Feature], str]]:
246
+ if mask is None:
247
+ return None
246
248
  if client_origin == "Python/Napari":
247
- features = encode_contents(data.astype(np.uint16))
249
+ features = encode_contents(mask.astype(np.uint16))
248
250
  elif client_origin == "Java/QuPath":
249
- features = mask2features(data)
251
+ features = mask2features(mask)
250
252
  else:
251
253
  raise ValueError(f"Unrecognized client origin: {client_origin}")
252
254
  return features
253
255
 
254
256
  @classmethod
255
- def deserialize(cls, serialized_data: Union[np.ndarray, str], client_origin):
257
+ def deserialize(cls, serialized_data: Optional[Union[np.ndarray, str]], client_origin: str) -> Optional[np.ndarray]:
258
+ if serialized_data is None:
259
+ return None
256
260
  if isinstance(serialized_data, str):
257
261
  if client_origin == "Python/Napari":
258
262
  data = decode_contents(serialized_data).astype(int)
@@ -40,12 +40,14 @@ class Notification(DataLayer):
40
40
  self.validate_data(data, self.meta, self.constraints)
41
41
 
42
42
  @classmethod
43
- def serialize(cls, data, client_origin):
44
- return str(data)
43
+ def serialize(cls, data: Optional[str], client_origin: str) -> Optional[str]:
44
+ if data is not None:
45
+ return str(data)
45
46
 
46
47
  @classmethod
47
- def deserialize(cls, serialized_data, client_origin):
48
- return str(serialized_data)
48
+ def deserialize(cls, serialized_data: Optional[str], client_origin: str) -> Optional[str]:
49
+ if serialized_data is not None:
50
+ return str(serialized_data)
49
51
 
50
52
  def __str__(self) -> str:
51
53
  level = self.meta.get("level", "info")
@@ -35,11 +35,11 @@ class Null(DataLayer):
35
35
  self.validate_data(data, self.meta, self.constraints)
36
36
 
37
37
  @classmethod
38
- def serialize(cls, data, client_origin):
38
+ def serialize(cls, data, client_origin: str):
39
39
  if data is not None:
40
40
  raise ValueError(f"Cannot serialize this object: {data}")
41
41
  return None
42
42
 
43
43
  @classmethod
44
- def deserialize(cls, serialized_data, client_origin):
44
+ def deserialize(cls, serialized_data, client_origin: str):
45
45
  return None
@@ -60,11 +60,14 @@ class Paths(DataLayer):
60
60
  return pixel_domain
61
61
 
62
62
  @classmethod
63
- def serialize(cls, data: List[np.ndarray], client_origin):
64
- return [encode_contents(arr.astype(np.float32)) for arr in data]
63
+ def serialize(cls, data: Optional[List[np.ndarray]], client_origin:str):
64
+ if data is not None:
65
+ return [encode_contents(arr.astype(np.float32)) for arr in data]
65
66
 
66
67
  @classmethod
67
- def deserialize(cls, serialized_data, client_origin):
68
+ def deserialize(cls, serialized_data: Optional[str], client_origin: str) -> Optional[List[np.ndarray]]:
69
+ if serialized_data is None:
70
+ return None
68
71
  data = []
69
72
  for f in serialized_data:
70
73
  if isinstance(f, str):
@@ -160,7 +160,9 @@ class Points(DataLayer):
160
160
  self.meta = merged_points_meta
161
161
 
162
162
  @classmethod
163
- def serialize(cls, points: np.ndarray, client_origin: str):
163
+ def serialize(cls, points: Optional[np.ndarray], client_origin: str) -> Optional[Union[str, List[Feature]]]:
164
+ if points is None:
165
+ return None
164
166
  if client_origin == "Python/Napari":
165
167
  point_features = encode_contents(points.astype(np.float32))
166
168
  elif client_origin == "Java/QuPath":
@@ -170,12 +172,16 @@ class Points(DataLayer):
170
172
  return point_features
171
173
 
172
174
  @classmethod
173
- def deserialize(cls, serialized_points: Union[np.ndarray, str], client_origin: str) -> np.ndarray:
175
+ def deserialize(cls, serialized_points: Optional[Union[np.ndarray, str]], client_origin: str) -> Optional[np.ndarray]:
176
+ if serialized_points is None:
177
+ return None
174
178
  if isinstance(serialized_points, str):
175
179
  if client_origin == "Python/Napari":
176
180
  points = decode_contents(serialized_points).astype(float)
177
181
  else:
178
182
  raise ValueError(f"Unrecognized client origin: {client_origin}")
183
+ else:
184
+ points = serialized_points
179
185
  return points
180
186
 
181
187
  @classmethod
@@ -36,9 +36,11 @@ class String(DataLayer):
36
36
  # TODO: implement string-specific properties, for example: max_length (could be validated).
37
37
 
38
38
  @classmethod
39
- def serialize(cls, data, client_origin):
40
- return str(data)
39
+ def serialize(cls, data: Optional[str], client_origin: str) -> Optional[str]:
40
+ if data is not None:
41
+ return str(data)
41
42
 
42
43
  @classmethod
43
- def deserialize(cls, serialized_data, client_origin):
44
- return str(serialized_data)
44
+ def deserialize(cls, serialized_data: Optional[str], client_origin: str) -> Optional[str]:
45
+ if serialized_data is not None:
46
+ return str(serialized_data)
@@ -49,11 +49,14 @@ class Tracks(DataLayer):
49
49
  # TODO: Implement object-specific properties, like max_objects or min_track_length (could be validated).
50
50
 
51
51
  @classmethod
52
- def serialize(cls, data, client_origin):
53
- return encode_contents(data.astype(np.float32))
52
+ def serialize(cls, data, client_origin: str) -> Optional[str]:
53
+ if data is not None:
54
+ return encode_contents(data.astype(np.float32))
54
55
 
55
56
  @classmethod
56
- def deserialize(cls, serialized_data: Union[np.ndarray, str], client_origin):
57
+ def deserialize(cls, serialized_data: Optional[Union[np.ndarray, str]], client_origin: str) -> Optional[np.ndarray]:
58
+ if serialized_data is None:
59
+ return None
57
60
  if isinstance(serialized_data, str):
58
61
  serialized_data = decode_contents(serialized_data)
59
62
  return serialized_data.astype(float)
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Optional, Tuple
1
+ from typing import Any, Dict, List, Optional, Tuple
2
2
  import numpy as np
3
3
  from geojson import Feature, LineString
4
4
 
@@ -141,7 +141,9 @@ class Vectors(DataLayer):
141
141
  self.meta = merged_vectors_meta
142
142
 
143
143
  @classmethod
144
- def serialize(cls, vectors, client_origin):
144
+ def serialize(cls, vectors: Optional[np.ndarray], client_origin: str) -> Optional[List[Feature]]:
145
+ if vectors is None:
146
+ return None
145
147
  serialized_vectors = []
146
148
  vectors = vectors[:, :, ::-1] # Invert XY
147
149
  for i, vector in enumerate(vectors):
@@ -158,7 +160,10 @@ class Vectors(DataLayer):
158
160
  return serialized_vectors
159
161
 
160
162
  @classmethod
161
- def deserialize(cls, serialized_vectors, client_origin: str) -> np.ndarray:
163
+ def deserialize(cls, serialized_vectors: Optional[List[Dict[str, Any]]], client_origin: str) -> Optional[np.ndarray]:
164
+ if serialized_vectors is None:
165
+ return None
166
+
162
167
  vectors_arr = np.array(
163
168
  [feature["geometry"]["coordinates"] for feature in serialized_vectors]
164
169
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: imaging-server-kit
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Convert image processing functions to GUIs, FastAPI servers, and more.
5
5
  Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
6
6
  License: Copyright (c) 2024, EPFL
@@ -52,10 +52,11 @@ Requires-Dist: numpy
52
52
  Requires-Dist: tifffile
53
53
  Requires-Dist: tqdm
54
54
  Requires-Dist: pydantic>=2.0
55
- Requires-Dist: scikit-image
55
+ Requires-Dist: scikit-image[optional]
56
56
  Requires-Dist: jinja2
57
57
  Requires-Dist: PyYAML
58
58
  Requires-Dist: cookiecutter
59
+ Requires-Dist: opencv-contrib-python-headless
59
60
  Requires-Dist: imantics
60
61
  Requires-Dist: geojson
61
62
  Requires-Dist: fastapi
@@ -73,14 +73,14 @@ src/imaging_server_kit/demo/examples.py
73
73
  src/imaging_server_kit/demo/sample_images/blobs.tif
74
74
  src/imaging_server_kit/demo/sample_images/tracks.tif
75
75
  src/imaging_server_kit/template/cookiecutter.json
76
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/.dockerignore
77
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/.gitignore
78
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/Dockerfile
79
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/README.md
80
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/docker-compose.yml
81
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/main.py
82
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/requirements.txt
83
- src/imaging_server_kit/template/serverkit-{{ cookiecutter.project_slug }}/samples/blobs.tif
76
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/.dockerignore
77
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/.gitignore
78
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/Dockerfile
79
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/README.md
80
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/docker-compose.yml
81
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/main.py
82
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/requirements.txt
83
+ src/imaging_server_kit/template/serverkit-{{cookiecutter.name}}/samples/blobs.tif
84
84
  src/imaging_server_kit/types/__init__.py
85
85
  src/imaging_server_kit/types/_bool.py
86
86
  src/imaging_server_kit/types/_boxes.py
@@ -2,10 +2,11 @@ numpy
2
2
  tifffile
3
3
  tqdm
4
4
  pydantic>=2.0
5
- scikit-image
5
+ scikit-image[optional]
6
6
  jinja2
7
7
  PyYAML
8
8
  cookiecutter
9
+ opencv-contrib-python-headless
9
10
  imantics
10
11
  geojson
11
12
  fastapi