pytme 0.2.1__cp311-cp311-macosx_14_0_arm64.whl → 0.2.3__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.
Files changed (52) hide show
  1. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/match_template.py +219 -216
  2. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/postprocess.py +86 -54
  3. pytme-0.2.3.data/scripts/preprocess.py +132 -0
  4. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/preprocessor_gui.py +181 -94
  5. pytme-0.2.3.dist-info/METADATA +92 -0
  6. pytme-0.2.3.dist-info/RECORD +75 -0
  7. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/WHEEL +1 -1
  8. pytme-0.2.1.data/scripts/preprocess.py → scripts/eval.py +1 -1
  9. scripts/extract_candidates.py +20 -13
  10. scripts/match_template.py +219 -216
  11. scripts/match_template_filters.py +154 -95
  12. scripts/postprocess.py +86 -54
  13. scripts/preprocess.py +95 -56
  14. scripts/preprocessor_gui.py +181 -94
  15. scripts/refine_matches.py +265 -61
  16. tme/__init__.py +0 -1
  17. tme/__version__.py +1 -1
  18. tme/analyzer.py +458 -813
  19. tme/backends/__init__.py +40 -11
  20. tme/backends/_jax_utils.py +187 -0
  21. tme/backends/cupy_backend.py +109 -226
  22. tme/backends/jax_backend.py +230 -152
  23. tme/backends/matching_backend.py +445 -384
  24. tme/backends/mlx_backend.py +32 -59
  25. tme/backends/npfftw_backend.py +240 -507
  26. tme/backends/pytorch_backend.py +30 -151
  27. tme/density.py +248 -371
  28. tme/extensions.cpython-311-darwin.so +0 -0
  29. tme/matching_data.py +328 -284
  30. tme/matching_exhaustive.py +195 -1499
  31. tme/matching_optimization.py +143 -106
  32. tme/matching_scores.py +887 -0
  33. tme/matching_utils.py +287 -388
  34. tme/memory.py +377 -0
  35. tme/orientations.py +78 -21
  36. tme/parser.py +3 -4
  37. tme/preprocessing/_utils.py +61 -32
  38. tme/preprocessing/composable_filter.py +7 -4
  39. tme/preprocessing/compose.py +7 -3
  40. tme/preprocessing/frequency_filters.py +49 -39
  41. tme/preprocessing/tilt_series.py +44 -72
  42. tme/preprocessor.py +560 -526
  43. tme/structure.py +491 -188
  44. tme/types.py +5 -3
  45. pytme-0.2.1.dist-info/METADATA +0 -73
  46. pytme-0.2.1.dist-info/RECORD +0 -73
  47. tme/helpers.py +0 -881
  48. tme/matching_constrained.py +0 -195
  49. {pytme-0.2.1.data → pytme-0.2.3.data}/scripts/estimate_ram_usage.py +0 -0
  50. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/LICENSE +0 -0
  51. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/entry_points.txt +0 -0
  52. {pytme-0.2.1.dist-info → pytme-0.2.3.dist-info}/top_level.txt +0 -0
tme/density.py CHANGED
@@ -18,24 +18,23 @@ 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
- zoom,
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:
@@ -44,44 +43,47 @@ class Density:
44
43
 
45
44
  Parameters
46
45
  ----------
47
- data : NDArray
48
- Array of data values.
49
- origin : NDArray, optional
50
- Origin of the coordinate system. Defaults to zero.
51
- sampling_rate : NDArray, optional
52
- Sampling rate along data axis. Defaults to one.
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
- The metadata parameter is not a dictionary.
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 = data)
69
+ >>> Density(data=data)
69
70
 
70
- Optional parameters are ``origin`` and ``sampling_rate`` that correspond
71
- to the coordinate system reference and the edge length per axis element,
72
- as well as the ``metadata`` dictionary. By default,
73
- :py:attr:`Density.origin` is set to zero and :py:attr:`Density.sampling_rate`
74
- to 1. If provided, origin or sampling_rate either need to be a single value:
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 = data, origin = 0, sampling_rate = 1)
78
+ >>> Density(data=data, origin=0, sampling_rate=1)
77
79
 
78
- Be specified along each data axis:
80
+ be specified along each data axis
79
81
 
80
- >>> Density(data = data, origin = (0, 0, 0), sampling_rate = (1.5, 1.1, 1.2))
82
+ >>> Density(data=data, origin=(0, 0, 0), sampling_rate=(1.5, 1.1, 1.2))
81
83
 
82
- Or a combination of both:
84
+ or be a combination of both
83
85
 
