imaging-server-kit 0.0.2__tar.gz → 0.0.4__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.
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/Dockerfile +2 -1
- imaging_server_kit-0.0.4/PKG-INFO +189 -0
- imaging_server_kit-0.0.4/README.md +126 -0
- imaging_server_kit-0.0.4/build.sh +6 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/main.py +4 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/pyproject.toml +1 -1
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/__init__.py +6 -2
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/_version.py +2 -2
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/client.py +7 -4
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/errors.py +14 -4
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/geometry.py +15 -18
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/registry.py +3 -3
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/serialization.py +1 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/server.py +33 -12
- imaging_server_kit-0.0.4/src/imaging_server_kit/static/index.css +47 -0
- imaging_server_kit-0.0.4/src/imaging_server_kit/static/info.css +124 -0
- imaging_server_kit-0.0.4/src/imaging_server_kit/templates/index.html +33 -0
- imaging_server_kit-0.0.4/src/imaging_server_kit/templates/info.html +77 -0
- imaging_server_kit-0.0.4/src/imaging_server_kit.egg-info/PKG-INFO +189 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit.egg-info/SOURCES.txt +0 -2
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit.egg-info/requires.txt +1 -1
- imaging_server_kit-0.0.4/start_registry.py +8 -0
- imaging_server_kit-0.0.2/Dockerfile-registry +0 -5
- imaging_server_kit-0.0.2/PKG-INFO +0 -154
- imaging_server_kit-0.0.2/README.md +0 -91
- imaging_server_kit-0.0.2/build.sh +0 -14
- imaging_server_kit-0.0.2/src/imaging_server_kit/autostartup_registry.py +0 -58
- imaging_server_kit-0.0.2/src/imaging_server_kit/static/index.css +0 -32
- imaging_server_kit-0.0.2/src/imaging_server_kit/static/info.css +0 -32
- imaging_server_kit-0.0.2/src/imaging_server_kit/templates/index.html +0 -25
- imaging_server_kit-0.0.2/src/imaging_server_kit/templates/info.html +0 -63
- imaging_server_kit-0.0.2/src/imaging_server_kit.egg-info/PKG-INFO +0 -154
- imaging_server_kit-0.0.2/start_registry.py +0 -4
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/.dockerignore +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/.gitattributes +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/.github/workflows/publish.yml +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/.gitignore +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/Dockerfile-GPU +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/LICENSE +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/MANIFEST.in +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/examples/.gitkeep +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/setup.cfg +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/assets/style.css +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/encoding.py +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/web_demo.py +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit.egg-info/dependency_links.txt +0 -0
- {imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: imaging-server-kit
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Deploy image processing algorithms in FastAPI servers.
|
|
5
|
+
Author-email: Mallory Wittwer <mallory.wittwer@epfl.ch>
|
|
6
|
+
License: Copyright (c) 2024, EPFL
|
|
7
|
+
All rights reserved.
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
13
|
+
list of conditions and the following disclaimer.
|
|
14
|
+
|
|
15
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
16
|
+
this list of conditions and the following disclaimer in the documentation
|
|
17
|
+
and/or other materials provided with the distribution.
|
|
18
|
+
|
|
19
|
+
* Neither the name of copyright holder nor the names of its
|
|
20
|
+
contributors may be used to endorse or promote products derived from
|
|
21
|
+
this software without specific prior written permission.
|
|
22
|
+
|
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
24
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
25
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
26
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
27
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
28
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
29
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
30
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
31
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
32
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
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
|
|
36
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
37
|
+
Classifier: Intended Audience :: Developers
|
|
38
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
39
|
+
Classifier: Operating System :: OS Independent
|
|
40
|
+
Classifier: Programming Language :: Python
|
|
41
|
+
Classifier: Programming Language :: Python :: 3
|
|
42
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
46
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
47
|
+
Requires-Python: >=3.9
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
License-File: LICENSE
|
|
50
|
+
Requires-Dist: fastapi
|
|
51
|
+
Requires-Dist: uvicorn[standard]
|
|
52
|
+
Requires-Dist: python-multipart
|
|
53
|
+
Requires-Dist: tifffile
|
|
54
|
+
Requires-Dist: numpy
|
|
55
|
+
Requires-Dist: scikit-image
|
|
56
|
+
Requires-Dist: geojson
|
|
57
|
+
Requires-Dist: jinja2
|
|
58
|
+
Requires-Dist: pyyaml
|
|
59
|
+
Requires-Dist: dash
|
|
60
|
+
Requires-Dist: dash-bootstrap-components
|
|
61
|
+
Requires-Dist: a2wsgi
|
|
62
|
+
Requires-Dist: pytest
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
# 🪐 Imaging Server Kit
|
|
66
|
+
|
|
67
|
+
Deploy image processing algorithms in FastAPI servers and easily run them from Napari, QuPath, and more.
|
|
68
|
+
|
|
69
|
+
The algorithm servers implement a common set of API endpoints for processing and to document the project. These include:
|
|
70
|
+
- `/process`: Runs the algorithm
|
|
71
|
+
- `/info`: Displays documentation about the algorithm usage
|
|
72
|
+
- `/sample_images`: Provides one or multiple sample images
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Algorithm servers
|
|
77
|
+
|
|
78
|
+
Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
|
|
79
|
+
|
|
80
|
+
- [StarDist](https://github.com/Imaging-Server-Kit/serverkit-stardist): Object detection with star-convex shapes
|
|
81
|
+
- [CellPose](https://github.com/Imaging-Server-Kit/serverkit-cellpose): A generalist algorithm for cellular segmentation
|
|
82
|
+
- [Spotiflow](https://github.com/Imaging-Server-Kit/serverkit-spotiflow): Accurate and efficient spot detection
|
|
83
|
+
- [Rembg](https://github.com/Imaging-Server-Kit/serverkit-rembg): A tool to remove images background
|
|
84
|
+
- [LoG detector](https://github.com/Imaging-Server-Kit/serverkit-skimage-LoG): Laplacian of Gaussian filter
|
|
85
|
+
- [Orientationpy](https://github.com/Imaging-Server-Kit/serverkit-orientationpy): Measurement of greyscale orientations
|
|
86
|
+
<!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
|
|
87
|
+
|
|
88
|
+
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):
|
|
89
|
+
|
|
90
|
+
```{docker-compose.yml}
|
|
91
|
+
services:
|
|
92
|
+
servers_registry:
|
|
93
|
+
image: mallorywittwerepfl/imaging-server-kit:registry
|
|
94
|
+
ports:
|
|
95
|
+
- "8000:8000"
|
|
96
|
+
rembg:
|
|
97
|
+
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
98
|
+
depends_on:
|
|
99
|
+
- servers_registry
|
|
100
|
+
skimage-log:
|
|
101
|
+
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-skimage-log:latest
|
|
102
|
+
depends_on:
|
|
103
|
+
- servers_registry
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Start the server with
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
docker compose up
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The server should be accessible at http://localhost:8000.
|
|
113
|
+
|
|
114
|
+
**Build the docker images yourself**
|
|
115
|
+
|
|
116
|
+
To build the algorithm server images yourself (e.g. if you are not from EPFL), see [Reference Deployment](https://github.com/Imaging-Server-Kit/serverkit-deploy-docker).
|
|
117
|
+
|
|
118
|
+
**Create a server for your project**
|
|
119
|
+
|
|
120
|
+
To learn how to create a server for your project, see [Algorithm Server Template](https://github.com/Imaging-Server-Kit/cookiecutter-serverkit).
|
|
121
|
+
|
|
122
|
+
### Python client
|
|
123
|
+
|
|
124
|
+
Once the server is running, connect to it and run algorithms from Python:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from imaging_server_kit import Client
|
|
128
|
+
|
|
129
|
+
client = Client("http://localhost:8000")
|
|
130
|
+
|
|
131
|
+
print(client.algorithms)
|
|
132
|
+
# [`rembg`, `stardist`, `cellpose`]
|
|
133
|
+
|
|
134
|
+
algo_output = client.run_algorithm(
|
|
135
|
+
algorithm="rembg",
|
|
136
|
+
image=(...),
|
|
137
|
+
rembg_model_name="silueta",
|
|
138
|
+
)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Napari plugin
|
|
142
|
+
|
|
143
|
+
Once the server is running, use the [Napari Server Kit](https://github.com/Imaging-Server-Kit/napari-serverkit) plugin to connect to it and run algorithms in Napari.
|
|
144
|
+
|
|
145
|
+
### QuPath extension
|
|
146
|
+
|
|
147
|
+
Once the server is running, use the [QuPath Extension Server Kit](https://github.com/Imaging-Server-Kit/qupath-extension-serverkit) to connect to it and run algorithms from within QuPath.
|
|
148
|
+
|
|
149
|
+
## Install locally
|
|
150
|
+
|
|
151
|
+
Install the `imaging-server-kit` package with `pip`:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
pip install imaging-server-kit
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
or clone the project and install the development version:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
git clone https://github.com/Imaging-Server-Kit/imaging-server-kit.git
|
|
161
|
+
cd imaging-server-kit
|
|
162
|
+
pip install -e .
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Build with `docker`
|
|
166
|
+
|
|
167
|
+
Build the `imaging-server-kit` docker image for a specific Python version:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
docker build --build-arg PYTHON_VERSION=3.9 -t imaging-server-kit:3.9 .
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Run [build.sh](./build.sh) to build docker images for Python 3.9, 3.10, 3.11, GPU and the server kit registry:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
bash ./build.sh
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
Contributions are very welcome.
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
This software is distributed under the terms of the [BSD-3](http://opensource.org/licenses/BSD-3-Clause) license.
|
|
186
|
+
|
|
187
|
+
## Issues
|
|
188
|
+
|
|
189
|
+
If you encounter any problems, please file an issue along with a detailed description.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+

|
|
2
|
+
# 🪐 Imaging Server Kit
|
|
3
|
+
|
|
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
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Algorithm servers
|
|
14
|
+
|
|
15
|
+
Set up and run a server with one, or multiple algorithms from the Imaging Server Kit collection (or your own):
|
|
16
|
+
|
|
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
|
|
23
|
+
<!-- - [Tau Fibrils Detector](https://github.com/Imaging-Server-Kit/serverkit-tau-fibrils-yolo) -->
|
|
24
|
+
|
|
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):
|
|
26
|
+
|
|
27
|
+
```{docker-compose.yml}
|
|
28
|
+
services:
|
|
29
|
+
servers_registry:
|
|
30
|
+
image: mallorywittwerepfl/imaging-server-kit:registry
|
|
31
|
+
ports:
|
|
32
|
+
- "8000:8000"
|
|
33
|
+
rembg:
|
|
34
|
+
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-rembg:latest
|
|
35
|
+
depends_on:
|
|
36
|
+
- servers_registry
|
|
37
|
+
skimage-log:
|
|
38
|
+
image: registry.rcp.epfl.ch/imaging-server-kit/serverkit-skimage-log:latest
|
|
39
|
+
depends_on:
|
|
40
|
+
- servers_registry
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Start the server with
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
docker compose up
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The server should be accessible at http://localhost:8000.
|
|
50
|
+
|
|
51
|
+
**Build the docker images yourself**
|
|
52
|
+
|
|
53
|
+
To build the algorithm server images yourself (e.g. if you are not from EPFL), see [Reference Deployment](https://github.com/Imaging-Server-Kit/serverkit-deploy-docker).
|
|
54
|
+
|
|
55
|
+
**Create a server for your project**
|
|
56
|
+
|
|
57
|
+
To learn how to create a server for your project, see [Algorithm Server Template](https://github.com/Imaging-Server-Kit/cookiecutter-serverkit).
|
|
58
|
+
|
|
59
|
+
### Python client
|
|
60
|
+
|
|
61
|
+
Once the server is running, connect to it and run algorithms from Python:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from imaging_server_kit import Client
|
|
65
|
+
|
|
66
|
+
client = Client("http://localhost:8000")
|
|
67
|
+
|
|
68
|
+
print(client.algorithms)
|
|
69
|
+
# [`rembg`, `stardist`, `cellpose`]
|
|
70
|
+
|
|
71
|
+
algo_output = client.run_algorithm(
|
|
72
|
+
algorithm="rembg",
|
|
73
|
+
image=(...),
|
|
74
|
+
rembg_model_name="silueta",
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Napari plugin
|
|
79
|
+
|
|
80
|
+
Once the server is running, use the [Napari Server Kit](https://github.com/Imaging-Server-Kit/napari-serverkit) plugin to connect to it and run algorithms in Napari.
|
|
81
|
+
|
|
82
|
+
### QuPath extension
|
|
83
|
+
|
|
84
|
+
Once the server is running, use the [QuPath Extension Server Kit](https://github.com/Imaging-Server-Kit/qupath-extension-serverkit) to connect to it and run algorithms from within QuPath.
|
|
85
|
+
|
|
86
|
+
## Install locally
|
|
87
|
+
|
|
88
|
+
Install the `imaging-server-kit` package with `pip`:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
pip install imaging-server-kit
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
or clone the project and install the development version:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
git clone https://github.com/Imaging-Server-Kit/imaging-server-kit.git
|
|
98
|
+
cd imaging-server-kit
|
|
99
|
+
pip install -e .
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Build with `docker`
|
|
103
|
+
|
|
104
|
+
Build the `imaging-server-kit` docker image for a specific Python version:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
docker build --build-arg PYTHON_VERSION=3.9 -t imaging-server-kit:3.9 .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Run [build.sh](./build.sh) to build docker images for Python 3.9, 3.10, 3.11, GPU and the server kit registry:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
bash ./build.sh
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Contributions are very welcome.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
This software is distributed under the terms of the [BSD-3](http://opensource.org/licenses/BSD-3-Clause) license.
|
|
123
|
+
|
|
124
|
+
## Issues
|
|
125
|
+
|
|
126
|
+
If you encounter any problems, please file an issue along with a detailed description.
|
|
@@ -29,11 +29,11 @@ dependencies = [
|
|
|
29
29
|
"numpy",
|
|
30
30
|
"scikit-image",
|
|
31
31
|
"geojson",
|
|
32
|
-
"docker", # For the autostartup regsitry
|
|
33
32
|
"jinja2", # For the /info route
|
|
34
33
|
"pyyaml", # For the /info route
|
|
35
34
|
"dash", # For the dash app
|
|
36
35
|
"dash-bootstrap-components", # For the dash app
|
|
36
|
+
"a2wsgi", # For the dash app
|
|
37
37
|
"pytest", # For testing
|
|
38
38
|
]
|
|
39
39
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from ._version import version as __version__
|
|
2
|
+
try:
|
|
3
|
+
from ._version import version as __version__
|
|
4
|
+
except ImportError:
|
|
5
|
+
__version__ = "unknown"
|
|
6
|
+
|
|
2
7
|
from imaging_server_kit.client import Client
|
|
3
8
|
from imaging_server_kit.server import Server, Parameters
|
|
4
9
|
from imaging_server_kit.registry import Registry
|
|
5
|
-
from imaging_server_kit.autostartup_registry import AutoStartupRegistry
|
|
6
10
|
from imaging_server_kit.encoding import encode_contents, decode_contents
|
|
7
11
|
from imaging_server_kit.serialization import (
|
|
8
12
|
serialize_result_tuple,
|
|
@@ -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.
|
|
33
|
+
raise AlgorithmServerError(response.status_code, response.text)
|
|
33
34
|
|
|
34
35
|
@property
|
|
35
36
|
def server_url(self) -> str:
|
|
@@ -52,7 +53,7 @@ class Client:
|
|
|
52
53
|
algo_params_encoded = self._encode_numpy_parameters(algo_params)
|
|
53
54
|
|
|
54
55
|
try:
|
|
55
|
-
endpoint = f"{self.server_url}/{algorithm}/"
|
|
56
|
+
endpoint = f"{self.server_url}/{algorithm}/process"
|
|
56
57
|
response = requests.post(
|
|
57
58
|
endpoint,
|
|
58
59
|
json=algo_params_encoded,
|
|
@@ -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.
|
|
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.
|
|
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
|
|
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,
|
|
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.
|
|
26
|
-
pydantic_details =
|
|
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")
|
|
@@ -17,37 +17,34 @@ def mask2features(segmentation_mask: np.ndarray) -> List[Feature]:
|
|
|
17
17
|
features = []
|
|
18
18
|
indices = np.unique(segmentation_mask)
|
|
19
19
|
indices = np.delete(indices, indices == 0) # remove background
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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]
|
|
20
|
+
|
|
21
|
+
if indices.size == 0:
|
|
22
|
+
return features
|
|
23
|
+
|
|
24
|
+
# TODO: This is really inefficient for big images! Will need to improve it.
|
|
25
|
+
for i in indices.tolist():
|
|
26
|
+
mask = np.array(segmentation_mask == i)
|
|
27
|
+
mask = np.pad(mask > 0, 1)
|
|
28
|
+
contours_find = find_contours(mask, 0.5)
|
|
29
|
+
for detection_id, contour in enumerate(contours_find):
|
|
35
30
|
contour -= 1 # reset padding
|
|
36
31
|
contour_as_numpy = contour[:, np.argsort([1, 0])] # Fix XY
|
|
37
32
|
geom = Polygon([contour_as_numpy.tolist()])
|
|
38
|
-
features.append(Feature(geometry=geom, properties={"Detection ID": i}))
|
|
33
|
+
features.append(Feature(geometry=geom, properties={"Detection ID": detection_id, "Class": i}))
|
|
34
|
+
|
|
39
35
|
return features
|
|
40
36
|
|
|
41
37
|
|
|
42
38
|
def features2mask(features, image_shape):
|
|
43
39
|
segmentation_mask = np.zeros(image_shape, dtype=np.uint16)
|
|
44
40
|
for idx, feature in enumerate(features):
|
|
45
|
-
label_idx = idx+1 # Labels start at 1
|
|
46
41
|
feature_coordinates = np.array(feature["geometry"]["coordinates"])
|
|
47
42
|
feature_coordinates = np.squeeze(feature_coordinates)
|
|
48
43
|
feature_coordinates = feature_coordinates[:, ::-1] # Invert XY
|
|
49
44
|
feature_mask = polygon2mask(image_shape, feature_coordinates)
|
|
50
|
-
|
|
45
|
+
feature_properites = feature.get("properties")
|
|
46
|
+
feature_class = feature_properites.get("Class")
|
|
47
|
+
segmentation_mask[feature_mask] = feature_class
|
|
51
48
|
return segmentation_mask
|
|
52
49
|
|
|
53
50
|
|
|
@@ -74,17 +74,17 @@ 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}/")
|
|
81
81
|
return response.json()
|
|
82
82
|
|
|
83
|
-
@self.app.post("/{algorithm}", status_code=status.HTTP_201_CREATED)
|
|
83
|
+
@self.app.post("/{algorithm}/process", status_code=status.HTTP_201_CREATED)
|
|
84
84
|
async def run_algorithm(algorithm, request: Request):
|
|
85
85
|
algo_url = self.services.get(algorithm)
|
|
86
86
|
data = await request.json()
|
|
87
|
-
response = requests.post(f"{algo_url}/{algorithm}/", json=data)
|
|
87
|
+
response = requests.post(f"{algo_url}/{algorithm}/process", json=data)
|
|
88
88
|
return response.json()
|
|
89
89
|
|
|
90
90
|
@self.app.get("/{algorithm}/parameters")
|
{imaging_server_kit-0.0.2 → imaging_server_kit-0.0.4}/src/imaging_server_kit/serialization.py
RENAMED
|
@@ -69,6 +69,7 @@ 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
|
|
74
75
|
elif data_type == "labels3d":
|
|
@@ -5,13 +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
|
-
from
|
|
14
|
+
from a2wsgi import WSGIMiddleware
|
|
15
|
+
from contextlib import asynccontextmanager
|
|
16
|
+
import asyncio
|
|
15
17
|
|
|
16
18
|
templates_dir = importlib.resources.files("imaging_server_kit").joinpath("templates")
|
|
17
19
|
static_dir = importlib.resources.files("imaging_server_kit").joinpath("static")
|
|
@@ -47,7 +49,7 @@ class Server:
|
|
|
47
49
|
self.algorithm_name = algorithm_name
|
|
48
50
|
self.parameters_model = parameters_model
|
|
49
51
|
|
|
50
|
-
self.app = FastAPI(title=algorithm_name)
|
|
52
|
+
self.app = FastAPI(title=algorithm_name, lifespan=self.lifespan)
|
|
51
53
|
self.app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
52
54
|
|
|
53
55
|
# Set up the web demo app
|
|
@@ -63,14 +65,17 @@ class Server:
|
|
|
63
65
|
|
|
64
66
|
self.app.mount(f"/{algorithm_name}/demo", WSGIMiddleware(dash_app.server))
|
|
65
67
|
|
|
66
|
-
self.app.on_event("startup")(self.register_with_registry)
|
|
67
|
-
self.app.on_event("shutdown")(self.deregister_from_registry)
|
|
68
|
-
|
|
69
68
|
self.register_routes()
|
|
70
69
|
|
|
71
70
|
self.services = [self.algorithm_name]
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
@asynccontextmanager
|
|
73
|
+
async def lifespan(self, app: FastAPI):
|
|
74
|
+
await self.register_with_registry()
|
|
75
|
+
yield
|
|
76
|
+
await self.deregister_from_registry()
|
|
77
|
+
|
|
78
|
+
async def register_with_registry(self):
|
|
74
79
|
try:
|
|
75
80
|
response = requests.get(f"{REGISTRY_URL}/")
|
|
76
81
|
except Exception:
|
|
@@ -89,7 +94,7 @@ class Server:
|
|
|
89
94
|
else:
|
|
90
95
|
print(f"Failed to register {self.algorithm_name}: {response.json()}")
|
|
91
96
|
|
|
92
|
-
def deregister_from_registry(self):
|
|
97
|
+
async def deregister_from_registry(self):
|
|
93
98
|
deregister_url = f"{REGISTRY_URL}/deregister"
|
|
94
99
|
response = requests.post(deregister_url, json={"name": self.algorithm_name})
|
|
95
100
|
if response.status_code == 201:
|
|
@@ -124,12 +129,20 @@ class Server:
|
|
|
124
129
|
},
|
|
125
130
|
)
|
|
126
131
|
|
|
127
|
-
@self.app.post(f"/{self.algorithm_name}/", status_code=status.HTTP_201_CREATED)
|
|
132
|
+
@self.app.post(f"/{self.algorithm_name}/process", status_code=status.HTTP_201_CREATED)
|
|
128
133
|
async def run_algo(algo_params: self.parameters_model):
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
try:
|
|
135
|
+
result_data_tuple = await asyncio.wait_for(self._run_algorithm(**algo_params.dict()), timeout=60)
|
|
136
|
+
except asyncio.TimeoutError: # This works but doesn't actually kill the process... we need something else to terminate it
|
|
137
|
+
raise HTTPException(status_code=504, detail="Request timeout during processing.")
|
|
138
|
+
try:
|
|
139
|
+
serialized_results = await asyncio.wait_for(self._serialize_result_tuple(result_data_tuple), timeout=10)
|
|
140
|
+
except asyncio.TimeoutError:
|
|
141
|
+
raise HTTPException(status_code=504, detail="Request timeout during serialization.")
|
|
142
|
+
|
|
131
143
|
return serialized_results
|
|
132
144
|
|
|
145
|
+
|
|
133
146
|
@self.app.get(f"/{self.algorithm_name}/parameters", response_model=dict)
|
|
134
147
|
def get_algo_params():
|
|
135
148
|
return self.parameters_model.model_json_schema()
|
|
@@ -142,8 +155,16 @@ class Server:
|
|
|
142
155
|
]
|
|
143
156
|
return {"sample_images": encoded_images}
|
|
144
157
|
|
|
158
|
+
async def _serialize_result_tuple(self, result_data_tuple):
|
|
159
|
+
serialized_results = await asyncio.to_thread(serverkit.serialize_result_tuple, result_data_tuple)
|
|
160
|
+
return serialized_results
|
|
161
|
+
|
|
162
|
+
async def _run_algorithm(self, **algo_params):
|
|
163
|
+
result_data_tuple = await asyncio.to_thread(self.run_algorithm, **algo_params)
|
|
164
|
+
return result_data_tuple
|
|
165
|
+
|
|
145
166
|
def load_sample_images(self) -> List["np.ndarray"]:
|
|
146
167
|
raise NotImplementedError("Subclasses should implement this method")
|
|
147
|
-
|
|
168
|
+
|
|
148
169
|
def run_algorithm(self, **algo_params) -> List[Tuple]:
|
|
149
170
|
raise NotImplementedError("Subclasses should implement this method")
|