Skip to content

Commit 8ae3648

Browse files
authored
Merge pull request #7 from kephale/main
Add support for dataset_ids CLI parameter and refactor serve functions
2 parents fb036c4 + 40098f3 commit 8ae3648

5 files changed

Lines changed: 165 additions & 35 deletions

File tree

README.md

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,81 @@
1-
# Launch a server
1+
# Copick Server
22

3+
A server for [copick](https://github.com/kephale/copick) projects.
4+
5+
## Installation
6+
7+
```bash
8+
pip install .
39
```
4-
uv run copick_server/demo_server.py
10+
11+
## Usage
12+
13+
### Launch a server
14+
15+
#### Using a config file
16+
17+
```bash
18+
python -m copick_server.server serve --config path/to/copick_config.json --cors "*" --port 8000
519
```
620

7-
# Launch a client
21+
#### Using dataset IDs from CZ cryoET Data Portal
822

23+
```bash
24+
python -m copick_server.server serve --dataset-ids 10440 --cors "*" --port 8017
925
```
10-
uv run copick_server/client.py
26+
27+
You can use multiple dataset IDs by repeating the option:
28+
29+
```bash
30+
python -m copick_server.server serve --dataset-ids 10440 --dataset-ids 10441 --cors "*" --port 8017
1131
```
1232

13-
# Running Tests
33+
#### Using uv run
1434

35+
With config file:
36+
```bash
37+
uv run copick_server/server.py serve --config ~/Data/copick/full_ml_challenge_czcdp.json --cors "*" --port 8017
1538
```
39+
40+
With dataset IDs:
41+
```bash
42+
uv run copick_server/server.py serve --dataset-ids 10440 --cors "*" --port 8017
43+
```
44+
45+
### Launch a client
46+
47+
```bash
48+
uv run copick_server/client.py
49+
```
50+
51+
## Running Tests
52+
53+
```bash
1654
pip install ".[test]"
1755
pytest
1856
```
1957

2058
For coverage report:
21-
```
59+
```bash
2260
pytest --cov=copick_server
2361
```
2462

25-
# References
63+
## CLI Options
64+
65+
```
66+
python -m copick_server.server serve --help
67+
```
68+
69+
| Option | Description | Default |
70+
|-------------------|--------------------------------------------------------|---------------|
71+
| -c, --config | Path to the configuration file | None |
72+
| -ds, --dataset-ids| Dataset IDs to include in the project | None |
73+
| --overlay-root | Root URL for the overlay storage | /tmp/overlay_root |
74+
| --cors | Origin to allow CORS. Use wildcard '*' to allow all | None |
75+
| --host | Bind socket to this host | 127.0.0.1 |
76+
| --port | Bind socket to this port | 8000 |
77+
| --reload | Enable auto-reload | False |
78+
79+
## References
2680

27-
The idea is influenced by https://github.com/manzt/simple-zarr-server, but aimed at supporting https://github.com/copick/copick.
81+
The idea is influenced by https://github.com/manzt/simple-zarr-server, but aimed at supporting https://github.com/kephale/copick.

copick_server/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
from fsspec import get_mapper
55

66
# First read the tomogram to get the shape
7-
store = get_mapper("http://localhost:8000/14268/Tomograms/VoxelSpacing7.84/wbp.zarr")
7+
store = get_mapper("http://localhost:8017/16463/Tomograms/VoxelSpacing10.012/wbp.zarr")
88
tomo = zarr.open(store, mode='r')
99
full_shape = tomo["0"].shape
1010
print(f"Tomogram shape: {full_shape}")
1111

1212
# Example parameters
13-
voxel_size = 7.84 # must match your tomogram's voxel spacing
13+
voxel_size = 10.012
1414
user_id = "kisharrington"
1515
session_id = "copickServer"
1616
name = "membrane" # This must match a pickable object in your config
@@ -33,7 +33,7 @@
3333
data_bytes = shape_header + data.tobytes()
3434

3535
# Send to server as a single request
36-
url = f"http://localhost:8000/14268/Segmentations/{seg_filename}"
36+
url = f"http://localhost:8017/16463/Segmentations/{seg_filename}"
3737
response = requests.put(url, data=data_bytes)
3838

3939
if response.status_code == 200:

copick_server/demo_server.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

copick_server/server.py

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -227,30 +227,54 @@ def create_copick_app(root: copick.models.CopickRoot, cors_origins: Optional[Lis
227227

228228
return app
229229

230-
def serve_copick(config_path: str, allowed_origins: Optional[List[str]] = None, **kwargs):
230+
def serve_copick(config_path: Optional[str] = None, dataset_ids: Optional[List[int]] = None, overlay_root: str = "/tmp/overlay_root", allowed_origins: Optional[List[str]] = None, **kwargs):
231231
"""Start an HTTP server serving a Copick project.
232232
233233
Parameters
234234
----------
235-
config_path : str
235+
config_path : str, optional
236236
Path to Copick config file
237+
dataset_ids : list of int, optional
238+
Dataset IDs to include in the project
239+
overlay_root : str, optional
240+
Root URL for the overlay storage, default is "/tmp/overlay_root"
237241
allowed_origins : list of str, optional
238242
List of allowed CORS origins. Use ["*"] to allow all.
239243
**kwargs
240244
Additional arguments passed to uvicorn.run()
245+
246+
Notes
247+
-----
248+
Either config_path or dataset_ids must be provided, but not both.
241249
"""
242-
root = copick.from_file(config_path)
250+
if config_path and dataset_ids:
251+
raise ValueError("Either config_path or dataset_ids must be provided, but not both.")
252+
elif config_path:
253+
root = copick.from_file(config_path)
254+
elif dataset_ids:
255+
root = copick.from_czcdp_datasets(
256+
dataset_ids=dataset_ids,
257+
overlay_root=overlay_root,
258+
overlay_fs_args={"auto_mkdir": True},
259+
)
260+
else:
261+
raise ValueError("Either config_path or dataset_ids must be provided.")
262+
243263
app = create_copick_app(root, allowed_origins)
244264
uvicorn.run(app, **kwargs)
245265
return app
246266

247-
def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]] = None, **kwargs):
267+
def serve_copick_threaded(config_path: Optional[str] = None, dataset_ids: Optional[List[int]] = None, overlay_root: str = "/tmp/overlay_root", allowed_origins: Optional[List[str]] = None, **kwargs):
248268
"""Start an HTTP server in a background thread and return the app.
249269
250270
Parameters
251271
----------
252-
config_path : str
272+
config_path : str, optional
253273
Path to Copick config file
274+
dataset_ids : list of int, optional
275+
Dataset IDs to include in the project
276+
overlay_root : str, optional
277+
Root URL for the overlay storage, default is "/tmp/overlay_root"
254278
allowed_origins : list of str, optional
255279
List of allowed CORS origins. Use ["*"] to allow all.
256280
**kwargs
@@ -260,8 +284,24 @@ def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]]
260284
-------
261285
app : FastAPI
262286
FastAPI application
287+
288+
Notes
289+
-----
290+
Either config_path or dataset_ids must be provided, but not both.
263291
"""
264-
root = copick.from_file(config_path)
292+
if config_path and dataset_ids:
293+
raise ValueError("Either config_path or dataset_ids must be provided, but not both.")
294+
elif config_path:
295+
root = copick.from_file(config_path)
296+
elif dataset_ids:
297+
root = copick.from_czcdp_datasets(
298+
dataset_ids=dataset_ids,
299+
overlay_root=overlay_root,
300+
overlay_fs_args={"auto_mkdir": True},
301+
)
302+
else:
303+
raise ValueError("Either config_path or dataset_ids must be provided.")
304+
265305
app = create_copick_app(root, allowed_origins)
266306

267307
# Start the server in a background thread
@@ -275,8 +315,36 @@ def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]]
275315

276316
return app
277317

278-
@click.command()
279-
@click.argument("config", type=click.Path(exists=True))
318+
@click.group()
319+
@click.pass_context
320+
def cli(ctx):
321+
pass
322+
323+
324+
@cli.command()
325+
@click.option(
326+
"-c",
327+
"--config",
328+
type=click.Path(exists=True),
329+
help="Path to the configuration file.",
330+
required=False,
331+
metavar="PATH",
332+
)
333+
@click.option(
334+
"-ds",
335+
"--dataset-ids",
336+
type=int,
337+
multiple=True,
338+
help="Dataset IDs to include in the project (multiple inputs possible).",
339+
metavar="ID",
340+
)
341+
@click.option(
342+
"--overlay-root",
343+
type=str,
344+
default="/tmp/overlay_root",
345+
help="Root URL for the overlay storage.",
346+
show_default=True,
347+
)
280348
@click.option(
281349
"--cors",
282350
type=str,
@@ -298,15 +366,30 @@ def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]]
298366
show_default=True,
299367
)
300368
@click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.")
301-
def main(config: str, cors: Optional[str], host: str, port: int, reload: bool):
369+
@click.pass_context
370+
def serve(ctx, config: Optional[str] = None, dataset_ids: Optional[tuple] = None, overlay_root: str = "/tmp/overlay_root", cors: Optional[str] = None, host: str = "127.0.0.1", port: int = 8000, reload: bool = False):
302371
"""Serve a Copick project over HTTP."""
303-
serve_copick(
304-
config,
305-
allowed_origins=[cors] if cors else None,
306-
host=host,
307-
port=port,
308-
reload=reload
309-
)
372+
if config and dataset_ids:
373+
ctx.fail("Either --config or --dataset-ids must be provided, not both.")
374+
elif not config and not dataset_ids:
375+
ctx.fail("Either --config or --dataset-ids must be provided.")
376+
377+
try:
378+
serve_copick(
379+
config_path=config,
380+
dataset_ids=dataset_ids if dataset_ids else None,
381+
overlay_root=overlay_root,
382+
allowed_origins=[cors] if cors else None,
383+
host=host,
384+
port=port,
385+
reload=reload
386+
)
387+
except Exception as e:
388+
ctx.fail(f"Error serving Copick project: {str(e)}")
389+
390+
391+
def main():
392+
cli()
310393

311394
if __name__ == "__main__":
312395
main()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.10"
77
dependencies = [
88
"click",
9-
"copick",
9+
"copick>=1.0.1",
1010
"numpy",
1111
"uvicorn",
1212
"zarr",

0 commit comments

Comments
 (0)