84
- >>> Density(data = data, origin = 0, sampling_rate = (1.5, 1.1, 1.2))
86
+ >>> Density(data=data, origin=0, sampling_rate=(1.5, 1.1, 1.2))
85
87
  """
86
88
 
87
89
  def __init__(
@@ -111,11 +113,11 @@ class Density:
111
113
  self.metadata = metadata
112
114
 
113
115
  def __repr__(self):
114
- response = "Density object at {}\nOrigin: {}, sampling_rate: {}, Shape: {}"
116
+ response = "Density object at {}\nOrigin: {}, Sampling Rate: {}, Shape: {}"
115
117
  return response.format(
116
118
  hex(id(self)),
117
- tuple(np.round(self.origin, 3)),
118
- tuple(np.round(self.sampling_rate, 3)),
119
+ tuple(round(float(x), 3) for x in self.origin),
120
+ tuple(round(float(x), 3) for x in self.sampling_rate),
119
121
  self.shape,
120
122
  )
121
123
 
@@ -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 in a file and converts it into :py:class:`Density` instance.
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. The file can be gzip compressed.
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
- Whether the Density objects data attribute should be memmory mapped.
139
+ Memory map the data contained in ``filename`` to save memory.
138
140
 
139
141
  Returns
140
142
  -------
141
- Density
142
- An instance of the :py:class:`Density` class.
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 CCP4/MRC, EM, or a format supported
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
- For large density maps, memory mapping can be used to read the file directly
167
- from disk without loading it entirely into memory. This is particularly useful
168
- for large datasets or when working with limited memory resources:
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]_. This fallback does not extract
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 with ".em" or ".em.gz" the method will parse it as EM file,
189
- if it ends with "h5" or "h5.gz" the method will parse the file as HDF5.
190
- Otherwise the method defaults to the CCP4/MRC format and on failure, switches to
191
- :obj:`skimage.io.imread` regardless of the extension. Currently, the later does not
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
  --------
@@ -305,6 +306,10 @@ class Density:
305
306
  "std": float(mrc.header.rms),
306
307
  }
307
308
 
309
+ non_standard_crs = not np.all(crs_index == (0, 1, 2))
310
+ if non_standard_crs:
311
+ warnings.warn("Non standard MAPC, MAPR, MAPS, adapting data and origin.")
312
+
308
313
  if is_gzipped(filename):
309
314
  if use_memmap:
310
315
  warnings.warn(
@@ -314,7 +319,11 @@ class Density:
314
319
  use_memmap = False
315
320
 
316
321
  if subset is not None:
317
- subset_shape = [x.stop - x.start for x in subset]
322
+ subset = tuple(
323
+ subset[i] if i < len(subset) else slice(0, data_shape[i])
324
+ for i in crs_index
325
+ )
326
+ subset_shape = tuple(x.stop - x.start for x in subset)
318
327
  if np.allclose(subset_shape, data_shape):
319
328
  return cls._load_mrc(
320
329
  filename=filename, subset=None, use_memmap=use_memmap
@@ -327,19 +336,16 @@ class Density:
327
336
  dtype=data_type,
328
337
  header_size=1024 + extended_header,
329
338
  )
330
- return data, origin, sampling_rate, metadata
331
-
332
- if not use_memmap:
339
+ elif subset is None and not use_memmap:
333
340
  with mrcfile.open(filename, header_only=False) as mrc:
334
341
  data = mrc.data.astype(np.float32, copy=False)
335
342
  else:
336
343
  with mrcfile.mrcmemmap.MrcMemmap(filename, header_only=False) as mrc:
337
344
  data = mrc.data
338
345
 
339
- if not np.all(crs_index == (0, 1, 2)):
340
- data.setflags(write=True)
346
+ if non_standard_crs:
341
347
  data = np.transpose(data, crs_index)
342
- start = np.take(start, crs_index)
348
+ origin = np.take(origin, crs_index)
343
349
 
344
350
  return data, origin, sampling_rate, metadata
345
351
 
@@ -642,7 +648,7 @@ class Density:
642
648
  sampling_rate: NDArray = np.ones(1),
643
649
  origin: Tuple[float] = None,
644
650
  weight_type: str = "atomic_weight",
645
- scattering_args: Dict = dict(),
651
+ weight_type_args: Dict = {},
646
652
  chain: str = None,
647
653
  filter_by_elements: Set = None,
648
654
  filter_by_residues: Set = None,
@@ -672,6 +678,8 @@ class Density:
672
678
  weight_type : str, optional
673
679
  Which weight should be given to individual atoms. For valid values
674
680
  see :py:meth:`tme.structure.Structure.to_volume`.
681
+ weight_type_args : dict, optional
682
+ Additional arguments for atom weight computation.
675
683
  chain : str, optional
676
684
  The chain that should be extracted from the structure. If multiple chains
677
685
  should be selected, they needto be a comma separated string,
@@ -680,8 +688,6 @@ class Density:
680
688
  Set of atomic elements to keep. Default is all atoms.
681
689
  filter_by_residues : set, optional
682
690
  Set of residues to keep. Default is all residues.
683
- scattering_args : dict, optional
684
- Additional arguments for scattering factor computation.
685
691
 
686
692
  Returns
687
693
  -------
@@ -738,14 +744,14 @@ class Density:
738
744
  >>> )
739
745
 
740
746
  :py:meth:`Density.from_structure` supports a variety of methods to convert
741
- atoms into densities. In additino to 'atomic_weight', 'atomic_number',
742
- 'van_der_waals_radius' its possible to use experimentally determined scattering
743
- factors from various sources:
747
+ atoms into densities. In addition to 'atomic_weight', 'atomic_number',
748
+ and 'van_der_waals_radius', its possible to use experimentally determined
749
+ scattering factors from various sources:
744
750
 
745
751
  >>> density = Density.from_structure(
746
752
  >>> filename_or_structure = path_to_structure,
747
753
  >>> weight_type = "scattering_factors",
748
- >>> scattering_args={"source": "dt1969"}
754
+ >>> weight_type_args={"source": "dt1969"}
749
755
  >>> )
750
756
 
751
757
  or a lowpass filtered representation introduced in [1]_:
@@ -753,7 +759,7 @@ class Density:
753
759
  >>> density = Density.from_structure(
754
760
  >>> filename_or_structure = path_to_structure,
755
761
  >>> weight_type = "lowpass_scattering_factors",
756
- >>> scattering_args={"source": "dt1969"}
762
+ >>> weight_type_args={"source": "dt1969"}
757
763
  >>> )
758
764
 
759
765
  See Also
@@ -775,27 +781,27 @@ class Density:
775
781
  origin=origin,
776
782
  chain=chain,
777
783
  weight_type=weight_type,
778
- scattering_args=scattering_args,
784
+ weight_type_args=weight_type_args,
779
785
  )
780
786
 
781
787
  return cls(
782
788
  data=volume,
783
789
  origin=origin,
784
790
  sampling_rate=sampling_rate,
785
- metadata=structure.details.copy(),
791
+ metadata=structure.metadata.copy(),
786
792
  )
787
793
 
788
794
  def to_file(self, filename: str, gzip: bool = False) -> None:
789
795
  """
790
- Writes current class instance to disk.
796
+ Writes class instance to disk.
791
797
 
792
798
  Parameters
793
799
  ----------
794
800
  filename : str
795
801
  Path to write to.
796
802
  gzip : bool, optional
797
- If True, the output will be gzip compressed and "gz" will be added
798
- to the filename if not already present. By default False.
803
+ Gzip compress the output and add corresponding suffix to filename
804
+ if not present. False by default.
799
805
 
800
806
  References
801
807
  ----------
@@ -811,7 +817,7 @@ class Density:
811
817
  >>> import numpy as np
812
818
  >>> from tme import Density
813
819
  >>> data = np.random.rand(50,50,50)
814
- >>> dens = Density(data = data, origin = (0, 0, 0), sampling_rate = (1, 1, 1))
820
+ >>> dens = Density(data=data, origin=(0, 0, 0), sampling_rate=(1, 1, 1))
815
821
  >>> dens.to_file("example.mrc")
