aignostics 0.2.23__tar.gz → 0.2.30__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 (146) hide show
  1. {aignostics-0.2.23 → aignostics-0.2.30}/PKG-INFO +1 -1
  2. {aignostics-0.2.23 → aignostics-0.2.30}/examples/notebook.py +1 -1
  3. {aignostics-0.2.23 → aignostics-0.2.30}/pyproject.toml +2 -2
  4. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/_page_application_run_describe.py +43 -18
  5. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_service.py +19 -7
  6. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/gui/_frame.py +3 -3
  7. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/notebook/_gui.py +23 -6
  8. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/notebook/_notebook.py +7 -30
  9. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/system/_cli.py +33 -44
  10. aignostics-0.2.30/src/aignostics/system/_gui.py +93 -0
  11. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/system/_service.py +85 -0
  12. aignostics-0.2.30/src/aignostics/system/assets/system.lottie +0 -0
  13. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/__init__.py +3 -0
  14. aignostics-0.2.30/src/aignostics/utils/_fs.py +74 -0
  15. aignostics-0.2.23/src/aignostics/system/_gui.py +0 -61
  16. {aignostics-0.2.23 → aignostics-0.2.30}/.gitignore +0 -0
  17. {aignostics-0.2.23 → aignostics-0.2.30}/LICENSE +0 -0
  18. {aignostics-0.2.23 → aignostics-0.2.30}/README.md +0 -0
  19. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/.openapi-generator/FILES +0 -0
  20. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/.openapi-generator/VERSION +0 -0
  21. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/api/public_api.py +0 -0
  22. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/api_client.py +0 -0
  23. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/api_response.py +0 -0
  24. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/configuration.py +0 -0
  25. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/exceptions.py +0 -0
  26. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/__init__.py +0 -0
  27. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/application_read_response.py +0 -0
  28. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/application_run_status.py +0 -0
  29. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/application_version_read_response.py +0 -0
  30. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/http_validation_error.py +0 -0
  31. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/input_artifact_creation_request.py +0 -0
  32. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/input_artifact_read_response.py +0 -0
  33. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/item_creation_request.py +0 -0
  34. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/item_result_read_response.py +0 -0
  35. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/item_status.py +0 -0
  36. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/output_artifact_read_response.py +0 -0
  37. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py +0 -0
  38. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/output_artifact_scope.py +0 -0
  39. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/payload_input_artifact.py +0 -0
  40. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/payload_item.py +0 -0
  41. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/payload_output_artifact.py +0 -0
  42. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/run_creation_request.py +0 -0
  43. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/run_creation_response.py +0 -0
  44. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/run_read_response.py +0 -0
  45. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/transfer_urls.py +0 -0
  46. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/user_payload.py +0 -0
  47. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/validation_error.py +0 -0
  48. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/models/validation_error_loc_inner.py +0 -0
  49. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/aignx/codegen/rest.py +0 -0
  50. {aignostics-0.2.23 → aignostics-0.2.30}/codegen/out/docs/PublicApi.md +0 -0
  51. {aignostics-0.2.23 → aignostics-0.2.30}/examples/__init__.py +0 -0
  52. {aignostics-0.2.23 → aignostics-0.2.30}/examples/e2e.py +0 -0
  53. {aignostics-0.2.23 → aignostics-0.2.30}/examples/notebook.ipynb +0 -0
  54. {aignostics-0.2.23 → aignostics-0.2.30}/examples/script.py +0 -0
  55. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/__init__.py +0 -0
  56. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/__init__.py +0 -0
  57. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_cli.py +0 -0
  58. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/_frame.py +0 -0
  59. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/_page_application_describe.py +0 -0
  60. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/_page_builder.py +0 -0
  61. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/_page_index.py +0 -0
  62. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/_utils.py +0 -0
  63. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/canceled.lottie +0 -0
  64. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/done.lottie +0 -0
  65. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/empty.lottie +0 -0
  66. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/error.lottie +0 -0
  67. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/error_alt.lottie +0 -0
  68. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/home-card-1.png +0 -0
  69. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/home-card-2.png +0 -0
  70. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/home-card-3.png +0 -0
  71. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/home-card-4.png +0 -0
  72. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/home-card-5.png +0 -0
  73. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/image-not-found.png +0 -0
  74. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/pending.lottie +0 -0
  75. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/pending_alt.lottie +0 -0
  76. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/ruo.png +0 -0
  77. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_gui/assets/ruo.svg +0 -0
  78. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_settings.py +0 -0
  79. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/application/_utils.py +0 -0
  80. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/__init__.py +0 -0
  81. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/_cli.py +0 -0
  82. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/_gui.py +0 -0
  83. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/_service.py +0 -0
  84. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/_settings.py +0 -0
  85. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/assets/Google-Cloud-logo.png +0 -0
  86. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/bucket/assets/Google-Cloud-logo.svg +0 -0
  87. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/cli.py +0 -0
  88. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/constants.py +0 -0
  89. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/dataset/__init__.py +0 -0
  90. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/dataset/_cli.py +0 -0
  91. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/dataset/_gui.py +0 -0
  92. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/dataset/_idc_index.py +0 -0
  93. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/dataset/_service.py +0 -0
  94. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/dataset/assets/NIH-IDC-logo.svg +0 -0
  95. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/gui/__init__.py +0 -0
  96. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/gui/_theme.py +0 -0
  97. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/gui/assets/cabin-v27-latin-regular.woff2 +0 -0
  98. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/gui/assets/logo.png +0 -0
  99. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/notebook/__init__.py +0 -0
  100. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/notebook/_service.py +0 -0
  101. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/notebook/assets/python.lottie +0 -0
  102. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/__init__.py +0 -0
  103. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_authentication.py +0 -0
  104. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_client.py +0 -0
  105. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_constants.py +0 -0
  106. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_messages.py +0 -0
  107. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_service.py +0 -0
  108. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_settings.py +0 -0
  109. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/_utils.py +0 -0
  110. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/resources/__init__.py +0 -0
  111. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/resources/applications.py +0 -0
  112. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/resources/runs.py +0 -0
  113. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/platform/resources/utils.py +0 -0
  114. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/__init__.py +0 -0
  115. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/_cli.py +0 -0
  116. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/_gui.py +0 -0
  117. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/_service.py +0 -0
  118. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/_settings.py +0 -0
  119. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/assets/download.lottie +0 -0
  120. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/qupath/assets/microscope.lottie +0 -0
  121. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/system/__init__.py +0 -0
  122. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/system/_exceptions.py +0 -0
  123. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/system/_settings.py +0 -0
  124. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/.vendored/bottle.py +0 -0
  125. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_cli.py +0 -0
  126. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_console.py +0 -0
  127. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_constants.py +0 -0
  128. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_di.py +0 -0
  129. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_gui.py +0 -0
  130. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_health.py +0 -0
  131. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_log.py +0 -0
  132. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_logfire.py +0 -0
  133. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_notebook.py +0 -0
  134. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_process.py +0 -0
  135. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_sentry.py +0 -0
  136. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_service.py +0 -0
  137. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/_settings.py +0 -0
  138. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/utils/boot.py +0 -0
  139. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/__init__.py +0 -0
  140. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/_cli.py +0 -0
  141. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/_gui.py +0 -0
  142. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/_openslide_handler.py +0 -0
  143. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/_pydicom_handler.py +0 -0
  144. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/_service.py +0 -0
  145. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/_utils.py +0 -0
  146. {aignostics-0.2.23 → aignostics-0.2.30}/src/aignostics/wsi/assets/fallback.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aignostics
