siibra 1.0a19__py3-none-any.whl → 1.0.1a0__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.
Potentially problematic release.
This version of siibra might be problematic. Click here for more details.
- siibra/VERSION +1 -1
- siibra/__init__.py +3 -3
- siibra/commons.py +0 -46
- siibra/configuration/factory.py +10 -20
- siibra/core/atlas.py +20 -14
- siibra/core/parcellation.py +67 -52
- siibra/core/region.py +133 -123
- siibra/exceptions.py +8 -0
- siibra/experimental/contour.py +6 -6
- siibra/experimental/patch.py +2 -2
- siibra/experimental/plane3d.py +8 -8
- siibra/features/anchor.py +12 -13
- siibra/features/connectivity/regional_connectivity.py +2 -2
- siibra/features/feature.py +14 -16
- siibra/features/tabular/bigbrain_intensity_profile.py +1 -1
- siibra/features/tabular/cell_density_profile.py +97 -63
- siibra/features/tabular/layerwise_cell_density.py +3 -22
- siibra/features/tabular/regional_timeseries_activity.py +2 -2
- siibra/livequeries/allen.py +39 -16
- siibra/livequeries/bigbrain.py +8 -8
- siibra/livequeries/query.py +0 -1
- siibra/locations/__init__.py +9 -9
- siibra/locations/boundingbox.py +29 -24
- siibra/locations/point.py +4 -4
- siibra/locations/{pointset.py → pointcloud.py} +30 -22
- siibra/retrieval/repositories.py +9 -26
- siibra/retrieval/requests.py +19 -2
- siibra/volumes/__init__.py +1 -1
- siibra/volumes/parcellationmap.py +88 -81
- siibra/volumes/providers/neuroglancer.py +62 -36
- siibra/volumes/providers/nifti.py +11 -25
- siibra/volumes/sparsemap.py +124 -245
- siibra/volumes/volume.py +141 -52
- {siibra-1.0a19.dist-info → siibra-1.0.1a0.dist-info}/METADATA +16 -3
- {siibra-1.0a19.dist-info → siibra-1.0.1a0.dist-info}/RECORD +38 -38
- {siibra-1.0a19.dist-info → siibra-1.0.1a0.dist-info}/WHEEL +1 -1
- {siibra-1.0a19.dist-info → siibra-1.0.1a0.dist-info}/LICENSE +0 -0
- {siibra-1.0a19.dist-info → siibra-1.0.1a0.dist-info}/top_level.txt +0 -0
|
@@ -32,8 +32,7 @@ from ..commons import (
|
|
|
32
32
|
generate_uuid
|
|
33
33
|
)
|
|
34
34
|
from ..core import concept, space, parcellation, region as _region
|
|
35
|
-
from ..locations import location, point,
|
|
36
|
-
from ..retrieval import requests
|
|
35
|
+
from ..locations import location, point, pointcloud
|
|
37
36
|
|
|
38
37
|
import numpy as np
|
|
39
38
|
from typing import Union, Dict, List, TYPE_CHECKING, Iterable, Tuple
|
|
@@ -160,6 +159,11 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
160
159
|
logger.warning(f"Non unique indices encountered in {self}: {duplicates}")
|
|
161
160
|
self._affine_cached = None
|
|
162
161
|
|
|
162
|
+
@property
|
|
163
|
+
def key(self):
|
|
164
|
+
_id = self.id
|
|
165
|
+
return create_key(_id[len("siibra-map-v0.0.1"):])
|
|
166
|
+
|
|
163
167
|
@property
|
|
164
168
|
def species(self) -> Species:
|
|
165
169
|
# lazy implementation
|
|
@@ -319,69 +323,27 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
319
323
|
def regions(self):
|
|
320
324
|
return list(self._indices)
|
|
321
325
|
|
|
322
|
-
def
|
|
326
|
+
def get_volume(
|
|
323
327
|
self,
|
|
324
|
-
|
|
328
|
+
region: Union[str, "Region"] = None,
|
|
325
329
|
*,
|
|
326
330
|
index: MapIndex = None,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
):
|
|
330
|
-
"""
|
|
331
|
-
Fetches one particular volume of this parcellation map.
|
|
332
|
-
|
|
333
|
-
If there's only one volume, this is the default, otherwise further
|
|
334
|
-
specification is requested:
|
|
335
|
-
- the volume index,
|
|
336
|
-
- the MapIndex (which results in a regional map being returned)
|
|
337
|
-
|
|
338
|
-
You might also consider fetch_iter() to iterate the volumes, or
|
|
339
|
-
compress() to produce a single-volume parcellation map.
|
|
340
|
-
|
|
341
|
-
Parameters
|
|
342
|
-
----------
|
|
343
|
-
region_or_index: str, Region, MapIndex
|
|
344
|
-
Lazy match the specification.
|
|
345
|
-
index: MapIndex
|
|
346
|
-
Explicit specification of the map index, typically resulting
|
|
347
|
-
in a regional map (mask or statistical map) to be returned.
|
|
348
|
-
Note that supplying 'region' will result in retrieving the map index of that region
|
|
349
|
-
automatically.
|
|
350
|
-
region: str, Region
|
|
351
|
-
Specification of a region name, resulting in a regional map
|
|
352
|
-
(mask or statistical map) to be returned.
|
|
353
|
-
**kwargs
|
|
354
|
-
- resolution_mm: resolution in millimeters
|
|
355
|
-
- format: the format of the volume, like "mesh" or "nii"
|
|
356
|
-
- voi: a BoundingBox of interest
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
Note
|
|
360
|
-
----
|
|
361
|
-
Not all keyword arguments are supported for volume formats. Format
|
|
362
|
-
is restricted by available formats (check formats property).
|
|
363
|
-
|
|
364
|
-
Returns
|
|
365
|
-
-------
|
|
366
|
-
An image or mesh
|
|
367
|
-
"""
|
|
331
|
+
**kwargs,
|
|
332
|
+
) -> Union[_volume.Volume, _volume.FilteredVolume, _volume.Subvolume]:
|
|
368
333
|
try:
|
|
369
|
-
length = len([arg for arg in [
|
|
334
|
+
length = len([arg for arg in [region, index] if arg is not None])
|
|
370
335
|
assert length == 1
|
|
371
336
|
except AssertionError:
|
|
372
337
|
if length > 1:
|
|
373
|
-
raise exceptions.ExcessiveArgumentException(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if isinstance(region_or_index, MapIndex):
|
|
377
|
-
index = region_or_index
|
|
378
|
-
|
|
379
|
-
if isinstance(region_or_index, (str, _region.Region)):
|
|
380
|
-
region = region_or_index
|
|
381
|
-
|
|
338
|
+
raise exceptions.ExcessiveArgumentException(
|
|
339
|
+
"One and only one of region or index can be defined for `get_volume`."
|
|
340
|
+
)
|
|
382
341
|
mapindex = None
|
|
383
342
|
if region is not None:
|
|
384
|
-
|
|
343
|
+
try:
|
|
344
|
+
assert isinstance(region, (str, _region.Region))
|
|
345
|
+
except AssertionError:
|
|
346
|
+
raise TypeError(f"Please provide a region name or region instance, not a {type(region)}")
|
|
385
347
|
mapindex = self.get_index(region)
|
|
386
348
|
if index is not None:
|
|
387
349
|
assert isinstance(index, MapIndex)
|
|
@@ -390,19 +352,17 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
390
352
|
if len(self) == 1:
|
|
391
353
|
mapindex = MapIndex(volume=0, label=None)
|
|
392
354
|
elif len(self) > 1:
|
|
355
|
+
assert self.maptype == MapType.LABELLED, f"Cannot merge multiple volumes of map type {self.maptype}. Please specify a region or index."
|
|
393
356
|
logger.info(
|
|
394
357
|
"Map provides multiple volumes and no specification is"
|
|
395
358
|
" provided. Resampling all volumes to the space."
|
|
396
359
|
)
|
|
397
360
|
labels = list(range(len(self.volumes)))
|
|
398
361
|
merged_volume = _volume.merge(self.volumes, labels, **kwargs)
|
|
399
|
-
return merged_volume
|
|
362
|
+
return merged_volume
|
|
400
363
|
else:
|
|
401
364
|
raise exceptions.NoVolumeFound("Map provides no volumes.")
|
|
402
365
|
|
|
403
|
-
if "resolution_mm" in kwargs and kwargs.get("format") is None:
|
|
404
|
-
kwargs["format"] = 'neuroglancer/precomputed'
|
|
405
|
-
|
|
406
366
|
kwargs_fragment = kwargs.pop("fragment", None)
|
|
407
367
|
if kwargs_fragment is not None:
|
|
408
368
|
if (mapindex.fragment is not None) and (kwargs_fragment != mapindex.fragment):
|
|
@@ -418,17 +378,60 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
418
378
|
raise IndexError(
|
|
419
379
|
f"{self} provides {len(self)} mapped volumes, but #{mapindex.volume} was requested."
|
|
420
380
|
)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
381
|
+
if mapindex.label is None and mapindex.fragment is None:
|
|
382
|
+
return self.volumes[mapindex.volume]
|
|
383
|
+
|
|
384
|
+
return _volume.FilteredVolume(
|
|
385
|
+
parent_volume=self.volumes[mapindex.volume],
|
|
386
|
+
label=mapindex.label,
|
|
387
|
+
fragment=mapindex.fragment,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
def fetch(
|
|
391
|
+
self,
|
|
392
|
+
region: Union[str, "Region"] = None,
|
|
393
|
+
*,
|
|
394
|
+
index: MapIndex = None,
|
|
395
|
+
**fetch_kwargs
|
|
396
|
+
):
|
|
397
|
+
"""
|
|
398
|
+
Fetches one particular volume of this parcellation map.
|
|
399
|
+
|
|
400
|
+
If there's only one volume, this is the default, otherwise further
|
|
401
|
+
specification is requested:
|
|
402
|
+
- the volume index,
|
|
403
|
+
- the MapIndex (which results in a regional map being returned)
|
|
404
|
+
|
|
405
|
+
You might also consider fetch_iter() to iterate the volumes, or
|
|
406
|
+
compress() to produce a single-volume parcellation map.
|
|
407
|
+
|
|
408
|
+
Parameters
|
|
409
|
+
----------
|
|
410
|
+
region: str, Region
|
|
411
|
+
Specification of a region name, resulting in a regional map
|
|
412
|
+
(mask or statistical map) to be returned.
|
|
413
|
+
index: MapIndex
|
|
414
|
+
Explicit specification of the map index, typically resulting
|
|
415
|
+
in a regional map (mask or statistical map) to be returned.
|
|
416
|
+
Note that supplying 'region' will result in retrieving the map index of that region
|
|
417
|
+
automatically.
|
|
418
|
+
**fetch_kwargs
|
|
419
|
+
- resolution_mm: resolution in millimeters
|
|
420
|
+
- format: the format of the volume, like "mesh" or "nii"
|
|
421
|
+
- voi: a BoundingBox of interest
|
|
427
422
|
|
|
428
|
-
if result is None:
|
|
429
|
-
raise RuntimeError(f"Error fetching {mapindex} from {self} as {kwargs.get('format', f'{self.formats}')}.")
|
|
430
423
|
|
|
431
|
-
|
|
424
|
+
Note
|
|
425
|
+
----
|
|
426
|
+
Not all keyword arguments are supported for volume formats. Format
|
|
427
|
+
is restricted by available formats (check formats property).
|
|
428
|
+
|
|
429
|
+
Returns
|
|
430
|
+
-------
|
|
431
|
+
An image or mesh
|
|
432
|
+
"""
|
|
433
|
+
vol = self.get_volume(region=region, index=index, **fetch_kwargs)
|
|
434
|
+
return vol.fetch(**fetch_kwargs)
|
|
432
435
|
|
|
433
436
|
def fetch_iter(self, **kwargs):
|
|
434
437
|
"""
|
|
@@ -531,7 +534,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
531
534
|
disable=(len(self.fragments) == 1 or self.fragments is None)
|
|
532
535
|
):
|
|
533
536
|
mapindex = MapIndex(volume=volidx, fragment=frag)
|
|
534
|
-
img = self.fetch(mapindex)
|
|
537
|
+
img = self.fetch(index=mapindex)
|
|
535
538
|
if np.allclose(img.affine, result_affine):
|
|
536
539
|
img_data = np.asanyarray(img.dataobj)
|
|
537
540
|
else:
|
|
@@ -572,11 +575,11 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
572
575
|
)]
|
|
573
576
|
)
|
|
574
577
|
|
|
575
|
-
def compute_centroids(self, split_components: bool = True) -> Dict[str,
|
|
578
|
+
def compute_centroids(self, split_components: bool = True, **fetch_kwargs) -> Dict[str, pointcloud.PointCloud]:
|
|
576
579
|
"""
|
|
577
580
|
Compute a dictionary of all regions in this map to their centroids.
|
|
578
581
|
By default, the regional masks will be split to connected components
|
|
579
|
-
and each point in the
|
|
582
|
+
and each point in the PointCloud corresponds to a region component.
|
|
580
583
|
|
|
581
584
|
Parameters
|
|
582
585
|
----------
|
|
@@ -589,6 +592,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
589
592
|
Dict[str, point.Point]
|
|
590
593
|
Region names as keys and computed centroids as items.
|
|
591
594
|
"""
|
|
595
|
+
assert self.provides_image, "Centroid computation for meshes is not supported yet."
|
|
592
596
|
centroids = dict()
|
|
593
597
|
for regionname, indexlist in siibra_tqdm(
|
|
594
598
|
self._indices.items(), unit="regions", desc="Computing centroids"
|
|
@@ -600,7 +604,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
600
604
|
merged_volume = _volume.merge(
|
|
601
605
|
[
|
|
602
606
|
_volume.from_nifti(
|
|
603
|
-
self.fetch(index=index),
|
|
607
|
+
self.fetch(index=index, **fetch_kwargs),
|
|
604
608
|
self.space,
|
|
605
609
|
f"{self.name} - {index}"
|
|
606
610
|
)
|
|
@@ -611,13 +615,16 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
611
615
|
mapimg = merged_volume.fetch()
|
|
612
616
|
elif len(indexlist) == 1:
|
|
613
617
|
index = indexlist[0]
|
|
614
|
-
mapimg = self.fetch(index=index) # returns a mask of the region
|
|
618
|
+
mapimg = self.fetch(index=index, **fetch_kwargs) # returns a mask of the region
|
|
615
619
|
props = _volume.ComponentSpatialProperties.compute_from_image(
|
|
616
620
|
img=mapimg,
|
|
617
621
|
space=self.space,
|
|
618
622
|
split_components=split_components,
|
|
619
623
|
)
|
|
620
|
-
|
|
624
|
+
try:
|
|
625
|
+
centroids[regionname] = pointcloud.from_points([c.centroid for c in props])
|
|
626
|
+
except exceptions.EmptyPointCloudError:
|
|
627
|
+
centroids[regionname] = None
|
|
621
628
|
return centroids
|
|
622
629
|
|
|
623
630
|
def get_resampled_template(self, **fetch_kwargs) -> _volume.Volume:
|
|
@@ -742,7 +749,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
742
749
|
|
|
743
750
|
Returns
|
|
744
751
|
-------
|
|
745
|
-
|
|
752
|
+
PointCloud
|
|
746
753
|
Sample points in physcial coordinates corresponding to this
|
|
747
754
|
parcellationmap
|
|
748
755
|
"""
|
|
@@ -760,7 +767,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
760
767
|
np.unravel_index(np.random.choice(len(p), numpoints, p=p), W.shape)
|
|
761
768
|
).T
|
|
762
769
|
XYZ = np.dot(mask.affine, np.c_[XYZ_, np.ones(numpoints)].T)[:3, :].T
|
|
763
|
-
return
|
|
770
|
+
return pointcloud.PointCloud(XYZ, space=self.space)
|
|
764
771
|
|
|
765
772
|
def to_sparse(self):
|
|
766
773
|
"""
|
|
@@ -831,10 +838,10 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
831
838
|
|
|
832
839
|
if isinstance(item, point.Point):
|
|
833
840
|
return self._assign_points(
|
|
834
|
-
|
|
841
|
+
pointcloud.PointCloud([item], item.space, sigma_mm=item.sigma),
|
|
835
842
|
lower_threshold
|
|
836
843
|
)
|
|
837
|
-
if isinstance(item,
|
|
844
|
+
if isinstance(item, pointcloud.PointCloud):
|
|
838
845
|
return self._assign_points(item, lower_threshold)
|
|
839
846
|
if isinstance(item, _volume.Volume):
|
|
840
847
|
return self._assign_volume(
|
|
@@ -989,9 +996,9 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
989
996
|
.dropna(axis='columns', how='all')
|
|
990
997
|
)
|
|
991
998
|
|
|
992
|
-
def _assign_points(self, points:
|
|
999
|
+
def _assign_points(self, points: pointcloud.PointCloud, lower_threshold: float) -> List[MapAssignment]:
|
|
993
1000
|
"""
|
|
994
|
-
assign a
|
|
1001
|
+
assign a PointCloud to this parcellation map.
|
|
995
1002
|
|
|
996
1003
|
Parameters
|
|
997
1004
|
-----------
|
|
@@ -37,6 +37,37 @@ from typing import Union, Dict, Tuple
|
|
|
37
37
|
import json
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
def shift_ng_transfrom(
|
|
41
|
+
transform_nm: np.ndarray, scale_resolution_nm: np.ndarray, max_resolution_nm: np.ndarray
|
|
42
|
+
) -> np.ndarray:
|
|
43
|
+
"""
|
|
44
|
+
Helper method to get nifti standard affine.
|
|
45
|
+
|
|
46
|
+
transfrorm.json stored with neuroglancer precomputed images and meshes
|
|
47
|
+
are meant to be used for neuroglancer viewers and hence they are not
|
|
48
|
+
representative of the affine in other tools. This method shifts back
|
|
49
|
+
half a voxel in each axis.
|
|
50
|
+
(see https://neuroglancer-scripts.readthedocs.io/en/latest/neuroglancer-info.html#different-conventions-for-coordinate-transformations)
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
transform_nm: np.ndarray
|
|
55
|
+
Transform array created for dispalying an image correctly from
|
|
56
|
+
neuroglancer precomputed format in neuroglancer viewer.
|
|
57
|
+
max_resolution_nm: np.ndarray
|
|
58
|
+
The voxel resolution of the highest level of resolution.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
np.ndarray
|
|
63
|
+
Standard affine in nm
|
|
64
|
+
"""
|
|
65
|
+
scaling = np.diag(np.r_[scale_resolution_nm, 1.0])
|
|
66
|
+
affine = np.dot(transform_nm, scaling)
|
|
67
|
+
affine[:3, 3] += (max_resolution_nm * 0.5)
|
|
68
|
+
return affine
|
|
69
|
+
|
|
70
|
+
|
|
40
71
|
class NeuroglancerProvider(_provider.VolumeProvider, srctype="neuroglancer/precomputed"):
|
|
41
72
|
|
|
42
73
|
def __init__(self, url: Union[str, Dict[str, str]]):
|
|
@@ -128,26 +159,20 @@ class NeuroglancerProvider(_provider.VolumeProvider, srctype="neuroglancer/preco
|
|
|
128
159
|
label = None
|
|
129
160
|
if label is not None:
|
|
130
161
|
result = nib.Nifti1Image(
|
|
131
|
-
(result.
|
|
132
|
-
result.affine
|
|
162
|
+
(np.asanyarray(result.dataobj) == label).astype('uint8'),
|
|
163
|
+
result.affine,
|
|
164
|
+
dtype='uint8',
|
|
133
165
|
)
|
|
134
166
|
|
|
135
167
|
return result
|
|
136
168
|
|
|
137
|
-
def get_boundingbox(self,
|
|
169
|
+
def get_boundingbox(self, **fetch_kwargs) -> "_boundingbox.BoundingBox":
|
|
138
170
|
"""
|
|
139
171
|
Return the bounding box in physical coordinates of the union of
|
|
140
172
|
fragments in this neuroglancer volume.
|
|
141
173
|
|
|
142
174
|
Parameters
|
|
143
175
|
----------
|
|
144
|
-
clip: bool, default: True
|
|
145
|
-
Whether to clip the background of the volume.
|
|
146
|
-
background: float, default: 0.0
|
|
147
|
-
The background value to clip.
|
|
148
|
-
Note
|
|
149
|
-
----
|
|
150
|
-
To use it, clip must be True.
|
|
151
176
|
fetch_kwargs:
|
|
152
177
|
key word arguments that are used for fetchin volumes,
|
|
153
178
|
such as voi or resolution_mm.
|
|
@@ -159,23 +184,17 @@ class NeuroglancerProvider(_provider.VolumeProvider, srctype="neuroglancer/preco
|
|
|
159
184
|
f"N-D Neuroglancer volume has shape {frag.shape}, but "
|
|
160
185
|
f"bounding box considers only {frag.shape[:3]}"
|
|
161
186
|
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
).transform(img.affine) # use the affine of the image matching fetch_kwargs
|
|
187
|
+
resolution_mm = fetch_kwargs.get("resolution_mm")
|
|
188
|
+
if resolution_mm is None:
|
|
189
|
+
affine = frag.affine
|
|
190
|
+
shape = frag.shape[:3]
|
|
167
191
|
else:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
affine = scale.affine
|
|
175
|
-
shape = scale.size[:3]
|
|
176
|
-
next_bbox = _boundingbox.BoundingBox(
|
|
177
|
-
(0, 0, 0), shape, space=None
|
|
178
|
-
).transform(affine)
|
|
192
|
+
scale = frag._select_scale(resolution_mm=resolution_mm)
|
|
193
|
+
affine = scale.affine
|
|
194
|
+
shape = scale.size[:3]
|
|
195
|
+
next_bbox = _boundingbox.BoundingBox(
|
|
196
|
+
(0, 0, 0), shape, space=None
|
|
197
|
+
).transform(affine)
|
|
179
198
|
bbox = next_bbox if bbox is None else bbox.union(next_bbox)
|
|
180
199
|
return bbox
|
|
181
200
|
|
|
@@ -246,7 +265,11 @@ class NeuroglancerVolume:
|
|
|
246
265
|
self._io: PrecomputedIO = None
|
|
247
266
|
|
|
248
267
|
@property
|
|
249
|
-
def transform_nm(self):
|
|
268
|
+
def transform_nm(self) -> np.ndarray:
|
|
269
|
+
"""
|
|
270
|
+
This is the transformation matrix created to cater neuroglancer viewer
|
|
271
|
+
for a neuroglancer precomputed images.
|
|
272
|
+
"""
|
|
250
273
|
if self._transform_nm is not None:
|
|
251
274
|
return self._transform_nm
|
|
252
275
|
try:
|
|
@@ -326,7 +349,7 @@ class NeuroglancerVolume:
|
|
|
326
349
|
):
|
|
327
350
|
# the caller has to make sure voi is defined in the correct reference space
|
|
328
351
|
scale = self._select_scale(resolution_mm=resolution_mm, bbox=voi, max_bytes=max_bytes)
|
|
329
|
-
return scale.fetch(voi=voi)
|
|
352
|
+
return scale.fetch(voi=voi, **kwargs)
|
|
330
353
|
|
|
331
354
|
def get_shape(self, resolution_mm=None, max_bytes: float = MAX_BYTES):
|
|
332
355
|
scale = self._select_scale(resolution_mm=resolution_mm, max_bytes=max_bytes)
|
|
@@ -445,10 +468,13 @@ class NeuroglancerScale:
|
|
|
445
468
|
|
|
446
469
|
@property
|
|
447
470
|
def affine(self):
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
471
|
+
affine_ = shift_ng_transfrom(
|
|
472
|
+
transform_nm=self.volume.transform_nm,
|
|
473
|
+
scale_resolution_nm=self.res_nm,
|
|
474
|
+
max_resolution_nm=self.volume.scales[0].res_nm[0],
|
|
475
|
+
)
|
|
476
|
+
affine_[:3, :] /= 1e6
|
|
477
|
+
return affine_
|
|
452
478
|
|
|
453
479
|
def _point_to_lower_chunk_idx(self, xyz):
|
|
454
480
|
return (
|
|
@@ -508,9 +534,9 @@ class NeuroglancerScale:
|
|
|
508
534
|
for dim in range(3):
|
|
509
535
|
if bbox_.shape[dim] < 1:
|
|
510
536
|
logger.warning(
|
|
511
|
-
f"Bounding box in voxel space will be enlarged to
|
|
537
|
+
f"Bounding box in voxel space will be enlarged to by {self.res_mm[dim]} along axis {dim}."
|
|
512
538
|
)
|
|
513
|
-
bbox_.maxpoint[dim] = bbox_.maxpoint[dim] +
|
|
539
|
+
bbox_.maxpoint[dim] = bbox_.maxpoint[dim] + self.res_mm[dim]
|
|
514
540
|
|
|
515
541
|
# extract minimum and maximum the chunk indices to be loaded
|
|
516
542
|
gx0, gy0, gz0 = self._point_to_lower_chunk_idx(tuple(bbox_.minpoint))
|
|
@@ -533,7 +559,7 @@ class NeuroglancerScale:
|
|
|
533
559
|
# exact bounding box requested, to cut off undesired borders
|
|
534
560
|
data_min = np.array([gx0, gy0, gz0]) * self.chunk_sizes
|
|
535
561
|
x0, y0, z0 = (np.array(bbox_.minpoint) - data_min).astype("int")
|
|
536
|
-
xd, yd, zd = np.ceil((np.array(bbox_.maxpoint))).astype(int) - np.floor((np.array(bbox_.minpoint))).astype(int)
|
|
562
|
+
xd, yd, zd = np.ceil((np.array(bbox_.maxpoint))).astype(int) - np.floor((np.array(bbox_.minpoint))).astype(int)
|
|
537
563
|
offset = tuple(bbox_.minpoint)
|
|
538
564
|
|
|
539
565
|
# build the nifti image
|
|
@@ -552,7 +578,7 @@ class NeuroglancerMesh(_provider.VolumeProvider, srctype="neuroglancer/precompme
|
|
|
552
578
|
|
|
553
579
|
@staticmethod
|
|
554
580
|
def _fragmentinfo(url: str) -> Dict[str, Union[str, np.ndarray, Dict]]:
|
|
555
|
-
"""
|
|
581
|
+
"""Prepare basic mesh fragment information from url."""
|
|
556
582
|
return {
|
|
557
583
|
"url": url,
|
|
558
584
|
"transform_nm": np.array(requests.HttpRequest(f"{url}/transform.json").data),
|
|
@@ -17,7 +17,7 @@ from . import provider as _provider
|
|
|
17
17
|
|
|
18
18
|
from ...commons import logger, resample_img_to_img
|
|
19
19
|
from ...retrieval import requests
|
|
20
|
-
from ...locations import
|
|
20
|
+
from ...locations import pointcloud, boundingbox as _boundingbox
|
|
21
21
|
|
|
22
22
|
from typing import Union, Dict, Tuple
|
|
23
23
|
import nibabel as nib
|
|
@@ -65,28 +65,16 @@ class NiftiProvider(_provider.VolumeProvider, srctype="nii"):
|
|
|
65
65
|
def fragments(self):
|
|
66
66
|
return [k for k in self._img_loaders if k is not None]
|
|
67
67
|
|
|
68
|
-
def get_boundingbox(self,
|
|
68
|
+
def get_boundingbox(self, **fetch_kwargs) -> "_boundingbox.BoundingBox":
|
|
69
69
|
"""
|
|
70
70
|
Return the bounding box in physical coordinates of the union of
|
|
71
71
|
fragments in this nifti volume.
|
|
72
72
|
|
|
73
73
|
Parameters
|
|
74
74
|
----------
|
|
75
|
-
clip : bool, default: True
|
|
76
|
-
Whether to clip the background of the volume.
|
|
77
|
-
background : float, default: 0.0
|
|
78
|
-
The background value to clip.
|
|
79
|
-
Note
|
|
80
|
-
----
|
|
81
|
-
To use it, clip must be True.
|
|
82
75
|
fetch_kwargs:
|
|
83
76
|
Not used
|
|
84
77
|
"""
|
|
85
|
-
if fetch_kwargs:
|
|
86
|
-
logger.warning(
|
|
87
|
-
"`volume.fetch()` keyword arguments supplied. Nifti volumes"
|
|
88
|
-
" cannot pass them for bounding box calculation."
|
|
89
|
-
)
|
|
90
78
|
bbox = None
|
|
91
79
|
for loader in self._img_loaders.values():
|
|
92
80
|
img = loader()
|
|
@@ -95,19 +83,17 @@ class NiftiProvider(_provider.VolumeProvider, srctype="nii"):
|
|
|
95
83
|
f"N-D NIfTI volume has shape {img.shape}, but "
|
|
96
84
|
f"bounding box considers only {img.shape[:3]}"
|
|
97
85
|
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
else:
|
|
103
|
-
shape = img.shape[:3]
|
|
104
|
-
next_bbox = _boundingbox.BoundingBox(
|
|
105
|
-
(0, 0, 0), shape, space=None
|
|
106
|
-
).transform(img.affine)
|
|
86
|
+
shape = img.shape[:3]
|
|
87
|
+
next_bbox = _boundingbox.BoundingBox(
|
|
88
|
+
(0, 0, 0), shape, space=None
|
|
89
|
+
).transform(img.affine)
|
|
107
90
|
bbox = next_bbox if bbox is None else bbox.union(next_bbox)
|
|
108
91
|
return bbox
|
|
109
92
|
|
|
110
93
|
def _merge_fragments(self) -> nib.Nifti1Image:
|
|
94
|
+
"""
|
|
95
|
+
Merge all fragments this volume contains into one Nifti1Image.
|
|
96
|
+
"""
|
|
111
97
|
bbox = self.get_boundingbox(clip=False, background=0.0)
|
|
112
98
|
num_conflicts = 0
|
|
113
99
|
result = None
|
|
@@ -243,7 +229,7 @@ class NiftiProvider(_provider.VolumeProvider, srctype="nii"):
|
|
|
243
229
|
|
|
244
230
|
Returns:
|
|
245
231
|
--------
|
|
246
|
-
|
|
232
|
+
PointCloud
|
|
247
233
|
"""
|
|
248
234
|
|
|
249
235
|
from skimage.feature.peak import peak_local_max
|
|
@@ -257,7 +243,7 @@ class NiftiProvider(_provider.VolumeProvider, srctype="nii"):
|
|
|
257
243
|
min_distance=dist,
|
|
258
244
|
)
|
|
259
245
|
return (
|
|
260
|
-
|
|
246
|
+
pointcloud.PointCloud(
|
|
261
247
|
[np.dot(img.affine, [x, y, z, 1])[:3] for x, y, z in voxels],
|
|
262
248
|
space=self.space,
|
|
263
249
|
),
|