816
822
 
817
823
  The output file can also be directly ``gzip`` compressed. The corresponding
@@ -826,14 +832,14 @@ class Density:
826
832
  In addition, a variety of image file formats are supported [3]_:
827
833
 
828
834
  >>> data = np.random.rand(50,50)
829
- >>> dens = Density(data = data, origin = (0, 0), sampling_rate = (1, 1))
835
+ >>> dens = Density(data=data, origin=(0, 0), sampling_rate=(1, 1))
830
836
  >>> dens.to_file("example.tiff")
831
837
 
832
838
  Notes
833
839
  -----
834
- If ``filename`` ends with "em" or "em.gz" will create an EM file, "h5" or
835
- "h5.gz" will create a HDF5 file. Otherwise, the method defaults to the CCP4/MRC
836
- format, and on failure, falls back to :obj:`skimage.io.imsave`.
840
+ If ``filename`` endswith ".em" or ".h5" a EM file or HDF5 file will be created.
841
+ The default output format is CCP4/MRC and on failure, :obj:`skimage.io.imsave`
842
+ is used.
837
843
 
838
844
  See Also
839
845
  --------
@@ -854,7 +860,7 @@ class Density:
854
860
 
855
861
  def _save_mrc(self, filename: str, gzip: bool = False) -> None:
856
862
  """
857
- Writes current class instance to disk as mrc file.
863
+ Writes class instance to disk as mrc file.
858
864
 
859
865
  Parameters
860
866
  ----------
@@ -873,6 +879,7 @@ class Density:
873
879
  mrc.header.nzstart, mrc.header.nystart, mrc.header.nxstart = np.rint(
874
880
  np.divide(self.origin, self.sampling_rate)
875
881
  )
882
+ mrc.header.origin = tuple(x for x in self.origin)
876
883
  # mrcfile library expects origin to be in xyz format
877
884
  mrc.header.mapc, mrc.header.mapr, mrc.header.maps = (1, 2, 3)
878
885
  mrc.header["origin"] = tuple(self.origin[::-1])
@@ -974,7 +981,7 @@ class Density:
974
981
  self.metadata["std"] = self.metadata.get("std", 0)
975
982
  self.metadata["min"] = self.metadata.get("min", 0)
976
983
  self.metadata["max"] = self.metadata.get("max", 0)
977
- if type(self.data) != np.memmap:
984
+ if not isinstance(self.data, np.memmap):
978
985
  self.metadata["mean"] = self.data.mean()
979
986
  self.metadata["std"] = self.data.std()
980
987
  self.metadata["min"] = self.data.min()
@@ -986,11 +993,15 @@ class Density:
986
993
  @property
987
994
  def empty(self) -> "Density":
988
995
  """
989
- Returns a copy of the current class instance with all elements in
990
- :py:attr:`Density.data` set to zero. :py:attr:`Density.origin` and
991
- :py:attr:`Density.sampling_rate` will be copied, while
992
- :py:attr:`Density.metadata` will be initialized to contain min, max,
993
- mean and standard deviation of :py:attr:`Density.data`.
996
+ Returns a copy of the class instance with all elements in
997
+ :py:attr:`Density.data` set to zero. :py:attr:`Density.metadata` will be
998
+ initialized accordingly. :py:attr:`Density.origin` and
999
+ :py:attr:`Density.sampling_rate` are copied.
1000
+
1001
+ Returns
1002
+ -------
1003
+ :py:class:`Density`
1004
+ Empty class instance.
994
1005
 
995
1006
  Examples
996
1007
  --------
@@ -1010,7 +1021,12 @@ class Density:
1010
1021
 
1011
1022
  def copy(self) -> "Density":
1012
1023
  """
1013
- Returns a copy of the current :py:class:`Density` instance.
1024
+ Create a copy of the class instance.
1025
+
1026
+ Returns
1027
+ -------
1028
+ :py:class:`Density`
1029
+ A copy of the class instance.
1014
1030
 
1015
1031
  Examples
1016
1032
  --------
@@ -1029,8 +1045,7 @@ class Density:
1029
1045
 
1030
1046
  def to_memmap(self) -> None:
1031
1047
  """
1032
- Converts the current class instance's :py:attr:`Density.data` attribute to
1033
- a :obj:`numpy.memmap` instance.
1048
+ Converts :py:attr:`Density.data` to a :obj:`numpy.memmap`.
1034
1049
 
1035
1050
  Examples
1036
1051
  --------
@@ -1055,7 +1070,7 @@ class Density:
1055
1070
  --------
1056
1071
  :py:meth:`Density.to_numpy`
1057
1072
  """
1058
- if type(self.data) == np.memmap:
1073
+ if isinstance(self.data, np.memmap):
1059
1074
  return None
1060
1075
 
1061
1076
  filename = array_to_memmap(arr=self.data)
@@ -1066,8 +1081,7 @@ class Density:
1066
1081
 
1067
1082
  def to_numpy(self) -> None:
1068
1083
  """
1069
- Converts the current class instance's :py:attr:`Density.data` attribute to
1070
- an in-memory :obj:`numpy.ndarray`.
1084
+ Converts :py:attr:`Density.data` to an in-memory :obj:`numpy.ndarray`.
1071
1085
 
1072
1086
  Examples
1073
1087
  --------
@@ -1085,8 +1099,7 @@ class Density:
1085
1099
  @property
1086
1100
  def shape(self) -> Tuple[int]:
1087
1101
  """
1088
- Returns the dimensions of current instance's :py:attr:`Density.data`
1089
- attribute.
1102
+ Returns the dimensions of :py:attr:`Density.data`.
1090
1103
 
1091
1104
  Returns
1092
1105
  -------
@@ -1095,8 +1108,6 @@ class Density:
1095
1108
 
1096
1109
  Examples
1097
1110
  --------
1098
- The following outlines the usage of :py:attr:`Density.shape`:
1099
-
1100
1111
  >>> import numpy as np
1101
1112
  >>> from tme import Density
1102
1113
  >>> dens = Density(np.array([0, 1, 1, 1, 0]))
@@ -1108,13 +1119,12 @@ class Density:
1108
1119
  @property
1109
1120
  def data(self) -> NDArray:
1110
1121
  """