3
- Version: 0.2.23
3
+ Version: 0.2.30
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/
@@ -2,7 +2,7 @@
2
2
  # requires-python = ">=3.13"
3
3
  # dependencies = [
4
4
  # "marimo",
5
- # "aignostics==0.2.23",
5
+ # "aignostics==0.2.30",
6
6
  # ]
7
7
  # ///
8
8
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aignostics"
3
- version = "0.2.23"
3
+ version = "0.2.30"
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 = [
@@ -355,7 +355,7 @@ source = ["src/"]
355
355
 
356
356
 
357
357
  [tool.bumpversion]
358
- current_version = "0.2.23"
358
+ current_version = "0.2.30"
359
359
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
360
360
  serialize = ["{major}.{minor}.{patch}"]
361
361
  search = "{current_version}"
@@ -4,7 +4,7 @@ from importlib.util import find_spec
4
4
  from multiprocessing import Manager
5
5
  from pathlib import Path
6
6
  from typing import Any
7
- from urllib.parse import quote as urlencode
7
+ from urllib.parse import quote
8
8
 
9
9
  from nicegui import run as nicegui_run
10
10
  from nicegui import ui # noq
@@ -87,7 +87,7 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
87
87
  return False
88
88
 
89
89
  @ui.refreshable
90
- def download_run_dialog_content(qupath_project: bool = False) -> None: # noqa: C901, PLR0915
90
+ def download_run_dialog_content(qupath_project: bool = False, marimo: bool = False) -> None: # noqa: C901, PLR0915
91
91
  if qupath_project:
92
92
  ui.markdown(
93
93
  "##### Visualize results in QuPath with one click \n"
@@ -97,6 +97,13 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
97
97
  "4. The QuPath project will reference the input slides and resulting image heatmaps. \n"
98
98
  "5. Detected cells will be added as annotations to input slides. \n"
99
99
  )
100
+ elif marimo:
101
+ ui.markdown(
102
+ "##### Open results in Marimo with one click \n"
103
+ "1. Use data directory of Launchpad or select a custom folder. \n"
104
+ "2. A subfolder with the application run will be created and all results downloaded there. \n"
105
+ "3. A Marimo notebook will be started pointing to the subfolder. \n"
106
+ )
100
107
  else:
101
108
  ui.markdown(
102
109
  "##### Download all results with one click \n"
@@ -223,6 +230,10 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
223
230
  ui.notify("Download and QuPath project creation completed.", type="positive")
224
231
  download_item_status.set_text("Opening QuPath ...")
225
232
  await open_qupath(project=results_folder / "qupath", button=download_button)
233
+ elif marimo:
234
+ ui.notify("Download and Notebook preparation completed.", type="positive")
235
+ download_item_status.set_text("Opening Notebook ...")
236
+ open_marimo(results_folder=results_folder, button=download_button)
226
237
  else:
227
238
  ui.notify("Download completed.", type="positive")
228
239
  show_in_file_manager(str(results_folder))
@@ -238,10 +249,20 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
238
249
 
239
250
  ui.separator()
240
251
  with ui.row(align_items="end").classes("w-full justify-end"):
252
+ if qupath_project:
253
+ label = "Visualize with QuPath"
254
+ icon = "zoom_in"
255
+ elif marimo:
256
+ label = "Analyze with Notebook"
257
+ icon = "analytics"
258
+ else:
259
+ label = "Download all results"
260
+ icon = "cloud_download"
261
+
241
262
  download_button = (
242
263
  ui.button(
243
- "Download all results" if not qupath_project else "Visualize with QuPath",
244
- icon="cloud_download" if not qupath_project else "zoom_in",
264
+ label,
265
+ icon=icon,
245
266
  on_click=start_download,
246
267
  )
247
268
  .props("color=primary")
@@ -254,9 +275,9 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
254
275
  with ui.dialog() as download_run_dialog, ui.card().style(WIDTH_1200px):
255
276
  download_run_dialog_content()
256
277
 
257
- def download_run_dialog_open(qupath_project: bool = False) -> None:
278
+ def download_run_dialog_open(qupath_project: bool = False, marimo: bool = False) -> None:
258
279
  """Open the CSV dialog."""
259
- download_run_dialog_content.refresh(qupath_project=qupath_project)
280
+ download_run_dialog_content.refresh(qupath_project=qupath_project, marimo=marimo)
260
281
  download_run_dialog.open()
261
282
 
262
283
  @ui.refreshable
@@ -315,7 +336,7 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
315
336
  if url:
316
337
  try:
317
338
  with ui.scroll_area().classes("w-full h-[calc(100vh-2rem)]"):
318
- ui.image("/tiff?url=" + urlencode(url))
339
+ ui.image("/tiff?url=" + quote(url))
319
340
  except Exception as e: # noqa: BLE001
320
341
  ui.notify(f"Failed to load CSV: {e!s}", type="negative")
321
342
 
@@ -361,11 +382,11 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
361
382
  button.enable()
362
383
  button.props(remove="loading")
363
384
 
364
- def open_marimo() -> None:
365
- marimo_button.disable()
366
- marimo_button.props(add="loading")
367
- ui.navigate.to(f"/notebook/{run.application_run_id}")
368
- marimo_button.enable()
385
+ def open_marimo(results_folder: Path, button: ui.button | None = None) -> None:
386
+ if button:
387
+ button.disable()
388
+ button.props(add="loading")
389
+ ui.navigate.to(f"/notebook/{run.application_run_id}?results_folder={quote(results_folder.as_posix())}")
369
390
 
370
391
  if run_data: # noqa: PLR1702
371
392
  with ui.row().classes("w-full justify-center"):
@@ -399,11 +420,15 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
399
420
  ):
400
421
  ui.tooltip("Open results in QuPath Microscopy Viewer")
401
422
  if find_spec("marimo"):
402
- with ui.button(
403
- "Marimo",
404
- icon="analytics",
405
- on_click=open_marimo,
406
- ).props("push") as marimo_button:
423
+ with (
424
+ ui.button(
425
+ "Marimo",
426
+ icon="analytics",
427
+ on_click=lambda _: download_run_dialog_open(qupath_project=False, marimo=True),
428
+ )
429
+ .mark("BUTTON_OPEN_NOTEBOOK")
430
+ .props("push")
431
+ ):
407
432
  ui.tooltip("Open results in Python Notebook served by Marimo")
408
433
  if run_data.status.value == ApplicationRunStatus.RUNNING:
409
434
  ui.button(
@@ -434,7 +459,7 @@ async def _page_application_run_describe(application_run_id: str) -> None: # no
434
459
  ):
435
460
  image_file: Path | None = Path(item.reference).resolve()
436
461
  if image_file and image_file.is_file():
437
- image_url = "/thumbnail?source=" + urlencode(image_file.as_posix())
462
+ image_url = "/thumbnail?source=" + quote(image_file.as_posix())
438
463
  else:
439
464
  image_file = None
440
465
  image_url = "/application_assets/image-not-found.png"
@@ -29,9 +29,9 @@ from aignostics.platform import (
29
29
  ItemResult,
30
30
  ItemStatus,
31
31
  NotFoundException,
32
- OutputArtifactData,
32
+ OutputArtifactElement,
33
33
  )
34
- from aignostics.utils import BaseService, Health, get_logger
34
+ from aignostics.utils import BaseService, Health, get_logger, sanitize_path_component
35
35
  from aignostics.wsi import Service as WSIService
36
36
 
37
37
  from ._settings import Settings
@@ -73,7 +73,7 @@ class DownloadProgress(BaseModel):
73
73
  item_count: int | None = None
74
74
  item_index: int | None = None
75
75
  item_reference: str | None = None
76
- artifact: OutputArtifactData | None = None
76
+ artifact: OutputArtifactElement | None = None
77
77
  artifact_count: int | None = None
78
78
  artifact_index: int | None = None
79
79
  artifact_path: Path | None = None
@@ -705,7 +705,7 @@ class Service(BaseService):
705
705
  download_progress_queue,
706
706
  )
707
707
 
708
- def application_run_download( # noqa: C901, PLR0912, PLR0913, PLR0915, PLR0917
708
+ def application_run_download( # noqa: C901, PLR0912, PLR0913, PLR0914, PLR0915, PLR0917
709
709
  self,
710
710
  run_id: str,
711
711
  destination_directory: Path,
@@ -876,12 +876,19 @@ class Service(BaseService):
876
876
  get_mime_type_for_artifact(artifact) == "application/geo+json"
877
877
  and artifact.name == "cell_classification:geojson_polygons"
878
878
  ):
879
+ artifact_name = artifact.name
879
880
  if create_subdirectory_per_item:
880
881
  reference_path = Path(item.reference)
881
882
  stem_name = reference_path.stem
882
- artifact_path = final_destination_directory / stem_name / f"{artifact.name}.json"
883
+ artifact_path = (
884
+ final_destination_directory
885
+ / stem_name
886
+ / f"{sanitize_path_component(artifact_name)}.json"
887
+ )
883
888
  else:
884
- artifact_path = final_destination_directory / f"{artifact.name}.json"
889
+ artifact_path = (
890
+ final_destination_directory / f"{sanitize_path_component(artifact_name)}.json"
891
+ )
885
892
  message = f"Annotating input slide '{image_path}' with artifact '{artifact_path}' ..."
886
893
  logger.debug(message)
887
894
  added = QuPathService.annotate(
@@ -943,6 +950,8 @@ class Service(BaseService):
943
950
  progress.status = DownloadProgressState.DOWNLOADING
944
951
  progress.item_index = item_index
945
952
  progress.item = item
953
+ progress.item_reference = item.reference
954
+
946
955
  progress.artifact_count = len(item.output_artifacts)
947
956
  Service._update_progress(progress, download_progress_callable, download_progress_queue)
948
957
 
@@ -1007,7 +1016,10 @@ class Service(BaseService):
1007
1016
  logger.error(message)
1008
1017
  raise ValueError(message)
1009
1018
 
1010
- artifact_path = destination_directory / f"{prefix}{artifact.name}{get_file_extension_for_artifact(artifact)}"
1019
+ artifact_path = (
1020
+ destination_directory
1021
+ / f"{prefix}{sanitize_path_component(artifact.name)}{get_file_extension_for_artifact(artifact)}"
1022
+ )
1011
1023
 
1012
1024
  if artifact_path.exists():
1013
1025
  checksum = google_crc32c.Checksum() # type: ignore[no-untyped-call]
@@ -44,9 +44,9 @@ def frame( # noqa: C901, PLR0915
44
44
  @ui.refreshable
45
45
  def health_icon() -> None:
46
46
  if launchpad_healthy:
47
- ui.icon("check_circle", color="positive")
47
+ ui.icon("settings", color="positive")
48
48
  elif launchpad_healthy is not None:
49
- ui.icon("error", color="negative")
49
+ ui.icon("settings", color="negative")
50
50
 
51
51
  @ui.refreshable
52
52
  def health_link() -> None:
@@ -215,7 +215,7 @@ def frame( # noqa: C901, PLR0915
215
215
  with ui.item_section().props("avatar"):
216
216
  health_icon()
217
217
  with ui.item_section():
218
- ui.label("Check Launchpad Status").tailwind.font_weight(
218
+ ui.label("Info and Settings").tailwind.font_weight(
219
219
  "bold" if context.client.page.path == "/system" else "normal"
220
220
  )
221
221
 
@@ -1,8 +1,10 @@
1
1
  """Notebook GUI."""
2
2
 
3
3
  from pathlib import Path
4
+ from urllib.parse import quote
4
5
 
5
6
  from aignostics.gui import frame, theme
7
+ from aignostics.system import Service as SystemService
6
8
  from aignostics.utils import BasePageBuilder, get_logger
7
9
 
8
10
  logger = get_logger(__name__)
@@ -24,6 +26,14 @@ class PageBuilder(BasePageBuilder):
24
26
  # Nothing to do here, just to show the page
25
27
  pass
26
28
 
29
+ def launch_marimo() -> None:
30
+ """Launch QuPath."""
31
+ launch_button.props(add="loading")
32
+ ui.notify("Launching Python Notebook...", color="blue")
33
+ ui.navigate.to(
34
+ f"/notebook/all?results_folder={quote(SystemService.get_user_data_directory('results').as_posix())}"
35
+ )
36
+
27
37
  ui.markdown(
28
38
  """
29
39
  ### Manage your Marimo Extension
@@ -36,7 +46,12 @@ class PageBuilder(BasePageBuilder):
36
46
  "Marimo is installed and ready to execute. "
37
47
  "Go to a completed application result and click the Notebook button."
38
48
  )
39
-
49
+ with ui.row().classes("w-full justify-between items-center"):
50
+ launch_button = ui.button(
51
+ "Open",
52
+ on_click=launch_marimo,
53
+ icon="visibility",
54
+ ).mark("BUTTON_NOTEBOOK_LAUNCH")
40
55
  ui.markdown(
41
56
  """
42
57
  ###### What is Marimo?
@@ -73,23 +88,25 @@ class PageBuilder(BasePageBuilder):
73
88
  ui.space()
74
89
 
75
90
  @ui.page("/notebook/{application_run_id}")
76
- def page_application_run_marimo(application_run_id: str) -> None:
91
+ def page_application_run_marimo(application_run_id: str, results_folder: str) -> None:
77
92
  """Inspect Application Run in Marimo."""
78
93
  theme()
79
94
 
80
95
  with ui.row().classes("w-full justify-end"):
81
96
  ui.button(
82
- "Back to Application Run",
97
+ "Back to Application Run" if application_run_id != "all" else "Back to Marimo Extension",
83
98
  icon="arrow_back",
84
99
  on_click=lambda _, application_run_id=application_run_id: ui.navigate.to(
85
- f"/application/run/{application_run_id}"
100
+ f"/application/run/{application_run_id}" if application_run_id != "all" else "/notebook"
86
101
  ),
87
- )
102
+ ).mark("BUTTON_NOTEBOOK_BACK")
88
103
 
89
104
  try:
90
105
  server_url = Service().start()
91
106
  ui.html(
92
- f'<iframe src="{server_url}?run_id={application_run_id}" width="100%" height="100%"></iframe>'
107
+ f'<iframe src="{server_url}?application_run_id={application_run_id}'
108
+ f'&results_folder={quote(results_folder)}" '
109
+ 'width="100%" height="100%"></iframe>'
93
110
  ).classes("w-full h-[calc(100vh-5rem)]")
94
111
  except Exception:
95
112
  message = "Failed to start Marimo server."
@@ -8,22 +8,13 @@
8
8
 
9
9
  import marimo
10
10
 
11
- __generated_with = "0.13.10"
11
+ __generated_with = "0.13.15"
12
12
  app = marimo.App(width="full")
13
13
 
14
14
 
15
- @app.cell
16
- def _(mo):
17
- query_params = mo.query_params()
18
- print(query_params)
19
-
20
- print("Hallo Test Omid")
21
- return
22
-
23
-
24
15
  @app.cell
25
16
  def _():
26
- """E2E Demo using DICOM data from National Cancer Institute
17
+ """Aignostics Notebook
27
18
 
28
19
  Notes:
29
20
  - See https://colab.research.google.com/github/ImagingDataCommons/IDC-Tutorials/blob/master/notebooks/pathomics/microscopy_dicom_ann_intro.ipynb#scrollTo=fMRsnFlzinO5
@@ -39,14 +30,6 @@ def _():
39
30
  from aignostics import dataset
40
31
  from wsidicom import WsiDicom
41
32
 
42
- load_dotenv()
43
-
44
- os.environ
45
-
46
- print(os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", "default"))
47
-
48
- print(Path.cwd())
49
-
50
33
  mo.sidebar([
51
34
  mo.md("# aignostics"),
52
35
  mo.nav_menu(
@@ -61,23 +44,17 @@ def _():
61
44
  orientation="vertical",
62
45
  ),
63
46
  ])
64
-
65
- mo.vstack([
66
- mo.ui.file_browser( # type: ignore
67
- GSPath("gs://aignx-storage-service-dev/sample_data_formatted"), # type: ignore
68
- filetypes=[".dcm", ".tif", ".tiff", ".svs", ".json"],
69
- multiple=True,
70
- restrict_navigation=True,
71
- )
72
- ]) # type: ignore
73
47
  return Path, WsiDicom, dataset, mo, plt
74
- ex
48
+
75
49
 
76
50
  @app.cell
77
51
  def _(Path, mo):
52
+ query_params = mo.query_params()
53
+ application_run_id = query_params["application_run_id"]
54
+ print(f"Application run id '{application_run_id}'")
78
55
  mo.vstack([
79
56
  mo.ui.file_browser(
80
- Path("tmp"),
57
+ Path(query_params["results_folder"]),
81
58
  filetypes=[".dcm", ".tif", ".tiff", ".svs"],
82
59
  multiple=True,
83
60
  restrict_navigation=True,
@@ -4,14 +4,13 @@ import json
4
4
  import sys
5
5
  from enum import StrEnum
6
6
  from importlib.util import find_spec
7
- from pathlib import Path
8
7
  from typing import Annotated
9
8
 
10
9
  import typer
11
10
  import yaml
12
11
 
13
12
  from ..constants import API_VERSIONS # noqa: TID252
14
- from ..utils import __project_name__, console, get_logger # noqa: TID252
13
+ from ..utils import console, get_logger # noqa: TID252
15
14
  from ._service import Service
16
15
 
17
16
  logger = get_logger(__name__)
@@ -160,7 +159,7 @@ cli.add_typer(config_app, name="config", help="Configure application settings.")
160
159
  @config_app.command()
161
160
  def get(key: Annotated[str, typer.Argument(help="Configuration key to get value for")]) -> None:
162
161
  """Set a configuration key to a value."""
163
- console.print(Service().dotenv_get(key.upper()))
162
+ console.print(Service.dotenv_get(key.upper()))
164
163
 
165
164
 
166
165
  @config_app.command()
@@ -170,7 +169,11 @@ def set( # noqa: A001
170
169
  ) -> None:
171
170
  """Set a configuration key to a value."""
172
171
  key = key.upper()
173
- Service().dotenv_set(key, value)
172
+ try:
173
+ Service.dotenv_set(key, value)
174
+ except ValueError as e:
175
+ console.print(f"Invalid configuration: {e!s}", style="error")
176
+ sys.exit(2)
174
177
  console.print(f"Configuration '{key}' set to '{value}'.", style="success")
175
178
 
176
179
 
@@ -180,24 +183,30 @@ def unset(
180
183
  ) -> None:
181
184
  """Set a configuration key to a value."""
182
185
  key = key.upper()
183
- Service().dotenv_unset(key)
186
+ Service.dotenv_unset(key)
184
187
  console.print(f"Configuration '{key}' unset.", style="success")
185
188
 
186
189
 
187
190
  @config_app.command()
188
191
  def remote_diagnostics_enable() -> None:
189
192
  """Enable remote diagnostics via Sentry and Logfire. Data stored in EU data centers."""
190
- Service().dotenv_set(f"{__project_name__.upper()}_SENTRY_ENABLED", "1")
191
- Service().dotenv_set(f"{__project_name__.upper()}_LOGFIRE_ENABLED", "1")
192
- console.print("Remote diagnostics enabled.", style="success")
193
+ try:
194
+ Service.remote_diagnostics_enable()
195
+ console.print("Remote diagnostics enabled.", style="success")
196
+ except ValueError as e:
197
+ console.print(f"Invalid diagnostics configuration: {e!s}", style="error")
198
+ sys.exit(2)
193
199
 
194
200
 
195
201
  @config_app.command()
196
202
  def remote_diagnostics_disable() -> None:
197
203
  """Disable remote diagnostics."""
198
- Service().dotenv_unset(f"{__project_name__.upper()}_SENTRY_ENABLED")
199
- Service().dotenv_unset(f"{__project_name__.upper()}_LOGFIRE_ENABLED")
200
- console.print("Remote diagnostics disabled.", style="success")
204
+ try:
205
+ Service.remote_diagnostics_disable()
206
+ console.print("Remote diagnostics disabled.", style="success")
207
+ except ValueError as e:
208
+ console.print(f"Invalid diagnostics configuration: {e!s}", style="error")
209
+ sys.exit(2)
201
210
 
202
211
 
203
212
  @config_app.command()
@@ -209,42 +218,22 @@ def http_proxy_enable(
209
218
  no_ssl_verify: Annotated[bool, typer.Option(help="Disable SSL verification")] = False,
210
219
  ) -> None:
211
220
  """Enable HTTP proxy."""
212
- url = f"{scheme}://{host}:{port}"
213
- Service().dotenv_set("HTTP_PROXY", url)
214
- Service().dotenv_set("HTTPS_PROXY", url)
215
- if ssl_cert_file is not None and no_ssl_verify:
216
- message = "Cannot set both 'ssl_cert_file' and 'ssl_disable_verify'. Please choose one."
217
- console.print(message, style="warning")
221
+ try:
222
+ Service().http_proxy_enable(
223
+ host=host, port=port, scheme=scheme, ssl_cert_file=ssl_cert_file, no_ssl_verify=no_ssl_verify
224
+ )
225
+ console.print("HTTP proxy enabled.", style="success")
226
+ except ValueError as e:
227
+ console.print(f"Invalid HTTP proxy configuration: {e!s}", style="error")
218
228
  sys.exit(2)
219
- if no_ssl_verify:
220
- Service().dotenv_set("SSL_NO_VERIFY", "1")
221
- Service().dotenv_set("SSL_CERT_FILE", "")
222
- Service().dotenv_set("REQUESTS_CA_BUNDLE", "")
223
- Service().dotenv_set("CURL_CA_BUNDLE", "")
224
- else:
225
- Service().dotenv_unset("SSL_NO_VERIFY")
226
- Service().dotenv_unset("SSL_CERT_FILE")
227
- Service().dotenv_unset("REQUESTS_CA_BUNDLE")
228
- Service().dotenv_unset("CURL_CA_BUNDLE")
229
- if ssl_cert_file:
230
- file = Path(ssl_cert_file).resolve()
231
- if not file.is_file():
232
- message = f"SSL certificate file '{ssl_cert_file}' does not exist."
233
- console.print(message, style="error")
234
- sys.exit(2)
235
- Service().dotenv_set("SSL_CERT_FILE", str(ssl_cert_file))
236
- Service().dotenv_set("REQUESTS_CA_BUNDLE", str(ssl_cert_file))
237
- Service().dotenv_set("CURL_CA_BUNDLE", str(ssl_cert_file))
238
- console.print("HTTP proxy enabled.", style="success")
239
229
 
240
230
 
241
231
  @config_app.command()
242
232
  def http_proxy_disable() -> None:
243
233
  """Disable HTTP proxy."""
244
- Service().dotenv_unset("HTTP_PROXY")
245
- Service().dotenv_unset("HTTPS_PROXY")
246
- Service().dotenv_unset("SSL_CERT_FILE")
247
- Service().dotenv_unset("SSL_NO_VERIFY")
248
- Service().dotenv_unset("REQUESTS_CA_BUNDLE")
249
- Service().dotenv_unset("CURL_CA_BUNDLE")
250
- console.print("HTTP proxy disabled.", style="success")
234
+ try:
235
+ Service.http_proxy_disable()
236
+ console.print("HTTP proxy disabled.", style="success")
237
+ except ValueError as e:
238
+ console.print(f"Invalid HTTP proxy configuration: {e!s}", style="error")
239
+ sys.exit(2)
@@ -0,0 +1,93 @@
1
+ """Homepage (index) of GUI."""
2
+
3
+ from pathlib import Path
4
+
5
+ from aignostics.gui import frame
6
+ from aignostics.utils import BaseService, locate_subclasses
7
+
8
+ from ..utils import BasePageBuilder # noqa: TID252
9
+ from ._service import Service
10
+
11
+
12
+ class PageBuilder(BasePageBuilder):
13
+ @staticmethod
14
+ def register_pages() -> None:
15
+ from nicegui import app, run, ui # noqa: PLC0415
16
+
17
+ locate_subclasses(BaseService) # Ensure settings are loaded
18
+ app.add_static_files("/system_assets", Path(__file__).parent / "assets")
19
+
20
+ ui.add_head_html("""
21
+ <style>
22
+ :global(.jse-modal-window.jse-modal-window-jsoneditor)
23
+ {
24
+ width: 100%;
25
+ height: 100%;
26
+ min-height: 900px;
27
+ }
28
+ </style>
29
+ """)
30
+
31
+ @ui.page("/system")
32
+ async def page_system() -> None:
33
+ """System info and settings page."""
34
+ with frame("Info and Settings", left_sidebar=False):
35
+ pass
36
+
37
+ with ui.row().classes("w-full gap-4 flex-nowrap"):
38
+ with ui.column().classes("w-3/5 flex-shrink-0"):
39
+ with ui.tabs().classes("w-full") as tabs:
40
+ tab_health = ui.tab("Health")
41
+ tab_info = ui.tab("Info")
42
+ tab_settings = ui.tab("Settings")
43
+ with ui.tab_panels(tabs, value=tab_health).classes("w-full"):
44
+ with ui.tab_panel(tab_health):
45
+ properties = {
46
+ "content": {"json": Service().health().model_dump()},
47
+ "mode": "tree",
48
+ "readOnly": True,
49
+ "mainMenuBar": False,
50
+ "navigationBar": False,
51
+ "statusBar": False,
52
+ }
53
+ ui.json_editor(properties).style("width: 100%").mark("JSON_EDITOR_INFO")
54
+ with ui.tab_panel(tab_info):
55
+ spinner = ui.spinner("dots", size="lg", color="red")
56
+ properties = {
57
+ "content": {"json": "Loading ..."},
58
+ "mode": "tree",
59
+ "readOnly": True,
60
+ "mainMenuBar": False,
61
+ "navigationBar": False,
62
+ "statusBar": False,
63
+ }
64
+ editor = ui.json_editor(properties).style("width: 100%").mark("JSON_EDITOR_INFO")
65
+ editor.set_visibility(False)
66
+ info = await run.cpu_bound(Service().info, True, True)
67
+ properties["content"] = {"json": info}
68
+ editor.update()
69
+ editor.run_editor_method(":expand", "path => true")
70
+ spinner.delete()
71
+ editor.set_visibility(True)
72
+ with (
73
+ ui.tab_panel(tab_settings),
74
+ ui.card().classes("w-full"),
75
+ ui.row().classes("items-center justify-between"),
76
+ ):
77
+ ui.switch(
78
+ value=Service.remote_diagnostics_enabled(),
79
+ on_change=lambda e: (
80
+ Service.remote_diagnostics_enable()
81
+ if e.value
82
+ else Service.remote_diagnostics_disable(),
83
+ ui.notify("Restart the app to apply changes.", color="warning"), # type: ignore[func-returns-value]
84
+ None,
85
+ )[0],
86
+ )
87
+ ui.label("Remote Diagnostics")
88
+ with ui.column().classes("w-2/5 flex-shrink-0 flex items-center justify-start mt-[200px]"):
89
+ ui.html(
90
+ '<dotlottie-player src="/system_assets/system.lottie" '
91
+ 'background="transparent" speed="1" style="width: 300px; height: 300px" '
92
+ 'direction="1" playMode="normal" loop autoplay></dotlottie-player>'
93
+ )