dsa_helpers 3.2.1__py3-none-any.whl

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 (45) hide show
  1. dsa_helpers/__init__.py +9 -0
  2. dsa_helpers/cli_utils.py +183 -0
  3. dsa_helpers/dash/__init__.py +3 -0
  4. dsa_helpers/dash/dash_paperdragon_utils.py +76 -0
  5. dsa_helpers/dash/header.py +155 -0
  6. dsa_helpers/dash/header_callbacks.py +99 -0
  7. dsa_helpers/girder_utils.py +1262 -0
  8. dsa_helpers/gpd_utils.py +400 -0
  9. dsa_helpers/image_utils.py +179 -0
  10. dsa_helpers/imread.py +32 -0
  11. dsa_helpers/imwrite.py +37 -0
  12. dsa_helpers/ml/__init__.py +2 -0
  13. dsa_helpers/ml/callbacks.py +134 -0
  14. dsa_helpers/ml/datasets/SegFormerSegmentationDataset.py +44 -0
  15. dsa_helpers/ml/datasets/__init__.py +4 -0
  16. dsa_helpers/ml/datasets/utils.py +53 -0
  17. dsa_helpers/ml/evaluate.py +307 -0
  18. dsa_helpers/ml/inference_results.py +67 -0
  19. dsa_helpers/ml/metrics.py +123 -0
  20. dsa_helpers/ml/object_detection/__init__.py +5 -0
  21. dsa_helpers/ml/object_detection/get_pearce_roi_images.py +136 -0
  22. dsa_helpers/ml/object_detection/tile_image.py +137 -0
  23. dsa_helpers/ml/object_detection/utils.py +104 -0
  24. dsa_helpers/ml/segformer_semantic_segmentation/__init__.py +3 -0
  25. dsa_helpers/ml/segformer_semantic_segmentation/datasets.py +55 -0
  26. dsa_helpers/ml/segformer_semantic_segmentation/evaluate.py +126 -0
  27. dsa_helpers/ml/segformer_semantic_segmentation/inference.py +608 -0
  28. dsa_helpers/ml/segformer_semantic_segmentation/train.py +337 -0
  29. dsa_helpers/ml/segformer_semantic_segmentation/transforms.py +128 -0
  30. dsa_helpers/ml/segformer_semantic_segmentation/utils.py +66 -0
  31. dsa_helpers/ml/transforms/__init__.py +1 -0
  32. dsa_helpers/ml/transforms/segformer_transforms.py +40 -0
  33. dsa_helpers/ml/yolo/__init__.py +0 -0
  34. dsa_helpers/ml/yolo/inference.py +661 -0
  35. dsa_helpers/ml/yolo/tiling.py +344 -0
  36. dsa_helpers/ml/yolo/train.py +241 -0
  37. dsa_helpers/mongo_utils.py +266 -0
  38. dsa_helpers/tile_utils.py +60 -0
  39. dsa_helpers/tiling.py +760 -0
  40. dsa_helpers/utils.py +245 -0
  41. dsa_helpers-3.2.1.dist-info/METADATA +69 -0
  42. dsa_helpers-3.2.1.dist-info/RECORD +45 -0
  43. dsa_helpers-3.2.1.dist-info/WHEEL +5 -0
  44. dsa_helpers-3.2.1.dist-info/licenses/LICENSE +19 -0
  45. dsa_helpers-3.2.1.dist-info/top_level.txt +1 -0
@@ -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
+ )
@@ -0,0 +1,99 @@
1
+ # Callbacks for the header component.
2
+ from dash import callback, Output, Input, State, no_update
3
+ from girder_client import GirderClient
4
+
5
+
6
+ def get_callbacks(dsa_api_url: str, store_id: str):
7
+ """Setup the callbacks to the DSA API url of interest and the proper store id."""
8
+
9
+ @callback(Output(f"{store_id}=login-btn", "children"), Input(store_id, "data"))
10
+ def check_user_store(data):
11
+ # Check if the user store has user info or if no one is logged in.
12
+ return data["user"] if len(data) else "Log in"
13
+
14
+ @callback(
15
+ [
16
+ Output(f"{store_id}=login-modal", "is_open", allow_duplicate=True),
17
+ Output(f"{store_id}=logout-modal", "is_open", allow_duplicate=True),
18
+ ],
19
+ [
20
+ Input(f"{store_id}=login-btn", "n_clicks"),
21
+ State(f"{store_id}=login-btn", "children"),
22
+ ],
23
+ prevent_initial_call=True,
24
+ )
25
+ def open_login_modal(n_clicks, children):
26
+ # Open login / logout modal.
27
+ if n_clicks:
28
+ if children == "Log in":
29
+ return True, False
30
+ else:
31
+ return False, True
32
+
33
+ return False, False
34
+
35
+ @callback(
36
+ [
37
+ Output(store_id, "data"),
38
+ Output(f"{store_id}=login-failed", "hidden", allow_duplicate=True),
39
+ Output(f"{store_id}=login-modal", "is_open", allow_duplicate=True),
40
+ Output(f"{store_id}=login", "value", allow_duplicate=True),
41
+ Output(f"{store_id}=password", "value", allow_duplicate=True),
42
+ ],
43
+ [
44
+ Input(f"{store_id}=log-in-btn", "n_clicks"),
45
+ State(f"{store_id}=login", "value"),
46
+ State(f"{store_id}=password", "value"),
47
+ ],
48
+ prevent_initial_call=True,
49
+ )
50
+ def login(n_clicks, login, password):
51
+ # Try to login.
52
+ gc = GirderClient(apiUrl=dsa_api_url)
53
+
54
+ try:
55
+ _ = gc.authenticate(username=login, password=password)
56
+
57
+ response = gc.get("token/session")
58
+
59
+ user = gc.get("user/me")["login"]
60
+
61
+ return {"user": user, "token": response["token"]}, True, False, "", ""
62
+ except:
63
+ return (
64
+ {},
65
+ False,
66
+ True,
67
+ no_update,
68
+ no_update,
69
+ )
70
+
71
+ @callback(
72
+ [
73
+ Output(f"{store_id}=login-modal", "is_open", allow_duplicate=True),
74
+ Output(f"{store_id}=login", "value", allow_duplicate=True),
75
+ Output(f"{store_id}=password", "value", allow_duplicate=True),
76
+ Output(f"{store_id}=login-failed", "hidden", allow_duplicate=True),
77
+ ],
78
+ Input(f"{store_id}=close-login-modal", "n_clicks"),
79
+ prevent_initial_call=True,
80
+ )
81
+ def close_login_modal(n_clicks):
82
+ if n_clicks:
83
+ return False, "", "", True
84
+
85
+ return False, "", "", True
86
+
87
+ @callback(
88
+ [
89
+ Output(store_id, "data", allow_duplicate=True),
90
+ Output(f"{store_id}=logout-modal", "is_open", allow_duplicate=True),
91
+ ],
92
+ Input(f"{store_id}=logout-btn", "n_clicks"),
93
+ prevent_initial_call=True,
94
+ )
95
+ def logout(n_clicks):
96
+ if n_clicks:
97
+ return {}, False
98
+
99
+ return no_update, False