1111
- Returns the value of the current instance's :py:attr:`Density.data`
1112
- attribute.
1122
+ Returns the value of :py:attr:`Density.data`.
1113
1123
 
1114
1124
  Returns
1115
1125
  -------
1116
1126
  NDArray
1117
- Value of the current instance's :py:attr:`Density.data` attribute.
1127
+ Value of the instance's :py:attr:`Density.data` attribute.
1118
1128
 
1119
1129
  Examples
1120
1130
  --------
@@ -1132,20 +1142,20 @@ class Density:
1132
1142
  @data.setter
1133
1143
  def data(self, data: NDArray) -> None:
1134
1144
  """
1135
- Sets the value of the current instance's :py:attr:`Density.data` attribute.
1145
+ Sets the value of the instance's :py:attr:`Density.data` attribute.
1136
1146
  """
1137
1147
  self._data = data
1138
1148
 
1139
1149
  @property
1140
1150
  def origin(self) -> NDArray:
1141
1151
  """
1142
- Returns the value of the current instance's :py:attr:`Density.origin`
1152
+ Returns the value of the instance's :py:attr:`Density.origin`
1143
1153
  attribute.
1144
1154
 
1145
1155
  Returns
1146
1156
  -------
1147
1157
  NDArray
1148
- Value of the current instance's :py:attr:`Density.origin` attribute.
1158
+ Value of the instance's :py:attr:`Density.origin` attribute.
1149
1159
 
1150
1160
  Examples
1151
1161
  --------
@@ -1171,8 +1181,7 @@ class Density:
1171
1181
  @property
1172
1182
  def sampling_rate(self) -> NDArray:
1173
1183
  """
1174
- Returns the value of the current instance's :py:attr:`Density.sampling_rate`
1175
- attribute.
1184
+ Returns the value of the instance's :py:attr:`Density.sampling_rate` attribute.
1176
1185
 
1177
1186
  Returns
1178
1187
  -------
@@ -1193,7 +1202,7 @@ class Density:
1193
1202
  @property
1194
1203
  def metadata(self) -> Dict:
1195
1204
  """
1196
- Returns the current instance's :py:attr:`Density.metadata` dictionary attribute.
1205
+ Returns the instance's :py:attr:`Density.metadata` attribute.
1197
1206
 
1198
1207
  Returns
1199
1208
  -------
@@ -1235,7 +1244,7 @@ class Density:
1235
1244
  Pads the internal data array according to box.
1236
1245
 
1237
1246
  Negative slices indices will result in a left-hand padding, while
1238
- slice indices larger than the box_size property of the current class
1247
+ slice indices larger than the box_size property of the class
1239
1248
  instance will result in a right-hand padding.
1240
1249
 
1241
1250
  Parameters
@@ -1264,17 +1273,15 @@ class Density:
1264
1273
 
1265
1274
  def adjust_box(self, box: Tuple[slice], pad_kwargs: Dict = {}) -> None:
1266
1275
  """
1267
- Adjusts the internal data array and origin of the current class instance
1276
+ Adjusts :py:attr:`Density.data` and :py:attr:`Density.origin`
1268
1277
  according to the provided box.
1269
1278
 
1270
1279
  Parameters
1271
1280
  ----------
1272
1281
  box : tuple of slices
1273
- A tuple of slices describing how each axis of the volume array
1274
- should be sliced. See :py:meth:`Density.trim_box` on how to produce
1275
- such an object.
1282
+ Description of how each axis of :py:attr:`Density.data` should be sliced.
1276
1283
  pad_kwargs: dict, optional
1277
- Parameter dictionary passed to numpy pad.
1284
+ Parameter dictionary passed to :obj:`numpy.pad`.
1278
1285
 
1279
1286
  See Also
1280
1287
  --------
@@ -1454,8 +1461,8 @@ class Density:
1454
1461
  Raises
1455
1462
  ------
1456
1463
  ValueError
1457
- If the length of `new_shape` does not match the dimensionality of the
1458
- internal data array.
1464
+ If the length of ``new_shape`` does not match the dimensionality of
1465
+ :py:attr:`Density.data`.
1459
1466
 
1460
1467
  Examples
1461
1468
  --------
@@ -1469,13 +1476,6 @@ class Density:
1469
1476
  >>> dens.data
1470
1477
  array([0, 1, 1, 1, 0])
1471
1478
 
1472
- It's also possible to pass a user-defined ``padding_value``:
1473
-
1474
- >>> dens = Density(np.array([1,1,1]))
1475
- >>> dens.pad(new_shape = (5,), center = True, padding_value = -1)
1476
- >>> dens.data
1477
- array([-1, 1, 1, 1, -1])
1478
-
1479
1479
  If ``center`` is set to False, the padding values will be appended:
1480
1480
 
1481
1481
  >>> dens = Density(np.array([1,1,1]))
@@ -1483,6 +1483,12 @@ class Density:
1483
1483
  >>> dens.data
1484
1484
  array([1, 1, 1, 0, 0])
1485
1485
 
