aignostics 0.2.16__tar.gz → 0.2.22__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.
- {aignostics-0.2.16 → aignostics-0.2.22}/PKG-INFO +16 -10
- {aignostics-0.2.16 → aignostics-0.2.22}/README.md +5 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/examples/notebook.py +1 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/pyproject.toml +18 -18
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/__init__.py +1 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_cli.py +46 -25
- aignostics-0.2.22/src/aignostics/application/_gui/_frame.py +164 -0
- aignostics-0.2.22/src/aignostics/application/_gui/_page_application_describe.py +634 -0
- aignostics-0.2.22/src/aignostics/application/_gui/_page_application_run_describe.py +546 -0
- aignostics-0.2.22/src/aignostics/application/_gui/_page_builder.py +42 -0
- aignostics-0.2.22/src/aignostics/application/_gui/_page_index.py +49 -0
- aignostics-0.2.22/src/aignostics/application/_gui/_utils.py +99 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/canceled.lottie +0 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/done.lottie +0 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/empty.lottie +0 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/error.lottie +0 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/error_alt.lottie +0 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/pending.lottie +0 -0
- aignostics-0.2.22/src/aignostics/application/_gui/assets/pending_alt.lottie +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_service.py +183 -64
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_utils.py +53 -3
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_cli.py +10 -10
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_gui.py +5 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_service.py +2 -1
- aignostics-0.2.22/src/aignostics/bucket/assets/Google-Cloud-logo.png +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/cli.py +0 -4
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/constants.py +3 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_cli.py +36 -12
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_gui.py +46 -30
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_idc_index.py +18 -2
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/_frame.py +84 -31
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/_theme.py +10 -4
- aignostics-0.2.22/src/aignostics/notebook/_gui.py +98 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/notebook/_service.py +13 -6
- aignostics-0.2.22/src/aignostics/notebook/assets/python.lottie +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/__init__.py +6 -2
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/runs.py +3 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/qupath/__init__.py +3 -1
- aignostics-0.2.22/src/aignostics/qupath/_cli.py +359 -0
- aignostics-0.2.22/src/aignostics/qupath/_gui.py +192 -0
- aignostics-0.2.22/src/aignostics/qupath/_service.py +1221 -0
- aignostics-0.2.22/src/aignostics/qupath/assets/download.lottie +0 -0
- aignostics-0.2.22/src/aignostics/qupath/assets/microscope.lottie +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_cli.py +104 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_gui.py +2 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_service.py +115 -2
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_console.py +1 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_logfire.py +10 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_sentry.py +9 -1
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/boot.py +3 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_service.py +10 -8
- aignostics-0.2.16/src/aignostics/application/_gui.py +0 -1194
- aignostics-0.2.16/src/aignostics/notebook/_gui.py +0 -33
- aignostics-0.2.16/src/aignostics/qupath/_cli.py +0 -106
- aignostics-0.2.16/src/aignostics/qupath/_gui.py +0 -80
- aignostics-0.2.16/src/aignostics/qupath/_service.py +0 -552
- {aignostics-0.2.16 → aignostics-0.2.22}/.gitignore +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/LICENSE +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/.openapi-generator/FILES +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/.openapi-generator/VERSION +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/api/public_api.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/api_client.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/api_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/configuration.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/exceptions.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/application_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/application_run_status.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/application_version_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/http_validation_error.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/input_artifact_creation_request.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/input_artifact_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/item_creation_request.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/item_result_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/item_status.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/output_artifact_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/output_artifact_scope.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/payload_input_artifact.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/payload_item.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/payload_output_artifact.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/run_creation_request.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/run_creation_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/run_read_response.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/transfer_urls.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/user_payload.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/validation_error.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/validation_error_loc_inner.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/rest.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/docs/PublicApi.md +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/examples/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/examples/e2e.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/examples/notebook.ipynb +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/examples/script.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/__init__.py +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-1.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-2.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-3.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-4.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-5.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/image-not-found.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/ruo.png +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/ruo.svg +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_settings.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_settings.py +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/bucket}/assets/Google-Cloud-logo.svg +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_service.py +0 -0
- {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/dataset}/assets/NIH-IDC-logo.svg +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/assets/cabin-v27-latin-regular.woff2 +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/assets/logo.png +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/notebook/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/notebook/_notebook.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_authentication.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_client.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_constants.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_messages.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_service.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_settings.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_utils.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/applications.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/utils.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/qupath/_settings.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_exceptions.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_settings.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/.vendored/bottle.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_cli.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_constants.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_di.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_gui.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_health.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_log.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_notebook.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_process.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_service.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_settings.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/__init__.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_cli.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_gui.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_openslide_handler.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_pydicom_handler.py +0 -0
- {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aignostics
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.22
|
|
4
4
|
Summary: 🔬 Python SDK providing access to the Aignostics Platform. Includes Aignostics Launchpad (Desktop Application), Aignostics CLI (Command-Line Interface), example notebooks, and Aignostics Client Library.
|
|
5
5
|
Project-URL: Homepage, https://aignostics.readthedocs.io/en/latest/
|
|
6
6
|
Project-URL: Documentation, https://aignostics.readthedocs.io/en/latest/
|
|
@@ -49,22 +49,23 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
49
49
|
Classifier: Typing :: Typed
|
|
50
50
|
Requires-Python: <4.0,>=3.11
|
|
51
51
|
Requires-Dist: appdirs>=1.4.4
|
|
52
|
-
Requires-Dist: boto3>=1.38.
|
|
52
|
+
Requires-Dist: boto3>=1.38.27
|
|
53
53
|
Requires-Dist: click
|
|
54
|
-
Requires-Dist: dicom-validator>=0.
|
|
55
|
-
Requires-Dist: dicomweb-client[gcp]>=0.59.
|
|
54
|
+
Requires-Dist: dicom-validator>=0.7.1
|
|
55
|
+
Requires-Dist: dicomweb-client[gcp]>=0.59.2
|
|
56
56
|
Requires-Dist: duckdb<=1.2.1,>=0.10.0
|
|
57
57
|
Requires-Dist: fastapi[all,standard]>=0.115.12
|
|
58
|
-
Requires-Dist: google-cloud-storage>=
|
|
58
|
+
Requires-Dist: google-cloud-storage>=2.12.0
|
|
59
59
|
Requires-Dist: google-crc32c>=1.7.1
|
|
60
60
|
Requires-Dist: highdicom>=0.25.1
|
|
61
61
|
Requires-Dist: httpx>=0.28.1
|
|
62
62
|
Requires-Dist: humanize>=4.12.3
|
|
63
63
|
Requires-Dist: idc-index-data==21.0.0
|
|
64
|
+
Requires-Dist: ijson>=3.4.0
|
|
64
65
|
Requires-Dist: jsf>=0.11.2
|
|
65
|
-
Requires-Dist: jsonschema>=4.
|
|
66
|
-
Requires-Dist: logfire[system-metrics]>=3.16.
|
|
67
|
-
Requires-Dist: nicegui[native]>=2.
|
|
66
|
+
Requires-Dist: jsonschema>=4.24.0
|
|
67
|
+
Requires-Dist: logfire[system-metrics]>=3.16.1
|
|
68
|
+
Requires-Dist: nicegui[native]>=2.18.0
|
|
68
69
|
Requires-Dist: openslide-bin>=4.0.0.8
|
|
69
70
|
Requires-Dist: openslide-python>=1.4.2
|
|
70
71
|
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.53b0
|
|
@@ -97,7 +98,7 @@ Requires-Dist: show-in-file-manager>=1.1.5
|
|
|
97
98
|
Requires-Dist: sphinx-click
|
|
98
99
|
Requires-Dist: tqdm
|
|
99
100
|
Requires-Dist: tqdm>=4.67.1
|
|
100
|
-
Requires-Dist: typer>=0.
|
|
101
|
+
Requires-Dist: typer>=0.16.0
|
|
101
102
|
Requires-Dist: uptime>=3.0.1
|
|
102
103
|
Requires-Dist: urllib3>=2.2.3
|
|
103
104
|
Requires-Dist: wsidicom>=0.27.1
|
|
@@ -105,7 +106,7 @@ Provides-Extra: jupyter
|
|
|
105
106
|
Requires-Dist: jupyter>=1.1.1; extra == 'jupyter'
|
|
106
107
|
Provides-Extra: marimo
|
|
107
108
|
Requires-Dist: cloudpathlib>=0.21.1; extra == 'marimo'
|
|
108
|
-
Requires-Dist: marimo>=0.13.
|
|
109
|
+
Requires-Dist: marimo>=0.13.15; extra == 'marimo'
|
|
109
110
|
Requires-Dist: matplotlib>=3.10.3; extra == 'marimo'
|
|
110
111
|
Requires-Dist: shapely>=2.1.0; extra == 'marimo'
|
|
111
112
|
Provides-Extra: qupath
|
|
@@ -195,6 +196,11 @@ The Launchpad is available for Mac OS X, Windows, and Linux, and can be installe
|
|
|
195
196
|
4. Follow the intuitive graphical interface to analyze public datasets or your own whole slide images
|
|
196
197
|
with [Atlas H&E-TME](https://www.aignostics.com/products/he-tme-profiling-product) and other computational pathology applications.
|
|
197
198
|
|
|
199
|
+
> [!Note]
|
|
200
|
+
> The Launchpad features a growing ecosystem of extensions that seamlessly integrate with standard digital pathology tools. To use the Launchpad with all available extensions, run `uvx --with aignostics[qupath,marimo] aignostics launchpad`. Currently available extensions are:
|
|
201
|
+
> 1. **QuPath extension**: View your application results in [QuPath](https://qupath.github.io/) with a single click. The Launchpad creates QuPath projects on-the-fly.
|
|
202
|
+
> 2. **Marimo extension**: Analyze your application results using [Marimo](https://marimo.io/) notebooks embedded in the Launchpad. You don't have to leave the Launchpad to do real data science.
|
|
203
|
+
|
|
198
204
|
### CLI: Manage datasets and application runs from your terminal
|
|
199
205
|
|
|
200
206
|
The Python SDK includes the **Aignostics CLI**, a Command-Line Interface that allows you to
|
|
@@ -79,6 +79,11 @@ The Launchpad is available for Mac OS X, Windows, and Linux, and can be installe
|
|
|
79
79
|
4. Follow the intuitive graphical interface to analyze public datasets or your own whole slide images
|
|
80
80
|
with [Atlas H&E-TME](https://www.aignostics.com/products/he-tme-profiling-product) and other computational pathology applications.
|
|
81
81
|
|
|
82
|
+
> [!Note]
|
|
83
|
+
> The Launchpad features a growing ecosystem of extensions that seamlessly integrate with standard digital pathology tools. To use the Launchpad with all available extensions, run `uvx --with aignostics[qupath,marimo] aignostics launchpad`. Currently available extensions are:
|
|
84
|
+
> 1. **QuPath extension**: View your application results in [QuPath](https://qupath.github.io/) with a single click. The Launchpad creates QuPath projects on-the-fly.
|
|
85
|
+
> 2. **Marimo extension**: Analyze your application results using [Marimo](https://marimo.io/) notebooks embedded in the Launchpad. You don't have to leave the Launchpad to do real data science.
|
|
86
|
+
|
|
82
87
|
### CLI: Manage datasets and application runs from your terminal
|
|
83
88
|
|
|
84
89
|
The Python SDK includes the **Aignostics CLI**, a Command-Line Interface that allows you to
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aignostics"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.22"
|
|
4
4
|
description = "🔬 Python SDK providing access to the Aignostics Platform. Includes Aignostics Launchpad (Desktop Application), Aignostics CLI (Command-Line Interface), example notebooks, and Aignostics Client Library."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -75,8 +75,8 @@ requires-python = ">=3.11, <4.0"
|
|
|
75
75
|
dependencies = [
|
|
76
76
|
# From Template
|
|
77
77
|
"fastapi[standard,all]>=0.115.12",
|
|
78
|
-
"logfire[system-metrics]>=3.16.
|
|
79
|
-
"nicegui[native]>=2.
|
|
78
|
+
"logfire[system-metrics]>=3.16.1",
|
|
79
|
+
"nicegui[native]>=2.18.0",
|
|
80
80
|
"opentelemetry-instrumentation-fastapi>=0.53b0",
|
|
81
81
|
"opentelemetry-instrumentation-httpx>=0.53b0",
|
|
82
82
|
"opentelemetry-instrumentation-jinja2>=0.53b0",
|
|
@@ -89,20 +89,20 @@ dependencies = [
|
|
|
89
89
|
"pydantic-settings>=2.9.1",
|
|
90
90
|
"pywin32>=310 ; sys_platform == 'win32'",
|
|
91
91
|
"sentry-sdk>=2.29.1",
|
|
92
|
-
"typer>=0.
|
|
92
|
+
"typer>=0.16.0",
|
|
93
93
|
"uptime>=3.0.1",
|
|
94
94
|
# Custom
|
|
95
95
|
"appdirs>=1.4.4",
|
|
96
|
-
"boto3>=1.38.
|
|
97
|
-
"dicom-validator>=0.
|
|
98
|
-
"dicomweb-client[gcp]>=0.59.
|
|
99
|
-
"google-cloud-storage>=
|
|
96
|
+
"boto3>=1.38.27",
|
|
97
|
+
"dicom-validator>=0.7.1",
|
|
98
|
+
"dicomweb-client[gcp]>=0.59.2",
|
|
99
|
+
"google-cloud-storage>=2.12.0",
|
|
100
100
|
"google-crc32c>=1.7.1",
|
|
101
101
|
"highdicom>=0.25.1",
|
|
102
102
|
"httpx>=0.28.1",
|
|
103
103
|
"humanize>=4.12.3",
|
|
104
104
|
"jsf>=0.11.2",
|
|
105
|
-
"jsonschema>=4.
|
|
105
|
+
"jsonschema>=4.24.0",
|
|
106
106
|
"openslide-bin>=4.0.0.8",
|
|
107
107
|
"openslide-python>=1.4.2",
|
|
108
108
|
"pyjwt[crypto]>=2.10.1",
|
|
@@ -130,13 +130,14 @@ dependencies = [
|
|
|
130
130
|
"s5cmd",
|
|
131
131
|
"sphinx-click",
|
|
132
132
|
"tqdm",
|
|
133
|
+
"ijson>=3.4.0",
|
|
133
134
|
]
|
|
134
135
|
|
|
135
136
|
[project.optional-dependencies]
|
|
136
137
|
jupyter = ["jupyter>=1.1.1"]
|
|
137
138
|
marimo = [
|
|
138
139
|
"cloudpathlib>=0.21.1",
|
|
139
|
-
"marimo>=0.13.
|
|
140
|
+
"marimo>=0.13.15",
|
|
140
141
|
"matplotlib>=3.10.3",
|
|
141
142
|
"shapely>=2.1.0",
|
|
142
143
|
]
|
|
@@ -151,25 +152,25 @@ dev = [
|
|
|
151
152
|
"furo>=2024.8.6",
|
|
152
153
|
"git-cliff>=2.8.0",
|
|
153
154
|
"matplotlib>=3.10.1",
|
|
154
|
-
"mypy>=1.
|
|
155
|
+
"mypy>=1.16.0",
|
|
155
156
|
"nox[uv]>=2025.5.1",
|
|
156
157
|
"pip-audit>=2.9.0",
|
|
157
158
|
"pip-licenses @ git+https://github.com/neXenio/pip-licenses.git@master", # https://github.com/raimon49/pip-licenses/pull/224
|
|
158
159
|
"pre-commit>=4.1.0",
|
|
159
160
|
"pyright>=1.1.401",
|
|
160
161
|
"pytest>=8.3.5",
|
|
161
|
-
"pytest-asyncio>=0.
|
|
162
|
+
"pytest-asyncio>=1.0.0",
|
|
162
163
|
"pytest-cov>=6.1.1",
|
|
163
|
-
"pytest-docker>=3.2.
|
|
164
|
+
"pytest-docker>=3.2.2",
|
|
164
165
|
"pytest-env>=1.1.5",
|
|
165
166
|
"pytest-md-report>=0.7.0",
|
|
166
|
-
"pytest-regressions>=2.
|
|
167
|
+
"pytest-regressions>=2.8.0",
|
|
167
168
|
"pytest-selenium>=4.1.0",
|
|
168
169
|
"pytest-subprocess>=1.5.3",
|
|
169
170
|
"pytest-timeout>=2.4.0",
|
|
170
171
|
"pytest-watcher>=0.4.3",
|
|
171
|
-
"pytest-xdist[psutil]>=3.
|
|
172
|
-
"ruff>=0.11.
|
|
172
|
+
"pytest-xdist[psutil]>=3.7.0",
|
|
173
|
+
"ruff>=0.11.12",
|
|
173
174
|
"scalene>=1.5.51",
|
|
174
175
|
"sphinx>=8.2.3",
|
|
175
176
|
"sphinx-autobuild>=2024.10.3",
|
|
@@ -344,7 +345,6 @@ omit = [
|
|
|
344
345
|
"*/_notebook.py",
|
|
345
346
|
"*/_pydicom_handler.py",
|
|
346
347
|
"*/_openslide_handler.py",
|
|
347
|
-
"*/qupath/*",
|
|
348
348
|
]
|
|
349
349
|
branch = true
|
|
350
350
|
parallel = true
|
|
@@ -355,7 +355,7 @@ source = ["src/"]
|
|
|
355
355
|
|
|
356
356
|
|
|
357
357
|
[tool.bumpversion]
|
|
358
|
-
current_version = "0.2.
|
|
358
|
+
current_version = "0.2.22"
|
|
359
359
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
360
360
|
serialize = ["{major}.{minor}.{patch}"]
|
|
361
361
|
search = "{current_version}"
|
|
@@ -6,24 +6,10 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Annotated
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
|
-
from rich.console import Group
|
|
10
|
-
from rich.live import Live
|
|
11
|
-
from rich.panel import Panel
|
|
12
|
-
from rich.progress import (
|
|
13
|
-
BarColumn,
|
|
14
|
-
FileSizeColumn,
|
|
15
|
-
Progress,
|
|
16
|
-
TaskID,
|
|
17
|
-
TaskProgressColumn,
|
|
18
|
-
TextColumn,
|
|
19
|
-
TimeElapsedColumn,
|
|
20
|
-
TimeRemainingColumn,
|
|
21
|
-
TotalFileSizeColumn,
|
|
22
|
-
TransferSpeedColumn,
|
|
23
|
-
)
|
|
24
9
|
|
|
25
10
|
from aignostics.bucket import Service as BucketService
|
|
26
11
|
from aignostics.platform import NotFoundException
|
|
12
|
+
from aignostics.system import Service as SystemService
|
|
27
13
|
from aignostics.utils import console, get_logger
|
|
28
14
|
|
|
29
15
|
from ._service import DownloadProgress, DownloadProgressState, Service
|
|
@@ -370,6 +356,17 @@ def run_upload(
|
|
|
370
356
|
2. Uploads the files referenced in the CSV file to the Aignostics platform.
|
|
371
357
|
3. Incrementally updates the CSV file with upload progress and the signed URLs for the uploaded files.
|
|
372
358
|
"""
|
|
359
|
+
from rich.progress import ( # noqa: PLC0415
|
|
360
|
+
BarColumn,
|
|
361
|
+
FileSizeColumn,
|
|
362
|
+
Progress,
|
|
363
|
+
TaskProgressColumn,
|
|
364
|
+
TextColumn,
|
|
365
|
+
TimeRemainingColumn,
|
|
366
|
+
TotalFileSizeColumn,
|
|
367
|
+
TransferSpeedColumn,
|
|
368
|
+
)
|
|
369
|
+
|
|
373
370
|
metadata_dict = read_metadata_csv_to_dict(metadata_csv_file=metadata_csv_file)
|
|
374
371
|
if not metadata_dict:
|
|
375
372
|
sys.exit(2)
|
|
@@ -542,7 +539,7 @@ def run_cancel(
|
|
|
542
539
|
|
|
543
540
|
|
|
544
541
|
@result_app.command("download")
|
|
545
|
-
def result_download(
|
|
542
|
+
def result_download( # noqa: PLR0913, PLR0917
|
|
546
543
|
run_id: Annotated[str, typer.Argument(..., help="Id of the run to download results for")],
|
|
547
544
|
destination_directory: Annotated[
|
|
548
545
|
Path,
|
|
@@ -555,7 +552,7 @@ def result_download(
|
|
|
555
552
|
readable=True,
|
|
556
553
|
resolve_path=True,
|
|
557
554
|
),
|
|
558
|
-
],
|
|
555
|
+
] = SystemService.get_user_data_directory("results"), # noqa: B008
|
|
559
556
|
create_subdirectory_for_run: Annotated[
|
|
560
557
|
bool,
|
|
561
558
|
typer.Option(
|
|
@@ -574,20 +571,46 @@ def result_download(
|
|
|
574
571
|
help="Wait for run completion and download results incrementally",
|
|
575
572
|
),
|
|
576
573
|
] = True,
|
|
574
|
+
qupath_project: Annotated[
|
|
575
|
+
bool,
|
|
576
|
+
typer.Option(
|
|
577
|
+
help="Create a QuPath project referencing input slides and results. \n"
|
|
578
|
+
"The QuPath project will be created in a subfolder of the destination directory. \n"
|
|
579
|
+
"This option requires the QuPath extension for Launchpad: "
|
|
580
|
+
'start the Launchpad with `uvx --with "aignostics[qupath]" aignostics ...` \n'
|
|
581
|
+
"This options requires installation of the QuPath application: "
|
|
582
|
+
'Run uvx --with "aignostics[qupath]" aignostics qupath install'
|
|
583
|
+
),
|
|
584
|
+
] = False,
|
|
577
585
|
) -> None:
|
|
578
586
|
"""Download results of a run."""
|
|
579
587
|
logger.debug(
|
|
580
588
|
"Downloading results for run with ID '%s' to '%s' with options: "
|
|
581
|
-
"create_subdirectory_for_run=%s, create_subdirectory_per_item=%s, wait_for_completion=%s",
|
|
589
|
+
"create_subdirectory_for_run=%s, create_subdirectory_per_item=%s, wait_for_completion=%s, qupath_project=%r",
|
|
582
590
|
run_id,
|
|
583
591
|
destination_directory,
|
|
584
592
|
create_subdirectory_for_run,
|
|
585
593
|
create_subdirectory_per_item,
|
|
586
594
|
wait_for_completion,
|
|
595
|
+
qupath_project,
|
|
596
|
+
)
|
|
597
|
+
from rich.console import Group # noqa: PLC0415
|
|
598
|
+
from rich.live import Live # noqa: PLC0415
|
|
599
|
+
from rich.panel import Panel # noqa: PLC0415
|
|
600
|
+
from rich.progress import ( # noqa: PLC0415
|
|
601
|
+
BarColumn,
|
|
602
|
+
FileSizeColumn,
|
|
603
|
+
Progress,
|
|
604
|
+
TaskID,
|
|
605
|
+
TaskProgressColumn,
|
|
606
|
+
TextColumn,
|
|
607
|
+
TimeElapsedColumn,
|
|
608
|
+
TimeRemainingColumn,
|
|
609
|
+
TotalFileSizeColumn,
|
|
610
|
+
TransferSpeedColumn,
|
|
587
611
|
)
|
|
588
612
|
|
|
589
613
|
try:
|
|
590
|
-
progress: DownloadProgress = DownloadProgress()
|
|
591
614
|
download_tasks: dict[str, TaskID] = {}
|
|
592
615
|
|
|
593
616
|
main_download_progress_ui = Progress(
|
|
@@ -612,15 +635,13 @@ def result_download(
|
|
|
612
635
|
panel = Panel(
|
|
613
636
|
Group(main_download_progress_ui, artifact_download_progress_ui),
|
|
614
637
|
title=f"Run {run_id}",
|
|
615
|
-
subtitle=
|
|
638
|
+
subtitle="",
|
|
616
639
|
highlight=True,
|
|
617
640
|
)
|
|
618
641
|
with Live(panel):
|
|
619
|
-
main_task = main_download_progress_ui.add_task(
|
|
620
|
-
description=progress.status, total=None, extra_description=""
|
|
621
|
-
)
|
|
642
|
+
main_task = main_download_progress_ui.add_task(description="", total=None, extra_description="")
|
|
622
643
|
|
|
623
|
-
def update_progress() -> None:
|
|
644
|
+
def update_progress(progress: DownloadProgress) -> None:
|
|
624
645
|
"""Update progress bar for file downloads."""
|
|
625
646
|
if progress.run:
|
|
626
647
|
panel.title = f"Run {progress.run.application_run_id} of {progress.run.application_version_id}"
|
|
@@ -669,12 +690,12 @@ def result_download(
|
|
|
669
690
|
)
|
|
670
691
|
|
|
671
692
|
destination_directory = Service().application_run_download(
|
|
672
|
-
progress=progress,
|
|
673
693
|
run_id=run_id,
|
|
674
694
|
destination_directory=destination_directory,
|
|
675
695
|
create_subdirectory_for_run=create_subdirectory_for_run,
|
|
676
696
|
create_subdirectory_per_item=create_subdirectory_per_item,
|
|
677
697
|
wait_for_completion=wait_for_completion,
|
|
698
|
+
qupath_project=qupath_project,
|
|
678
699
|
download_progress_callable=update_progress,
|
|
679
700
|
)
|
|
680
701
|
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from nicegui import app, background_tasks, context, ui # noq
|
|
4
|
+
from nicegui import run as nicegui_run
|
|
5
|
+
|
|
6
|
+
from aignostics.gui import frame
|
|
7
|
+
from aignostics.utils import get_logger
|
|
8
|
+
|
|
9
|
+
from .._service import Service # noqa: TID252
|
|
10
|
+
from ._utils import application_id_to_icon, run_status_to_icon_and_color
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
BORDERED_SEPARATOR = "bordered separator"
|
|
15
|
+
RUNS_LIMIT = 500
|
|
16
|
+
STORAGE_TAB_RUNS_COMPLETED_ONLY = "runs_completed_only"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def _frame( # noqa: C901, PLR0913, PLR0915, PLR0917
|
|
20
|
+
navigation_title: str,
|
|
21
|
+
navigation_icon: str | None = None,
|
|
22
|
+
navigation_icon_color: str | None = None,
|
|
23
|
+
navigation_icon_tooltip: str | None = None,
|
|
24
|
+
left_sidebar: bool = False,
|
|
25
|
+
args: dict[str, Any] | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
if args is None:
|
|
28
|
+
args = {}
|
|
29
|
+
service = Service()
|
|
30
|
+
with frame( # noqa: PLR1702
|
|
31
|
+
navigation_title=navigation_title,
|
|
32
|
+
navigation_icon=navigation_icon,
|
|
33
|
+
navigation_icon_color=navigation_icon_color,
|
|
34
|
+
navigation_icon_tooltip=navigation_icon_tooltip,
|
|
35
|
+
left_sidebar=left_sidebar,
|
|
36
|
+
):
|
|
37
|
+
try:
|
|
38
|
+
with ui.list().props(BORDERED_SEPARATOR).classes("full-width"):
|
|
39
|
+
ui.item_label("Applications").props("header")
|
|
40
|
+
ui.separator()
|
|
41
|
+
for application in service.applications():
|
|
42
|
+
with (
|
|
43
|
+
ui.item(
|
|
44
|
+
on_click=lambda app_id=application.application_id: ui.navigate.to(f"/application/{app_id}")
|
|
45
|
+
)
|
|
46
|
+
.mark(f"SIDEBAR_APPLICATION:{application.application_id}")
|
|
47
|
+
.props("clickable")
|
|
48
|
+
):
|
|
49
|
+
with (
|
|
50
|
+
ui.item_section().props("avatar"),
|
|
51
|
+
ui.icon(application_id_to_icon(application.application_id), color="primary"),
|
|
52
|
+
):
|
|
53
|
+
ui.tooltip(application.application_id)
|
|
54
|
+
with ui.item_section():
|
|
55
|
+
ui.label(f"{application.name}").tailwind.font_weight(
|
|
56
|
+
"bold"
|
|
57
|
+
if context.client.page.path == "/application/{application_id}"
|
|
58
|
+
and args
|
|
59
|
+
and args.get("application_id") == application.application_id
|
|
60
|
+
else "normal"
|
|
61
|
+
)
|
|
62
|
+
except Exception as e: # noqa: BLE001
|
|
63
|
+
ui.label(f"Failed to list applications: {e!s}").mark("LABEL_ERROR")
|
|
64
|
+
|
|
65
|
+
async def application_runs_load_and_render(runs_column: ui.column, completed_only: bool = False) -> None:
|
|
66
|
+
with runs_column:
|
|
67
|
+
try:
|
|
68
|
+
runs = await nicegui_run.io_bound(
|
|
69
|
+
Service.application_runs_static, limit=RUNS_LIMIT, completed_only=completed_only
|
|
70
|
+
)
|
|
71
|
+
if runs is None:
|
|
72
|
+
message = "nicegui_run.io_bound(Service.application_runs_static) returned None" # type: ignore[unreachable]
|
|
73
|
+
logger.error(message)
|
|
74
|
+
raise RuntimeError(message) # noqa: TRY301
|
|
75
|
+
runs_column.clear()
|
|
76
|
+
for index, run_data in enumerate(runs):
|
|
77
|
+
with (
|
|
78
|
+
ui.item(
|
|
79
|
+
on_click=lambda run_id=run_data["application_run_id"]: ui.navigate.to(
|
|
80
|
+
f"/application/run/{run_id}"
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
.props("clickable")
|
|
84
|
+
.classes("w-full")
|
|
85
|
+
.mark(f"SIDEBAR_RUN_ITEM:{index}")
|
|
86
|
+
):
|
|
87
|
+
with ui.item_section().props("avatar"):
|
|
88
|
+
icon, color = run_status_to_icon_and_color(run_data["status"])
|
|
89
|
+
with ui.icon(icon, color=color):
|
|
90
|
+
ui.tooltip(
|
|
91
|
+
f"Run {run_data['application_run_id']}, "
|
|
92
|
+
f"status {run_data['status'].value.upper()}"
|
|
93
|
+
)
|
|
94
|
+
with ui.item_section():
|
|
95
|
+
ui.label(f"{run_data['application_version_id']}").tailwind.font_weight(
|
|
96
|
+
"bold"
|
|
97
|
+
if context.client.page.path == "/application/run/{application_run_id}"
|
|
98
|
+
and args
|
|
99
|
+
and args.get("application_run_id") == run_data["application_run_id"]
|
|
100
|
+
else "normal"
|
|
101
|
+
)
|
|
102
|
+
ui.label(
|
|
103
|
+
f"triggered on {run_data['triggered_at'].astimezone().strftime('%m-%d %H:%M')}"
|
|
104
|
+
)
|
|
105
|
+
if not runs:
|
|
106
|
+
with ui.item():
|
|
107
|
+
with ui.item_section().props("avatar"):
|
|
108
|
+
ui.icon("info")
|
|
109
|
+
with ui.item_section():
|
|
110
|
+
ui.label("You did not yet create a run.")
|
|
111
|
+
except Exception:
|
|
112
|
+
runs_column.clear()
|
|
113
|
+
with ui.item():
|
|
114
|
+
with ui.item_section().props("avatar"):
|
|
115
|
+
ui.icon("error")
|
|
116
|
+
with ui.item_section():
|
|
117
|
+
ui.label("Failed to load application runs.")
|
|
118
|
+
logger.exception("Failed to load application runs")
|
|
119
|
+
|
|
120
|
+
@ui.refreshable
|
|
121
|
+
async def _runs_list() -> None:
|
|
122
|
+
with ui.column().classes("full-width justify-center") as runs_column:
|
|
123
|
+
with ui.row().classes("w-full justify-center"):
|
|
124
|
+
ui.spinner(size="lg").classes("m-5")
|
|
125
|
+
await ui.context.client.connected()
|
|
126
|
+
background_tasks.create_lazy(
|
|
127
|
+
coroutine=application_runs_load_and_render(
|
|
128
|
+
runs_column=runs_column,
|
|
129
|
+
completed_only=app.storage.tab.get(STORAGE_TAB_RUNS_COMPLETED_ONLY, False),
|
|
130
|
+
),
|
|
131
|
+
name="_runs_list",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
class RunFilterButton(ui.icon):
|
|
135
|
+
def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
|
|
136
|
+
super().__init__(*args, **kwargs)
|
|
137
|
+
self._state = app.storage.tab.get(STORAGE_TAB_RUNS_COMPLETED_ONLY, False)
|
|
138
|
+
self.on("click", self.toggle)
|
|
139
|
+
|
|
140
|
+
def toggle(self) -> None:
|
|
141
|
+
self._state = not self._state
|
|
142
|
+
app.storage.tab[STORAGE_TAB_RUNS_COMPLETED_ONLY] = self._state
|
|
143
|
+
self.update()
|
|
144
|
+
_runs_list.refresh()
|
|
145
|
+
|
|
146
|
+
def update(self) -> None:
|
|
147
|
+
self.props(f"color={'positive' if self._state else 'grey'}")
|
|
148
|
+
super().update()
|
|
149
|
+
|
|
150
|
+
def is_active(self) -> bool:
|
|
151
|
+
return bool(self._state)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
with ui.list().props(BORDERED_SEPARATOR).classes("full-width"):
|
|
155
|
+
with ui.row(align_items="center").classes("justify-center"):
|
|
156
|
+
ui.item_label("Runs").props("header")
|
|
157
|
+
ui.space()
|
|
158
|
+
await ui.context.client.connected()
|
|
159
|
+
with RunFilterButton("done_all", size="sm").classes("mr-3").mark("BUTTON_RUNS_FILTER_COMPLETED"):
|
|
160
|
+
ui.tooltip("Show completed runs only")
|
|
161
|
+
ui.separator()
|
|
162
|
+
await _runs_list()
|
|
163
|
+
except Exception as e: # noqa: BLE001
|
|
164
|
+
ui.label(f"Failed to list application runs: {e!s}").mark("LABEL_ERROR")
|