aignostics 0.2.13__tar.gz → 0.2.16__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.13 → aignostics-0.2.16}/.gitignore +1 -1
- {aignostics-0.2.13 → aignostics-0.2.16}/PKG-INFO +1 -1
- {aignostics-0.2.13 → aignostics-0.2.16}/examples/notebook.py +1 -1
- {aignostics-0.2.13 → aignostics-0.2.16}/pyproject.toml +3 -2
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/application/_cli.py +2 -3
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/application/_gui.py +78 -49
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/application/_service.py +44 -31
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/dataset/_gui.py +39 -14
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/_frame.py +54 -16
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/_theme.py +20 -12
- aignostics-0.2.16/src/aignostics/gui/assets/cabin-v27-latin-regular.woff2 +0 -0
- aignostics-0.2.16/src/aignostics/gui/assets/home-card-1.png +0 -0
- aignostics-0.2.16/src/aignostics/gui/assets/home-card-2.png +0 -0
- aignostics-0.2.16/src/aignostics/gui/assets/home-card-3.png +0 -0
- aignostics-0.2.16/src/aignostics/gui/assets/home-card-4.png +0 -0
- aignostics-0.2.16/src/aignostics/gui/assets/home-card-5.png +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/notebook/_service.py +4 -0
- aignostics-0.2.16/src/aignostics/qupath/__init__.py +17 -0
- aignostics-0.2.16/src/aignostics/qupath/_cli.py +106 -0
- aignostics-0.2.16/src/aignostics/qupath/_gui.py +80 -0
- aignostics-0.2.16/src/aignostics/qupath/_service.py +552 -0
- aignostics-0.2.16/src/aignostics/qupath/_settings.py +16 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/system/_gui.py +1 -1
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_health.py +8 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/wsi/_service.py +1 -1
- aignostics-0.2.13/src/aignostics/gui/assets/home-card-1.png +0 -0
- aignostics-0.2.13/src/aignostics/gui/assets/home-card-2.png +0 -0
- aignostics-0.2.13/src/aignostics/gui/assets/home-card-3.png +0 -0
- aignostics-0.2.13/src/aignostics/gui/assets/home-card-4.png +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/LICENSE +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/README.md +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/.openapi-generator/FILES +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/.openapi-generator/VERSION +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/api/public_api.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/api_client.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/api_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/configuration.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/exceptions.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/application_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/application_run_status.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/application_version_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/http_validation_error.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/input_artifact_creation_request.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/input_artifact_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/item_creation_request.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/item_result_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/item_status.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/output_artifact_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/output_artifact_result_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/output_artifact_scope.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/payload_input_artifact.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/payload_item.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/payload_output_artifact.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/run_creation_request.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/run_creation_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/run_read_response.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/transfer_urls.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/user_payload.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/validation_error.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/models/validation_error_loc_inner.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/aignx/codegen/rest.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/codegen/out/docs/PublicApi.md +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/examples/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/examples/e2e.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/examples/notebook.ipynb +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/examples/script.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/application/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/application/_settings.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/application/_utils.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/bucket/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/bucket/_cli.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/bucket/_gui.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/bucket/_service.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/bucket/_settings.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/cli.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/constants.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/dataset/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/dataset/_cli.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/dataset/_idc_index.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/dataset/_service.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/assets/Google-Cloud-logo.svg +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/assets/NIH-IDC-logo.svg +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/assets/image-not-found.png +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/assets/logo.png +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/assets/ruo.png +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/gui/assets/ruo.svg +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/notebook/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/notebook/_gui.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/notebook/_notebook.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_authentication.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_client.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_constants.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_messages.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_service.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_settings.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/_utils.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/resources/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/resources/applications.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/resources/runs.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/platform/resources/utils.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/system/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/system/_cli.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/system/_exceptions.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/system/_service.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/system/_settings.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/.vendored/bottle.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_cli.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_console.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_constants.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_di.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_gui.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_log.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_logfire.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_notebook.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_process.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_sentry.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_service.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/_settings.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/utils/boot.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/wsi/__init__.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/wsi/_cli.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/wsi/_gui.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/wsi/_openslide_handler.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/src/aignostics/wsi/_pydicom_handler.py +0 -0
- {aignostics-0.2.13 → aignostics-0.2.16}/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.16
|
|
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/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aignostics"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.16"
|
|
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 = [
|
|
@@ -344,6 +344,7 @@ omit = [
|
|
|
344
344
|
"*/_notebook.py",
|
|
345
345
|
"*/_pydicom_handler.py",
|
|
346
346
|
"*/_openslide_handler.py",
|
|
347
|
+
"*/qupath/*",
|
|
347
348
|
]
|
|
348
349
|
branch = true
|
|
349
350
|
parallel = true
|
|
@@ -354,7 +355,7 @@ source = ["src/"]
|
|
|
354
355
|
|
|
355
356
|
|
|
356
357
|
[tool.bumpversion]
|
|
357
|
-
current_version = "0.2.
|
|
358
|
+
current_version = "0.2.16"
|
|
358
359
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
359
360
|
serialize = ["{major}.{minor}.{patch}"]
|
|
360
361
|
search = "{current_version}"
|
|
@@ -376,7 +376,7 @@ def run_upload(
|
|
|
376
376
|
|
|
377
377
|
total_bytes = 0
|
|
378
378
|
for i, entry in enumerate(metadata_dict):
|
|
379
|
-
source = entry["
|
|
379
|
+
source = entry["reference"]
|
|
380
380
|
source_file_path = Path(source)
|
|
381
381
|
if not source_file_path.is_file():
|
|
382
382
|
logger.warning("Source file '%s' (row %d) does not exist", source_file_path, i)
|
|
@@ -403,8 +403,7 @@ def run_upload(
|
|
|
403
403
|
def update_progress(bytes_uploaded: int, source: Path, platform_bucket_url: str) -> None:
|
|
404
404
|
progress.update(task, advance=bytes_uploaded, description=f"{source.name}")
|
|
405
405
|
for entry in metadata_dict:
|
|
406
|
-
if entry["
|
|
407
|
-
entry["file_upload_progress"] = float(entry["file_upload_progress"]) + bytes_uploaded
|
|
406
|
+
if entry["reference"] == str(source):
|
|
408
407
|
entry["platform_bucket_url"] = platform_bucket_url
|
|
409
408
|
break
|
|
410
409
|
write_metadata_dict_to_csv(
|
|
@@ -32,6 +32,9 @@ class PageBuilder(BasePageBuilder):
|
|
|
32
32
|
from nicegui import app, background_tasks, binding, context, ui # noq # noqa: PLC0415
|
|
33
33
|
from nicegui import run as nicegui_run # noqa: PLC0415
|
|
34
34
|
|
|
35
|
+
if find_spec("paquo"):
|
|
36
|
+
from aignostics.qupath import Service as QuPathService # noqa: PLC0415
|
|
37
|
+
|
|
35
38
|
@binding.bindable_dataclass
|
|
36
39
|
class SubmitForm:
|
|
37
40
|
"""Submit form."""
|
|
@@ -392,7 +395,10 @@ class PageBuilder(BasePageBuilder):
|
|
|
392
395
|
f"Selected folder {submit_form.source} to analyze."
|
|
393
396
|
) if submit_form.wsi_step_label else None
|
|
394
397
|
submit_form.wsi_next_button.enable() if submit_form.wsi_next_button else None
|
|
395
|
-
ui.notify(
|
|
398
|
+
ui.notify(
|
|
399
|
+
f"You chose directory {submit_form.source}. Feel free to continue to the next step.",
|
|
400
|
+
type="positive",
|
|
401
|
+
)
|
|
396
402
|
else:
|
|
397
403
|
submit_form.source = None
|
|
398
404
|
submit_form.wsi_step_label.set_text(
|
|
@@ -413,7 +419,10 @@ class PageBuilder(BasePageBuilder):
|
|
|
413
419
|
f"Selected folder {submit_form.source} to analyze."
|
|
414
420
|
) if submit_form.wsi_step_label else None
|
|
415
421
|
submit_form.wsi_next_button.enable() if submit_form.wsi_next_button else None
|
|
416
|
-
ui.notify(
|
|
422
|
+
ui.notify(
|
|
423
|
+
f"You chose directory {submit_form.source}. Feel free to continue to the next step.",
|
|
424
|
+
type="positive",
|
|
425
|
+
)
|
|
417
426
|
|
|
418
427
|
async def _on_wsi_next_click() -> None:
|
|
419
428
|
"""Handle the 'Next' button click in WSI step.
|
|
@@ -443,6 +452,7 @@ class PageBuilder(BasePageBuilder):
|
|
|
443
452
|
Service.generate_metadata_from_source_directory,
|
|
444
453
|
str(submit_form.application_version_id),
|
|
445
454
|
submit_form.source,
|
|
455
|
+
True,
|
|
446
456
|
[".*:staining_method=H&E"],
|
|
447
457
|
)
|
|
448
458
|
if submit_form.metadata_grid.options["rowData"] is None:
|
|
@@ -453,7 +463,8 @@ class PageBuilder(BasePageBuilder):
|
|
|
453
463
|
submit_form.wsi_spinner.set_visibility(False)
|
|
454
464
|
submit_form.metadata_grid.update()
|
|
455
465
|
ui.notify(
|
|
456
|
-
f"Found {len(submit_form.metadata_grid.options['rowData'])} slides for analysis."
|
|
466
|
+
f"Found {len(submit_form.metadata_grid.options['rowData'])} slides for analysis."
|
|
467
|
+
"Please provide missing metadata.",
|
|
457
468
|
type="positive",
|
|
458
469
|
)
|
|
459
470
|
stepper.next()
|
|
@@ -506,10 +517,13 @@ class PageBuilder(BasePageBuilder):
|
|
|
506
517
|
ui.button("Close", on_click=info_dialog.close)
|
|
507
518
|
|
|
508
519
|
with ui.stepper().props("vertical").classes("w-full") as stepper: # noqa: PLR1702
|
|
509
|
-
with ui.step("Application Version"):
|
|
520
|
+
with ui.step("Select Application Version"):
|
|
510
521
|
with ui.row().classes("w-full justify-center"):
|
|
511
522
|
with ui.column():
|
|
512
|
-
ui.label(
|
|
523
|
+
ui.label(
|
|
524
|
+
f"Select the version of {application.name} you want to run. Not sure? "
|
|
525
|
+
"Click “Next” to auto-select the latest version"
|
|
526
|
+
)
|
|
513
527
|
ui.select(
|
|
514
528
|
{version.application_version_id: version.version for version in application_versions},
|
|
515
529
|
value=latest_application_version_id,
|
|
@@ -522,9 +536,9 @@ class PageBuilder(BasePageBuilder):
|
|
|
522
536
|
"BUTTON_APPLICATION_VERSION_NEXT"
|
|
523
537
|
)
|
|
524
538
|
|
|
525
|
-
with ui.step("Whole Slide Images"):
|
|
539
|
+
with ui.step("Select Whole Slide Images"):
|
|
526
540
|
submit_form.wsi_step_label = ui.label(
|
|
527
|
-
"Select
|
|
541
|
+
"Select the folder with the whole slide images you want to analyze then click Next."
|
|
528
542
|
)
|
|
529
543
|
with ui.stepper_navigation():
|
|
530
544
|
if "pytest" in sys.modules:
|
|
@@ -536,13 +550,21 @@ class PageBuilder(BasePageBuilder):
|
|
|
536
550
|
submit_form.wsi_spinner.set_visibility(False)
|
|
537
551
|
ui.button("Back", on_click=stepper.previous).props("flat")
|
|
538
552
|
|
|
539
|
-
with ui.step("Metadata"):
|
|
553
|
+
with ui.step("Choose Images and Edit Metadata"):
|
|
540
554
|
ui.markdown(
|
|
541
555
|
"""
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
556
|
+
The Launchpad has found all compatible slide files in your selected folder.
|
|
557
|
+
|
|
558
|
+
1. Check the slides that have been found.
|
|
559
|
+
If you wish to exclude any from analysis at this point, check the boxes next to those slides
|
|
560
|
+
and click “Exclude” to remove them.
|
|
561
|
+
2. For the slides you wish to analyze,
|
|
562
|
+
provide the missing metadata to finalize the upload.
|
|
563
|
+
Double click red cells to edit the missing data with the available options.
|
|
564
|
+
3. Once all the metadata has been added and your slide selection has been finalized,
|
|
565
|
+
click “Next”.
|
|
566
|
+
|
|
567
|
+
You can revert to the previous step and reupload at any point by clicking “Back”.
|
|
546
568
|
"""
|
|
547
569
|
)
|
|
548
570
|
|
|
@@ -616,7 +638,8 @@ class PageBuilder(BasePageBuilder):
|
|
|
616
638
|
submit_form.metadata = await submit_form.metadata_grid.get_client_data()
|
|
617
639
|
_upload_ui.refresh(submit_form.metadata)
|
|
618
640
|
submit_form.submission_upload_button.enable()
|
|
619
|
-
|
|
641
|
+
if "pytest" in sys.modules:
|
|
642
|
+
ui.notify("Prepared upload UI.", type="info")
|
|
620
643
|
stepper.next()
|
|
621
644
|
|
|
622
645
|
async def _delete_selected() -> None:
|
|
@@ -662,7 +685,7 @@ class PageBuilder(BasePageBuilder):
|
|
|
662
685
|
submit_form.metadata_grid = (
|
|
663
686
|
ui.aggrid({
|
|
664
687
|
"columnDefs": [
|
|
665
|
-
{"headerName": "Reference", "field": "
|
|
688
|
+
{"headerName": "Reference", "field": "reference_short", "checkboxSelection": True},
|
|
666
689
|
{
|
|
667
690
|
"headerName": "Thumbnail",
|
|
668
691
|
"field": "thumbnail",
|
|
@@ -750,7 +773,6 @@ class PageBuilder(BasePageBuilder):
|
|
|
750
773
|
.on("selectionChanged", _handle_grid_selection_changed)
|
|
751
774
|
.mark("GRID_METADATA")
|
|
752
775
|
)
|
|
753
|
-
# .style("height: auto; width: 1000px")
|
|
754
776
|
# use ui timer to update the grid class depending on dark mode, with a frequency of once per second
|
|
755
777
|
ui.timer(
|
|
756
778
|
interval=1,
|
|
@@ -768,15 +790,33 @@ class PageBuilder(BasePageBuilder):
|
|
|
768
790
|
with ui.stepper_navigation():
|
|
769
791
|
if "pytest" in sys.modules:
|
|
770
792
|
ui.button("Select", on_click=_pytest_meta, icon="folder").mark("BUTTON_PYTEST_META")
|
|
793
|
+
submit_form.metadata_next_button = ui.button("Next", on_click=_metadata_next)
|
|
794
|
+
submit_form.metadata_next_button.mark("BUTTON_METADATA_NEXT").disable()
|
|
771
795
|
submit_form.metadata_exclude_button = ui.button(
|
|
772
796
|
"Exclude selected", on_click=_delete_selected
|
|
773
797
|
).mark("BUTTON_DELETE_SELECTED")
|
|
774
798
|
submit_form.metadata_exclude_button.set_text("Exclude")
|
|
775
799
|
submit_form.metadata_exclude_button.disable()
|
|
776
|
-
submit_form.metadata_next_button = ui.button("Next", on_click=_metadata_next)
|
|
777
|
-
submit_form.metadata_next_button.mark("BUTTON_METADATA_NEXT").disable()
|
|
778
800
|
ui.button("Back", on_click=stepper.previous).props("flat")
|
|
779
801
|
|
|
802
|
+
def _submit() -> None:
|
|
803
|
+
"""Submit the application run."""
|
|
804
|
+
ui.notify("Submitting application run ...", type="info")
|
|
805
|
+
try:
|
|
806
|
+
run = service.application_run_submit_from_metadata(
|
|
807
|
+
str(submit_form.application_version_id),
|
|
808
|
+
submit_form.metadata or [],
|
|
809
|
+
)
|
|
810
|
+
except Exception as e: # noqa: BLE001
|
|
811
|
+
ui.notify(f"Failed to submit application run: {e}.", type="warning")
|
|
812
|
+
return
|
|
813
|
+
ui.notify(
|
|
814
|
+
f"Application run submitted with id '{run.application_run_id}'. "
|
|
815
|
+
"Navigating to application run ...",
|
|
816
|
+
type="positive",
|
|
817
|
+
)
|
|
818
|
+
ui.navigate.to(f"/application/run/{run.application_run_id}")
|
|
819
|
+
|
|
780
820
|
async def _upload() -> None:
|
|
781
821
|
"""Upload prepared slides."""
|
|
782
822
|
if submit_form.submission_submit_button is None or submit_form.submission_upload_button is None:
|
|
@@ -796,12 +836,13 @@ class PageBuilder(BasePageBuilder):
|
|
|
796
836
|
ui.notify("Upload to Aignostics Platform completed.", type="positive")
|
|
797
837
|
submit_form.submission_submit_button.enable()
|
|
798
838
|
submit_form.submission_upload_button.disable()
|
|
839
|
+
_submit()
|
|
799
840
|
|
|
800
841
|
@ui.refreshable
|
|
801
842
|
def _upload_ui(metadata: list[dict[str, Any]]) -> None:
|
|
802
843
|
"""Upload UI."""
|
|
803
844
|
with ui.column(align_items="start"):
|
|
804
|
-
ui.label(f"
|
|
845
|
+
ui.label(f"Upload and submit your {len(metadata)} slide(s) for analysis.")
|
|
805
846
|
upload_complete = True
|
|
806
847
|
for row in metadata or []:
|
|
807
848
|
upload_complete = upload_complete and row["file_upload_progress"] == 1
|
|
@@ -809,11 +850,6 @@ class PageBuilder(BasePageBuilder):
|
|
|
809
850
|
with ui.circular_progress(value=row["file_upload_progress"], show_value=False):
|
|
810
851
|
ui.button(icon="cloud_upload").props("flat round").disable()
|
|
811
852
|
ui.label(f"{row['source']} ({row['file_size_human']})").classes("w-4/5")
|
|
812
|
-
if upload_complete:
|
|
813
|
-
ui.label(
|
|
814
|
-
f"2. All uploads completed successfully. Click submit to run "
|
|
815
|
-
f"{submit_form.application_version_id} on {len(metadata)} slides."
|
|
816
|
-
)
|
|
817
853
|
|
|
818
854
|
def _update_upload_progress() -> None:
|
|
819
855
|
"""Update the upload progress for each file."""
|
|
@@ -832,28 +868,14 @@ class PageBuilder(BasePageBuilder):
|
|
|
832
868
|
break
|
|
833
869
|
_upload_ui.refresh(submit_form.metadata)
|
|
834
870
|
|
|
835
|
-
|
|
836
|
-
"""Submit the application run."""
|
|
837
|
-
ui.notify("Submitting application run ...", type="info")
|
|
838
|
-
try:
|
|
839
|
-
run = service.application_run_submit_from_metadata(
|
|
840
|
-
str(submit_form.application_version_id),
|
|
841
|
-
submit_form.metadata or [],
|
|
842
|
-
)
|
|
843
|
-
except Exception as e: # noqa: BLE001
|
|
844
|
-
ui.notify(f"Failed to submit application run: {e}.", type="warning")
|
|
845
|
-
return
|
|
846
|
-
ui.notify(f"Application run submitted with id '{run.application_run_id}'.", type="positive")
|
|
847
|
-
ui.navigate.to(f"/application/run/{run.application_run_id}")
|
|
848
|
-
|
|
849
|
-
with ui.step("Submission"):
|
|
871
|
+
with ui.step("Slide Submission"):
|
|
850
872
|
_upload_ui([])
|
|
851
873
|
upload_message_queue = Manager().Queue()
|
|
852
874
|
ui.timer(0.1, callback=_update_upload_progress)
|
|
853
875
|
|
|
854
876
|
with ui.stepper_navigation():
|
|
855
877
|
submit_form.submission_upload_button = ui.button(
|
|
856
|
-
"Upload",
|
|
878
|
+
"Upload and Submit",
|
|
857
879
|
on_click=_upload,
|
|
858
880
|
icon="check",
|
|
859
881
|
).mark("BUTTON_SUBMISSION_UPLOAD")
|
|
@@ -862,6 +884,7 @@ class PageBuilder(BasePageBuilder):
|
|
|
862
884
|
on_click=_submit,
|
|
863
885
|
icon="check",
|
|
864
886
|
)
|
|
887
|
+
submit_form.submission_submit_button.set_visibility(False)
|
|
865
888
|
submit_form.submission_submit_button.mark("BUTTON_SUBMISSION_SUBMIT").disable()
|
|
866
889
|
ui.button("Back", on_click=stepper.previous).props("flat")
|
|
867
890
|
|
|
@@ -906,8 +929,8 @@ class PageBuilder(BasePageBuilder):
|
|
|
906
929
|
ui.notify(f"Canceling application run with id '{run_id}' ...", type="info")
|
|
907
930
|
try:
|
|
908
931
|
service.application_run_cancel(run_id)
|
|
909
|
-
ui.notify("Application run cancelled!", type="positive")
|
|
910
932
|
ui.navigate.reload()
|
|
933
|
+
ui.notify("Application run cancelled!", type="positive")
|
|
911
934
|
return True
|
|
912
935
|
except Exception as e: # noqa: BLE001
|
|
913
936
|
ui.notify(f"Failed to cancel application run: {e}.", type="warning")
|
|
@@ -934,11 +957,14 @@ class PageBuilder(BasePageBuilder):
|
|
|
934
957
|
selected_folder.value = str(Path.home())
|
|
935
958
|
|
|
936
959
|
ui.label("Select a folder to download all results for this application run.")
|
|
937
|
-
ui.
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
960
|
+
with ui.row().classes("w-full"):
|
|
961
|
+
ui.button("Use Home", on_click=_select_home, icon="home").mark(
|
|
962
|
+
"BUTTON_DOWNLOAD_DESTINATION_HOME"
|
|
963
|
+
)
|
|
964
|
+
ui.space()
|
|
965
|
+
ui.button(
|
|
966
|
+
"Select Download Folder", on_click=_select_download_destination, icon="folder_open"
|
|
967
|
+
).mark("BUTTON_DOWNLOAD_DESTINATION_SELECT")
|
|
942
968
|
|
|
943
969
|
download_item_status = ui.label("")
|
|
944
970
|
download_item_status.set_visibility(False)
|
|
@@ -1004,7 +1030,7 @@ class PageBuilder(BasePageBuilder):
|
|
|
1004
1030
|
ui.notify("Download completed.", type="positive")
|
|
1005
1031
|
|
|
1006
1032
|
ui.separator()
|
|
1007
|
-
with ui.row(align_items="end").classes("w-full"):
|
|
1033
|
+
with ui.row(align_items="end").classes("w-full justify-end"):
|
|
1008
1034
|
ui.button("Close", on_click=download_run_dialog.close, color="secondary").props("outline").mark(
|
|
1009
1035
|
"DIALOG_BUTTON_DOWNLOAD_CLOSE"
|
|
1010
1036
|
)
|
|
@@ -1013,7 +1039,7 @@ class PageBuilder(BasePageBuilder):
|
|
|
1013
1039
|
"DIALOG_BUTTON_DOWNLOAD_RUN"
|
|
1014
1040
|
)
|
|
1015
1041
|
|
|
1016
|
-
with ui.dialog() as qupath_project_create_dialog, ui.card().style(WIDTH_1200px):
|
|
1042
|
+
with ui.dialog() as qupath_project_create_dialog, ui.card().style(WIDTH_1200px): # noqa: F841
|
|
1017
1043
|
ui.button("Select QuPath folder")
|
|
1018
1044
|
with ui.row(align_items="end").classes("w-full"), ui.column(align_items="end").classes("w-full"):
|
|
1019
1045
|
ui.button("Close", on_click=download_run_dialog.close)
|
|
@@ -1078,11 +1104,14 @@ class PageBuilder(BasePageBuilder):
|
|
|
1078
1104
|
|
|
1079
1105
|
if run_data and run_data.status.value == ApplicationRunStatus.COMPLETED:
|
|
1080
1106
|
with ui.row().classes("w-full justify-end"):
|
|
1081
|
-
if find_spec("paquo"):
|
|
1107
|
+
if find_spec("paquo") and QuPathService.find_qupath():
|
|
1082
1108
|
ui.button(
|
|
1083
1109
|
"Open in QuPath Microscopy Viewer",
|
|
1084
1110
|
icon="zoom_in",
|
|
1085
|
-
on_click=
|
|
1111
|
+
on_click=lambda: (
|
|
1112
|
+
ui.notify("Opening QuPath ...", type="info"), # type: ignore[func-returns-value]
|
|
1113
|
+
QuPathService.launch_qupath(),
|
|
1114
|
+
),
|
|
1086
1115
|
)
|
|
1087
1116
|
if find_spec("marimo"):
|
|
1088
1117
|
ui.button(
|
|
@@ -311,12 +311,13 @@ class Service(BaseService):
|
|
|
311
311
|
def generate_metadata_from_source_directory(
|
|
312
312
|
application_version_id: str,
|
|
313
313
|
source_directory: Path,
|
|
314
|
+
with_gui_metadata: bool = False,
|
|
314
315
|
mappings: list[str] | None = None,
|
|
315
316
|
) -> list[dict[str, Any]]:
|
|
316
317
|
"""Generate metadata from the source directory.
|
|
317
318
|
|
|
318
319
|
Steps:
|
|
319
|
-
1. Recursively files ending with .tiff, .tif and .dcm in the source directory
|
|
320
|
+
1. Recursively files ending with .tiff, .tif, .svs and .dcm in the source directory
|
|
320
321
|
2. Creates a dict with the following columns
|
|
321
322
|
- reference (str): The reference of the file, by default equivalent to the absolute file name
|
|
322
323
|
- source (str): The absolute filename
|
|
@@ -331,6 +332,7 @@ class Service(BaseService):
|
|
|
331
332
|
application_version_id (str): The ID of the application version.
|
|
332
333
|
If application id is given, the latest version of that application is used.
|
|
333
334
|
source_directory (Path): The source directory to generate metadata from.
|
|
335
|
+
with_gui_metadata (bool): If True, include additional metadata for GUI.
|
|
334
336
|
mappings (list[str]): Mappings of the form '<regexp>:<key>:<value>,<key>:<value>,...'.
|
|
335
337
|
The regular expression is matched against the reference attribute of the entry.
|
|
336
338
|
The key/value pairs are applied to the entry if the pattern matches.
|
|
@@ -350,7 +352,7 @@ class Service(BaseService):
|
|
|
350
352
|
application_version = Service().application_version(application_version_id, use_latest_if_no_version_given=True) # noqa: F841
|
|
351
353
|
|
|
352
354
|
metadata = []
|
|
353
|
-
file_extensions = [".tiff", ".tif", ".dcm"]
|
|
355
|
+
file_extensions = [".tiff", ".tif", ".svs", ".dcm"]
|
|
354
356
|
|
|
355
357
|
try:
|
|
356
358
|
for extension in file_extensions:
|
|
@@ -361,31 +363,42 @@ class Service(BaseService):
|
|
|
361
363
|
while chunk := f.read(1024):
|
|
362
364
|
hash_sum.update(chunk) # type: ignore[no-untyped-call]
|
|
363
365
|
checksum = str(base64.b64encode(hash_sum.digest()), "UTF-8") # type: ignore[no-untyped-call]
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
366
|
+
try:
|
|
367
|
+
image_metadata = WSIService().get_metadata(file_path)
|
|
368
|
+
width = image_metadata["dimensions"]["width"]
|
|
369
|
+
height = image_metadata["dimensions"]["height"]
|
|
370
|
+
mpp = image_metadata["resolution"]["mpp_x"]
|
|
371
|
+
file_size_human = image_metadata["file"]["size_human"]
|
|
372
|
+
reference = file_path.absolute()
|
|
373
|
+
entry = {
|
|
374
|
+
"reference": str(reference),
|
|
375
|
+
"reference_short": str(reference.name),
|
|
376
|
+
"source": str(file_path),
|
|
377
|
+
"checksum_base64_crc32c": checksum,
|
|
378
|
+
"resolution_mpp": mpp,
|
|
379
|
+
"width_px": width,
|
|
380
|
+
"height_px": height,
|
|
381
|
+
"staining_method": None,
|
|
382
|
+
"tissue": None,
|
|
383
|
+
"disease": None,
|
|
384
|
+
"file_size_human": file_size_human,
|
|
385
|
+
"file_upload_progress": 0.0,
|
|
386
|
+
"platform_bucket_url": None,
|
|
387
|
+
}
|
|
388
|
+
if not with_gui_metadata:
|
|
389
|
+
entry.pop("reference_short", None)
|
|
390
|
+
entry.pop("source", None)
|
|
391
|
+
entry.pop("file_size_human", None)
|
|
392
|
+
entry.pop("file_upload_progress", None)
|
|
393
|
+
|
|
394
|
+
if mappings:
|
|
395
|
+
Service._apply_mappings_to_entry(entry, mappings)
|
|
396
|
+
|
|
397
|
+
metadata.append(entry)
|
|
398
|
+
except Exception:
|
|
399
|
+
message = f"Failed to process file '{file_path}'"
|
|
400
|
+
logger.exception(message)
|
|
401
|
+
continue
|
|
389
402
|
|
|
390
403
|
logger.debug("Generated metadata for %d files", len(metadata))
|
|
391
404
|
return metadata
|
|
@@ -421,9 +434,9 @@ class Service(BaseService):
|
|
|
421
434
|
application_version = Service().application_version(application_version_id, use_latest_if_no_version_given=True)
|
|
422
435
|
for row in metadata:
|
|
423
436
|
reference = row["reference"]
|
|
424
|
-
source_file_path = Path(row["
|
|
437
|
+
source_file_path = Path(row["reference"])
|
|
425
438
|
if not source_file_path.is_file():
|
|
426
|
-
logger.warning("Source file '%s' does not exist.", row["
|
|
439
|
+
logger.warning("Source file '%s' does not exist.", row["referebce"])
|
|
427
440
|
return False
|
|
428
441
|
object_key = (
|
|
429
442
|
f"{psutil.Process().username()}/{upload_prefix}/"
|
|
@@ -583,9 +596,9 @@ class Service(BaseService):
|
|
|
583
596
|
"width_px": int(row["width_px"]),
|
|
584
597
|
"media_type": (
|
|
585
598
|
"image/tiff"
|
|
586
|
-
if row["
|
|
599
|
+
if row["reference"].lower().endswith((".tif", ".tiff"))
|
|
587
600
|
else "application/dicom"
|
|
588
|
-
if row["
|
|
601
|
+
if row["reference"].lower().endswith(".dcm")
|
|
589
602
|
else "application/octet-stream"
|
|
590
603
|
),
|
|
591
604
|
"resolution_mpp": float(row["resolution_mpp"]),
|
|
@@ -42,21 +42,46 @@ class PageBuilder(BasePageBuilder):
|
|
|
42
42
|
# No need to do anything here
|
|
43
43
|
pass
|
|
44
44
|
with ui.row(align_items="start").classes("full-width"):
|
|
45
|
-
ui.markdown(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
5. Run Applications via the ☰ menu, select an application, and the folder
|
|
53
|
-
""").classes("w-3/5")
|
|
45
|
+
ui.markdown(
|
|
46
|
+
"""
|
|
47
|
+
### Download public DICOM datasets
|
|
48
|
+
Explore the IDC Portal from NCI to find datasets of interest and upload them to the Launchpad
|
|
49
|
+
to test Aignostics applications.
|
|
50
|
+
"""
|
|
51
|
+
).classes("w-3/5")
|
|
54
52
|
ui.space()
|
|
55
53
|
with ui.column().classes("w-1/5"):
|
|
56
54
|
ui.image("/assets/NIH-IDC-logo.svg").classes("w-25").style("margin-top:1.25rem")
|
|
57
55
|
with ui.link(target=PORTAL_URL, new_tab=True):
|
|
58
56
|
ui.button("Explore Portal", icon="search")
|
|
59
57
|
|
|
58
|
+
with ui.row(align_items="start").classes("w-full"):
|
|
59
|
+
ui.markdown("""
|
|
60
|
+
##### Pick a Dataset
|
|
61
|
+
1. Click “Explore Portal” on the right and find a DICOM dataset of interest
|
|
62
|
+
2. Copy the UID into the field below
|
|
63
|
+
""").classes("w-2/5")
|
|
64
|
+
ui.space()
|
|
65
|
+
ui.label("OR").style("margin-top: 2rem; font-size: 2rem; color: grey")
|
|
66
|
+
ui.space()
|
|
67
|
+
ui.markdown("""
|
|
68
|
+
##### Quick Start
|
|
69
|
+
1. Click “Use Example Dataset” below to automatically populate a pre-selected DICOM dataset
|
|
70
|
+
""").classes("w-2/5")
|
|
71
|
+
|
|
72
|
+
with ui.row(align_items="start").classes("w-full"):
|
|
73
|
+
ui.markdown(
|
|
74
|
+
"""
|
|
75
|
+
Once your dataset UID is added, click "Use Home" or “Select Download Folder” and select a
|
|
76
|
+
folder on your computer for the dataset to be stored.
|
|
77
|
+
Once you've chosen, the “Download” button should light up and you can download your
|
|
78
|
+
selected dataset.
|
|
79
|
+
|
|
80
|
+
When you're ready to begin image analysis, open ☰ menu and click “Run Applications” to return to
|
|
81
|
+
the main menu.
|
|
82
|
+
"""
|
|
83
|
+
)
|
|
84
|
+
|
|
60
85
|
def _on_source_input_change(e: ValueChangeEventArguments) -> None:
|
|
61
86
|
"""On change event."""
|
|
62
87
|
if download_form.download_button is None:
|
|
@@ -192,6 +217,11 @@ class PageBuilder(BasePageBuilder):
|
|
|
192
217
|
color="secondary",
|
|
193
218
|
).mark("BUTTON_EXAMPLE_DATASET")
|
|
194
219
|
ui.space()
|
|
220
|
+
ui.button("Use Home", on_click=_select_home, icon="home").mark("BUTTON_DOWNLOAD_DESTINATION_HOME")
|
|
221
|
+
ui.button("Select Download Folder", on_click=_select_destination, icon="folder").mark(
|
|
222
|
+
"BUTTON_DOWNLOAD_DESTINATION"
|
|
223
|
+
)
|
|
224
|
+
ui.space()
|
|
195
225
|
with ui.row(align_items="center"):
|
|
196
226
|
download_form.download_button = ui.button("Download", icon="cloud_download").mark(
|
|
197
227
|
"BUTTON_DOWNLOAD"
|
|
@@ -204,11 +234,6 @@ class PageBuilder(BasePageBuilder):
|
|
|
204
234
|
with download_form.download_progress:
|
|
205
235
|
ui.button(icon="cloud_download").props("flat round").disable()
|
|
206
236
|
download_form.download_progress.visible = False
|
|
207
|
-
ui.space()
|
|
208
|
-
ui.button("Use Home", on_click=_select_home, icon="home").mark("BUTTON_DOWNLOAD_DESTINATION_HOME")
|
|
209
|
-
ui.button("Select Download Folder", on_click=_select_destination, icon="folder").mark(
|
|
210
|
-
"BUTTON_DOWNLOAD_DESTINATION"
|
|
211
|
-
)
|
|
212
237
|
|
|
213
238
|
download_message_queue = Manager().Queue()
|
|
214
239
|
|