1486
+ It's also possible to pass a user-defined ``padding_value``:
1487
+
1488
+ >>> dens = Density(np.array([1,1,1]))
1489
+ >>> dens.pad(new_shape = (5,), center = True, padding_value = -1)
1490
+ >>> dens.data
1491
+ array([-1, 1, 1, 1, -1])
1486
1492
  """
1487
1493
  if len(new_shape) != self.data.ndim:
1488
1494
  raise ValueError(
@@ -1522,7 +1528,7 @@ class Density:
1522
1528
  Returns
1523
1529
  -------
1524
1530
  :py:class:`Density`
1525
- A centered copy of the current class instance.
1531
+ A centered copy of the class instance.
1526
1532
  NDArray
1527
1533
  The offset between array center and center of mass.
1528
1534
 
@@ -1531,7 +1537,6 @@ class Density:
1531
1537
  :py:meth:`Density.trim_box`
1532
1538
  :py:meth:`Density.minimum_enclosing_box`
1533
1539
 
1534
-
1535
1540
  Examples
1536
1541
  --------
1537
1542
  :py:meth:`Density.centered` returns a tuple containing a centered version
@@ -1591,123 +1596,15 @@ class Density:
1591
1596
  shift = np.subtract(center, self.center_of_mass(ret.data, cutoff))
1592
1597
  return ret, shift
1593
1598
 
1594
- @classmethod
1595
- def rotate_array(
1596
- cls,
1597
- arr: NDArray,
1598
- rotation_matrix: NDArray,
1599
- arr_mask: NDArray = None,
1600
- translation: NDArray = None,
1601
- use_geometric_center: bool = False,
1602
- out: NDArray = None,
1603
- out_mask: NDArray = None,
1604
- order: int = 3,
1605
- ) -> None:
1606
- """
1607
- Rotates coordinates of arr according to rotation_matrix.
1608
-
1609
- If no output array is provided, this method will compute an array with
1610
- sufficient space to hold all elements. If both `arr` and `arr_mask`
1611
- are provided, `arr_mask` will be centered according to arr.
1612
-
1613
- Parameters
1614
- ----------
1615
- arr : NDArray
1616
- The input array to be rotated.
1617
- arr_mask : NDArray, optional
1618
- The mask of `arr` that will be equivalently rotated.
1619
- rotation_matrix : NDArray
1620
- The rotation matrix to apply [d x d].
1621
- translation : NDArray
1622
- The translation to apply [d].
1623
- use_geometric_center : bool, optional
1624
- Whether the rotation should be centered around the geometric
1625
- or mass center. Default is mass center.
1626
- out : NDArray, optional
1627
- The output array to write the rotation of `arr` to.
1628
- out_mask : NDArray, optional
1629
- The output array to write the rotation of `arr_mask` to.
1630
- order : int, optional
1631
- Spline interpolation order. Has to be in the range 0-5.
1632
- """
1633
-
1634
- return NumpyFFTWBackend().rotate_array(
1635
- arr=arr,
1636
- rotation_matrix=rotation_matrix,
1637
- arr_mask=arr_mask,
1638
- translation=translation,
1639
- use_geometric_center=use_geometric_center,
1640
- out=out,
1641
- out_mask=out_mask,
1642
- order=order,
1643
- )
1644
-
1645
- @staticmethod
1646
- def rotate_array_coordinates(
1647
- arr: NDArray,
1648
- coordinates: NDArray,
1649
- rotation_matrix: NDArray,
1650
- translation: NDArray = None,
1651
- out: NDArray = None,
1652
- use_geometric_center: bool = True,
1653
- arr_mask: NDArray = None,
1654
- mask_coordinates: NDArray = None,
1655
- out_mask: NDArray = None,
1656
- ) -> None:
1657
- """
1658
- Rotates coordinates of arr according to rotation_matrix.
1659
-
1660
- If no output array is provided, this method will compute an array with
1661
- sufficient space to hold all elements. If both `arr` and `arr_mask`
1662
- are provided, `arr_mask` will be centered according to arr.
1663
-
1664
- No centering will be performed if the rotation matrix is the identity matrix.
1665
-
1666
- Parameters
1667
- ----------
1668
- arr : NDArray
1669
- The input array to be rotated.
1670
- coordinates : NDArray
1671
- The pointcloud [d x N] containing elements of `arr` that should be rotated.
1672
- See :py:meth:`Density.to_pointcloud` on how to obtain the coordinates.
1673
- rotation_matrix : NDArray
1674
- The rotation matrix to apply [d x d].
1675
- rotation_matrix : NDArray
1676
- The translation to apply [d].
1677
- out : NDArray, optional
1678
- The output array to write the rotation of `arr` to.
1679
- use_geometric_center : bool, optional
1680
- Whether the rotation should be centered around the geometric
1681
- or mass center.
1682
- arr_mask : NDArray, optional
1683
- The mask of `arr` that will be equivalently rotated.
1684
- mask_coordinates : NDArray, optional
1685
- Equivalent to `coordinates`, but containing elements of `arr_mask`
1686
- that should be rotated.
1687
- out_mask : NDArray, optional
1688
- The output array to write the rotation of `arr_mask` to.
1689
- """
1690
- return NumpyFFTWBackend().rotate_array_coordinates(
1691
- arr=arr,
1692
- coordinates=coordinates,
1693
- rotation_matrix=rotation_matrix,
1694
- translation=translation,
1695
- out=out,
1696
- use_geometric_center=use_geometric_center,
1697
- arr_mask=arr_mask,
1698
- mask_coordinates=mask_coordinates,
1699
- out_mask=out_mask,
1700
- )
1701
-
1702
1599
  def rigid_transform(
1703
1600
  self,
1704
1601
  rotation_matrix: NDArray,
1705
1602
  translation: NDArray = None,
1706
1603
  order: int = 3,
1707
- use_geometric_center: bool = False,
1604
+ use_geometric_center: bool = True,
1708
1605
  ) -> "Density":
1709
1606
  """
1710
- Performs a rigid transform of the current class instance.
1607
+ Performs a rigid transform of the class instance.
1711
1608
 
1712
1609
  Parameters
1713
1610
  ----------
@@ -1756,89 +1653,134 @@ class Density:
1756
1653
  --------
1757
1654
  :py:meth:`Density.centered`, :py:meth:`Density.minimum_enclosing_box`
1758
1655
  """
1759
- transformed_map = self.empty
1760
-
1761
- self.rotate_array(
1656
+ ret = self.empty
1657
+ NumpyFFTWBackend().rigid_transform(
1762
1658
  arr=self.data,
1763
1659
  rotation_matrix=rotation_matrix,
1764
1660
  translation=translation,
1765
- order=order,
1766
1661
  use_geometric_center=use_geometric_center,
1767
- out=transformed_map.data,
1662
+ out=ret.data,
1663
+ order=order,
1768
1664
  )
1769
- eps = np.finfo(transformed_map.data.dtype).eps
1770
- transformed_map.data[transformed_map.data < eps] = 0
1771
- return transformed_map
1772
1665
 
1773
- def align_origins(self, other_map: "Density") -> "Density":
1666
+ eps = np.finfo(ret.data.dtype).eps
1667
+ ret.data[np.abs(ret.data) < eps] = 0
1668
+ return ret
1669
+
1670
+ def resample(
1671
+ self, new_sampling_rate: Tuple[float], method: str = "spline", order: int = 1
1672
+ ) -> "Density":
1774
1673
  """
