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.
Files changed (147) hide show
  1. {aignostics-0.2.16 → aignostics-0.2.22}/PKG-INFO +16 -10
  2. {aignostics-0.2.16 → aignostics-0.2.22}/README.md +5 -0
  3. {aignostics-0.2.16 → aignostics-0.2.22}/examples/notebook.py +1 -1
  4. {aignostics-0.2.16 → aignostics-0.2.22}/pyproject.toml +18 -18
  5. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/__init__.py +1 -1
  6. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_cli.py +46 -25
  7. aignostics-0.2.22/src/aignostics/application/_gui/_frame.py +164 -0
  8. aignostics-0.2.22/src/aignostics/application/_gui/_page_application_describe.py +634 -0
  9. aignostics-0.2.22/src/aignostics/application/_gui/_page_application_run_describe.py +546 -0
  10. aignostics-0.2.22/src/aignostics/application/_gui/_page_builder.py +42 -0
  11. aignostics-0.2.22/src/aignostics/application/_gui/_page_index.py +49 -0
  12. aignostics-0.2.22/src/aignostics/application/_gui/_utils.py +99 -0
  13. aignostics-0.2.22/src/aignostics/application/_gui/assets/canceled.lottie +0 -0
  14. aignostics-0.2.22/src/aignostics/application/_gui/assets/done.lottie +0 -0
  15. aignostics-0.2.22/src/aignostics/application/_gui/assets/empty.lottie +0 -0
  16. aignostics-0.2.22/src/aignostics/application/_gui/assets/error.lottie +0 -0
  17. aignostics-0.2.22/src/aignostics/application/_gui/assets/error_alt.lottie +0 -0
  18. aignostics-0.2.22/src/aignostics/application/_gui/assets/pending.lottie +0 -0
  19. aignostics-0.2.22/src/aignostics/application/_gui/assets/pending_alt.lottie +0 -0
  20. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_service.py +183 -64
  21. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_utils.py +53 -3
  22. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_cli.py +10 -10
  23. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_gui.py +5 -1
  24. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_service.py +2 -1
  25. aignostics-0.2.22/src/aignostics/bucket/assets/Google-Cloud-logo.png +0 -0
  26. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/cli.py +0 -4
  27. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/constants.py +3 -0
  28. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_cli.py +36 -12
  29. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_gui.py +46 -30
  30. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_idc_index.py +18 -2
  31. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/_frame.py +84 -31
  32. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/_theme.py +10 -4
  33. aignostics-0.2.22/src/aignostics/notebook/_gui.py +98 -0
  34. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/notebook/_service.py +13 -6
  35. aignostics-0.2.22/src/aignostics/notebook/assets/python.lottie +0 -0
  36. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/__init__.py +6 -2
  37. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/runs.py +3 -1
  38. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/qupath/__init__.py +3 -1
  39. aignostics-0.2.22/src/aignostics/qupath/_cli.py +359 -0
  40. aignostics-0.2.22/src/aignostics/qupath/_gui.py +192 -0
  41. aignostics-0.2.22/src/aignostics/qupath/_service.py +1221 -0
  42. aignostics-0.2.22/src/aignostics/qupath/assets/download.lottie +0 -0
  43. aignostics-0.2.22/src/aignostics/qupath/assets/microscope.lottie +0 -0
  44. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_cli.py +104 -1
  45. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_gui.py +2 -1
  46. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_service.py +115 -2
  47. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_console.py +1 -0
  48. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_logfire.py +10 -1
  49. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_sentry.py +9 -1
  50. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/boot.py +3 -0
  51. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_service.py +10 -8
  52. aignostics-0.2.16/src/aignostics/application/_gui.py +0 -1194
  53. aignostics-0.2.16/src/aignostics/notebook/_gui.py +0 -33
  54. aignostics-0.2.16/src/aignostics/qupath/_cli.py +0 -106
  55. aignostics-0.2.16/src/aignostics/qupath/_gui.py +0 -80
  56. aignostics-0.2.16/src/aignostics/qupath/_service.py +0 -552
  57. {aignostics-0.2.16 → aignostics-0.2.22}/.gitignore +0 -0
  58. {aignostics-0.2.16 → aignostics-0.2.22}/LICENSE +0 -0
  59. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/.openapi-generator/FILES +0 -0
  60. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/.openapi-generator/VERSION +0 -0
  61. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/api/public_api.py +0 -0
  62. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/api_client.py +0 -0
  63. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/api_response.py +0 -0
  64. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/configuration.py +0 -0
  65. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/exceptions.py +0 -0
  66. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/__init__.py +0 -0
  67. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/application_read_response.py +0 -0
  68. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/application_run_status.py +0 -0
  69. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/application_version_read_response.py +0 -0
  70. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/http_validation_error.py +0 -0
  71. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/input_artifact_creation_request.py +0 -0
  72. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/input_artifact_read_response.py +0 -0
  73. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/item_creation_request.py +0 -0
  74. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/item_result_read_response.py +0 -0
  75. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/item_status.py +0 -0
  76. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/output_artifact_read_response.py +0 -0
  77. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py +0 -0
  78. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/output_artifact_scope.py +0 -0
  79. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/payload_input_artifact.py +0 -0
  80. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/payload_item.py +0 -0
  81. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/payload_output_artifact.py +0 -0
  82. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/run_creation_request.py +0 -0
  83. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/run_creation_response.py +0 -0
  84. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/run_read_response.py +0 -0
  85. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/transfer_urls.py +0 -0
  86. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/user_payload.py +0 -0
  87. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/validation_error.py +0 -0
  88. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/models/validation_error_loc_inner.py +0 -0
  89. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/aignx/codegen/rest.py +0 -0
  90. {aignostics-0.2.16 → aignostics-0.2.22}/codegen/out/docs/PublicApi.md +0 -0
  91. {aignostics-0.2.16 → aignostics-0.2.22}/examples/__init__.py +0 -0
  92. {aignostics-0.2.16 → aignostics-0.2.22}/examples/e2e.py +0 -0
  93. {aignostics-0.2.16 → aignostics-0.2.22}/examples/notebook.ipynb +0 -0
  94. {aignostics-0.2.16 → aignostics-0.2.22}/examples/script.py +0 -0
  95. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/__init__.py +0 -0
  96. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-1.png +0 -0
  97. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-2.png +0 -0
  98. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-3.png +0 -0
  99. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-4.png +0 -0
  100. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/home-card-5.png +0 -0
  101. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/image-not-found.png +0 -0
  102. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/ruo.png +0 -0
  103. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/application/_gui}/assets/ruo.svg +0 -0
  104. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/application/_settings.py +0 -0
  105. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/__init__.py +0 -0
  106. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/bucket/_settings.py +0 -0
  107. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/bucket}/assets/Google-Cloud-logo.svg +0 -0
  108. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/__init__.py +0 -0
  109. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/dataset/_service.py +0 -0
  110. {aignostics-0.2.16/src/aignostics/gui → aignostics-0.2.22/src/aignostics/dataset}/assets/NIH-IDC-logo.svg +0 -0
  111. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/__init__.py +0 -0
  112. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/assets/cabin-v27-latin-regular.woff2 +0 -0
  113. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/gui/assets/logo.png +0 -0
  114. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/notebook/__init__.py +0 -0
  115. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/notebook/_notebook.py +0 -0
  116. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_authentication.py +0 -0
  117. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_client.py +0 -0
  118. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_constants.py +0 -0
  119. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_messages.py +0 -0
  120. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_service.py +0 -0
  121. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_settings.py +0 -0
  122. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/_utils.py +0 -0
  123. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/__init__.py +0 -0
  124. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/applications.py +0 -0
  125. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/platform/resources/utils.py +0 -0
  126. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/qupath/_settings.py +0 -0
  127. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/__init__.py +0 -0
  128. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_exceptions.py +0 -0
  129. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/system/_settings.py +0 -0
  130. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/.vendored/bottle.py +0 -0
  131. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/__init__.py +0 -0
  132. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_cli.py +0 -0
  133. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_constants.py +0 -0
  134. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_di.py +0 -0
  135. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_gui.py +0 -0
  136. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_health.py +0 -0
  137. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_log.py +0 -0
  138. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_notebook.py +0 -0
  139. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_process.py +0 -0
  140. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_service.py +0 -0
  141. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/utils/_settings.py +0 -0
  142. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/__init__.py +0 -0
  143. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_cli.py +0 -0
  144. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_gui.py +0 -0
  145. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_openslide_handler.py +0 -0
  146. {aignostics-0.2.16 → aignostics-0.2.22}/src/aignostics/wsi/_pydicom_handler.py +0 -0
  147. {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.16
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.22
52
+ Requires-Dist: boto3>=1.38.27
53
53
  Requires-Dist: click
54
- Requires-Dist: dicom-validator>=0.6.2
55
- Requires-Dist: dicomweb-client[gcp]>=0.59.3
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>=3.1.0
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.23.0
66
- Requires-Dist: logfire[system-metrics]>=3.16.0
67
- Requires-Dist: nicegui[native]>=2.17.0
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.15.4
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.11; extra == 'marimo'
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
@@ -2,7 +2,7 @@
2
2
  # requires-python = ">=3.13"
3
3
  # dependencies = [
4
4
  # "marimo",
5
- # "aignostics==0.2.16",
5
+ # "aignostics==0.2.22",
6
6
  # ]
7
7
  # ///
8
8
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aignostics"
3
- version = "0.2.16"
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.0",
79
- "nicegui[native]>=2.17.0",
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.15.4",
92
+ "typer>=0.16.0",
93
93
  "uptime>=3.0.1",
94
94
  # Custom
95
95
  "appdirs>=1.4.4",
96
- "boto3>=1.38.22",
97
- "dicom-validator>=0.6.2",
98
- "dicomweb-client[gcp]>=0.59.3",
99
- "google-cloud-storage>=3.1.0",
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.23.0",
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.11",
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.5.0",
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.26.0",
162
+ "pytest-asyncio>=1.0.0",
162
163
  "pytest-cov>=6.1.1",
163
- "pytest-docker>=3.2.1",
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.7.0",
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.6.1",
172
- "ruff>=0.11.10",
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.16"
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}"
@@ -12,7 +12,7 @@ from importlib.util import find_spec
12
12
 
13
13
  # advertise PageBuilder to enable auto-discovery
14
14
  if find_spec("nicegui"):
15
- from ._gui import PageBuilder
15
+ from ._gui._page_builder import PageBuilder
16
16
 
17
17
  __all__ += [
18
18
  "PageBuilder",
@@ -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=progress.status,
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")