pytme 0.2.0b0__cp311-cp311-macosx_14_0_arm64.whl → 0.2.2__cp311-cp311-macosx_14_0_arm64.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.
- pytme-0.2.2.data/scripts/match_template.py +1187 -0
- {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/postprocess.py +170 -71
- {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/preprocessor_gui.py +179 -86
- pytme-0.2.2.dist-info/METADATA +91 -0
- pytme-0.2.2.dist-info/RECORD +74 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/WHEEL +1 -1
- scripts/extract_candidates.py +126 -87
- scripts/match_template.py +596 -209
- scripts/match_template_filters.py +571 -223
- scripts/postprocess.py +170 -71
- scripts/preprocessor_gui.py +179 -86
- scripts/refine_matches.py +567 -159
- tme/__init__.py +0 -1
- tme/__version__.py +1 -1
- tme/analyzer.py +627 -855
- tme/backends/__init__.py +41 -11
- tme/backends/_jax_utils.py +185 -0
- tme/backends/cupy_backend.py +120 -225
- tme/backends/jax_backend.py +282 -0
- tme/backends/matching_backend.py +464 -388
- tme/backends/mlx_backend.py +45 -68
- tme/backends/npfftw_backend.py +256 -514
- tme/backends/pytorch_backend.py +41 -154
- tme/density.py +312 -421
- tme/extensions.cpython-311-darwin.so +0 -0
- tme/matching_data.py +366 -303
- tme/matching_exhaustive.py +279 -1521
- tme/matching_optimization.py +234 -129
- tme/matching_scores.py +884 -0
- tme/matching_utils.py +281 -387
- tme/memory.py +377 -0
- tme/orientations.py +226 -66
- tme/parser.py +3 -4
- tme/preprocessing/__init__.py +2 -0
- tme/preprocessing/_utils.py +217 -0
- tme/preprocessing/composable_filter.py +31 -0
- tme/preprocessing/compose.py +55 -0
- tme/preprocessing/frequency_filters.py +388 -0
- tme/preprocessing/tilt_series.py +1011 -0
- tme/preprocessor.py +574 -530
- tme/structure.py +495 -189
- tme/types.py +5 -3
- pytme-0.2.0b0.data/scripts/match_template.py +0 -800
- pytme-0.2.0b0.dist-info/METADATA +0 -73
- pytme-0.2.0b0.dist-info/RECORD +0 -66
- tme/helpers.py +0 -881
- tme/matching_constrained.py +0 -195
- {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/estimate_ram_usage.py +0 -0
- {pytme-0.2.0b0.data → pytme-0.2.2.data}/scripts/preprocess.py +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/LICENSE +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/entry_points.txt +0 -0
- {pytme-0.2.0b0.dist-info → pytme-0.2.2.dist-info}/top_level.txt +0 -0
tme/density.py
CHANGED
@@ -18,70 +18,72 @@ import numpy as np
|
|
18
18
|
import skimage.io as skio
|
19
19
|
|
20
20
|
from scipy.ndimage import (
|
21
|
+
zoom,
|
21
22
|
laplace,
|
22
|
-
generic_gradient_magnitude,
|
23
|
-
minimum_filter,
|
24
23
|
sobel,
|
24
|
+
minimum_filter,
|
25
25
|
binary_erosion,
|
26
|
-
|
26
|
+
generic_gradient_magnitude,
|
27
27
|
)
|
28
28
|
from scipy.spatial import ConvexHull
|
29
29
|
|
30
|
+
from .types import NDArray
|
31
|
+
from .backends import NumpyFFTWBackend
|
30
32
|
from .structure import Structure
|
31
33
|
from .matching_utils import (
|
32
|
-
minimum_enclosing_box,
|
33
34
|
array_to_memmap,
|
34
35
|
memmap_to_array,
|
36
|
+
minimum_enclosing_box,
|
35
37
|
)
|
36
|
-
from .types import NDArray
|
37
|
-
from .helpers import is_gzipped
|
38
|
-
from .backends import NumpyFFTWBackend
|
39
38
|
|
40
39
|
|
41
40
|
class Density:
|
42
41
|
"""
|
43
|
-
|
42
|
+
Abstract representation of N-dimensional densities.
|
44
43
|
|
45
44
|
Parameters
|
46
45
|
----------
|
47
|
-
data :
|
48
|
-
|
49
|
-
origin :
|
50
|
-
Origin of the coordinate system
|
51
|
-
sampling_rate :
|
52
|
-
Sampling rate along data axis
|
46
|
+
data : array_like
|
47
|
+
Array of densities.
|
48
|
+
origin : array_like, optional
|
49
|
+
Origin of the coordinate system, zero by default.
|
50
|
+
sampling_rate : array_like, optional
|
51
|
+
Sampling rate along data axis, one by default.
|
53
52
|
metadata : dict, optional
|
54
53
|
Dictionary with metadata information, empty by default.
|
55
54
|
|
56
55
|
Raises
|
57
56
|
------
|
58
57
|
ValueError
|
59
|
-
|
58
|
+
If metadata is not a dictionary.
|
59
|
+
|
60
|
+
If sampling rate / origin is not defined for a single or all axes.
|
60
61
|
|
61
62
|
Examples
|
62
63
|
--------
|
63
|
-
The following achieves the minimal definition of a :py:class:`Density` instance
|
64
|
+
The following achieves the minimal definition of a :py:class:`Density` instance
|
64
65
|
|
65
66
|
>>> import numpy as np
|
66
67
|
>>> from tme import Density
|
67
68
|
>>> data = np.random.rand(50,70,40)
|
68
|
-
>>> Density(data
|
69
|
+
>>> Density(data=data)
|
69
70
|
|
70
|
-
Optional parameters
|
71
|
-
to the
|
72
|
-
|
73
|
-
:py:attr:`Density.origin` is set to zero
|
74
|
-
to
|
71
|
+
Optional parameters ``origin`` correspond to the coordinate system reference,
|
72
|
+
``sampling_rate`` to the spatial length per axis element, and ``metadata`` to
|
73
|
+
a dictionary with supplementary information. By default,
|
74
|
+
:py:attr:`Density.origin` is set to zero, :py:attr:`Density.sampling_rate`
|
75
|
+
to one, and :py:attr:`Density.metadata` is an empty dictionary. If provided,
|
76
|
+
``origin`` and ``sampling_rate`` either need to be a single value
|
75
77
|
|
76
|
-
>>> Density(data
|
78
|
+
>>> Density(data=data, origin=0, sampling_rate=1)
|
77
79
|
|
78
|
-
|
80
|
+
be specified along each data axis
|
79
81
|
|
80
|
-
>>> Density(data
|
82
|
+
>>> Density(data=data, origin=(0, 0, 0), sampling_rate=(1.5, 1.1, 1.2))
|
81
83
|
|
82
|
-
|
84
|
+
or be a combination of both
|
83
85
|
|
84
|
-
>>> Density(data
|
86
|
+
>>> Density(data=data, origin=0, sampling_rate=(1.5, 1.1, 1.2))
|
85
87
|
"""
|
86
88
|
|
87
89
|
def __init__(
|
@@ -111,7 +113,7 @@ class Density:
|
|
111
113
|
self.metadata = metadata
|
112
114
|
|
113
115
|
def __repr__(self):
|
114
|
-
response = "Density object at {}\nOrigin: {},
|
116
|
+
response = "Density object at {}\nOrigin: {}, Sampling Rate: {}, Shape: {}"
|
115
117
|
return response.format(
|
116
118
|
hex(id(self)),
|
117
119
|
tuple(np.round(self.origin, 3)),
|
@@ -124,22 +126,22 @@ class Density:
|
|
124
126
|
cls, filename: str, subset: Tuple[slice] = None, use_memmap: bool = False
|
125
127
|
) -> "Density":
|
126
128
|
"""
|
127
|
-
Reads
|
129
|
+
Reads a file into a :py:class:`Density` instance.
|
128
130
|
|
129
131
|
Parameters
|
130
132
|
----------
|
131
133
|
filename : str
|
132
134
|
Path to a file in CCP4/MRC, EM, HDF5 or a format supported by
|
133
|
-
skimage.io.imread
|
135
|
+
:obj:`skimage.io.imread`. The file can be gzip compressed.
|
134
136
|
subset : tuple of slices, optional
|
135
137
|
Slices representing the desired subset along each dimension.
|
136
138
|
use_memmap : bool, optional
|
137
|
-
|
139
|
+
Memory map the data contained in ``filename`` to save memory.
|
138
140
|
|
139
141
|
Returns
|
140
142
|
-------
|
141
|
-
Density
|
142
|
-
|
143
|
+
:py:class:`Density`
|
144
|
+
Class instance representing the data in ``filename``.
|
143
145
|
|
144
146
|
References
|
145
147
|
----------
|
@@ -149,7 +151,7 @@ class Density:
|
|
149
151
|
|
150
152
|
Examples
|
151
153
|
--------
|
152
|
-
:py:meth:`Density.from_file` reads files in
|
154
|
+
:py:meth:`Density.from_file` reads files in CCP4/MRC, EM, or a format supported
|
153
155
|
by skimage.io.imread and converts them into a :py:class:`Density` instance. The
|
154
156
|
following outlines how to read a file in the CCP4/MRC format [1]_:
|
155
157
|
|
@@ -163,9 +165,9 @@ class Density:
|
|
163
165
|
>>> subset_slices = (slice(0, 50), slice(0, 50), slice(0, 50))
|
164
166
|
>>> Density.from_file("/path/to/file.mrc", subset=subset_slices)
|
165
167
|
|
166
|
-
|
167
|
-
|
168
|
-
|
168
|
+
Memory mapping can be used to read the file from disk without loading
|
169
|
+
it entirely into memory. This is particularly useful for large datasets
|
170
|
+
or when working with limited memory resources:
|
169
171
|
|
170
172
|
>>> Density.from_file("/path/to/large_file.mrc", use_memmap=True)
|
171
173
|
|
@@ -178,18 +180,17 @@ class Density:
|
|
178
180
|
>>> Density.from_file("/path/to/file.em.gz")
|
179
181
|
|
180
182
|
If the file format is not CCP4/MRC or EM, :py:meth:`Density.from_file` attempts
|
181
|
-
to use skimage.io.imread to read the file [3]_
|
183
|
+
to use :obj:`skimage.io.imread` to read the file [3]_, which does not extract
|
182
184
|
origin or sampling_rate information from the file:
|
183
185
|
|
184
186
|
>>> Density.from_file("/path/to/other_format.tif")
|
185
187
|
|
186
188
|
Notes
|
187
189
|
-----
|
188
|
-
If ``filename`` ends
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
extract origin or sampling_rate information from the file.
|
190
|
+
If ``filename`` ends ".em" or ".h5" it will be parsed as EM or HDF5 file.
|
191
|
+
Otherwise, the default reader is CCP4/MRC and on failure
|
192
|
+
:obj:`skimage.io.imread` is used regardless of extension. The later does
|
193
|
+
not extract origin or sampling_rate information from the file.
|
193
194
|
|
194
195
|
See Also
|
195
196
|
--------
|
@@ -267,9 +268,9 @@ class Density:
|
|
267
268
|
# nx := column; ny := row; nz := section
|
268
269
|
start = np.array(
|
269
270
|
[
|
270
|
-
|
271
|
-
|
272
|
-
|
271
|
+
mrc.header["nzstart"],
|
272
|
+
mrc.header["nystart"],
|
273
|
+
mrc.header["nxstart"],
|
273
274
|
]
|
274
275
|
)
|
275
276
|
|
@@ -291,17 +292,9 @@ class Density:
|
|
291
292
|
sampling_rate = mrc.voxel_size.astype(
|
292
293
|
[("x", "<f4"), ("y", "<f4"), ("z", "<f4")]
|
293
294
|
).view(("<f4", 3))
|
294
|
-
sampling_rate = sampling_rate[::-1]
|
295
|
-
sampling_rate = np.array(sampling_rate)
|
295
|
+
sampling_rate = np.array(sampling_rate)[::-1]
|
296
296
|
|
297
|
-
if np.
|
298
|
-
pass
|
299
|
-
elif np.all(origin == 0) and not np.all(start == 0):
|
300
|
-
origin = np.multiply(start, sampling_rate)
|
301
|
-
elif np.all(
|
302
|
-
np.abs(origin.astype(int))
|
303
|
-
!= np.abs((start * sampling_rate).astype(int))
|
304
|
-
) and not np.all(start == 0):
|
297
|
+
if np.allclose(origin, 0) and not np.allclose(start, 0):
|
305
298
|
origin = np.multiply(start, sampling_rate)
|
306
299
|
|
307
300
|
extended_header = mrc.header.nsymbt
|
@@ -317,12 +310,12 @@ class Density:
|
|
317
310
|
if use_memmap:
|
318
311
|
warnings.warn(
|
319
312
|
f"Cannot open gzipped file {filename} as memmap."
|
320
|
-
f" Please gunzip {filename} to use memmap functionality."
|
313
|
+
f" Please run 'gunzip {filename}' to use memmap functionality."
|
321
314
|
)
|
322
315
|
use_memmap = False
|
323
316
|
|
324
317
|
if subset is not None:
|
325
|
-
subset_shape =
|
318
|
+
subset_shape = tuple(x.stop - x.start for x in subset)
|
326
319
|
if np.allclose(subset_shape, data_shape):
|
327
320
|
return cls._load_mrc(
|
328
321
|
filename=filename, subset=None, use_memmap=use_memmap
|
@@ -345,7 +338,6 @@ class Density:
|
|
345
338
|
data = mrc.data
|
346
339
|
|
347
340
|
if not np.all(crs_index == (0, 1, 2)):
|
348
|
-
data.setflags(write=True)
|
349
341
|
data = np.transpose(data, crs_index)
|
350
342
|
start = np.take(start, crs_index)
|
351
343
|
|
@@ -650,7 +642,7 @@ class Density:
|
|
650
642
|
sampling_rate: NDArray = np.ones(1),
|
651
643
|
origin: Tuple[float] = None,
|
652
644
|
weight_type: str = "atomic_weight",
|
653
|
-
|
645
|
+
weight_type_args: Dict = {},
|
654
646
|
chain: str = None,
|
655
647
|
filter_by_elements: Set = None,
|
656
648
|
filter_by_residues: Set = None,
|
@@ -680,6 +672,8 @@ class Density:
|
|
680
672
|
weight_type : str, optional
|
681
673
|
Which weight should be given to individual atoms. For valid values
|
682
674
|
see :py:meth:`tme.structure.Structure.to_volume`.
|
675
|
+
weight_type_args : dict, optional
|
676
|
+
Additional arguments for atom weight computation.
|
683
677
|
chain : str, optional
|
684
678
|
The chain that should be extracted from the structure. If multiple chains
|
685
679
|
should be selected, they needto be a comma separated string,
|
@@ -688,8 +682,6 @@ class Density:
|
|
688
682
|
Set of atomic elements to keep. Default is all atoms.
|
689
683
|
filter_by_residues : set, optional
|
690
684
|
Set of residues to keep. Default is all residues.
|
691
|
-
scattering_args : dict, optional
|
692
|
-
Additional arguments for scattering factor computation.
|
693
685
|
|
694
686
|
Returns
|
695
687
|
-------
|
@@ -746,14 +738,14 @@ class Density:
|
|
746
738
|
>>> )
|
747
739
|
|
748
740
|
:py:meth:`Density.from_structure` supports a variety of methods to convert
|
749
|
-
atoms into densities. In
|
750
|
-
'van_der_waals_radius' its possible to use experimentally determined
|
751
|
-
factors from various sources:
|
741
|
+
atoms into densities. In addition to 'atomic_weight', 'atomic_number',
|
742
|
+
and 'van_der_waals_radius', its possible to use experimentally determined
|
743
|
+
scattering factors from various sources:
|
752
744
|
|
753
745
|
>>> density = Density.from_structure(
|
754
746
|
>>> filename_or_structure = path_to_structure,
|
755
747
|
>>> weight_type = "scattering_factors",
|
756
|
-
>>>
|
748
|
+
>>> weight_type_args={"source": "dt1969"}
|
757
749
|
>>> )
|
758
750
|
|
759
751
|
or a lowpass filtered representation introduced in [1]_:
|
@@ -761,7 +753,7 @@ class Density:
|
|
761
753
|
>>> density = Density.from_structure(
|
762
754
|
>>> filename_or_structure = path_to_structure,
|
763
755
|
>>> weight_type = "lowpass_scattering_factors",
|
764
|
-
>>>
|
756
|
+
>>> weight_type_args={"source": "dt1969"}
|
765
757
|
>>> )
|
766
758
|
|
767
759
|
See Also
|
@@ -783,27 +775,27 @@ class Density:
|
|
783
775
|
origin=origin,
|
784
776
|
chain=chain,
|
785
777
|
weight_type=weight_type,
|
786
|
-
|
778
|
+
weight_type_args=weight_type_args,
|
787
779
|
)
|
788
780
|
|
789
781
|
return cls(
|
790
782
|
data=volume,
|
791
783
|
origin=origin,
|
792
784
|
sampling_rate=sampling_rate,
|
793
|
-
metadata=structure.
|
785
|
+
metadata=structure.metadata.copy(),
|
794
786
|
)
|
795
787
|
|
796
788
|
def to_file(self, filename: str, gzip: bool = False) -> None:
|
797
789
|
"""
|
798
|
-
Writes
|
790
|
+
Writes class instance to disk.
|
799
791
|
|
800
792
|
Parameters
|
801
793
|
----------
|
802
794
|
filename : str
|
803
795
|
Path to write to.
|
804
796
|
gzip : bool, optional
|
805
|
-
|
806
|
-
|
797
|
+
Gzip compress the output and add corresponding suffix to filename
|
798
|
+
if not present. False by default.
|
807
799
|
|
808
800
|
References
|
809
801
|
----------
|
@@ -819,7 +811,7 @@ class Density:
|
|
819
811
|
>>> import numpy as np
|
820
812
|
>>> from tme import Density
|
821
813
|
>>> data = np.random.rand(50,50,50)
|
822
|
-
>>> dens = Density(data
|
814
|
+
>>> dens = Density(data=data, origin=(0, 0, 0), sampling_rate=(1, 1, 1))
|
823
815
|
>>> dens.to_file("example.mrc")
|
824
816
|
|
825
817
|
The output file can also be directly ``gzip`` compressed. The corresponding
|
@@ -834,14 +826,14 @@ class Density:
|
|
834
826
|
In addition, a variety of image file formats are supported [3]_:
|
835
827
|
|
836
828
|
>>> data = np.random.rand(50,50)
|
837
|
-
>>> dens = Density(data
|
829
|
+
>>> dens = Density(data=data, origin=(0, 0), sampling_rate=(1, 1))
|
838
830
|
>>> dens.to_file("example.tiff")
|
839
831
|
|
840
832
|
Notes
|
841
833
|
-----
|
842
|
-
If ``filename``
|
843
|
-
|
844
|
-
|
834
|
+
If ``filename`` endswith ".em" or ".h5" a EM file or HDF5 file will be created.
|
835
|
+
The default output format is CCP4/MRC and on failure, :obj:`skimage.io.imsave`
|
836
|
+
is used.
|
845
837
|
|
846
838
|
See Also
|
847
839
|
--------
|
@@ -862,7 +854,7 @@ class Density:
|
|
862
854
|
|
863
855
|
def _save_mrc(self, filename: str, gzip: bool = False) -> None:
|
864
856
|
"""
|
865
|
-
Writes
|
857
|
+
Writes class instance to disk as mrc file.
|
866
858
|
|
867
859
|
Parameters
|
868
860
|
----------
|
@@ -878,7 +870,7 @@ class Density:
|
|
878
870
|
compression = "gzip" if gzip else None
|
879
871
|
with mrcfile.new(filename, overwrite=True, compression=compression) as mrc:
|
880
872
|
mrc.set_data(self.data.astype("float32"))
|
881
|
-
mrc.header.nzstart, mrc.header.nystart, mrc.header.nxstart = np.
|
873
|
+
mrc.header.nzstart, mrc.header.nystart, mrc.header.nxstart = np.rint(
|
882
874
|
np.divide(self.origin, self.sampling_rate)
|
883
875
|
)
|
884
876
|
# mrcfile library expects origin to be in xyz format
|
@@ -982,7 +974,7 @@ class Density:
|
|
982
974
|
self.metadata["std"] = self.metadata.get("std", 0)
|
983
975
|
self.metadata["min"] = self.metadata.get("min", 0)
|
984
976
|
self.metadata["max"] = self.metadata.get("max", 0)
|
985
|
-
if
|
977
|
+
if not isinstance(self.data, np.memmap):
|
986
978
|
self.metadata["mean"] = self.data.mean()
|
987
979
|
self.metadata["std"] = self.data.std()
|
988
980
|
self.metadata["min"] = self.data.min()
|
@@ -994,11 +986,15 @@ class Density:
|
|
994
986
|
@property
|
995
987
|
def empty(self) -> "Density":
|
996
988
|
"""
|
997
|
-
Returns a copy of the
|
998
|
-
:py:attr:`Density.data` set to zero. :py:attr:`Density.
|
999
|
-
:py:attr:`Density.
|
1000
|
-
:py:attr:`Density.
|
1001
|
-
|
989
|
+
Returns a copy of the class instance with all elements in
|
990
|
+
:py:attr:`Density.data` set to zero. :py:attr:`Density.metadata` will be
|
991
|
+
initialized accordingly. :py:attr:`Density.origin` and
|
992
|
+
:py:attr:`Density.sampling_rate` are copied.
|
993
|
+
|
994
|
+
Returns
|
995
|
+
-------
|
996
|
+
:py:class:`Density`
|
997
|
+
Empty class instance.
|
1002
998
|
|
1003
999
|
Examples
|
1004
1000
|
--------
|
@@ -1018,7 +1014,12 @@ class Density:
|
|
1018
1014
|
|
1019
1015
|
def copy(self) -> "Density":
|
1020
1016
|
"""
|
1021
|
-
|
1017
|
+
Create a copy of the class instance.
|
1018
|
+
|
1019
|
+
Returns
|
1020
|
+
-------
|
1021
|
+
:py:class:`Density`
|
1022
|
+
A copy of the class instance.
|
1022
1023
|
|
1023
1024
|
Examples
|
1024
1025
|
--------
|
@@ -1037,8 +1038,7 @@ class Density:
|
|
1037
1038
|
|
1038
1039
|
def to_memmap(self) -> None:
|
1039
1040
|
"""
|
1040
|
-
Converts
|
1041
|
-
a :obj:`numpy.memmap` instance.
|
1041
|
+
Converts :py:attr:`Density.data` to a :obj:`numpy.memmap`.
|
1042
1042
|
|
1043
1043
|
Examples
|
1044
1044
|
--------
|
@@ -1063,7 +1063,7 @@ class Density:
|
|
1063
1063
|
--------
|
1064
1064
|
:py:meth:`Density.to_numpy`
|
1065
1065
|
"""
|
1066
|
-
if
|
1066
|
+
if isinstance(self.data, np.memmap):
|
1067
1067
|
return None
|
1068
1068
|
|
1069
1069
|
filename = array_to_memmap(arr=self.data)
|
@@ -1074,8 +1074,7 @@ class Density:
|
|
1074
1074
|
|
1075
1075
|
def to_numpy(self) -> None:
|
1076
1076
|
"""
|
1077
|
-
Converts
|
1078
|
-
an in-memory :obj:`numpy.ndarray`.
|
1077
|
+
Converts :py:attr:`Density.data` to an in-memory :obj:`numpy.ndarray`.
|
1079
1078
|
|
1080
1079
|
Examples
|
1081
1080
|
--------
|
@@ -1093,8 +1092,7 @@ class Density:
|
|
1093
1092
|
@property
|
1094
1093
|
def shape(self) -> Tuple[int]:
|
1095
1094
|
"""
|
1096
|
-
Returns the dimensions of
|
1097
|
-
attribute.
|
1095
|
+
Returns the dimensions of :py:attr:`Density.data`.
|
1098
1096
|
|
1099
1097
|
Returns
|
1100
1098
|
-------
|
@@ -1103,8 +1101,6 @@ class Density:
|
|
1103
1101
|
|
1104
1102
|
Examples
|
1105
1103
|
--------
|
1106
|
-
The following outlines the usage of :py:attr:`Density.shape`:
|
1107
|
-
|
1108
1104
|
>>> import numpy as np
|
1109
1105
|
>>> from tme import Density
|
1110
1106
|
>>> dens = Density(np.array([0, 1, 1, 1, 0]))
|
@@ -1116,13 +1112,12 @@ class Density:
|
|
1116
1112
|
@property
|
1117
1113
|
def data(self) -> NDArray:
|
1118
1114
|
"""
|
1119
|
-
Returns the value of
|
1120
|
-
attribute.
|
1115
|
+
Returns the value of :py:attr:`Density.data`.
|
1121
1116
|
|
1122
1117
|
Returns
|
1123
1118
|
-------
|
1124
1119
|
NDArray
|
1125
|
-
Value of the
|
1120
|
+
Value of the instance's :py:attr:`Density.data` attribute.
|
1126
1121
|
|
1127
1122
|
Examples
|
1128
1123
|
--------
|
@@ -1140,20 +1135,20 @@ class Density:
|
|
1140
1135
|
@data.setter
|
1141
1136
|
def data(self, data: NDArray) -> None:
|
1142
1137
|
"""
|
1143
|
-
Sets the value of the
|
1138
|
+
Sets the value of the instance's :py:attr:`Density.data` attribute.
|
1144
1139
|
"""
|
1145
1140
|
self._data = data
|
1146
1141
|
|
1147
1142
|
@property
|
1148
1143
|
def origin(self) -> NDArray:
|
1149
1144
|
"""
|
1150
|
-
Returns the value of the
|
1145
|
+
Returns the value of the instance's :py:attr:`Density.origin`
|
1151
1146
|
attribute.
|
1152
1147
|
|
1153
1148
|
Returns
|
1154
1149
|
-------
|
1155
1150
|
NDArray
|
1156
|
-
Value of the
|
1151
|
+
Value of the instance's :py:attr:`Density.origin` attribute.
|
1157
1152
|
|
1158
1153
|
Examples
|
1159
1154
|
--------
|
@@ -1179,8 +1174,7 @@ class Density:
|
|
1179
1174
|
@property
|
1180
1175
|
def sampling_rate(self) -> NDArray:
|
1181
1176
|
"""
|
1182
|
-
Returns the value of the
|
1183
|
-
attribute.
|
1177
|
+
Returns the value of the instance's :py:attr:`Density.sampling_rate` attribute.
|
1184
1178
|
|
1185
1179
|
Returns
|
1186
1180
|
-------
|
@@ -1201,7 +1195,7 @@ class Density:
|
|
1201
1195
|
@property
|
1202
1196
|
def metadata(self) -> Dict:
|
1203
1197
|
"""
|
1204
|
-
Returns the
|
1198
|
+
Returns the instance's :py:attr:`Density.metadata` attribute.
|
1205
1199
|
|
1206
1200
|
Returns
|
1207
1201
|
-------
|
@@ -1243,7 +1237,7 @@ class Density:
|
|
1243
1237
|
Pads the internal data array according to box.
|
1244
1238
|
|
1245
1239
|
Negative slices indices will result in a left-hand padding, while
|
1246
|
-
slice indices larger than the box_size property of the
|
1240
|
+
slice indices larger than the box_size property of the class
|
1247
1241
|
instance will result in a right-hand padding.
|
1248
1242
|
|
1249
1243
|
Parameters
|
@@ -1272,17 +1266,15 @@ class Density:
|
|
1272
1266
|
|
1273
1267
|
def adjust_box(self, box: Tuple[slice], pad_kwargs: Dict = {}) -> None:
|
1274
1268
|
"""
|
1275
|
-
Adjusts
|
1269
|
+
Adjusts :py:attr:`Density.data` and :py:attr:`Density.origin`
|
1276
1270
|
according to the provided box.
|
1277
1271
|
|
1278
1272
|
Parameters
|
1279
1273
|
----------
|
1280
1274
|
box : tuple of slices
|
1281
|
-
|
1282
|
-
should be sliced. See :py:meth:`Density.trim_box` on how to produce
|
1283
|
-
such an object.
|
1275
|
+
Description of how each axis of :py:attr:`Density.data` should be sliced.
|
1284
1276
|
pad_kwargs: dict, optional
|
1285
|
-
Parameter dictionary passed to numpy
|
1277
|
+
Parameter dictionary passed to :obj:`numpy.pad`.
|
1286
1278
|
|
1287
1279
|
See Also
|
1288
1280
|
--------
|
@@ -1462,8 +1454,8 @@ class Density:
|
|
1462
1454
|
Raises
|
1463
1455
|
------
|
1464
1456
|
ValueError
|
1465
|
-
If the length of
|
1466
|
-
|
1457
|
+
If the length of ``new_shape`` does not match the dimensionality of
|
1458
|
+
:py:attr:`Density.data`.
|
1467
1459
|
|
1468
1460
|
Examples
|
1469
1461
|
--------
|
@@ -1477,13 +1469,6 @@ class Density:
|
|
1477
1469
|
>>> dens.data
|
1478
1470
|
array([0, 1, 1, 1, 0])
|
1479
1471
|
|
1480
|
-
It's also possible to pass a user-defined ``padding_value``:
|
1481
|
-
|
1482
|
-
>>> dens = Density(np.array([1,1,1]))
|
1483
|
-
>>> dens.pad(new_shape = (5,), center = True, padding_value = -1)
|
1484
|
-
>>> dens.data
|
1485
|
-
array([-1, 1, 1, 1, -1])
|
1486
|
-
|
1487
1472
|
If ``center`` is set to False, the padding values will be appended:
|
1488
1473
|
|
1489
1474
|
>>> dens = Density(np.array([1,1,1]))
|
@@ -1491,6 +1476,12 @@ class Density:
|
|
1491
1476
|
>>> dens.data
|
1492
1477
|
array([1, 1, 1, 0, 0])
|
1493
1478
|
|
1479
|
+
It's also possible to pass a user-defined ``padding_value``:
|
1480
|
+
|
1481
|
+
>>> dens = Density(np.array([1,1,1]))
|
1482
|
+
>>> dens.pad(new_shape = (5,), center = True, padding_value = -1)
|
1483
|
+
>>> dens.data
|
1484
|
+
array([-1, 1, 1, 1, -1])
|
1494
1485
|
"""
|
1495
1486
|
if len(new_shape) != self.data.ndim:
|
1496
1487
|
raise ValueError(
|
@@ -1529,64 +1520,51 @@ class Density:
|
|
1529
1520
|
|
1530
1521
|
Returns
|
1531
1522
|
-------
|
1532
|
-
Density
|
1533
|
-
A copy of the class instance
|
1534
|
-
center of the data array.
|
1523
|
+
:py:class:`Density`
|
1524
|
+
A centered copy of the class instance.
|
1535
1525
|
NDArray
|
1536
|
-
The
|
1526
|
+
The offset between array center and center of mass.
|
1537
1527
|
|
1538
1528
|
See Also
|
1539
1529
|
--------
|
1540
1530
|
:py:meth:`Density.trim_box`
|
1541
1531
|
:py:meth:`Density.minimum_enclosing_box`
|
1542
1532
|
|
1543
|
-
|
1544
1533
|
Examples
|
1545
1534
|
--------
|
1546
1535
|
:py:meth:`Density.centered` returns a tuple containing a centered version
|
1547
1536
|
of the current :py:class:`Density` instance, as well as an array with
|
1548
|
-
translations. The translation corresponds to the shift
|
1549
|
-
center of mass
|
1537
|
+
translations. The translation corresponds to the shift between the original and
|
1538
|
+
current center of mass.
|
1550
1539
|
|
1551
1540
|
>>> import numpy as np
|
1552
1541
|
>>> from tme import Density
|
1553
|
-
>>> dens = Density(np.ones((5,5)))
|
1542
|
+
>>> dens = Density(np.ones((5,5,5)))
|
1554
1543
|
>>> centered_dens, translation = dens.centered(0)
|
1555
1544
|
>>> translation
|
1556
|
-
array([
|
1545
|
+
array([0., 0., 0.])
|
1557
1546
|
|
1558
1547
|
:py:meth:`Density.centered` extended the :py:attr:`Density.data` attribute
|
1559
1548
|
of the current :py:class:`Density` instance and modified
|
1560
1549
|
:py:attr:`Density.origin` accordingly.
|
1561
1550
|
|
1562
1551
|
>>> centered_dens
|
1563
|
-
Origin: (-
|
1564
|
-
|
1565
|
-
:py:meth:`Density.centered` achieves centering via zero-padding
|
1566
|
-
internal :py:attr:`Density.data` attribute
|
1567
|
-
|
1568
|
-
|
1569
|
-
array([[0., 0., 0., 0., 0., 0., 0.],
|
1570
|
-
[0., 1., 1., 1., 1., 1., 0.],
|
1571
|
-
[0., 1., 1., 1., 1., 1., 0.],
|
1572
|
-
[0., 1., 1., 1., 1., 1., 0.],
|
1573
|
-
[0., 1., 1., 1., 1., 1., 0.],
|
1574
|
-
[0., 1., 1., 1., 1., 1., 0.],
|
1575
|
-
[0., 0., 0., 0., 0., 0., 0.]])
|
1576
|
-
|
1577
|
-
`centered_dens` is sufficiently large to represent all rotations that
|
1578
|
-
could be applied to the :py:attr:`Density.data` attribute. Lets look
|
1579
|
-
at a random rotation obtained from
|
1552
|
+
Origin: (-2.0, -2.0, -2.0), sampling_rate: (1, 1, 1), Shape: (9, 9, 9)
|
1553
|
+
|
1554
|
+
:py:meth:`Density.centered` achieves centering via zero-padding and
|
1555
|
+
rigid-transform of the internal :py:attr:`Density.data` attribute.
|
1556
|
+
`centered_dens` is sufficiently large to represent all rotations of the
|
1557
|
+
:py:attr:`Density.data` attribute, such as ones obtained from
|
1580
1558
|
:py:meth:`tme.matching_utils.get_rotation_matrices`.
|
1581
1559
|
|
1582
1560
|
>>> from tme.matching_utils import get_rotation_matrices
|
1583
|
-
>>> rotation_matrix = get_rotation_matrices(dim =
|
1561
|
+
>>> rotation_matrix = get_rotation_matrices(dim = 3 ,angular_sampling = 10)[0]
|
1584
1562
|
>>> rotated_centered_dens = centered_dens.rigid_transform(
|
1585
1563
|
>>> rotation_matrix = rotation_matrix,
|
1586
1564
|
>>> order = None
|
1587
1565
|
>>> )
|
1588
1566
|
>>> print(centered_dens.data.sum(), rotated_centered_dens.data.sum())
|
1589
|
-
|
1567
|
+
125.0 125.0
|
1590
1568
|
|
1591
1569
|
"""
|
1592
1570
|
ret = self.copy()
|
@@ -1595,10 +1573,11 @@ class Density:
|
|
1595
1573
|
ret.adjust_box(box)
|
1596
1574
|
|
1597
1575
|
new_shape = np.maximum(ret.shape, self.shape)
|
1576
|
+
new_shape = np.add(new_shape, 1 - np.mod(new_shape, 2))
|
1598
1577
|
ret.pad(new_shape)
|
1599
1578
|
|
1600
1579
|
center = self.center_of_mass(ret.data, cutoff)
|
1601
|
-
shift = np.subtract(np.divide(ret.shape, 2), center)
|
1580
|
+
shift = np.subtract(np.divide(np.subtract(ret.shape, 1), 2), center)
|
1602
1581
|
|
1603
1582
|
ret = ret.rigid_transform(
|
1604
1583
|
translation=shift,
|
@@ -1606,117 +1585,9 @@ class Density:
|
|
1606
1585
|
use_geometric_center=False,
|
1607
1586
|
order=1,
|
1608
1587
|
)
|
1609
|
-
offset = np.subtract(center, self.center_of_mass(ret.data, cutoff))
|
1610
|
-
|
1611
|
-
return ret, offset
|
1612
|
-
|
1613
|
-
@classmethod
|
1614
|
-
def rotate_array(
|
1615
|
-
cls,
|
1616
|
-
arr: NDArray,
|
1617
|
-
rotation_matrix: NDArray,
|
1618
|
-
arr_mask: NDArray = None,
|
1619
|
-
translation: NDArray = None,
|
1620
|
-
use_geometric_center: bool = False,
|
1621
|
-
out: NDArray = None,
|
1622
|
-
out_mask: NDArray = None,
|
1623
|
-
order: int = 3,
|
1624
|
-
) -> None:
|
1625
|
-
"""
|
1626
|
-
Rotates coordinates of arr according to rotation_matrix.
|
1627
|
-
|
1628
|
-
If no output array is provided, this method will compute an array with
|
1629
|
-
sufficient space to hold all elements. If both `arr` and `arr_mask`
|
1630
|
-
are provided, `arr_mask` will be centered according to arr.
|
1631
|
-
|
1632
|
-
Parameters
|
1633
|
-
----------
|
1634
|
-
arr : NDArray
|
1635
|
-
The input array to be rotated.
|
1636
|
-
arr_mask : NDArray, optional
|
1637
|
-
The mask of `arr` that will be equivalently rotated.
|
1638
|
-
rotation_matrix : NDArray
|
1639
|
-
The rotation matrix to apply [d x d].
|
1640
|
-
translation : NDArray
|
1641
|
-
The translation to apply [d].
|
1642
|
-
use_geometric_center : bool, optional
|
1643
|
-
Whether the rotation should be centered around the geometric
|
1644
|
-
or mass center. Default is mass center.
|
1645
|
-
out : NDArray, optional
|
1646
|
-
The output array to write the rotation of `arr` to.
|
1647
|
-
out_mask : NDArray, optional
|
1648
|
-
The output array to write the rotation of `arr_mask` to.
|
1649
|
-
order : int, optional
|
1650
|
-
Spline interpolation order. Has to be in the range 0-5.
|
1651
|
-
"""
|
1652
|
-
|
1653
|
-
return NumpyFFTWBackend().rotate_array(
|
1654
|
-
arr=arr,
|
1655
|
-
rotation_matrix=rotation_matrix,
|
1656
|
-
arr_mask=arr_mask,
|
1657
|
-
translation=translation,
|
1658
|
-
use_geometric_center=use_geometric_center,
|
1659
|
-
out=out,
|
1660
|
-
out_mask=out_mask,
|
1661
|
-
order=order,
|
1662
|
-
)
|
1663
|
-
|
1664
|
-
@staticmethod
|
1665
|
-
def rotate_array_coordinates(
|
1666
|
-
arr: NDArray,
|
1667
|
-
coordinates: NDArray,
|
1668
|
-
rotation_matrix: NDArray,
|
1669
|
-
translation: NDArray = None,
|
1670
|
-
out: NDArray = None,
|
1671
|
-
use_geometric_center: bool = True,
|
1672
|
-
arr_mask: NDArray = None,
|
1673
|
-
mask_coordinates: NDArray = None,
|
1674
|
-
out_mask: NDArray = None,
|
1675
|
-
) -> None:
|
1676
|
-
"""
|
1677
|
-
Rotates coordinates of arr according to rotation_matrix.
|
1678
|
-
|
1679
|
-
If no output array is provided, this method will compute an array with
|
1680
|
-
sufficient space to hold all elements. If both `arr` and `arr_mask`
|
1681
|
-
are provided, `arr_mask` will be centered according to arr.
|
1682
1588
|
|
1683
|
-
|
1684
|
-
|
1685
|
-
Parameters
|
1686
|
-
----------
|
1687
|
-
arr : NDArray
|
1688
|
-
The input array to be rotated.
|
1689
|
-
coordinates : NDArray
|
1690
|
-
The pointcloud [d x N] containing elements of `arr` that should be rotated.
|
1691
|
-
See :py:meth:`Density.to_pointcloud` on how to obtain the coordinates.
|
1692
|
-
rotation_matrix : NDArray
|
1693
|
-
The rotation matrix to apply [d x d].
|
1694
|
-
rotation_matrix : NDArray
|
1695
|
-
The translation to apply [d].
|
1696
|
-
out : NDArray, optional
|
1697
|
-
The output array to write the rotation of `arr` to.
|
1698
|
-
use_geometric_center : bool, optional
|
1699
|
-
Whether the rotation should be centered around the geometric
|
1700
|
-
or mass center.
|
1701
|
-
arr_mask : NDArray, optional
|
1702
|
-
The mask of `arr` that will be equivalently rotated.
|
1703
|
-
mask_coordinates : NDArray, optional
|
1704
|
-
Equivalent to `coordinates`, but containing elements of `arr_mask`
|
1705
|
-
that should be rotated.
|
1706
|
-
out_mask : NDArray, optional
|
1707
|
-
The output array to write the rotation of `arr_mask` to.
|
1708
|
-
"""
|
1709
|
-
return NumpyFFTWBackend().rotate_array_coordinates(
|
1710
|
-
arr=arr,
|
1711
|
-
coordinates=coordinates,
|
1712
|
-
rotation_matrix=rotation_matrix,
|
1713
|
-
translation=translation,
|
1714
|
-
out=out,
|
1715
|
-
use_geometric_center=use_geometric_center,
|
1716
|
-
arr_mask=arr_mask,
|
1717
|
-
mask_coordinates=mask_coordinates,
|
1718
|
-
out_mask=out_mask,
|
1719
|
-
)
|
1589
|
+
shift = np.subtract(center, self.center_of_mass(ret.data, cutoff))
|
1590
|
+
return ret, shift
|
1720
1591
|
|
1721
1592
|
def rigid_transform(
|
1722
1593
|
self,
|
@@ -1726,124 +1597,183 @@ class Density:
|
|
1726
1597
|
use_geometric_center: bool = False,
|
1727
1598
|
) -> "Density":
|
1728
1599
|
"""
|
1729
|
-
Performs a rigid transform of the
|
1600
|
+
Performs a rigid transform of the class instance.
|
1730
1601
|
|
1731
1602
|
Parameters
|
1732
1603
|
----------
|
1733
1604
|
rotation_matrix : NDArray
|
1734
|
-
Rotation matrix to apply
|
1605
|
+
Rotation matrix to apply.
|
1735
1606
|
translation : NDArray
|
1736
|
-
Translation to apply
|
1607
|
+
Translation to apply.
|
1737
1608
|
order : int, optional
|
1738
|
-
|
1609
|
+
Interpolation order to use. Default is 3, has to be in range 0-5.
|
1739
1610
|
use_geometric_center : bool, optional
|
1740
|
-
|
1741
|
-
class instance should be centered using :py:meth:`Density.centered`.
|
1611
|
+
Use geometric or mass center as rotation center.
|
1742
1612
|
|
1743
1613
|
Returns
|
1744
1614
|
-------
|
1745
1615
|
Density
|
1746
|
-
The transformed instance of :py:class:`
|
1616
|
+
The transformed instance of :py:class:`Density`.
|
1747
1617
|
|
1748
1618
|
Examples
|
1749
1619
|
--------
|
1620
|
+
Define the :py:class:`Density` instance
|
1621
|
+
|
1750
1622
|
>>> import numpy as np
|
1751
|
-
>>>
|
1752
|
-
>>>
|
1753
|
-
>>>
|
1623
|
+
>>> from tme import Density
|
1624
|
+
>>> dens = Density(np.arange(9).reshape(3,3).astype(np.float32))
|
1625
|
+
>>> dens, translation = dens.centered(0)
|
1626
|
+
|
1627
|
+
and apply the rotation, in this case a mirror around the z-axis
|
1628
|
+
|
1629
|
+
>>> rotation_matrix = np.eye(dens.data.ndim)
|
1630
|
+
>>> rotation_matrix[0, 0] = -1
|
1631
|
+
>>> dens_transform = dens.rigid_transform(rotation_matrix = rotation_matrix)
|
1632
|
+
>>> dens_transform.data
|
1633
|
+
array([[0. , 0. , 0. , 0. , 0. ],
|
1634
|
+
[0.5 , 3.0833333 , 3.5833333 , 3.3333333 , 0. ],
|
1635
|
+
[0.75 , 4.6666665 , 5.6666665 , 5.4166665 , 0. ],
|
1636
|
+
[0.25 , 1.6666666 , 2.6666667 , 2.9166667 , 0. ],
|
1637
|
+
[0. , 0.08333334, 0.5833333 , 0.8333333 , 0. ]],
|
1638
|
+
dtype=float32)
|
1754
1639
|
|
1755
1640
|
Notes
|
1756
1641
|
-----
|
1757
|
-
:py:
|
1758
|
-
sufficiently sized to
|
1642
|
+
This function assumes the internal :py:attr:`Density.data` attribute is
|
1643
|
+
sufficiently sized to hold the transformation.
|
1759
1644
|
|
1760
1645
|
See Also
|
1761
1646
|
--------
|
1762
1647
|
:py:meth:`Density.centered`, :py:meth:`Density.minimum_enclosing_box`
|
1763
1648
|
"""
|
1764
|
-
|
1765
|
-
|
1766
|
-
self.rotate_array(
|
1649
|
+
ret = self.empty
|
1650
|
+
NumpyFFTWBackend().rigid_transform(
|
1767
1651
|
arr=self.data,
|
1768
1652
|
rotation_matrix=rotation_matrix,
|
1769
1653
|
translation=translation,
|
1770
|
-
order=order,
|
1771
1654
|
use_geometric_center=use_geometric_center,
|
1772
|
-
out=
|
1655
|
+
out=ret.data,
|
1656
|
+
order=order,
|
1773
1657
|
)
|
1774
|
-
eps = np.finfo(transformed_map.data.dtype).eps
|
1775
|
-
transformed_map.data[transformed_map.data < eps] = 0
|
1776
|
-
return transformed_map
|
1777
1658
|
|
1778
|
-
|
1659
|
+
eps = np.finfo(ret.data.dtype).eps
|
1660
|
+
ret.data[np.abs(ret.data) < eps] = 0
|
1661
|
+
return ret
|
1662
|
+
|
1663
|
+
def resample(
|
1664
|
+
self, new_sampling_rate: Tuple[float], method: str = "spline", order: int = 1
|
1665
|
+
) -> "Density":
|
1779
1666
|
"""
|
1780
|
-
|
1667
|
+
Resamples :py:attr:`Density.data` to ``new_sampling_rate``.
|
1781
1668
|
|
1782
1669
|
Parameters
|
1783
1670
|
----------
|
1784
|
-
|
1785
|
-
|
1671
|
+
new_sampling_rate : tuple of floats or float
|
1672
|
+
Sampling rate to resample to for a single or all axes.
|
1673
|
+
method: str, optional
|
1674
|
+
Resampling method to use, defaults to `spline`. Availabe options are:
|
1675
|
+
|
1676
|
+
+---------+----------------------------------------------------------+
|
1677
|
+
| spline | Smooth spline interpolation via :obj:`scipy.ndimage.zoom`|
|
1678
|
+
+---------+----------------------------------------------------------+
|
1679
|
+
| fourier | Frequency preserving Fourier cropping |
|
1680
|
+
+---------+----------------------------------------------------------+
|
1681
|
+
|
1682
|
+
order : int, optional
|
1683
|
+
Order of spline used for interpolation, by default 1. Ignored when
|
1684
|
+
``method`` is `fourier`.
|
1786
1685
|
|
1787
1686
|
Raises
|
1788
1687
|
------
|
1789
1688
|
ValueError
|
1790
|
-
If
|
1689
|
+
If ``method`` is not supported.
|
1791
1690
|
|
1792
1691
|
Returns
|
1793
1692
|
-------
|
1794
|
-
Density
|
1795
|
-
A
|
1796
|
-
"""
|
1797
|
-
if not np.allclose(self.sampling_rate, other_map.sampling_rate):
|
1798
|
-
raise ValueError("sampling_rate of both maps have to match.")
|
1693
|
+
:py:class:`Density`
|
1694
|
+
A resampled copy of the class instance.
|
1799
1695
|
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1696
|
+
Examples
|
1697
|
+
--------
|
1698
|
+
The following makes use of :py:meth:`tme.matching_utils.create_mask`
|
1699
|
+
to define a :py:class:`Density` instance containing a 2D circle with
|
1700
|
+
a sampling rate of 2
|
1804
1701
|
|
1805
|
-
|
1806
|
-
|
1702
|
+
>>> from tme import Density
|
1703
|
+
>>> from tme.matching_utils import create_mask
|
1704
|
+
>>> mask = create_mask(
|
1705
|
+
>>> mask_type="ellipse",
|
1706
|
+
>>> shape=(11,11),
|
1707
|
+
>>> center=(5,5),
|
1708
|
+
>>> radius=3
|
1709
|
+
>>> )
|
1710
|
+
>>> dens = Density(mask, sampling_rate=2)
|
1711
|
+
>>> dens
|
1712
|
+
Origin: (0.0, 0.0), sampling_rate: (2, 2), Shape: (11, 11)
|
1807
1713
|
|
1808
|
-
|
1714
|
+
Using :py:meth:`Density.resample` we can modulate the sampling rate
|
1715
|
+
using spline interpolation of desired order
|
1809
1716
|
|
1810
|
-
|
1811
|
-
|
1812
|
-
return ret
|
1717
|
+
>>> dens.resample(new_sampling_rate= 4, method="spline", order=3)
|
1718
|
+
Origin: (0.0, 0.0), sampling_rate: (4, 4), Shape: (6, 6)
|
1813
1719
|
|
1814
|
-
|
1815
|
-
|
1816
|
-
Resamples the current class instance to ``new_sampling_rate`` using
|
1817
|
-
spline interpolation of order ``order``.
|
1720
|
+
Or Fourier cropping which results in a less smooth output, but more faithfully
|
1721
|
+
captures the contained frequency information
|
1818
1722
|
|
1819
|
-
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1723
|
+
>>> dens.resample(new_sampling_rate=4, method="fourier")
|
1724
|
+
Origin: (0.0, 0.0), sampling_rate: (4, 4), Shape: (6, 6)
|
1725
|
+
|
1726
|
+
``new_sampling_rate`` can also be specified per axis
|
1727
|
+
|
1728
|
+
>>> dens.resample(new_sampling_rate=(4,1), method="spline", order=3)
|
1729
|
+
Origin: (0.0, 0.0), sampling_rate: (4, 1), Shape: (6, 22)
|
1825
1730
|
|
1826
|
-
Returns
|
1827
|
-
-------
|
1828
|
-
Density
|
1829
|
-
A resampled instance of `Density` class.
|
1830
1731
|
"""
|
1831
|
-
|
1732
|
+
_supported_methods = ("spline", "fourier")
|
1733
|
+
if method not in _supported_methods:
|
1734
|
+
raise ValueError(
|
1735
|
+
f"Expected method to be one of {_supported_methods}, got '{method}'."
|
1736
|
+
)
|
1737
|
+
new_sampling_rate = np.array(new_sampling_rate)
|
1832
1738
|
new_sampling_rate = np.repeat(
|
1833
|
-
new_sampling_rate,
|
1739
|
+
new_sampling_rate, self.data.ndim // new_sampling_rate.size
|
1834
1740
|
)
|
1835
|
-
scale_factor = np.divide(map_copy.sampling_rate, new_sampling_rate)
|
1836
1741
|
|
1837
|
-
|
1838
|
-
|
1742
|
+
ret = self.copy()
|
1743
|
+
scale_factor = np.divide(ret.sampling_rate, new_sampling_rate)
|
1744
|
+
if method == "spline":
|
1745
|
+
ret.data = zoom(ret.data, scale_factor, order=order)
|
1746
|
+
elif method == "fourier":
|
1747
|
+
ret_shape = np.round(np.multiply(scale_factor, ret.shape)).astype(int)
|
1748
|
+
|
1749
|
+
axis = range(len(ret_shape))
|
1750
|
+
mask = np.zeros(self.shape, dtype=bool)
|
1751
|
+
mask[tuple(slice(0, x) for x in ret_shape)] = 1
|
1752
|
+
mask = np.roll(
|
1753
|
+
mask, shift=-np.floor(np.divide(ret_shape, 2)).astype(int), axis=axis
|
1754
|
+
)
|
1755
|
+
mask_ret = np.zeros(ret_shape, dtype=bool)
|
1756
|
+
mask_ret[tuple(slice(0, x) for x in self.shape)] = 1
|
1757
|
+
mask_ret = np.roll(
|
1758
|
+
mask_ret,
|
1759
|
+
shift=-np.floor(np.divide(self.shape, 2)).astype(int),
|
1760
|
+
axis=axis,
|
1761
|
+
)
|
1839
1762
|
|
1840
|
-
|
1763
|
+
arr_ft = np.fft.fftn(self.data)
|
1764
|
+
arr_ft *= np.prod(ret_shape) / np.prod(self.shape)
|
1765
|
+
ret_ft = np.zeros(ret_shape, dtype=arr_ft.dtype)
|
1766
|
+
ret_ft[mask_ret] = arr_ft[mask]
|
1767
|
+
ret.data = np.real(np.fft.ifftn(ret_ft))
|
1768
|
+
|
1769
|
+
ret.sampling_rate = new_sampling_rate
|
1770
|
+
return ret
|
1841
1771
|
|
1842
1772
|
def density_boundary(
|
1843
1773
|
self, weight: float, fraction_surface: float = 0.1, volume_factor: float = 1.21
|
1844
1774
|
) -> Tuple[float]:
|
1845
1775
|
"""
|
1846
|
-
Computes the density boundary of the
|
1776
|
+
Computes the density boundary of the class instance. The density
|
1847
1777
|
boundary in this setting is defined as minimal and maximal density value
|
1848
1778
|
enclosing a certain ``weight``.
|
1849
1779
|
|
@@ -1899,31 +1829,31 @@ class Density:
|
|
1899
1829
|
self, density_boundaries: Tuple[float], method: str = "ConvexHull"
|
1900
1830
|
) -> NDArray:
|
1901
1831
|
"""
|
1902
|
-
Calculates the surface coordinates of the
|
1832
|
+
Calculates the surface coordinates of the class instance using
|
1903
1833
|
different boundary and surface detection methods. This method is relevant
|
1904
|
-
for determining coordinates used in template matching,
|
1905
|
-
see :py:class:`tme.
|
1834
|
+
for determining coordinates used in non-exhaustive template matching,
|
1835
|
+
see :py:class:`tme.matching_optimization.optimize_match`.
|
1906
1836
|
|
1907
1837
|
Parameters
|
1908
1838
|
----------
|
1909
1839
|
density_boundaries : tuple
|
1910
|
-
|
1911
|
-
|
1840
|
+
Lower and upper bound of density values to be considered
|
1841
|
+
(can be obtained from :py:meth:`Density.density_boundary`).
|
1912
1842
|
method : str, optional
|
1913
|
-
|
1843
|
+
Method to use for surface coordinate computation
|
1914
1844
|
|
1915
1845
|
+--------------+-----------------------------------------------------+
|
1916
|
-
|
|
1846
|
+
| ConvexHull | Use the lower bound density convex hull vertices. |
|
1917
1847
|
+--------------+-----------------------------------------------------+
|
1918
|
-
|
|
1848
|
+
| Weight | Use all coordinates within ``density_boundaries``. |
|
1919
1849
|
+--------------+-----------------------------------------------------+
|
1920
|
-
|
|
1850
|
+
| Sobel | Set densities below the lower bound density to zero |
|
1921
1851
|
| | apply a sobel filter and return density coordinates |
|
1922
1852
|
| | larger than 0.5 times the maximum filter value. |
|
1923
1853
|
+--------------+-----------------------------------------------------+
|
1924
|
-
|
|
1854
|
+
| Laplace | Like 'Sobel', but with a Laplace filter. |
|
1925
1855
|
+--------------+-----------------------------------------------------+
|
1926
|
-
|
|
1856
|
+
| Minimum | Like 'Sobel' and 'Laplace' but with a spherical |
|
1927
1857
|
| | minimum filter on the lower density bound. |
|
1928
1858
|
+--------------+-----------------------------------------------------+
|
1929
1859
|
|
@@ -1935,15 +1865,11 @@ class Density:
|
|
1935
1865
|
Returns
|
1936
1866
|
-------
|
1937
1867
|
NDArray
|
1938
|
-
An array of surface coordinates with shape (
|
1868
|
+
An array of surface coordinates with shape (points, dimensions).
|
1939
1869
|
|
1940
1870
|
References
|
1941
1871
|
----------
|
1942
|
-
.. [1] Cragnolini T,
|
1943
|
-
Vasishtan D, Topf M (2021a) TEMPy2: A Python library with
|
1944
|
-
improved 3D electron microscopy density-fitting and validation
|
1945
|
-
workflows. Acta Crystallogr Sect D Struct Biol 77:41–47.
|
1946
|
-
https://doi.org/10.1107/S2059798320014928
|
1872
|
+
.. [1] Cragnolini T, et al. (2021) Acta Crys Sect D Struct Biol
|
1947
1873
|
|
1948
1874
|
See Also
|
1949
1875
|
--------
|
@@ -1953,12 +1879,12 @@ class Density:
|
|
1953
1879
|
:py:class:`tme.matching_optimization.Envelope`
|
1954
1880
|
:py:class:`tme.matching_optimization.Chamfer`
|
1955
1881
|
"""
|
1956
|
-
|
1882
|
+
_available_methods = ["ConvexHull", "Weight", "Sobel", "Laplace", "Minimum"]
|
1957
1883
|
|
1958
|
-
if method not in
|
1884
|
+
if method not in _available_methods:
|
1959
1885
|
raise ValueError(
|
1960
1886
|
"Argument method has to be one of the following: %s"
|
1961
|
-
% ", ".join(
|
1887
|
+
% ", ".join(_available_methods)
|
1962
1888
|
)
|
1963
1889
|
|
1964
1890
|
lower_bound, upper_bound = density_boundaries
|
@@ -2006,7 +1932,7 @@ class Density:
|
|
2006
1932
|
def normal_vectors(self, coordinates: NDArray) -> NDArray:
|
2007
1933
|
"""
|
2008
1934
|
Calculates the normal vectors for the given coordinates on the densities
|
2009
|
-
of the
|
1935
|
+
of the class instance. If the normal vector to a given coordinate
|
2010
1936
|
can not be computed, the zero vector is returned instead. The output of this
|
2011
1937
|
function can e.g. be used in
|
2012
1938
|
:py:class:`tme.matching_optimization.NormalVectorScore`.
|
@@ -2056,12 +1982,7 @@ class Density:
|
|
2056
1982
|
in_box = np.logical_and(
|
2057
1983
|
coordinates < np.array(self.shape), coordinates >= 0
|
2058
1984
|
).min(axis=1)
|
2059
|
-
|
2060
|
-
out_of_box = np.invert(in_box)
|
2061
|
-
if out_of_box.sum() > 0:
|
2062
|
-
print(coordinates[out_of_box, :])
|
2063
|
-
raise ValueError("Coordinates outside of self.data detected.")
|
2064
|
-
|
1985
|
+
coordinates = coordinates[in_box, :]
|
2065
1986
|
for index in range(coordinates.shape[0]):
|
2066
1987
|
point = coordinates[index, :]
|
2067
1988
|
start = np.maximum(point - 1, 0)
|
@@ -2081,22 +2002,16 @@ class Density:
|
|
2081
2002
|
|
2082
2003
|
def core_mask(self) -> NDArray:
|
2083
2004
|
"""
|
2084
|
-
Calculates
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
in a mask with same shape as the internal data array. Therefore,
|
2090
|
-
data elements in the output array with a value of n remained non-zero for
|
2091
|
-
n rounds of binary erosion. The higher the value, the more likely a data element
|
2092
|
-
is part of the core of the density map.
|
2005
|
+
Calculates a weighted core mask by performing iterative binary erosion on
|
2006
|
+
:py:attr:`Density.data`. In each iteration, all mask elements corresponding
|
2007
|
+
to a non-zero data elemnt are incremented by one. Therefore, a mask element
|
2008
|
+
with value N corresponds to a data value that remained non-zero for N iterations.
|
2009
|
+
Mask elements with high values are likely part of the core density [1]_.
|
2093
2010
|
|
2094
2011
|
Returns
|
2095
2012
|
-------
|
2096
2013
|
NDArray
|
2097
|
-
|
2098
|
-
indicate how many rounds of binary erosion were necessary to nullify
|
2099
|
-
a given data element.
|
2014
|
+
Core-weighted mask with shape of :py:attr:`Density.data`.
|
2100
2015
|
|
2101
2016
|
References
|
2102
2017
|
----------
|
@@ -2131,19 +2046,7 @@ class Density:
|
|
2131
2046
|
NDArray
|
2132
2047
|
Center of mass with shape (arr.ndim).
|
2133
2048
|
"""
|
2134
|
-
|
2135
|
-
arr = np.where(arr > cutoff, arr, 0)
|
2136
|
-
denominator = np.sum(arr)
|
2137
|
-
grids = np.ogrid[tuple(slice(0, i) for i in arr.shape)]
|
2138
|
-
|
2139
|
-
center_of_mass = np.array(
|
2140
|
-
[
|
2141
|
-
np.sum(np.multiply(arr, grids[dim].astype(float))) / denominator
|
2142
|
-
for dim in range(arr.ndim)
|
2143
|
-
]
|
2144
|
-
)
|
2145
|
-
|
2146
|
-
return center_of_mass
|
2049
|
+
return NumpyFFTWBackend().center_of_mass(arr, cutoff)
|
2147
2050
|
|
2148
2051
|
@classmethod
|
2149
2052
|
def match_densities(
|
@@ -2153,6 +2056,7 @@ class Density:
|
|
2153
2056
|
cutoff_target: float = 0,
|
2154
2057
|
cutoff_template: float = 0,
|
2155
2058
|
scoring_method: str = "NormalizedCrossCorrelation",
|
2059
|
+
**kwargs,
|
2156
2060
|
) -> Tuple["Density", NDArray, NDArray, NDArray]:
|
2157
2061
|
"""
|
2158
2062
|
Aligns two :py:class:`Density` instances target and template and returns
|
@@ -2177,6 +2081,9 @@ class Density:
|
|
2177
2081
|
The scoring method to use for alignment. See
|
2178
2082
|
:py:class:`tme.matching_optimization.create_score_object` for available methods,
|
2179
2083
|
by default "NormalizedCrossCorrelation".
|
2084
|
+
kwargs : dict, optional
|
2085
|
+
Optional keyword arguments passed to
|
2086
|
+
:py:meth:`tme.matching_optimization.optimize_match`.
|
2180
2087
|
|
2181
2088
|
Returns
|
2182
2089
|
-------
|
@@ -2188,11 +2095,20 @@ class Density:
|
|
2188
2095
|
-----
|
2189
2096
|
No densities below cutoff_template are present in the returned Density object.
|
2190
2097
|
"""
|
2098
|
+
from .matching_utils import normalize_template
|
2191
2099
|
from .matching_optimization import optimize_match, create_score_object
|
2192
2100
|
|
2101
|
+
template_mask = template.empty
|
2102
|
+
template_mask.data.fill(1)
|
2103
|
+
|
2104
|
+
normalize_template(
|
2105
|
+
template=template.data,
|
2106
|
+
mask=template_mask.data,
|
2107
|
+
n_observations=template_mask.data.sum(),
|
2108
|
+
)
|
2109
|
+
|
2193
2110
|
target_sampling_rate = np.array(target.sampling_rate)
|
2194
2111
|
template_sampling_rate = np.array(template.sampling_rate)
|
2195
|
-
|
2196
2112
|
target_sampling_rate = np.repeat(
|
2197
2113
|
target_sampling_rate, target.data.ndim // target_sampling_rate.size
|
2198
2114
|
)
|
@@ -2224,16 +2140,21 @@ class Density:
|
|
2224
2140
|
).astype(int)
|
2225
2141
|
template_coordinates += mass_center_difference[:, None]
|
2226
2142
|
|
2143
|
+
coordinates_mask = template_mask.to_pointcloud()
|
2144
|
+
coordinates_mask = coordinates_mask * template_scaling[:, None]
|
2145
|
+
coordinates_mask += mass_center_difference[:, None]
|
2146
|
+
|
2227
2147
|
score_object = create_score_object(
|
2228
2148
|
score=scoring_method,
|
2229
2149
|
target=target.data,
|
2230
2150
|
template_coordinates=template_coordinates,
|
2151
|
+
template_mask_coordinates=coordinates_mask,
|
2231
2152
|
template_weights=template_weights,
|
2232
2153
|
sampling_rate=np.ones(template.data.ndim),
|
2233
2154
|
)
|
2234
2155
|
|
2235
2156
|
translation, rotation_matrix, score = optimize_match(
|
2236
|
-
score_object=score_object,
|
2157
|
+
score_object=score_object, **kwargs
|
2237
2158
|
)
|
2238
2159
|
|
2239
2160
|
translation += mass_center_difference
|
@@ -2255,6 +2176,8 @@ class Density:
|
|
2255
2176
|
template: "Structure",
|
2256
2177
|
cutoff_target: float = 0,
|
2257
2178
|
scoring_method: str = "NormalizedCrossCorrelation",
|
2179
|
+
optimization_method: str = "basinhopping",
|
2180
|
+
maxiter: int = 500,
|
2258
2181
|
) -> Tuple["Structure", NDArray, NDArray]:
|
2259
2182
|
"""
|
2260
2183
|
Aligns a :py:class:`tme.structure.Structure` template to :py:class:`Density`
|
@@ -2279,6 +2202,12 @@ class Density:
|
|
2279
2202
|
The scoring method to use for template matching. See
|
2280
2203
|
:py:class:`tme.matching_optimization.create_score_object` for available methods,
|
2281
2204
|
by default "NormalizedCrossCorrelation".
|
2205
|
+
optimization_method : str, optional
|
2206
|
+
Optimizer that is used.
|
2207
|
+
See :py:meth:`tme.matching_optimization.optimize_match`.
|
2208
|
+
maxiter : int, optional
|
2209
|
+
Maximum number of iterations for the optimizer.
|
2210
|
+
See :py:meth:`tme.matching_optimization.optimize_match`.
|
2282
2211
|
|
2283
2212
|
Returns
|
2284
2213
|
-------
|
@@ -2302,66 +2231,22 @@ class Density:
|
|
2302
2231
|
cutoff_target=cutoff_target,
|
2303
2232
|
cutoff_template=0,
|
2304
2233
|
scoring_method=scoring_method,
|
2234
|
+
optimization_method=optimization_method,
|
2235
|
+
maxiter=maxiter,
|
2305
2236
|
)
|
2306
2237
|
out = template.copy()
|
2307
|
-
final_translation = np.
|
2308
|
-
-template_density.origin,
|
2309
|
-
np.multiply(translation, template_density.sampling_rate),
|
2310
|
-
)
|
2238
|
+
final_translation = np.subtract(ret.origin, template_density.origin)
|
2311
2239
|
|
2312
2240
|
# Atom coordinates are in xyz
|
2313
2241
|
final_translation = final_translation[::-1]
|
2314
2242
|
rotation_matrix = rotation_matrix[::-1, ::-1]
|
2315
2243
|
|
2316
|
-
out.rigid_transform(
|
2244
|
+
out = out.rigid_transform(
|
2317
2245
|
translation=final_translation, rotation_matrix=rotation_matrix
|
2318
2246
|
)
|
2319
2247
|
|
2320
2248
|
return out, final_translation, rotation_matrix
|
2321
2249
|
|
2322
|
-
@staticmethod
|
2323
|
-
def align_coordinate_systems(target: "Density", template: "Density") -> "Density":
|
2324
|
-
"""
|
2325
|
-
Aligns the coordinate system of `target` and `template`.
|
2326
|
-
|
2327
|
-
Parameters
|
2328
|
-
----------
|
2329
|
-
target : Density
|
2330
|
-
The target density whose coordinate system should remain unchanged.
|
2331
|
-
template : Density
|
2332
|
-
The template density that will be aligned to match the target's
|
2333
|
-
coordinate system.
|
2334
|
-
|
2335
|
-
Raises
|
2336
|
-
------
|
2337
|
-
ValueError
|
2338
|
-
If the `sampling_rate` of `target` and `template` do not match.
|
2339
|
-
|
2340
|
-
Returns
|
2341
|
-
-------
|
2342
|
-
Density
|
2343
|
-
A copy of `template` aligned to the coordinate system of `target`.
|
2344
|
-
The `box_size` and `origin` will match that of `target`.
|
2345
|
-
|
2346
|
-
See Also
|
2347
|
-
--------
|
2348
|
-
:py:meth:`Density.match_densities` : To match aligned template to target.
|
2349
|
-
"""
|
2350
|
-
if not np.allclose(target.sampling_rate, template.sampling_rate):
|
2351
|
-
raise ValueError("sampling_rate of both maps have to match.")
|
2352
|
-
|
2353
|
-
template = template.copy()
|
2354
|
-
template.pad(target.shape, center=True)
|
2355
|
-
|
2356
|
-
origin_difference = np.divide(
|
2357
|
-
np.subtract(template.origin, target.origin), target.sampling_rate
|
2358
|
-
)
|
2359
|
-
template = template.rigid_transform(
|
2360
|
-
rotation_matrix=np.eye(template.data.ndim), translation=origin_difference
|
2361
|
-
)
|
2362
|
-
template.origin = target.origin.copy()
|
2363
|
-
return template
|
2364
|
-
|
2365
2250
|
@staticmethod
|
2366
2251
|
def fourier_shell_correlation(density1: "Density", density2: "Density") -> NDArray:
|
2367
2252
|
"""
|
@@ -2418,3 +2303,9 @@ class Density:
|
|
2418
2303
|
qidx = np.where(qbins < qx.max())
|
2419
2304
|
|
2420
2305
|
return np.vstack((qbins[qidx], FSC[qidx])).T
|
2306
|
+
|
2307
|
+
|
2308
|
+
def is_gzipped(filename: str) -> bool:
|
2309
|
+
"""Check if a file is a gzip file by reading its magic number."""
|
2310
|
+
with open(filename, "rb") as f:
|
2311
|
+
return f.read(2) == b"\x1f\x8b"
|