1775
- Aligns the origin of another to the origin of the current class instance.
1674
+ Resamples :py:attr:`Density.data` to ``new_sampling_rate``.
1776
1675
 
1777
1676
  Parameters
1778
1677
  ----------
1779
- other_map : Density
1780
- An instance of :py:class:`Density` class to align with the current map.
1678
+ new_sampling_rate : tuple of floats or float
1679
+ Sampling rate to resample to for a single or all axes.
1680
+ method: str, optional
1681
+ Resampling method to use, defaults to `spline`. Availabe options are:
1682
+
1683
+ +---------+----------------------------------------------------------+
1684
+ | spline | Smooth spline interpolation via :obj:`scipy.ndimage.zoom`|
1685
+ +---------+----------------------------------------------------------+
1686
+ | fourier | Frequency preserving Fourier cropping |
1687
+ +---------+----------------------------------------------------------+
1688
+
1689
+ order : int, optional
1690
+ Order of spline used for interpolation, by default 1. Ignored when
1691
+ ``method`` is `fourier`.
1781
1692
 
1782
1693
  Raises
1783
1694
  ------
1784
1695
  ValueError
1785
- If the sampling_rate of both class instances does not match.
1696
+ If ``method`` is not supported.
1786
1697
 
1787
1698
  Returns
1788
1699
  -------
1789
- Density
1790
- A modified copy of `other_map` with aligned origin.
1791
- """
1792
- if not np.allclose(self.sampling_rate, other_map.sampling_rate):
1793
- raise ValueError("sampling_rate of both maps have to match.")
1700
+ :py:class:`Density`
1701
+ A resampled copy of the class instance.
1794
1702
 
1795
- origin_difference = np.divide(
1796
- np.subtract(self.origin, other_map.origin), self.sampling_rate
1797
- )
1798
- origin_difference = origin_difference.astype(int)
1703
+ Examples
1704
+ --------
1705
+ The following makes use of :py:meth:`tme.matching_utils.create_mask`
1706
+ to define a :py:class:`Density` instance containing a 2D circle with
1707
+ a sampling rate of 2
1799
1708
 
1800
- box_start = np.minimum(origin_difference, other_map.shape)
1801
- box_end = np.maximum(origin_difference, other_map.shape)
1709
+ >>> from tme import Density
1710
+ >>> from tme.matching_utils import create_mask
1711
+ >>> mask = create_mask(
1712
+ >>> mask_type="ellipse",
1713
+ >>> shape=(11,11),
1714
+ >>> center=(5,5),
1715
+ >>> radius=3
1716
+ >>> )
1717
+ >>> dens = Density(mask, sampling_rate=2)
1718
+ >>> dens
1719
+ Origin: (0.0, 0.0), sampling_rate: (2, 2), Shape: (11, 11)
1802
1720
 
1803
- new_box = tuple(slice(*pos) for pos in zip(box_start, box_end))
1721
+ Using :py:meth:`Density.resample` we can modulate the sampling rate
1722
+ using spline interpolation of desired order
1804
1723
 
1805
- ret = other_map.copy()
1806
- ret.adjust_box(new_box)
1807
- return ret
1724
+ >>> dens.resample(new_sampling_rate= 4, method="spline", order=3)
1725
+ Origin: (0.0, 0.0), sampling_rate: (4, 4), Shape: (6, 6)
1808
1726
 
1809
- def resample(self, new_sampling_rate: Tuple[float], order: int = 1) -> "Density":
1810
- """
1811
- Resamples the current class instance to ``new_sampling_rate`` using
1812
- spline interpolation of order ``order``.
1727
+ Or Fourier cropping which results in a less smooth output, but more faithfully
1728
+ captures the contained frequency information
1813
1729
 
1814
- Parameters
1815
- ----------
1816
- new_sampling_rate : tuple of floats or float
1817
- Sampling rate to resample to.
1818
- order : int, optional
1819
- Order of spline used for interpolation, by default 1.
1730
+ >>> dens.resample(new_sampling_rate=4, method="fourier")
1731
+ Origin: (0.0, 0.0), sampling_rate: (4, 4), Shape: (6, 6)
1732
+
1733
+ ``new_sampling_rate`` can also be specified per axis
1734
+
1735
+ >>> dens.resample(new_sampling_rate=(4,1), method="spline", order=3)
1736
+ Origin: (0.0, 0.0), sampling_rate: (4, 1), Shape: (6, 22)
1820
1737
 
1821
- Returns
1822
- -------
1823
- Density
1824
- A resampled instance of `Density` class.
1825
1738
  """
1826
- map_copy, new_sampling_rate = self.copy(), np.array(new_sampling_rate)
1739
+ _supported_methods = ("spline", "fourier")
1740
+ if method not in _supported_methods:
1741
+ raise ValueError(
1742
+ f"Expected method to be one of {_supported_methods}, got '{method}'."
1743
+ )
1744
+ new_sampling_rate = np.array(new_sampling_rate)
1827
1745
  new_sampling_rate = np.repeat(
1828
- new_sampling_rate, map_copy.data.ndim // new_sampling_rate.size
1746
+ new_sampling_rate, self.data.ndim // new_sampling_rate.size
1829
1747
  )
1830
- scale_factor = np.divide(map_copy.sampling_rate, new_sampling_rate)
1831
1748
 
1832
- map_copy.data = zoom(map_copy.data, scale_factor, order=order)
1833
- map_copy.sampling_rate = new_sampling_rate
1749
+ ret = self.copy()
1750
+ scale_factor = np.divide(ret.sampling_rate, new_sampling_rate)
1751
+ if method == "spline":
1752
+ ret.data = zoom(ret.data, scale_factor, order=order)
1753
+ elif method == "fourier":
1754
+ ret_shape = np.round(np.multiply(scale_factor, ret.shape)).astype(int)
1755
+
1756
+ axis = range(len(ret_shape))
1757
+ mask = np.zeros(self.shape, dtype=bool)
1758
+ mask[tuple(slice(0, x) for x in ret_shape)] = 1
1759
+ mask = np.roll(
1760
+ mask, shift=-np.floor(np.divide(ret_shape, 2)).astype(int), axis=axis
1761
+ )
1762
+ mask_ret = np.zeros(ret_shape, dtype=bool)
1763
+ mask_ret[tuple(slice(0, x) for x in self.shape)] = 1
1764
+ mask_ret = np.roll(
1765
+ mask_ret,
1766
+ shift=-np.floor(np.divide(self.shape, 2)).astype(int),
1767
+ axis=axis,
1768
+ )
1769
+
1770
+ arr_ft = np.fft.fftn(self.data)
1771
+ arr_ft *= np.prod(ret_shape) / np.prod(self.shape)
1772
+ ret_ft = np.zeros(ret_shape, dtype=arr_ft.dtype)
1773
+ ret_ft[mask_ret] = arr_ft[mask]
1774
+ ret.data = np.real(np.fft.ifftn(ret_ft))
1834
1775
 
1835
- return map_copy
1776
+ ret.sampling_rate = new_sampling_rate
1777
+ return ret
1836
1778
 
1837
1779
  def density_boundary(
1838
1780
  self, weight: float, fraction_surface: float = 0.1, volume_factor: float = 1.21
1839
1781
  ) -> Tuple[float]:
1840
1782
  """
