From acbcb9dcbf95558bdda9d3d0b8d37c6bab8b287a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 24 Apr 2025 11:43:49 -0400 Subject: [PATCH 1/6] Add support for dataset_ids CLI parameter and refactor serve functions --- copick_server/server.py | 115 ++++++++++++++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 16 deletions(-) diff --git a/copick_server/server.py b/copick_server/server.py index 9123cc5..19e8b21 100644 --- a/copick_server/server.py +++ b/copick_server/server.py @@ -227,30 +227,54 @@ def create_copick_app(root: copick.models.CopickRoot, cors_origins: Optional[Lis return app -def serve_copick(config_path: str, allowed_origins: Optional[List[str]] = None, **kwargs): +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): """Start an HTTP server serving a Copick project. Parameters ---------- - config_path : str + config_path : str, optional Path to Copick config file + dataset_ids : list of int, optional + Dataset IDs to include in the project + overlay_root : str, optional + Root URL for the overlay storage, default is "/tmp/overlay_root" allowed_origins : list of str, optional List of allowed CORS origins. Use ["*"] to allow all. **kwargs Additional arguments passed to uvicorn.run() + + Notes + ----- + Either config_path or dataset_ids must be provided, but not both. """ - root = copick.from_file(config_path) + if config_path and dataset_ids: + raise ValueError("Either config_path or dataset_ids must be provided, but not both.") + elif config_path: + root = copick.from_file(config_path) + elif dataset_ids: + root = copick.from_czcdp_datasets( + dataset_ids=dataset_ids, + overlay_root=overlay_root, + overlay_fs_args={"auto_mkdir": True}, + ) + else: + raise ValueError("Either config_path or dataset_ids must be provided.") + app = create_copick_app(root, allowed_origins) uvicorn.run(app, **kwargs) return app -def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]] = None, **kwargs): +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): """Start an HTTP server in a background thread and return the app. Parameters ---------- - config_path : str + config_path : str, optional Path to Copick config file + dataset_ids : list of int, optional + Dataset IDs to include in the project + overlay_root : str, optional + Root URL for the overlay storage, default is "/tmp/overlay_root" allowed_origins : list of str, optional List of allowed CORS origins. Use ["*"] to allow all. **kwargs @@ -260,8 +284,24 @@ def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]] ------- app : FastAPI FastAPI application + + Notes + ----- + Either config_path or dataset_ids must be provided, but not both. """ - root = copick.from_file(config_path) + if config_path and dataset_ids: + raise ValueError("Either config_path or dataset_ids must be provided, but not both.") + elif config_path: + root = copick.from_file(config_path) + elif dataset_ids: + root = copick.from_czcdp_datasets( + dataset_ids=dataset_ids, + overlay_root=overlay_root, + overlay_fs_args={"auto_mkdir": True}, + ) + else: + raise ValueError("Either config_path or dataset_ids must be provided.") + app = create_copick_app(root, allowed_origins) # Start the server in a background thread @@ -275,8 +315,36 @@ def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]] return app -@click.command() -@click.argument("config", type=click.Path(exists=True)) +@click.group() +@click.pass_context +def cli(ctx): + pass + + +@cli.command() +@click.option( + "-c", + "--config", + type=click.Path(exists=True), + help="Path to the configuration file.", + required=False, + metavar="PATH", +) +@click.option( + "-ds", + "--dataset-ids", + type=int, + multiple=True, + help="Dataset IDs to include in the project (multiple inputs possible).", + metavar="ID", +) +@click.option( + "--overlay-root", + type=str, + default="/tmp/overlay_root", + help="Root URL for the overlay storage.", + show_default=True, +) @click.option( "--cors", type=str, @@ -298,15 +366,30 @@ def serve_copick_threaded(config_path: str, allowed_origins: Optional[List[str]] show_default=True, ) @click.option("--reload", is_flag=True, default=False, help="Enable auto-reload.") -def main(config: str, cors: Optional[str], host: str, port: int, reload: bool): +@click.pass_context +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): """Serve a Copick project over HTTP.""" - serve_copick( - config, - allowed_origins=[cors] if cors else None, - host=host, - port=port, - reload=reload - ) + if config and dataset_ids: + ctx.fail("Either --config or --dataset-ids must be provided, not both.") + elif not config and not dataset_ids: + ctx.fail("Either --config or --dataset-ids must be provided.") + + try: + serve_copick( + config_path=config, + dataset_ids=dataset_ids if dataset_ids else None, + overlay_root=overlay_root, + allowed_origins=[cors] if cors else None, + host=host, + port=port, + reload=reload + ) + except Exception as e: + ctx.fail(f"Error serving Copick project: {str(e)}") + + +def main(): + cli() if __name__ == "__main__": main() \ No newline at end of file From 2be4068e2c57cb9c23f44804ab977e5c47a7ed31 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 24 Apr 2025 11:44:12 -0400 Subject: [PATCH 2/6] Update demo_server.py to use dataset_ids instead of config file --- copick_server/demo_server.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/copick_server/demo_server.py b/copick_server/demo_server.py index ac9bd35..87ea0d1 100644 --- a/copick_server/demo_server.py +++ b/copick_server/demo_server.py @@ -1,7 +1,15 @@ -# As a module +# Demo server using the dataset_ids parameter from copick_server.server import serve_copick -serve_copick("/Users/kharrington/git/copick/copick-server/example_copick.json", allowed_origins=["*"]) +# Use dataset_id instead of config file +serve_copick(dataset_ids=[14268], overlay_root="/tmp/overlay_root", allowed_origins=["*"]) -# Or from command line -# $ python server.py path/to/copick_config.json --cors "*" +# Previous config-based approach: +# serve_copick("/Users/kharrington/git/copick/copick-server/example_copick.json", allowed_origins=["*"]) + +# Command line usage: +# With config file: +# $ python -m copick_server.server serve --config path/to/copick_config.json --cors "*" +# +# With dataset IDs: +# $ python -m copick_server.server serve --dataset-ids 14268 --cors "*" --port 8017 From 26c868f590b156b067933660af35f9143c8bd6fd Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 24 Apr 2025 11:44:29 -0400 Subject: [PATCH 3/6] Update README.md with CLI instructions for dataset_ids --- README.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2686f94..3c8e579 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,89 @@ -# Launch a server +# Copick Server +A server for [copick](https://github.com/kephale/copick) projects. + +## Installation + +```bash +pip install . ``` -uv run copick_server/demo_server.py + +## Usage + +### Launch a server + +#### Using a config file + +```bash +python -m copick_server.server serve --config path/to/copick_config.json --cors "*" --port 8000 ``` -# Launch a client +#### Using dataset IDs from CZ cryoET Data Portal +```bash +python -m copick_server.server serve --dataset-ids 14268 --cors "*" --port 8017 ``` -uv run copick_server/client.py + +You can use multiple dataset IDs by repeating the option: + +```bash +python -m copick_server.server serve --dataset-ids 14268 --dataset-ids 10301 --cors "*" --port 8017 ``` -# Running Tests +#### Using uv run + +With config file: +```bash +uv run copick_server/server.py serve --config ~/Data/copick/full_ml_challenge_czcdp.json --cors "*" --port 8017 +``` +With dataset IDs: +```bash +uv run copick_server/server.py serve --dataset-ids 14268 --cors "*" --port 8017 ``` + +#### Demo server + +The demo server uses a dataset ID by default: + +```bash +python -m copick_server.demo_server +``` + +### Launch a client + +```bash +python -m copick_server.client +``` + +## Running Tests + +```bash pip install ".[test]" pytest ``` For coverage report: -``` +```bash pytest --cov=copick_server ``` -# References +## CLI Options + +``` +python -m copick_server.server serve --help +``` + +| Option | Description | Default | +|-------------------|--------------------------------------------------------|---------------| +| -c, --config | Path to the configuration file | None | +| -ds, --dataset-ids| Dataset IDs to include in the project | None | +| --overlay-root | Root URL for the overlay storage | /tmp/overlay_root | +| --cors | Origin to allow CORS. Use wildcard '*' to allow all | None | +| --host | Bind socket to this host | 127.0.0.1 | +| --port | Bind socket to this port | 8000 | +| --reload | Enable auto-reload | False | + +## References -The idea is influenced by https://github.com/manzt/simple-zarr-server, but aimed at supporting https://github.com/copick/copick. +The idea is influenced by https://github.com/manzt/simple-zarr-server, but aimed at supporting https://github.com/kephale/copick. From 46215a042b5ed62229d076b0448c672c50086f8a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 24 Apr 2025 11:54:17 -0400 Subject: [PATCH 4/6] Update demo for data portal and MLC --- copick_server/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/copick_server/client.py b/copick_server/client.py index 4fce61c..e5e0312 100644 --- a/copick_server/client.py +++ b/copick_server/client.py @@ -4,13 +4,13 @@ from fsspec import get_mapper # First read the tomogram to get the shape -store = get_mapper("http://localhost:8000/14268/Tomograms/VoxelSpacing7.84/wbp.zarr") +store = get_mapper("http://localhost:8017/16463/Tomograms/VoxelSpacing10.012/wbp.zarr") tomo = zarr.open(store, mode='r') full_shape = tomo["0"].shape print(f"Tomogram shape: {full_shape}") # Example parameters -voxel_size = 7.84 # must match your tomogram's voxel spacing +voxel_size = 10.012 user_id = "kisharrington" session_id = "copickServer" name = "membrane" # This must match a pickable object in your config @@ -33,7 +33,7 @@ data_bytes = shape_header + data.tobytes() # Send to server as a single request -url = f"http://localhost:8000/14268/Segmentations/{seg_filename}" +url = f"http://localhost:8017/16463/Segmentations/{seg_filename}" response = requests.put(url, data=data_bytes) if response.status_code == 200: From 600c8aa076dbde2cf118c5c99ac69435f524b06a Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 24 Apr 2025 11:56:12 -0400 Subject: [PATCH 5/6] Update dataset ids in readme --- README.md | 16 ++++------------ copick_server/demo_server.py | 15 --------------- 2 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 copick_server/demo_server.py diff --git a/README.md b/README.md index 3c8e579..9525310 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ python -m copick_server.server serve --config path/to/copick_config.json --cors #### Using dataset IDs from CZ cryoET Data Portal ```bash -python -m copick_server.server serve --dataset-ids 14268 --cors "*" --port 8017 +python -m copick_server.server serve --dataset-ids 10440 --cors "*" --port 8017 ``` You can use multiple dataset IDs by repeating the option: ```bash -python -m copick_server.server serve --dataset-ids 14268 --dataset-ids 10301 --cors "*" --port 8017 +python -m copick_server.server serve --dataset-ids 10440 --dataset-ids 10441 --cors "*" --port 8017 ``` #### Using uv run @@ -39,21 +39,13 @@ uv run copick_server/server.py serve --config ~/Data/copick/full_ml_challenge_cz With dataset IDs: ```bash -uv run copick_server/server.py serve --dataset-ids 14268 --cors "*" --port 8017 -``` - -#### Demo server - -The demo server uses a dataset ID by default: - -```bash -python -m copick_server.demo_server +uv run copick_server/server.py serve --dataset-ids 10440 --cors "*" --port 8017 ``` ### Launch a client ```bash -python -m copick_server.client +uv run copick_server/client.py ``` ## Running Tests diff --git a/copick_server/demo_server.py b/copick_server/demo_server.py deleted file mode 100644 index 87ea0d1..0000000 --- a/copick_server/demo_server.py +++ /dev/null @@ -1,15 +0,0 @@ -# Demo server using the dataset_ids parameter -from copick_server.server import serve_copick - -# Use dataset_id instead of config file -serve_copick(dataset_ids=[14268], overlay_root="/tmp/overlay_root", allowed_origins=["*"]) - -# Previous config-based approach: -# serve_copick("/Users/kharrington/git/copick/copick-server/example_copick.json", allowed_origins=["*"]) - -# Command line usage: -# With config file: -# $ python -m copick_server.server serve --config path/to/copick_config.json --cors "*" -# -# With dataset IDs: -# $ python -m copick_server.server serve --dataset-ids 14268 --cors "*" --port 8017 From 40098f36c6a89afc9b8b71502ba5532ed5ba35f4 Mon Sep 17 00:00:00 2001 From: Kyle Harrington Date: Thu, 24 Apr 2025 12:01:18 -0400 Subject: [PATCH 6/6] Update copick version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 33bd562..bb9eaa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "click", - "copick", + "copick>=1.0.1", "numpy", "uvicorn", "zarr",