dsa_helpers 3.2.1__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 (57) hide show
  1. dsa_helpers-3.2.1/.gitignore +18 -0
  2. dsa_helpers-3.2.1/.readthedocs.yaml +16 -0
  3. dsa_helpers-3.2.1/LICENSE +19 -0
  4. dsa_helpers-3.2.1/PKG-INFO +69 -0
  5. dsa_helpers-3.2.1/README.md +21 -0
  6. dsa_helpers-3.2.1/docker/2.0.6-pytorch2.9.0-cuda13.0-cudnn9/Dockerfile +14 -0
  7. dsa_helpers-3.2.1/docker/README.md +2 -0
  8. dsa_helpers-3.2.1/docs/requirements.txt +16 -0
  9. dsa_helpers-3.2.1/docs/source/conf.py +42 -0
  10. dsa_helpers-3.2.1/docs/source/index.rst +18 -0
  11. dsa_helpers-3.2.1/dsa_helpers/__init__.py +9 -0
  12. dsa_helpers-3.2.1/dsa_helpers/cli_utils.py +183 -0
  13. dsa_helpers-3.2.1/dsa_helpers/dash/__init__.py +3 -0
  14. dsa_helpers-3.2.1/dsa_helpers/dash/dash_paperdragon_utils.py +76 -0
  15. dsa_helpers-3.2.1/dsa_helpers/dash/header.py +155 -0
  16. dsa_helpers-3.2.1/dsa_helpers/dash/header_callbacks.py +99 -0
  17. dsa_helpers-3.2.1/dsa_helpers/girder_utils.py +1262 -0
  18. dsa_helpers-3.2.1/dsa_helpers/gpd_utils.py +400 -0
  19. dsa_helpers-3.2.1/dsa_helpers/image_utils.py +179 -0
  20. dsa_helpers-3.2.1/dsa_helpers/imread.py +32 -0
  21. dsa_helpers-3.2.1/dsa_helpers/imwrite.py +37 -0
  22. dsa_helpers-3.2.1/dsa_helpers/ml/__init__.py +2 -0
  23. dsa_helpers-3.2.1/dsa_helpers/ml/callbacks.py +134 -0
  24. dsa_helpers-3.2.1/dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py +44 -0
  25. dsa_helpers-3.2.1/dsa_helpers/ml/datasets/__init__.py +4 -0
  26. dsa_helpers-3.2.1/dsa_helpers/ml/datasets/utils.py +53 -0
  27. dsa_helpers-3.2.1/dsa_helpers/ml/evaluate.py +307 -0
  28. dsa_helpers-3.2.1/dsa_helpers/ml/inference_results.py +67 -0
  29. dsa_helpers-3.2.1/dsa_helpers/ml/metrics.py +123 -0
  30. dsa_helpers-3.2.1/dsa_helpers/ml/object_detection/__init__.py +5 -0
  31. dsa_helpers-3.2.1/dsa_helpers/ml/object_detection/get_pearce_roi_images.py +136 -0
  32. dsa_helpers-3.2.1/dsa_helpers/ml/object_detection/tile_image.py +137 -0
  33. dsa_helpers-3.2.1/dsa_helpers/ml/object_detection/utils.py +104 -0
  34. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/__init__.py +3 -0
  35. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/datasets.py +55 -0
  36. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/evaluate.py +126 -0
  37. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/inference.py +608 -0
  38. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/train.py +337 -0
  39. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/transforms.py +128 -0
  40. dsa_helpers-3.2.1/dsa_helpers/ml/segformer_semantic_segmentation/utils.py +66 -0
  41. dsa_helpers-3.2.1/dsa_helpers/ml/transforms/__init__.py +1 -0
  42. dsa_helpers-3.2.1/dsa_helpers/ml/transforms/segformer_transforms.py +40 -0
  43. dsa_helpers-3.2.1/dsa_helpers/ml/yolo/__init__.py +0 -0
  44. dsa_helpers-3.2.1/dsa_helpers/ml/yolo/inference.py +661 -0
  45. dsa_helpers-3.2.1/dsa_helpers/ml/yolo/tiling.py +344 -0
  46. dsa_helpers-3.2.1/dsa_helpers/ml/yolo/train.py +241 -0
  47. dsa_helpers-3.2.1/dsa_helpers/mongo_utils.py +266 -0
  48. dsa_helpers-3.2.1/dsa_helpers/tile_utils.py +60 -0
  49. dsa_helpers-3.2.1/dsa_helpers/tiling.py +760 -0
  50. dsa_helpers-3.2.1/dsa_helpers/utils.py +245 -0
  51. dsa_helpers-3.2.1/dsa_helpers.egg-info/PKG-INFO +69 -0
  52. dsa_helpers-3.2.1/dsa_helpers.egg-info/SOURCES.txt +55 -0
  53. dsa_helpers-3.2.1/dsa_helpers.egg-info/dependency_links.txt +1 -0
  54. dsa_helpers-3.2.1/dsa_helpers.egg-info/requires.txt +38 -0
  55. dsa_helpers-3.2.1/dsa_helpers.egg-info/top_level.txt +1 -0
  56. dsa_helpers-3.2.1/pyproject.toml +63 -0
  57. dsa_helpers-3.2.1/setup.cfg +4 -0