1841
- Computes the density boundary of the current class instance. The density
1783
+ Computes the density boundary of the class instance. The density
1842
1784
  boundary in this setting is defined as minimal and maximal density value
1843
1785
  enclosing a certain ``weight``.
1844
1786
 
@@ -1894,31 +1836,31 @@ class Density:
1894
1836
  self, density_boundaries: Tuple[float], method: str = "ConvexHull"
1895
1837
  ) -> NDArray:
1896
1838
  """
1897
- Calculates the surface coordinates of the current class instance using
1839
+ Calculates the surface coordinates of the class instance using
1898
1840
  different boundary and surface detection methods. This method is relevant
1899
- for determining coordinates used in template matching,
1900
- see :py:class:`tme.matching_exhaustive.FitRefinement`.
1841
+ for determining coordinates used in non-exhaustive template matching,
1842
+ see :py:class:`tme.matching_optimization.optimize_match`.
1901
1843
 
1902
1844
  Parameters
1903
1845
  ----------
1904
1846
  density_boundaries : tuple
1905
- Tuple of two floats with lower and upper bounds of density values
1906
- to be considered on the surface (see :py:meth:`Density.density_boundary`).
1847
+ Lower and upper bound of density values to be considered
1848
+ (can be obtained from :py:meth:`Density.density_boundary`).
1907
1849
  method : str, optional
1908
- Surface coordinates are determined using this method:
1850
+ Method to use for surface coordinate computation
1909
1851
 
1910
1852
  +--------------+-----------------------------------------------------+
1911
- | 'ConvexHull' | Use the lower bound density convex hull vertices. |
1853
+ | ConvexHull | Use the lower bound density convex hull vertices. |
1912
1854
  +--------------+-----------------------------------------------------+
1913
- | 'Weight' | Use all coordinates within ``density_boundaries``. |
1855
+ | Weight | Use all coordinates within ``density_boundaries``. |
1914
1856
  +--------------+-----------------------------------------------------+
1915
- | 'Sobel' | Set densities below the lower bound density to zero |
1857
+ | Sobel | Set densities below the lower bound density to zero |
1916
1858
  | | apply a sobel filter and return density coordinates |
1917
1859
  | | larger than 0.5 times the maximum filter value. |
1918
1860
  +--------------+-----------------------------------------------------+
1919
- | 'Laplace' | Like 'Sobel' but with a laplace filter. |
1861
+ | Laplace | Like 'Sobel', but with a Laplace filter. |
1920
1862
  +--------------+-----------------------------------------------------+
1921
- | 'Minimum' | Like 'Sobel' and 'Laplace' but with a spherical |
1863
+ | Minimum | Like 'Sobel' and 'Laplace' but with a spherical |
1922
1864
  | | minimum filter on the lower density bound. |
1923
1865
  +--------------+-----------------------------------------------------+
1924
1866
 
@@ -1930,15 +1872,11 @@ class Density:
1930
1872
  Returns
1931
1873
  -------
1932
1874
  NDArray
1933
- An array of surface coordinates with shape (number_of_points, dimensions).
1875
+ An array of surface coordinates with shape (points, dimensions).
1934
1876
 
1935
1877
  References
1936
1878
  ----------
1937
- .. [1] Cragnolini T, Sahota H, Joseph AP, Sweeney A, Malhotra S,
1938
- Vasishtan D, Topf M (2021a) TEMPy2: A Python library with
1939
- improved 3D electron microscopy density-fitting and validation
1940
- workflows. Acta Crystallogr Sect D Struct Biol 77:41–47.
1941
- https://doi.org/10.1107/S2059798320014928
1879
+ .. [1] Cragnolini T, et al. (2021) Acta Crys Sect D Struct Biol
1942
1880
 
1943
1881
  See Also
1944
1882
  --------
@@ -1948,12 +1886,12 @@ class Density:
1948
1886
  :py:class:`tme.matching_optimization.Envelope`
1949
1887
  :py:class:`tme.matching_optimization.Chamfer`
1950
1888
  """
1951
- available_methods = ["ConvexHull", "Weight", "Sobel", "Laplace", "Minimum"]
1889
+ _available_methods = ["ConvexHull", "Weight", "Sobel", "Laplace", "Minimum"]
1952
1890
 
1953
- if method not in available_methods:
1891
+ if method not in _available_methods:
1954
1892
  raise ValueError(
1955
1893
  "Argument method has to be one of the following: %s"
1956
- % ", ".join(available_methods)
1894
+ % ", ".join(_available_methods)
1957
1895
  )
1958
1896
 
1959
1897
  lower_bound, upper_bound = density_boundaries
@@ -2001,7 +1939,7 @@ class Density:
2001
1939
  def normal_vectors(self, coordinates: NDArray) -> NDArray:
