cortical-tools 0.0.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.
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,473 @@
1
+ import datetime
2
+ from typing import TYPE_CHECKING, Literal, Optional
3
+
4
+ if TYPE_CHECKING:
5
+ from meshparty.meshwork import Meshwork
6
+
7
+ import numpy as np
8
+ import numpy.typing as npt
9
+ import pandas as pd
10
+ import pcg_skel
11
+ import standard_transform
12
+ from caveclient import CAVEclient
13
+ from caveclient.frameworkclient import CAVEclientFull
14
+ from nglui import statebuilder as sb
15
+
16
+ from .files import TableExportClient
17
+ from .mesh import MeshClient
18
+
19
+
20
+ def null_function_factory(arguments_to_set=[]):
21
+ def null_function(*args, **kwargs):
22
+ """
23
+ A placeholder function that does nothing.
24
+ """
25
+ raise NotImplementedError(
26
+ "This function is a placeholder. Arguments must be set in the main class: {}".format(
27
+ arguments_to_set
28
+ )
29
+ )
30
+
31
+
32
+ def cell_id_to_root_id_factory(
33
+ default_datastack_name,
34
+ default_server_address,
35
+ lookup_view_name,
36
+ ):
37
+ def cell_id_to_root_id(
38
+ cell_ids: list[int],
39
+ client: Optional[CAVEclient] = None,
40
+ timestamp: Optional[datetime.datetime] = None,
41
+ materialization_version: Optional[int] = None,
42
+ filter_empty: bool = True,
43
+ ) -> npt.NDArray:
44
+ """
45
+ Convert cell IDs to root IDs using the CAVEclient.
46
+
47
+ Parameters
48
+ ----------
49
+ cell_ids : list[int]
50
+ List of cell IDs to convert.
51
+ client : CAVEclient, optional
52
+ CAVEclient instance, by default None.
53
+ timestamp : datetime.datetime, optional
54
+ Timestamp for the query, by default current time.
55
+ materialization_version : int, optional
56
+ Materialization version, by default None.
57
+
58
+ Returns
59
+ -------
60
+ pd.Series
61
+ Series containing the root IDs with cell IDs as index.
62
+ Cell ids that do not map to a root id will have NaN values.
63
+ """
64
+ if client is None:
65
+ client = CAVEclient(
66
+ datastack_name=default_datastack_name,
67
+ server_address=default_server_address,
68
+ )
69
+ view_name = lookup_view_name
70
+ nuc_df = (
71
+ client.materialize.views[view_name](
72
+ id=cell_ids,
73
+ )
74
+ .query(
75
+ split_positions=True, materialization_version=materialization_version
76
+ )
77
+ .set_index("id")
78
+ )
79
+
80
+ if timestamp is not None:
81
+ nuc_df["pt_root_id"] = client.chunkedgraph.get_roots(
82
+ nuc_df["pt_supervoxel_id"], timestamp=timestamp
83
+ )
84
+
85
+ cell_id_df = pd.DataFrame(
86
+ index=cell_ids,
87
+ )
88
+
89
+ cell_id_df = cell_id_df.merge(
90
+ nuc_df[["pt_root_id"]],
91
+ left_index=True,
92
+ right_index=True,
93
+ how="inner",
94
+ ).sort_index()
95
+ add_back_index = np.setdiff1d(cell_ids, cell_id_df.index.values)
96
+ if len(add_back_index) > 0 and not filter_empty:
97
+ cell_id_df = pd.concat(
98
+ [
99
+ cell_id_df,
100
+ pd.DataFrame(index=add_back_index, data={"pt_root_id": -1}),
101
+ ]
102
+ )
103
+ cell_id_df = cell_id_df.loc[cell_ids]
104
+ return cell_id_df["pt_root_id"].rename("root_id")
105
+
106
+ return cell_id_to_root_id
107
+
108
+
109
+ def _selective_lookup(
110
+ query_idx: pd.Index,
111
+ client: CAVEclient,
112
+ timestamp: datetime.datetime,
113
+ main_table: str,
114
+ alt_tables: list,
115
+ ):
116
+ lookup_df_main = client.materialize.tables[main_table](
117
+ pt_root_id=query_idx.values
118
+ ).live_query(timestamp=timestamp, split_positions=True)
119
+ lookup_df_main = lookup_df_main[["id", "pt_root_id"]].set_index("pt_root_id")
120
+
121
+ if len(lookup_df_main) < len(query_idx):
122
+ lookup_df_alts = []
123
+ for alt_table in alt_tables:
124
+ lookup_df_alt = client.materialize.tables[alt_table](
125
+ pt_ref_root_id=query_idx.values,
126
+ ).live_query(timestamp=timestamp, split_positions=True)
127
+ if len(lookup_df_alt) > 0:
128
+ lookup_df_alts.append(lookup_df_alt[["id", "pt_root_id"]])
129
+ if len(lookup_df_alts) > 0:
130
+ lookup_df_alt_concat = pd.concat(lookup_df_alts)[
131
+ ["id", "pt_root_id"]
132
+ ].set_index("pt_root_id")
133
+ else:
134
+ lookup_df_alt_concat = pd.DataFrame()
135
+
136
+ lookup_df = pd.concat([lookup_df_main, lookup_df_alt_concat])
137
+ else:
138
+ lookup_df = lookup_df_main
139
+ return lookup_df
140
+
141
+
142
+ def root_id_to_cell_id_factory(
143
+ default_datastack_name: str,
144
+ default_server_address: str,
145
+ main_table: str,
146
+ alt_tables: list[str],
147
+ ):
148
+ def root_id_to_cell_id(
149
+ root_ids: list[int],
150
+ client: Optional[CAVEclient] = None,
151
+ filter_empty: bool = False,
152
+ ):
153
+ """
154
+ Lookup the cell id for a list of root ids in the microns dataset
155
+ """
156
+ if client is None:
157
+ client = CAVEclient(
158
+ datastack_name=default_datastack_name,
159
+ server_address=default_server_address,
160
+ )
161
+
162
+ root_ids = np.unique(root_ids)
163
+ all_cell_df = pd.DataFrame(
164
+ index=root_ids,
165
+ data={"cell_id": -1, "done": False},
166
+ )
167
+ all_cell_df["cell_id"] = all_cell_df["cell_id"].astype(int)
168
+ earliest_timestamp = client.chunkedgraph.get_root_timestamps(
169
+ root_ids, latest=False
170
+ )
171
+ latest_timestamp = client.chunkedgraph.get_root_timestamps(
172
+ root_ids, latest=True
173
+ )
174
+ all_cell_df["ts0"] = earliest_timestamp
175
+ all_cell_df["ts1"] = latest_timestamp
176
+
177
+ while not np.all(all_cell_df["done"].values):
178
+ ts = all_cell_df.query("done == False").ts1.iloc[0]
179
+ qry_idx = all_cell_df[
180
+ (all_cell_df.ts0 < ts) & (all_cell_df.ts1 >= ts)
181
+ ].index
182
+
183
+ lookup_df = _selective_lookup(
184
+ qry_idx,
185
+ client,
186
+ ts,
187
+ main_table=main_table,
188
+ alt_tables=alt_tables,
189
+ )
190
+
191
+ # Update the pt root ids of found cells, but the done status of all queried cells
192
+ all_cell_df.loc[lookup_df.index, "cell_id"] = lookup_df["id"].astype(int)
193
+ all_cell_df.loc[qry_idx, "done"] = True
194
+
195
+ if filter_empty:
196
+ all_cell_df = all_cell_df.query("cell_id != -1")
197
+ return all_cell_df["cell_id"].astype(int).loc[root_ids]
198
+
199
+ return root_id_to_cell_id
200
+
201
+
202
+ class DatasetClient:
203
+ def __init__(
204
+ self,
205
+ datastack_name: Optional[str] = None,
206
+ server_address: Optional[str] = None,
207
+ caveclient: Optional[CAVEclient] = None,
208
+ *,
209
+ materialization_version: Optional[int] = None,
210
+ cell_id_lookup_view: Optional[str] = None,
211
+ root_id_lookup_main_table: Optional[str] = None,
212
+ root_id_lookup_alt_tables: Optional[list[str]] = None,
213
+ dataset_transform: Optional[standard_transform.datasets.Dataset] = None,
214
+ static_table_cloudpath: Optional[str] = None,
215
+ ):
216
+ if caveclient is None:
217
+ caveclient = CAVEclient(
218
+ datastack_name=datastack_name,
219
+ server_address=server_address,
220
+ version=materialization_version,
221
+ )
222
+ else:
223
+ datastack_name = caveclient.datastack_name
224
+ server_address = caveclient.server_address
225
+
226
+ self._client = caveclient
227
+ self._client.version = (
228
+ int(caveclient.materialize.version)
229
+ if materialization_version is None
230
+ else int(materialization_version)
231
+ )
232
+
233
+ self._datastack_name = datastack_name
234
+ self._server_address = server_address
235
+
236
+ if cell_id_lookup_view is None:
237
+ self.cell_id_to_root_id = null_function_factory(
238
+ arguments_to_set=["cell_id_lookup_view"]
239
+ )
240
+ else:
241
+ self.cell_id_to_root_id = cell_id_to_root_id_factory(
242
+ default_datastack_name=datastack_name,
243
+ default_server_address=server_address,
244
+ lookup_view_name=cell_id_lookup_view,
245
+ )
246
+
247
+ if root_id_lookup_main_table is None:
248
+ self.root_id_to_cell_id = null_function_factory(
249
+ arguments_to_set=["root_id_lookup_main_table"]
250
+ )
251
+ else:
252
+ self.root_id_to_cell_id = root_id_to_cell_id_factory(
253
+ default_datastack_name=datastack_name,
254
+ default_server_address=server_address,
255
+ main_table=root_id_lookup_main_table,
256
+ alt_tables=root_id_lookup_alt_tables,
257
+ )
258
+
259
+ if dataset_transform is None:
260
+ self._dataset_transform = null_function_factory(
261
+ arguments_to_set=["dataset_transform"]
262
+ )
263
+ else:
264
+ self._dataset_transform = dataset_transform
265
+
266
+ self._mesh_client = MeshClient(caveclient=caveclient)
267
+
268
+ self.tables = self.cave.materialize.tables
269
+ self.views = self.cave.materialize.views
270
+
271
+ if static_table_cloudpath is None:
272
+ self.exports = null_function_factory(
273
+ arguments_to_set=["static_table_cloudpath"]
274
+ )
275
+ else:
276
+ self.exports = TableExportClient(static_table_cloudpath)
277
+
278
+ def set_export_cloudpath(self, cloudpath: str):
279
+ """
280
+ Set the cloud path for static table exports.
281
+ """
282
+ self.exports = TableExportClient(cloudpath)
283
+
284
+ @property
285
+ def cave(self) -> CAVEclientFull:
286
+ """
287
+ Get the CAVEclient instance for this CortexClient.
288
+ """
289
+ return self._client
290
+
291
+ @property
292
+ def datastack_name(self) -> str:
293
+ """
294
+ Get the name of the datastack associated with this CortexClient.
295
+ """
296
+ return self._datastack_name
297
+
298
+ @property
299
+ def server_address(self) -> str:
300
+ """
301
+ Get the server address associated with this CortexClient.
302
+ """
303
+ return self._server_address
304
+
305
+ @property
306
+ def dataset_transform(self) -> standard_transform.datasets.Dataset:
307
+ """
308
+ Get the dataset transform associated with this CortexClient.
309
+ """
310
+ return self._dataset_transform
311
+
312
+ @property
313
+ def mesh(self) -> MeshClient:
314
+ """
315
+ Get the MeshClient instance for this CortexClient.
316
+ """
317
+ return self._mesh_client
318
+
319
+ @property
320
+ def space(self) -> standard_transform.datasets.Dataset:
321
+ """
322
+ Get the dataset transform for this CortexClient.
323
+ """
324
+ return self._dataset_transform
325
+
326
+ @property
327
+ def version(self) -> int:
328
+ """
329
+ Get the materialization version of the CAVEclient.
330
+ """
331
+ return self.cave.materialize.version
332
+
333
+ @version.setter
334
+ def version(self, value: int):
335
+ """
336
+ Set the materialization version of the CAVEclient.
337
+ """
338
+ self.cave.materialize.version = value
339
+
340
+ def get_l2_ids(self, root_id: int) -> list[int]:
341
+ return self.cave.chunkedgraph.get_roots(root_id, stop_layer=2)
342
+
343
+ def get_skeleton(
344
+ self,
345
+ root_id: int,
346
+ synapses: bool = True,
347
+ restore_graph: bool = False,
348
+ restore_properties: bool = True,
349
+ synapse_reference_tables: Optional[dict] = None,
350
+ skeleton_version: Optional[int] = None,
351
+ transform: Optional[Literal["rigid", "streamline"]] = None,
352
+ ) -> "Meshwork":
353
+ if skeleton_version is None:
354
+ skeleton_version = 4
355
+ nrn = pcg_skel.get_meshwork_from_client(
356
+ client=self.cave,
357
+ root_id=root_id,
358
+ synapses=synapses,
359
+ restore_graph=restore_graph,
360
+ restore_properties=restore_properties,
361
+ synapse_reference_tables=synapse_reference_tables,
362
+ skeleton_version=skeleton_version,
363
+ )
364
+ if transform == "rigid":
365
+ self.space.transform_nm.apply_meshwork_vertices(nrn, inplace=True)
366
+ if synapses:
367
+ space_cols = [
368
+ x for x in nrn.anno.pre_syn.df.columns if "pt_position" in x
369
+ ]
370
+ anno_dict = {"pre_syn": space_cols, "post_syn": space_cols}
371
+ self.space.transform_nm.apply_meshwork_annotations(
372
+ nrn, anno_dict, inplace=True
373
+ )
374
+ elif transform == "streamline":
375
+ self.space.streamline_nm.apply_meshwork_vertices(nrn, inplace=True)
376
+ if synapses:
377
+ space_cols = [
378
+ x for x in nrn.anno.pre_syn.df.columns if "pt_position" in x
379
+ ]
380
+ anno_dict = {"pre_syn": space_cols, "post_syn": space_cols}
381
+ self.space.streamline_nm.apply_meshwork_annotations(
382
+ nrn, anno_dict, inplace=True
383
+ )
384
+ return nrn
385
+
386
+ @staticmethod
387
+ def now() -> datetime.datetime:
388
+ """
389
+ Get the current time in UTC timezone.
390
+ """
391
+ return datetime.datetime.now(datetime.timezone.utc)
392
+
393
+ def version_timestamp(self, version: Optional[int] = None) -> datetime.datetime:
394
+ """
395
+ Get the timestamp for a specific materialization version.
396
+
397
+ Parameters
398
+ ----------
399
+ version : int, optional
400
+ The materialization version to get the timestamp for, by default None (uses current version).
401
+
402
+ Returns
403
+ -------
404
+ datetime.datetime
405
+ The timestamp of the specified materialization version.
406
+ """
407
+ if version is None:
408
+ version = self.cave.materialize.version
409
+ return self.cave.materialize.get_version_timestamp(version)
410
+
411
+ def latest_valid_timestamp(
412
+ self,
413
+ root_ids: list[int],
414
+ ) -> npt.NDArray:
415
+ """
416
+ Get the latest valid timestamps for a list of root IDs.
417
+ If the root ID is out of date, it will return the last timestamp at which it was valid and could be used in queries.
418
+ If the root ID is up to date, it will return the current timestamp at the request time, which is still ensured to be valid.
419
+
420
+ Parameters
421
+ ----------
422
+ root_ids : list[int]
423
+ The list of root IDs to get the latest valid timestamps for.
424
+
425
+ Returns
426
+ -------
427
+ npt.NDArray
428
+ The latest valid timestamps for the specified root IDs.
429
+ """
430
+ return self.cave.chunkedgraph.get_root_timestamps(root_ids, latest=True)
431
+
432
+ def neuroglancer_url(
433
+ self,
434
+ target_url: Optional[str] = None,
435
+ clipboard=False,
436
+ shorten=False,
437
+ ) -> str:
438
+ """
439
+ Get the Neuroglancer URL for the current datastack and version.
440
+
441
+ Parameters
442
+ ----------
443
+ target_url : str, optional
444
+ The base URL for Neuroglancer, by default None (uses default server address).
445
+
446
+ Returns
447
+ -------
448
+ str
449
+ The Neuroglancer URL.
450
+ """
451
+ vs = sb.ViewerState(client=self.cave).add_layers_from_client()
452
+ if clipboard:
453
+ return vs.to_clipboard(
454
+ target_url=target_url,
455
+ shorten=shorten,
456
+ )
457
+ else:
458
+ return vs.to_url(
459
+ target_url=target_url,
460
+ shorten=shorten,
461
+ )
462
+
463
+ def __repr__(self) -> str:
464
+ return f"DatasetClient(datastack_name={self.datastack_name}, version={self.cave.materialize.version})"
465
+
466
+ def _repr_mimebundle_(self, include=None, exclude=None):
467
+ """Necessary for IPython to detect _repr_html_ for subclasses."""
468
+ return {"text/html": self.__repr_html__()}, {}
469
+
470
+ def __repr_html__(self) -> str:
471
+ neuroglancer_url = self.neuroglancer_url()
472
+ html_str = f"<html><body><a href='{neuroglancer_url}'>{self.__repr__()}</a></body></html>"
473
+ return html_str
File without changes
@@ -0,0 +1,31 @@
1
+ from standard_transform.datasets import minnie_ds
2
+
3
+ from ..common import DatasetClient
4
+
5
+
6
+ class MicronsProdClient(DatasetClient):
7
+ datastack_name = "minnie65_phase3_v1"
8
+ server_address = "https://global.daf-apis.com"
9
+ _cell_id_lookup_view = "nucleus_detection_lookup_v1"
10
+ _root_id_lookup_main_table = "nucleus_detection_v0"
11
+ _root_id_lookup_alt_tables = ["nucleus_alternative_points"]
12
+
13
+ def __init__(self):
14
+ super().__init__(
15
+ datastack_name=self.datastack_name,
16
+ server_address=self.server_address,
17
+ cell_id_lookup_view=self._cell_id_lookup_view,
18
+ root_id_lookup_main_table=self._root_id_lookup_main_table,
19
+ root_id_lookup_alt_tables=self._root_id_lookup_alt_tables,
20
+ dataset_transform=minnie_ds,
21
+ )
22
+
23
+ def __repr__(self):
24
+ return f"MicronsPublicClient(datastack_name={self.datastack_name}, version={self.cave.materialize.version})"
25
+
26
+ def _repr_mimebundle_(self, include=None, exclude=None):
27
+ """Necessary for IPython to detect _repr_html_ for subclasses."""
28
+ return {"text/html": self.__repr_html__()}, {}
29
+
30
+
31
+ client = MicronsProdClient()
@@ -0,0 +1,31 @@
1
+ from standard_transform.datasets import minnie_ds
2
+
3
+ from ..common import DatasetClient
4
+
5
+
6
+ class MinniePublicClient(DatasetClient):
7
+ datastack_name = "minnie65_public"
8
+ server_address = "https://global.daf-apis.com"
9
+ _cell_id_lookup_view = "nucleus_detection_lookup_v1"
10
+ _root_id_lookup_main_table = "nucleus_detection_v0"
11
+ _root_id_lookup_alt_tables = ["nucleus_alternative_points"]
12
+
13
+ def __init__(self):
14
+ super().__init__(
15
+ datastack_name=self.datastack_name,
16
+ server_address=self.server_address,
17
+ cell_id_lookup_view=self._cell_id_lookup_view,
18
+ root_id_lookup_main_table=self._root_id_lookup_main_table,
19
+ root_id_lookup_alt_tables=self._root_id_lookup_alt_tables,
20
+ dataset_transform=minnie_ds,
21
+ )
22
+
23
+ def __repr__(self):
24
+ return f"MinniePublicClient(datastack_name={self.datastack_name}, version={self.cave.materialize.version})"
25
+
26
+ def _repr_mimebundle_(self, include=None, exclude=None):
27
+ """Necessary for IPython to detect _repr_html_ for subclasses."""
28
+ return {"text/html": self.__repr_html__()}, {}
29
+
30
+
31
+ client = MinniePublicClient()
@@ -0,0 +1,31 @@
1
+ from standard_transform.datasets import v1dd_ds
2
+
3
+ from ..common import DatasetClient
4
+
5
+
6
+ class V1ddClient(DatasetClient):
7
+ datastack_name = "v1dd"
8
+ server_address = "https://global.em.brain.allentech.org"
9
+ _cell_id_lookup_view = "nucleus_alternative_lookup"
10
+ _root_id_lookup_main_table = "nucleus_detection_v0"
11
+ _root_id_lookup_alt_tables = ["nucleus_alternative_points"]
12
+
13
+ def __init__(self):
14
+ super().__init__(
15
+ datastack_name=self.datastack_name,
16
+ server_address=self.server_address,
17
+ cell_id_lookup_view=self._cell_id_lookup_view,
18
+ root_id_lookup_main_table=self._root_id_lookup_main_table,
19
+ root_id_lookup_alt_tables=self._root_id_lookup_alt_tables,
20
+ dataset_transform=v1dd_ds,
21
+ )
22
+
23
+ def __repr__(self):
24
+ return f"V1ddClient(datastack_name={self.datastack_name}, version={self.cave.materialize.version})"
25
+
26
+ def _repr_mimebundle_(self, include=None, exclude=None):
27
+ """Necessary for IPython to detect _repr_html_ for subclasses."""
28
+ return {"text/html": self.__repr_html__()}, {}
29
+
30
+
31
+ client = V1ddClient()
@@ -0,0 +1,31 @@
1
+ from standard_transform.datasets import v1dd_ds
2
+
3
+ from ..common import DatasetClient
4
+
5
+
6
+ class V1ddPublicClient(DatasetClient):
7
+ datastack_name = "v1dd_public"
8
+ server_address = "https://global.em.brain.allentech.org"
9
+ _cell_id_lookup_view = "nucleus_alternative_lookup"
10
+ _root_id_lookup_main_table = "nucleus_detection_v0"
11
+ _root_id_lookup_alt_tables = ["nucleus_alternative_points"]
12
+
13
+ def __init__(self):
14
+ super().__init__(
15
+ datastack_name=self.datastack_name,
16
+ server_address=self.server_address,
17
+ cell_id_lookup_view=self._cell_id_lookup_view,
18
+ root_id_lookup_main_table=self._root_id_lookup_main_table,
19
+ root_id_lookup_alt_tables=self._root_id_lookup_alt_tables,
20
+ dataset_transform=v1dd_ds,
21
+ )
22
+
23
+ def __repr__(self):
24
+ return f"V1ddPublicClient(datastack_name={self.datastack_name}, version={self.cave.materialize.version})"
25
+
26
+ def _repr_mimebundle_(self, include=None, exclude=None):
27
+ """Necessary for IPython to detect _repr_html_ for subclasses."""
28
+ return {"text/html": self.__repr_html__()}, {}
29
+
30
+
31
+ client = V1ddPublicClient()