@@ -0,0 +1,18 @@
1
+ # Python environment directories.
2
+ env/
3
+ pyenv/
4
+ .venv/
5
+
6
+ *.pyc
7
+ *.egg-info
8
+ dist/
9
+ .env
10
+
11
+ # Ignore data directory created for testing the package.
12
+ tests/data/
13
+ tests/models/
14
+
15
+ temp*
16
+
17
+ large_image-*.whl
18
+ *.pt
@@ -0,0 +1,16 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: "ubuntu-24.04"
5
+ tools:
6
+ python: "3.12"
7
+ jobs:
8
+ pre_build:
9
+ - sphinx-apidoc -o docs/source dsa_helpers/
10
+
11
+ python:
12
+ install:
13
+ - requirements: docs/requirements.txt
14
+
15
+ sphinx:
16
+ configuration: docs/source/conf.py
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: dsa_helpers
3
+ Version: 3.2.1
4
+ Summary: Utility functions for working with the DSA girder client.
5
+ Author-email: "Juan C. Vizcarra" <jvizcar@emory.edu>
6
+ Maintainer-email: "Juan C. Vizcarra" <jvizcar@emory.edu>
7
+ Project-URL: Repository, https://github.com/Gutman-Lab/dsa-python-utils
8
+ Keywords: dsa,girder,girder_client
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: matplotlib
15
+ Requires-Dist: girder-client
16
+ Requires-Dist: numpy
17
+ Requires-Dist: setuptools
18
+ Requires-Dist: pandas
19
+ Requires-Dist: pymongo
20
+ Requires-Dist: pillow
21
+ Requires-Dist: dash
22
+ Requires-Dist: dash-bootstrap-components
23
+ Requires-Dist: shapely
24
+ Requires-Dist: geopandas
25
+ Requires-Dist: rasterio
26
+ Requires-Dist: colorama
27
+ Requires-Dist: histomicstk
28
+ Requires-Dist: pykdtree
29
+ Provides-Extra: opencv
30
+ Requires-Dist: opencv-python; extra == "opencv"
31
+ Provides-Extra: opencv-headless
32
+ Requires-Dist: opencv-python-headless; extra == "opencv-headless"
33
+ Provides-Extra: ultralytics
34
+ Requires-Dist: ultralytics; extra == "ultralytics"
35
+ Provides-Extra: segformer
36
+ Requires-Dist: transformers[torch]; extra == "segformer"
37
+ Requires-Dist: datasets; extra == "segformer"
38
+ Requires-Dist: torchvision; extra == "segformer"
39
+ Requires-Dist: albumentations; extra == "segformer"
40
+ Provides-Extra: all
41
+ Requires-Dist: opencv-python-headless; extra == "all"
42
+ Requires-Dist: ultralytics; extra == "all"
43
+ Requires-Dist: transformers[torch]; extra == "all"
44
+ Requires-Dist: datasets; extra == "all"
45
+ Requires-Dist: torchvision; extra == "all"
46
+ Requires-Dist: albumentations; extra == "all"
47
+ Dynamic: license-file
48
+
49
+ # Digital Slide Archive (DSA) Helpers
50
+ Digital Slide Archive Python utility library.
51
+
52
+ This library is available for installation through [Python Package Index (PyPI)](https://pypi.org/).
53
+
54
+ This library was tested using Python version 3.11.8 and uses the dependencies described in requirements.txt.
55
+
56
+ This Python PyPI package is found [here](https://pypi.org/project/dsa-helpers/).
57
+
58
+ ## Installation instructions
59
+ DSA Helpers depends on large image and large image eager iterator and OpenCV, which you must install separately. Also, you must install the eager iterator from wheel and it should be installed at the end. Python 3.12 or below is supported, Python 3.14 is not currently supported!
60
+
61
+ 1. Install large with all tile sources:
62
+ ```
63
+ $ pip install large-image[all] --find-links https://girder.github.io/large_image_wheels
64
+ ```
65
+ 2. Install OpenCV, either the headless version (better for running in Docker) or full version
66
+ 3. Install dsa-helpers from pip
67
+ 4. Pip install the large image with eager iterator wheel, use the --force flag and you can safely igonore the warnings that you get about some large image library compatibilities
68
+
69
+ A readthedocs for this library can be found [here](https://david-andrew-gutman-dsa-helpers.readthedocs-hosted.com/en/latest/index.html).
@@ -0,0 +1,21 @@
1
+ # Digital Slide Archive (DSA) Helpers
2
+ Digital Slide Archive Python utility library.
3
+
4
+ This library is available for installation through [Python Package Index (PyPI)](https://pypi.org/).
5
+
6
+ This library was tested using Python version 3.11.8 and uses the dependencies described in requirements.txt.
7
+
8
+ This Python PyPI package is found [here](https://pypi.org/project/dsa-helpers/).
9
+
10
+ ## Installation instructions
11
+ DSA Helpers depends on large image and large image eager iterator and OpenCV, which you must install separately. Also, you must install the eager iterator from wheel and it should be installed at the end. Python 3.12 or below is supported, Python 3.14 is not currently supported!
12
+
13
+ 1. Install large with all tile sources:
14
+ ```
15
+ $ pip install large-image[all] --find-links https://girder.github.io/large_image_wheels
16
+ ```
17
+ 2. Install OpenCV, either the headless version (better for running in Docker) or full version
18
+ 3. Install dsa-helpers from pip
19
+ 4. Pip install the large image with eager iterator wheel, use the --force flag and you can safely igonore the warnings that you get about some large image library compatibilities
20
+
21
+ A readthedocs for this library can be found [here](https://david-andrew-gutman-dsa-helpers.readthedocs-hosted.com/en/latest/index.html).
@@ -0,0 +1,14 @@
1
+ FROM pytorch/pytorch:2.9.0-cuda13.0-cudnn9-runtime
2
+
3
+ ENV PYTHONUNBUFFERED=TRUE
4
+ ARG DEBIAN_FRONTEND=noninteractive
5
+
6
+ # Install large image and its tile sources.
7
+ RUN pip install large-image[all] --find-links https://girder.github.io/large_image_wheels
8
+
9
+ # Install dsa-helpers.
10
+ RUN pip install --no-cache-dir dsa-helpers==2.0.6
11
+
12
+ # Create a cache directory.
13
+ RUN mkdir /.cache
14
+ RUN chmod 777 /.cache
@@ -0,0 +1,2 @@
1
+ # Docker
2
+ dsa-helpers image hosted on jvizcar account. Subfolders found for each version of the image.
@@ -0,0 +1,16 @@
1
+ girder-client
2
+ numpy
3
+ setuptools
4
+ opencv-python
5
+ pandas
6
+ pymongo
7
+ pillow
8
+ dash
9
+ dash-bootstrap-components
10
+ shapely
11
+ geopandas
12
+ transformers[torch]
13
+ datasets
14
+ torchvision
15
+ large-image
16
+ sphinx_rtd_theme
@@ -0,0 +1,42 @@
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # For the full list of built-in configuration values, see the documentation:
4
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html
5
+
6
+ # -- Path setup --------------------------------------------------------------
7
+
8
+ # If extensions (or modules to document with autodoc) are in another directory,
9
+ # add these directories to sys.path here. If the directory is relative to the
10
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
11
+ #
12
+ import os
13
+ import sys
14
+
15
+ sys.path.insert(0, os.path.abspath("../.."))
16
+
17
+ # -- Project information -----------------------------------------------------
18
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
19
+
20
+ project = "DSA Helpers"
21
+ copyright = "2024, Juan Carlos Vizcarra"
22
+ author = "Juan Carlos Vizcarra"
23
+ release = "00.00.01"
24
+
25
+ # -- General configuration ---------------------------------------------------
26
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
27
+
28
+ extensions = [
29
+ "sphinx.ext.autodoc",
30
+ "sphinx.ext.viewcode",
31
+ "sphinx.ext.napoleon",
32
+ ]
33
+
34
+ templates_path = ["_templates"]
35
+ exclude_patterns = []
36
+
37
+
38
+ # -- Options for HTML output -------------------------------------------------
39
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
40
+
41
+ html_theme = "sphinx_rtd_theme"
42
+ html_static_path = ["_static"]
@@ -0,0 +1,18 @@
1
+ .. DSA Helpers documentation master file, created by
2
+ sphinx-quickstart on Fri Dec 6 12:07:31 2024.
3
+ You can adapt this file completely to your liking, but it should at least
4
+ contain the root `toctree` directive.
5
+
6
+ DSA Helpers documentation
7
+ =========================
8
+
9
+ Add your content using ``reStructuredText`` syntax. See the
10
+ `reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
11
+ documentation for details.
12
+
13
+
14
+ .. toctree::
15
+ :maxdepth: 2
16
+ :caption: Contents:
17
+
18
+ modules
@@ -0,0 +1,9 @@
1
+ # Shadow imports.
2
+ from .imread import imread
3
+ from .imwrite import imwrite
4
+
5
+ # Version of the dsa-helpers package
6
+ __version__ = "3.2.1"
7
+
8
+ # To avoid slow downs, do not allow from dsa_helpers import * to import anything.
9
+ __all__ = []
@@ -0,0 +1,183 @@
1
+ from collections import deque
2
+ import psutil, threading, time
3
+ import numpy as np
4
+
5
+
6
+ class SystemMonitor:
7
+ def __init__(self, interval=0.5):
8
+ self.interval = interval
9
+ self.cpu_percentages = deque(maxlen=100)
10
+ self.memory_usage = deque(maxlen=100)
11
+ self.io_wait = deque(maxlen=100)
12
+ self.cpu_per_core = deque(maxlen=100) # Track per-core usage
13
+ self.memory_bandwidth = deque(maxlen=100)
14
+ self.io_stats = deque(maxlen=100)
15
+ self.stop_flag = False
16
+ self.process = psutil.Process()
17
+
18
+ def start(self):
19
+ self.stop_flag = False
20
+ self.monitor_thread = threading.Thread(target=self._monitor)
21
+ self.monitor_thread.start()
22
+
23
+ def stop(self):
24
+ self.stop_flag = True
25
+ self.monitor_thread.join()
26
+
27
+ # Calculate statistics with safety checks
28
+ cpu_stats = {
29
+ "mean": (
30
+ np.mean(self.cpu_percentages)
31
+ if len(self.cpu_percentages) > 0
32
+ else 0
33
+ ),
34
+ "max": (
35
+ np.max(self.cpu_percentages)
36
+ if len(self.cpu_percentages) > 0
37
+ else 0
38
+ ),
39
+ "min": (
40
+ np.min(self.cpu_percentages)
41
+ if len(self.cpu_percentages) > 0
42
+ else 0
43
+ ),
44
+ "per_core_mean": (
45
+ np.mean(self.cpu_per_core, axis=0)
46
+ if len(self.cpu_per_core) > 0
47
+ else np.zeros(psutil.cpu_count())
48
+ ),
49
+ "per_core_max": (
50
+ np.max(self.cpu_per_core, axis=0)
51
+ if len(self.cpu_per_core) > 0
52
+ else np.zeros(psutil.cpu_count())
53
+ ),
54
+ "active_cores_mean": (
55
+ np.mean(
56
+ [
57
+ sum(1 for x in cores if x > 10)
58
+ for cores in self.cpu_per_core
59
+ ]
60
+ )
61
+ if len(self.cpu_per_core) > 0
62
+ else 0
63
+ ),
64
+ }
65
+
66
+ mem_stats = {
67
+ "mean": (
68
+ np.mean(self.memory_usage) if len(self.memory_usage) > 0 else 0
69
+ ),
70
+ "max": (
71
+ np.max(self.memory_usage) if len(self.memory_usage) > 0 else 0
72
+ ),
73
+ "min": (
74
+ np.min(self.memory_usage) if len(self.memory_usage) > 0 else 0
75
+ ),
76
+ }
77
+
78
+ io_stats = {
79
+ "wait_mean": np.mean(self.io_wait) if len(self.io_wait) > 0 else 0,
80
+ "wait_max": np.max(self.io_wait) if len(self.io_wait) > 0 else 0,
81
+ "throughput_read": (
82
+ np.mean(
83
+ [
84
+ sum(disk["read_bytes"] for disk in x["disk"].values())
85
+ / x["time_delta"]
86
+ for x in self.io_stats
87
+ ]
88
+ )
89
+ / 1024
90
+ / 1024
91
+ if len(self.io_stats) > 0
92
+ else 0
93
+ ),
94
+ "throughput_write": (
95
+ np.mean(
96
+ [
97
+ sum(disk["write_bytes"] for disk in x["disk"].values())
98
+ / x["time_delta"]
99
+ for x in self.io_stats
100
+ ]
101
+ )
102
+ / 1024
103
+ / 1024
104
+ if len(self.io_stats) > 0
105
+ else 0
106
+ ),
107
+ "network_read": (
108
+ np.mean(
109
+ [
110
+ x["network"]["bytes_recv"] / x["time_delta"]
111
+ for x in self.io_stats
112
+ ]
113
+ )
114
+ / 1024
115
+ / 1024
116
+ if len(self.io_stats) > 0
117
+ else 0
118
+ ),
119
+ "network_write": (
120
+ np.mean(
121
+ [
122
+ x["network"]["bytes_sent"] / x["time_delta"]
123
+ for x in self.io_stats
124
+ ]
125
+ )
126
+ / 1024
127
+ / 1024
128
+ if len(self.io_stats) > 0
129
+ else 0
130
+ ),
131
+ }
132
+
133
+ return {"cpu": cpu_stats, "memory": mem_stats, "io": io_stats}
134
+
135
+ def _monitor(self):
136
+ last_io = psutil.disk_io_counters(perdisk=True) # Monitor per-disk
137
+ last_net = psutil.net_io_counters() # Add network monitoring
138
+ last_time = time.time()
139
+
140
+ while not self.stop_flag:
141
+ # CPU usage (overall and per-core)
142
+ per_core = psutil.cpu_percent(percpu=True)
143
+ self.cpu_percentages.append(np.mean(per_core))
144
+ self.cpu_per_core.append(per_core)
145
+
146
+ # Memory usage
147
+ mem = self.process.memory_info()
148
+ self.memory_usage.append(mem.rss / 1024 / 1024) # Convert to MB
149
+
150
+ # IO wait and throughput
151
+ cpu_times = psutil.cpu_times_percent()
152
+ self.io_wait.append(cpu_times.iowait)
153
+
154
+ # Enhanced IO monitoring
155
+ current_io = psutil.disk_io_counters(perdisk=True)
156
+ current_net = psutil.net_io_counters()
157
+ current_time = time.time()
158
+
159
+ # Calculate both disk and network throughput
160
+ delta_time = current_time - last_time
161
+ io_delta = {
162
+ "disk": {
163
+ disk: {
164
+ "read_bytes": current_io[disk].read_bytes
165
+ - last_io[disk].read_bytes,
166
+ "write_bytes": current_io[disk].write_bytes
167
+ - last_io[disk].write_bytes,
168
+ }
169
+ for disk in current_io
170
+ },
171
+ "network": {
172
+ "bytes_recv": current_net.bytes_recv - last_net.bytes_recv,
173
+ "bytes_sent": current_net.bytes_sent - last_net.bytes_sent,
174
+ },
175
+ "time_delta": delta_time,
176
+ }
177
+ self.io_stats.append(io_delta)
178
+
179
+ last_io = current_io
180
+ last_net = current_net
181
+ last_time = current_time
182
+
183
+ time.sleep(self.interval)
@@ -0,0 +1,3 @@
1
+ from . import header
2
+
3
+ __all__ = []
@@ -0,0 +1,76 @@
1
+ def format_ann_docs_for_paperjs(
2
+ geojson_ann_docs: list[dict], return_as_input_to_paper: bool = False
3
+ ) -> list[dict]:
4
+ """Format DSA annotations in the geojson format into a format that paperdragon
5
+ can read and show as polygons.
6
+
7
+ Args:
8
+ geojson_ann_docs (list[dict]): A list of geojson annotation documents.
9
+ return_as_input_to_paper (bool): Whether to return the formatted documents as
10
+ input to paper. Defaults to False.
11
+
12
+ Returns:
13
+ list[dict]: A list of formatted annotation documents for paperjs. Note that
14
+ it will return an empty list if the documents could not be formatted.
15
+
16
+ """
17
+ # NOTE: hard-coded to only do the first doc.
18
+ formatted_docs = []
19
+
20
+ # Loop through each annotation document.
21
+ for ann_doc in geojson_ann_docs:
22
+ # Grab the features of this document, contains the polygon info.
23
+ features_to_include = []
24
+
25
+ for feature in ann_doc["features"]:
26
+ geometry = feature["geometry"]
27
+ if geometry["type"] == "Polygon":
28
+ properties = feature["properties"]
29
+ properties["rescale"] = {"strokeWidth": properties["lineWidth"]}
30
+ properties["strokeColor"] = properties["lineColor"]
31
+ properties["source"] = "dsa"
32
+
33
+ # Paperjs only supports multipolygons.
34
+ geometry["type"] = "MultiPolygon"
35
+
36
+ coordinates = geometry["coordinates"]
37
+
38
+ adjusted_coordinates = [
39
+ [[[int(coord) for coord in point[:2]] for point in coordinates[0]]]
40
+ ]
41
+
42
+ geometry["coordinates"] = adjusted_coordinates
43
+
44
+ features_to_include.append(feature)
45
+
46
+ if len(features_to_include):
47
+ formatted_docs.append(features_to_include)
48
+
49
+ if return_as_input_to_paper:
50
+ return get_input_to_paper_dict(formatted_docs)
51
+
52
+ return formatted_docs
53
+
54
+
55
+ def get_input_to_paper_dict(formatted_docs: list[dict] | None = None) -> dict:
56
+ """Get the input to paper dictionary.
57
+
58
+ Args:
59
+ formatted_docs (list[dict]): A list of formatted annotation documents for paperjs.
60
+
61
+ Returns:
62
+ dict: The input to paper dictionary.
63
+
64
+ """
65
+ input_to_paper = [{"type": "clearItems"}]
66
+
67
+ if formatted_docs is not None and len(formatted_docs):
68
+ for doc in formatted_docs:
69
+ input_to_paper.append(
70
+ {
71
+ "type": "drawItems",
72
+ "itemList": doc,
73
+ }
74
+ )
75
+
76
+ return {"actions": input_to_paper}
@@ -0,0 +1,155 @@
1
+ from dash import html, dcc
2
+ import dash_bootstrap_components as dbc
3
+ from .header_callbacks import get_callbacks
4
+
5
+
6
+ def get_header(
7
+ dsa_api_url: str,
8
+ title: str = "App",
9
+ store_id: str = "user-store",
10
+ config: dict | None = None,
11
+ ) -> html.Div:
12
+ """Get the Dash html component that contains the header of the application
13
+ with login capabilities.
14
+
15
+ Args:
16
+ dsa_api_url (str): The URL of the DSA API.
17
+ title (str, optional): The title of the application. Defaults to "App".
18
+ config (dict, optional): The configuration of the component.
19
+ Defaults to None which uses default settings.
20
+
21
+ Returns:
22
+ html.Div: The header component.
23
+
24
+ """
25
+ if config is None:
26
+ config = {}
27
+
28
+ get_callbacks(dsa_api_url, store_id)
29
+
30
+ login_modal = dbc.Modal(
31
+ [
32
+ dbc.ModalHeader("Log in"),
33
+ dbc.ModalBody(
34
+ [
35
+ html.Div(
36
+ "Login or email",
37
+ style={"margin": 5, "fontWeight": "bold"},
38
+ ),
39
+ dbc.Input(
40
+ id=f"{store_id}=login",
41
+ type="text",
42
+ placeholder="Enter login",
43
+ style={"margin": 5},
44
+ ),
45
+ html.Div(
46
+ "Password",
47
+ style={
48
+ "margin": 5,
49
+ "marginTop": 15,
50
+ "fontWeight": "bold",
51
+ },
52
+ ),
53
+ dbc.Input(
54
+ id=f"{store_id}=password",
55
+ type="password",
56
+ placeholder="Enter password",
57
+ style={"margin": 5},
58
+ ),
59
+ html.Div(
60
+ "Login failed.",
61
+ hidden=True,
62
+ id=f"{store_id}=login-failed",
63
+ style={
64
+ "color": "red",
65
+ "fontWeight": "bold",
66
+ "margin": 10,
67
+ },
68
+ ),
69
+ ],
70
+ ),
71
+ dbc.ModalFooter(
72
+ dbc.Row(
73
+ [
74
+ dbc.Col(
75
+ dbc.Button(
76
+ "Close",
77
+ id=f"{store_id}=close-login-modal",
78
+ className="me-1",
79
+ color="light",
80
+ )
81
+ ),
82
+ dbc.Col(
83
+ dbc.Button(
84
+ "Login",
85
+ id=f"{store_id}=log-in-btn",
86
+ className="me-1",
87
+ color="primary",
88
+ )
89
+ ),
90
+ ],
91
+ )
92
+ ),
93
+ ],
94
+ is_open=False,
95
+ id=f"{store_id}=login-modal",
96
+ )
97
+
98
+ logout_modal = dbc.Modal(
99
+ [
100
+ dbc.ModalFooter(
101
+ [
102
+ dbc.Button(
103
+ "Log out",
104
+ id=f"{store_id}=logout-btn",
105
+ color="danger",
106
+ className="me-1",
107
+ ),
108
+ ]
109
+ )
110
+ ],
111
+ is_open=False,
112
+ id=f"{store_id}=logout-modal",
113
+ )
114
+
115
+ return html.Div(
116
+ [
117
+ dbc.Row(
118
+ [
119
+ dbc.Col(
120
+ html.H1(
121
+ title,
122
+ style={
123
+ "fontWeight": "bold",
124
+ "color": config.get("titleColor", "#d9d9d6"),
125
+ "marginLeft": 5,
126
+ },
127
+ ),
128
+ width="auto",
129
+ ),
130
+ dbc.Col(
131
+ dbc.Button(
132
+ "Log in",
133
+ id=f"{store_id}=login-btn",
134
+ color=config.get("backgroundColor", "#012169"),
135
+ style={
136
+ "color": config.get("fontColor", "#f2a900"),
137
+ "fontSize": "1.2rem",
138
+ },
139
+ className="me-1",
140
+ ),
141
+ width="auto",
142
+ ),
143
+ ],
144
+ justify="between",
145
+ align="center",
146
+ ),
147
+ login_modal,
148
+ logout_modal,
149
+ dcc.Store(id=store_id, storage_type="local", data={}),
150
+ ],
151
+ style={
152
+ "backgroundColor": config.get("backgroundColor", "#012169"),
153
+ "padding": 5,
154
+ },
155
+ )