2002
1940
  """
2003
1941
  Calculates the normal vectors for the given coordinates on the densities
2004
- of the current class instance. If the normal vector to a given coordinate
1942
+ of the class instance. If the normal vector to a given coordinate
2005
1943
  can not be computed, the zero vector is returned instead. The output of this
2006
1944
  function can e.g. be used in
2007
1945
  :py:class:`tme.matching_optimization.NormalVectorScore`.
@@ -2051,12 +1989,7 @@ class Density:
2051
1989
  in_box = np.logical_and(
2052
1990
  coordinates < np.array(self.shape), coordinates >= 0
2053
1991
  ).min(axis=1)
2054
-
2055
- out_of_box = np.invert(in_box)
2056
- if out_of_box.sum() > 0:
2057
- print(coordinates[out_of_box, :])
2058
- raise ValueError("Coordinates outside of self.data detected.")
2059
-
1992
+ coordinates = coordinates[in_box, :]
2060
1993
  for index in range(coordinates.shape[0]):
2061
1994
  point = coordinates[index, :]
2062
1995
  start = np.maximum(point - 1, 0)
@@ -2076,22 +2009,16 @@ class Density:
2076
2009
 
2077
2010
  def core_mask(self) -> NDArray:
2078
2011
  """
2079
- Calculates the weighted core mask of the current class instance.
2080
-
2081
- Core mask is calculated by performing binary erosion on the internal
2082
- data array in an iterative fashion until no non-zero data elements remain.
2083
- In each iteration, all data elements larger than zero are incremented by one
2084
- in a mask with same shape as the internal data array. Therefore,
2085
- data elements in the output array with a value of n remained non-zero for
2086
- n rounds of binary erosion. The higher the value, the more likely a data element
2087
- is part of the core of the density map.
2012
+ Calculates a weighted core mask by performing iterative binary erosion on
2013
+ :py:attr:`Density.data`. In each iteration, all mask elements corresponding
2014
+ to a non-zero data elemnt are incremented by one. Therefore, a mask element
2015
+ with value N corresponds to a data value that remained non-zero for N iterations.
2016
+ Mask elements with high values are likely part of the core density [1]_.
2088
2017
 
2089
2018
  Returns
2090
2019
  -------
2091
2020
  NDArray
2092
- An array with same shape as internal data array. Values contained
2093
- indicate how many rounds of binary erosion were necessary to nullify
2094
- a given data element.
2021
+ Core-weighted mask with shape of :py:attr:`Density.data`.
2095
2022
 
2096
2023
  References
2097
2024
  ----------
@@ -2126,19 +2053,7 @@ class Density:
2126
2053
  NDArray
2127
2054
  Center of mass with shape (arr.ndim).
2128
2055
  """
2129
- cutoff = arr.min() - 1 if cutoff is None else cutoff
2130
- arr = np.where(arr > cutoff, arr, 0)
2131
- denominator = np.sum(arr)
2132
- grids = np.ogrid[tuple(slice(0, i) for i in arr.shape)]
2133
-
2134
- center_of_mass = np.array(
2135
- [
2136
- np.sum(np.multiply(arr, grids[dim].astype(float))) / denominator
2137
- for dim in range(arr.ndim)
2138
- ]
2139
- )
2140
-
2141
- return center_of_mass
2056
+ return NumpyFFTWBackend().center_of_mass(arr, cutoff)
2142
2057
 
2143
2058
  @classmethod
2144
2059
  def match_densities(
@@ -2187,21 +2102,20 @@ class Density:
2187
2102
  -----
2188
2103
  No densities below cutoff_template are present in the returned Density object.
2189
2104
  """
2190
- from .matching_exhaustive import normalize_under_mask
2105
+ from .matching_utils import normalize_template
2191
2106
  from .matching_optimization import optimize_match, create_score_object
2192
2107
 
2193
2108
  template_mask = template.empty
2194
- template_mask.data[:] = 1
2109
+ template_mask.data.fill(1)
2195
2110
 
2196
- normalize_under_mask(
2111
+ normalize_template(
2197
2112
  template=template.data,
2198
2113
  mask=template_mask.data,
2199
- mask_intensity=template_mask.data.sum(),
2114
+ n_observations=template_mask.data.sum(),
2200
2115
  )
2201
2116
 
2202
2117
  target_sampling_rate = np.array(target.sampling_rate)
2203
2118
  template_sampling_rate = np.array(template.sampling_rate)
2204
-
2205
2119
  target_sampling_rate = np.repeat(
2206
2120
  target_sampling_rate, target.data.ndim // target_sampling_rate.size
2207
2121
  )
@@ -2340,49 +2254,6 @@ class Density:
2340
2254
 
2341
2255
  return out, final_translation, rotation_matrix
2342
2256
 
2343
- @staticmethod
2344
- def align_coordinate_systems(target: "Density", template: "Density") -> "Density":
2345
- """
2346
- Aligns the coordinate system of `target` and `template`.
2347
-
2348
- Parameters
2349
- ----------
2350
- target : Density
2351
- The target density whose coordinate system should remain unchanged.
2352
- template : Density
2353
- The template density that will be aligned to match the target's
2354
- coordinate system.
2355
-
2356
- Raises
2357
- ------
2358
- ValueError
2359
- If the `sampling_rate` of `target` and `template` do not match.
2360
-
2361
- Returns
2362
- -------
2363
- Density
2364
- A copy of `template` aligned to the coordinate system of `target`.
2365
- The `box_size` and `origin` will match that of `target`.
2366
-
2367
- See Also
2368
- --------
2369
- :py:meth:`Density.match_densities` : To match aligned template to target.
2370
- """
2371
- if not np.allclose(target.sampling_rate, template.sampling_rate):
2372
- raise ValueError("sampling_rate of both maps have to match.")
2373
-
2374
- template = template.copy()
2375
- template.pad(target.shape, center=True)
2376
-
2377
- origin_difference = np.divide(
2378
- np.subtract(template.origin, target.origin), target.sampling_rate
2379
- )
2380
- template = template.rigid_transform(
2381
- rotation_matrix=np.eye(template.data.ndim), translation=origin_difference
2382
- )
2383
- template.origin = target.origin.copy()
2384
- return template
2385
-
2386
2257
  @staticmethod
2387
2258
  def fourier_shell_correlation(density1: "Density", density2: "Density") -> NDArray:
2388
2259
  """
@@ -2439,3 +2310,9 @@ class Density:
2439
2310
  qidx = np.where(qbins < qx.max())
2440
2311
 
2441
2312
  return np.vstack((qbins[qidx], FSC[qidx])).T
2313
+
2314
+
2315
+ def is_gzipped(filename: str) -> bool:
2316
+ """Check if a file is a gzip file by reading its magic number."""
2317
+ with open(filename, "rb") as f:
2318
+ return f.read(2) == b"\x1f\x8b"