pytme 0.2.1__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.
Files changed (49) hide show
  1. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/match_template.py +147 -93
  2. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/postprocess.py +67 -26
  3. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/preprocessor_gui.py +175 -85
  4. pytme-0.2.2.dist-info/METADATA +91 -0
  5. pytme-0.2.2.dist-info/RECORD +74 -0
  6. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/WHEEL +1 -1
  7. scripts/extract_candidates.py +20 -13
  8. scripts/match_template.py +147 -93
  9. scripts/match_template_filters.py +154 -95
  10. scripts/postprocess.py +67 -26
  11. scripts/preprocessor_gui.py +175 -85
  12. scripts/refine_matches.py +265 -61
  13. tme/__init__.py +0 -1
  14. tme/__version__.py +1 -1
  15. tme/analyzer.py +451 -809
  16. tme/backends/__init__.py +40 -11
  17. tme/backends/_jax_utils.py +185 -0
  18. tme/backends/cupy_backend.py +111 -223
  19. tme/backends/jax_backend.py +214 -150
  20. tme/backends/matching_backend.py +445 -384
  21. tme/backends/mlx_backend.py +32 -59
  22. tme/backends/npfftw_backend.py +239 -507
  23. tme/backends/pytorch_backend.py +21 -145
  24. tme/density.py +233 -363
  25. tme/extensions.cpython-311-darwin.so +0 -0
  26. tme/matching_data.py +322 -285
  27. tme/matching_exhaustive.py +172 -1493
  28. tme/matching_optimization.py +143 -106
  29. tme/matching_scores.py +884 -0
  30. tme/matching_utils.py +280 -386
  31. tme/memory.py +377 -0
  32. tme/orientations.py +52 -12
  33. tme/parser.py +3 -4
  34. tme/preprocessing/_utils.py +61 -32
  35. tme/preprocessing/compose.py +7 -3
  36. tme/preprocessing/frequency_filters.py +49 -39
  37. tme/preprocessing/tilt_series.py +34 -40
  38. tme/preprocessor.py +560 -526
  39. tme/structure.py +491 -188
  40. tme/types.py +5 -3
  41. pytme-0.2.1.dist-info/METADATA +0 -73
  42. pytme-0.2.1.dist-info/RECORD +0 -73
  43. tme/helpers.py +0 -881
  44. tme/matching_constrained.py +0 -195
  45. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/estimate_ram_usage.py +0 -0
  46. {pytme-0.2.1.data → pytme-0.2.2.data}/scripts/preprocess.py +0 -0
  47. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/LICENSE +0 -0
  48. {pytme-0.2.1.dist-info → pytme-0.2.2.dist-info}/entry_points.txt +0 -0
  49. {pytme-0.2.1.dist-info → pytme-0.2.2.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,7 +113,7 @@ 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
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 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
  --------
@@ -314,7 +315,7 @@ class Density:
314
315
  use_memmap = False
315
316
 
316
317
  if subset is not None:
317
- subset_shape = [x.stop - x.start for x in subset]
318
+ subset_shape = tuple(x.stop - x.start for x in subset)
318
319
  if np.allclose(subset_shape, data_shape):
319
320
  return cls._load_mrc(
320
321
  filename=filename, subset=None, use_memmap=use_memmap
@@ -337,7 +338,6 @@ class Density:
337
338
  data = mrc.data
338
339
 
339
340
  if not np.all(crs_index == (0, 1, 2)):
340
- data.setflags(write=True)
341
341
  data = np.transpose(data, crs_index)
342
342
  start = np.take(start, crs_index)
343
343
 
@@ -642,7 +642,7 @@ class Density:
642
642
  sampling_rate: NDArray = np.ones(1),
643
643
  origin: Tuple[float] = None,
644
644
  weight_type: str = "atomic_weight",
645
- scattering_args: Dict = dict(),
645
+ weight_type_args: Dict = {},
646
646
  chain: str = None,
647
647
  filter_by_elements: Set = None,
648
648
  filter_by_residues: Set = None,
@@ -672,6 +672,8 @@ class Density:
672
672
  weight_type : str, optional
673
673
  Which weight should be given to individual atoms. For valid values
674
674
  see :py:meth:`tme.structure.Structure.to_volume`.
675
+ weight_type_args : dict, optional
676
+ Additional arguments for atom weight computation.
675
677
  chain : str, optional
676
678
  The chain that should be extracted from the structure. If multiple chains
677
679
  should be selected, they needto be a comma separated string,
@@ -680,8 +682,6 @@ class Density:
680
682
  Set of atomic elements to keep. Default is all atoms.
681
683
  filter_by_residues : set, optional
682
684
  Set of residues to keep. Default is all residues.
683
- scattering_args : dict, optional
684
- Additional arguments for scattering factor computation.
685
685
 
686
686
  Returns
687
687
  -------
@@ -738,14 +738,14 @@ class Density:
738
738
  >>> )
739
739
 
740
740
  :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:
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:
744
744
 
745
745
  >>> density = Density.from_structure(
746
746
  >>> filename_or_structure = path_to_structure,
747
747
  >>> weight_type = "scattering_factors",
748
- >>> scattering_args={"source": "dt1969"}
748
+ >>> weight_type_args={"source": "dt1969"}
749
749
  >>> )
750
750
 
751
751
  or a lowpass filtered representation introduced in [1]_:
@@ -753,7 +753,7 @@ class Density:
753
753
  >>> density = Density.from_structure(
754
754
  >>> filename_or_structure = path_to_structure,
755
755
  >>> weight_type = "lowpass_scattering_factors",
756
- >>> scattering_args={"source": "dt1969"}
756
+ >>> weight_type_args={"source": "dt1969"}
757
757
  >>> )
758
758
 
759
759
  See Also
@@ -775,27 +775,27 @@ class Density:
775
775
  origin=origin,
776
776
  chain=chain,
777
777
  weight_type=weight_type,
778
- scattering_args=scattering_args,
778
+ weight_type_args=weight_type_args,
779
779
  )
780
780
 
781
781
  return cls(
782
782
  data=volume,
783
783
  origin=origin,
784
784
  sampling_rate=sampling_rate,
785
- metadata=structure.details.copy(),
785
+ metadata=structure.metadata.copy(),
786
786
  )
787
787
 
788
788
  def to_file(self, filename: str, gzip: bool = False) -> None:
789
789
  """
790
- Writes current class instance to disk.
790
+ Writes class instance to disk.
791
791
 
792
792
  Parameters
793
793
  ----------
794
794
  filename : str
795
795
  Path to write to.
796
796
  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.
797
+ Gzip compress the output and add corresponding suffix to filename
798
+ if not present. False by default.
799
799
 
800
800
  References
801
801
  ----------
@@ -811,7 +811,7 @@ class Density:
811
811
  >>> import numpy as np
812
812
  >>> from tme import Density
813
813
  >>> data = np.random.rand(50,50,50)
814
- >>> dens = Density(data = data, origin = (0, 0, 0), sampling_rate = (1, 1, 1))
814
+ >>> dens = Density(data=data, origin=(0, 0, 0), sampling_rate=(1, 1, 1))
815
815
  >>> dens.to_file("example.mrc")
816
816
 
817
817
  The output file can also be directly ``gzip`` compressed. The corresponding
@@ -826,14 +826,14 @@ class Density:
826
826
  In addition, a variety of image file formats are supported [3]_:
827
827
 
828
828
  >>> data = np.random.rand(50,50)
829
- >>> dens = Density(data = data, origin = (0, 0), sampling_rate = (1, 1))
829
+ >>> dens = Density(data=data, origin=(0, 0), sampling_rate=(1, 1))
830
830
  >>> dens.to_file("example.tiff")
831
831
 
832
832
  Notes
833
833
  -----
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`.
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.
837
837
 
838
838
  See Also
839
839
  --------
@@ -854,7 +854,7 @@ class Density:
854
854
 
855
855
  def _save_mrc(self, filename: str, gzip: bool = False) -> None:
856
856
  """
857
- Writes current class instance to disk as mrc file.
857
+ Writes class instance to disk as mrc file.
858
858
 
859
859
  Parameters
860
860
  ----------
@@ -974,7 +974,7 @@ class Density:
974
974
  self.metadata["std"] = self.metadata.get("std", 0)
975
975
  self.metadata["min"] = self.metadata.get("min", 0)
976
976
  self.metadata["max"] = self.metadata.get("max", 0)
977
- if type(self.data) != np.memmap:
977
+ if not isinstance(self.data, np.memmap):
978
978
  self.metadata["mean"] = self.data.mean()
979
979
  self.metadata["std"] = self.data.std()
980
980
  self.metadata["min"] = self.data.min()
@@ -986,11 +986,15 @@ class Density:
986
986
  @property
987
987
  def empty(self) -> "Density":
988
988
  """
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`.
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.
994
998
 
995
999
  Examples
996
1000
  --------
@@ -1010,7 +1014,12 @@ class Density:
1010
1014
 
1011
1015
  def copy(self) -> "Density":
1012
1016
  """
1013
- Returns a copy of the current :py:class:`Density` instance.
1017
+ Create a copy of the class instance.
1018
+
1019
+ Returns
1020
+ -------
1021
+ :py:class:`Density`
1022
+ A copy of the class instance.
1014
1023
 
1015
1024
  Examples
1016
1025
  --------
@@ -1029,8 +1038,7 @@ class Density:
1029
1038
 
1030
1039
  def to_memmap(self) -> None:
1031
1040
  """
1032
- Converts the current class instance's :py:attr:`Density.data` attribute to
1033
- a :obj:`numpy.memmap` instance.
1041
+ Converts :py:attr:`Density.data` to a :obj:`numpy.memmap`.
1034
1042
 
1035
1043
  Examples
1036
1044
  --------
@@ -1055,7 +1063,7 @@ class Density:
1055
1063
  --------
1056
1064
  :py:meth:`Density.to_numpy`
1057
1065
  """
1058
- if type(self.data) == np.memmap:
1066
+ if isinstance(self.data, np.memmap):
1059
1067
  return None
1060
1068
 
1061
1069
  filename = array_to_memmap(arr=self.data)
@@ -1066,8 +1074,7 @@ class Density:
1066
1074
 
1067
1075
  def to_numpy(self) -> None:
1068
1076
  """
1069
- Converts the current class instance's :py:attr:`Density.data` attribute to
1070
- an in-memory :obj:`numpy.ndarray`.
1077
+ Converts :py:attr:`Density.data` to an in-memory :obj:`numpy.ndarray`.
1071
1078
 
1072
1079
  Examples
1073
1080
  --------
@@ -1085,8 +1092,7 @@ class Density:
1085
1092
  @property
1086
1093
  def shape(self) -> Tuple[int]:
1087
1094
  """
1088
- Returns the dimensions of current instance's :py:attr:`Density.data`
1089
- attribute.
1095
+ Returns the dimensions of :py:attr:`Density.data`.
1090
1096
 
1091
1097
  Returns
1092
1098
  -------
@@ -1095,8 +1101,6 @@ class Density:
1095
1101
 
1096
1102
  Examples
1097
1103
  --------
1098
- The following outlines the usage of :py:attr:`Density.shape`:
1099
-
1100
1104
  >>> import numpy as np
1101
1105
  >>> from tme import Density
1102
1106
  >>> dens = Density(np.array([0, 1, 1, 1, 0]))
@@ -1108,13 +1112,12 @@ class Density:
1108
1112
  @property
1109
1113
  def data(self) -> NDArray:
1110
1114
  """
1111
- Returns the value of the current instance's :py:attr:`Density.data`
1112
- attribute.
1115
+ Returns the value of :py:attr:`Density.data`.
1113
1116
 
1114
1117
  Returns
1115
1118
  -------
1116
1119
  NDArray
1117
- Value of the current instance's :py:attr:`Density.data` attribute.
1120
+ Value of the instance's :py:attr:`Density.data` attribute.
1118
1121
 
1119
1122
  Examples
1120
1123
  --------
@@ -1132,20 +1135,20 @@ class Density:
1132
1135
  @data.setter
1133
1136
  def data(self, data: NDArray) -> None:
1134
1137
  """
1135
- Sets the value of the current instance's :py:attr:`Density.data` attribute.
1138
+ Sets the value of the instance's :py:attr:`Density.data` attribute.
1136
1139
  """
1137
1140
  self._data = data
1138
1141
 
1139
1142
  @property
1140
1143
  def origin(self) -> NDArray:
1141
1144
  """
1142
- Returns the value of the current instance's :py:attr:`Density.origin`
1145
+ Returns the value of the instance's :py:attr:`Density.origin`
1143
1146
  attribute.
1144
1147
 
1145
1148
  Returns
1146
1149
  -------
1147
1150
  NDArray
1148
- Value of the current instance's :py:attr:`Density.origin` attribute.
1151
+ Value of the instance's :py:attr:`Density.origin` attribute.
1149
1152
 
1150
1153
  Examples
1151
1154
  --------
@@ -1171,8 +1174,7 @@ class Density:
1171
1174
  @property
1172
1175
  def sampling_rate(self) -> NDArray:
1173
1176
  """
1174
- Returns the value of the current instance's :py:attr:`Density.sampling_rate`
1175
- attribute.
1177
+ Returns the value of the instance's :py:attr:`Density.sampling_rate` attribute.
1176
1178
 
1177
1179
  Returns
1178
1180
  -------
@@ -1193,7 +1195,7 @@ class Density:
1193
1195
  @property
1194
1196
  def metadata(self) -> Dict:
1195
1197
  """
1196
- Returns the current instance's :py:attr:`Density.metadata` dictionary attribute.
1198
+ Returns the instance's :py:attr:`Density.metadata` attribute.
1197
1199
 
1198
1200
  Returns
1199
1201
  -------
@@ -1235,7 +1237,7 @@ class Density:
1235
1237
  Pads the internal data array according to box.
1236
1238
 
1237
1239
  Negative slices indices will result in a left-hand padding, while
1238
- slice indices larger than the box_size property of the current class
1240
+ slice indices larger than the box_size property of the class
1239
1241
  instance will result in a right-hand padding.
1240
1242
 
1241
1243
  Parameters
@@ -1264,17 +1266,15 @@ class Density:
1264
1266
 
1265
1267
  def adjust_box(self, box: Tuple[slice], pad_kwargs: Dict = {}) -> None:
1266
1268
  """
1267
- Adjusts the internal data array and origin of the current class instance
1269
+ Adjusts :py:attr:`Density.data` and :py:attr:`Density.origin`
1268
1270
  according to the provided box.
1269
1271
 
1270
1272
  Parameters
1271
1273
  ----------
1272
1274
  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.
1275
+ Description of how each axis of :py:attr:`Density.data` should be sliced.
1276
1276
  pad_kwargs: dict, optional
1277
- Parameter dictionary passed to numpy pad.
1277
+ Parameter dictionary passed to :obj:`numpy.pad`.
1278
1278
 
1279
1279
  See Also
1280
1280
  --------
@@ -1454,8 +1454,8 @@ class Density:
1454
1454
  Raises
1455
1455
  ------
1456
1456
  ValueError
1457
- If the length of `new_shape` does not match the dimensionality of the
1458
- internal data array.
1457
+ If the length of ``new_shape`` does not match the dimensionality of
1458
+ :py:attr:`Density.data`.
1459
1459
 
1460
1460
  Examples
1461
1461
  --------
@@ -1469,13 +1469,6 @@ class Density:
1469
1469
  >>> dens.data
1470
1470
  array([0, 1, 1, 1, 0])
1471
1471
 
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
1472
  If ``center`` is set to False, the padding values will be appended:
1480
1473
 
1481
1474
  >>> dens = Density(np.array([1,1,1]))
@@ -1483,6 +1476,12 @@ class Density:
1483
1476
  >>> dens.data
1484
1477
  array([1, 1, 1, 0, 0])
1485
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])
1486
1485
  """
1487
1486
  if len(new_shape) != self.data.ndim:
1488
1487
  raise ValueError(
@@ -1522,7 +1521,7 @@ class Density:
1522
1521
  Returns
1523
1522
  -------
1524
1523
  :py:class:`Density`
1525
- A centered copy of the current class instance.
1524
+ A centered copy of the class instance.
1526
1525
  NDArray
1527
1526
  The offset between array center and center of mass.
1528
1527
 
@@ -1531,7 +1530,6 @@ class Density:
1531
1530
  :py:meth:`Density.trim_box`
1532
1531
  :py:meth:`Density.minimum_enclosing_box`
1533
1532
 
1534
-
1535
1533
  Examples
1536
1534
  --------
1537
1535
  :py:meth:`Density.centered` returns a tuple containing a centered version
@@ -1591,114 +1589,6 @@ class Density:
1591
1589
  shift = np.subtract(center, self.center_of_mass(ret.data, cutoff))
1592
1590
  return ret, shift
1593
1591
 
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
1592
  def rigid_transform(
1703
1593
  self,
1704
1594
  rotation_matrix: NDArray,
@@ -1707,7 +1597,7 @@ class Density:
1707
1597
  use_geometric_center: bool = False,
1708
1598
  ) -> "Density":
1709
1599
  """
1710
- Performs a rigid transform of the current class instance.
1600
+ Performs a rigid transform of the class instance.
1711
1601
 
1712
1602
  Parameters
1713
1603
  ----------
@@ -1756,89 +1646,134 @@ class Density:
1756
1646
  --------
1757
1647
  :py:meth:`Density.centered`, :py:meth:`Density.minimum_enclosing_box`
1758
1648
  """
1759
- transformed_map = self.empty
1760
-
1761
- self.rotate_array(
1649
+ ret = self.empty
1650
+ NumpyFFTWBackend().rigid_transform(
1762
1651
  arr=self.data,
1763
1652
  rotation_matrix=rotation_matrix,
1764
1653
  translation=translation,
1765
- order=order,
1766
1654
  use_geometric_center=use_geometric_center,
1767
- out=transformed_map.data,
1655
+ out=ret.data,
1656
+ order=order,
1768
1657
  )
1769
- eps = np.finfo(transformed_map.data.dtype).eps
1770
- transformed_map.data[transformed_map.data < eps] = 0
1771
- return transformed_map
1772
1658
 
1773
- def align_origins(self, other_map: "Density") -> "Density":
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":
1774
1666
  """
1775
- Aligns the origin of another to the origin of the current class instance.
1667
+ Resamples :py:attr:`Density.data` to ``new_sampling_rate``.
1776
1668
 
1777
1669
  Parameters
1778
1670
  ----------
1779
- other_map : Density
1780
- An instance of :py:class:`Density` class to align with the current map.
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`.
1781
1685
 
1782
1686
  Raises
1783
1687
  ------
1784
1688
  ValueError
1785
- If the sampling_rate of both class instances does not match.
1689
+ If ``method`` is not supported.
1786
1690
 
1787
1691
  Returns
1788
1692
  -------
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.")
1693
+ :py:class:`Density`
1694
+ A resampled copy of the class instance.
1794
1695
 
1795
- origin_difference = np.divide(
1796
- np.subtract(self.origin, other_map.origin), self.sampling_rate
1797
- )
1798
- origin_difference = origin_difference.astype(int)
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
1799
1701
 
1800
- box_start = np.minimum(origin_difference, other_map.shape)
1801
- box_end = np.maximum(origin_difference, other_map.shape)
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)
1802
1713
 
1803
- new_box = tuple(slice(*pos) for pos in zip(box_start, box_end))
1714
+ Using :py:meth:`Density.resample` we can modulate the sampling rate
1715
+ using spline interpolation of desired order
1804
1716
 
1805
- ret = other_map.copy()
1806
- ret.adjust_box(new_box)
1807
- 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)
1808
1719
 
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``.
1720
+ Or Fourier cropping which results in a less smooth output, but more faithfully
1721
+ captures the contained frequency information
1813
1722
 
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.
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)
1820
1730
 
1821
- Returns
1822
- -------
1823
- Density
1824
- A resampled instance of `Density` class.
1825
1731
  """
1826
- map_copy, new_sampling_rate = self.copy(), np.array(new_sampling_rate)
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)
1827
1738
  new_sampling_rate = np.repeat(
1828
- new_sampling_rate, map_copy.data.ndim // new_sampling_rate.size
1739
+ new_sampling_rate, self.data.ndim // new_sampling_rate.size
1829
1740
  )
1830
- scale_factor = np.divide(map_copy.sampling_rate, new_sampling_rate)
1831
1741
 
1832
- map_copy.data = zoom(map_copy.data, scale_factor, order=order)
1833
- map_copy.sampling_rate = new_sampling_rate
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
+ )
1834
1762
 
1835
- return map_copy
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
1836
1771
 
1837
1772
  def density_boundary(
1838
1773
  self, weight: float, fraction_surface: float = 0.1, volume_factor: float = 1.21
1839
1774
  ) -> Tuple[float]:
1840
1775
  """
1841
- Computes the density boundary of the current class instance. The density
1776
+ Computes the density boundary of the class instance. The density
1842
1777
  boundary in this setting is defined as minimal and maximal density value
1843
1778
  enclosing a certain ``weight``.
1844
1779
 
@@ -1894,31 +1829,31 @@ class Density:
1894
1829
  self, density_boundaries: Tuple[float], method: str = "ConvexHull"
1895
1830
  ) -> NDArray:
1896
1831
  """
1897
- Calculates the surface coordinates of the current class instance using
1832
+ Calculates the surface coordinates of the class instance using
1898
1833
  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`.
1834
+ for determining coordinates used in non-exhaustive template matching,
1835
+ see :py:class:`tme.matching_optimization.optimize_match`.
1901
1836
 
1902
1837
  Parameters
1903
1838
  ----------
1904
1839
  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`).
1840
+ Lower and upper bound of density values to be considered
1841
+ (can be obtained from :py:meth:`Density.density_boundary`).
1907
1842
  method : str, optional
1908
- Surface coordinates are determined using this method:
1843
+ Method to use for surface coordinate computation
1909
1844
 
1910
1845
  +--------------+-----------------------------------------------------+
1911
- | 'ConvexHull' | Use the lower bound density convex hull vertices. |
1846
+ | ConvexHull | Use the lower bound density convex hull vertices. |
1912
1847
  +--------------+-----------------------------------------------------+
1913
- | 'Weight' | Use all coordinates within ``density_boundaries``. |
1848
+ | Weight | Use all coordinates within ``density_boundaries``. |
1914
1849
  +--------------+-----------------------------------------------------+
1915
- | 'Sobel' | Set densities below the lower bound density to zero |
1850
+ | Sobel | Set densities below the lower bound density to zero |
1916
1851
  | | apply a sobel filter and return density coordinates |
1917
1852
  | | larger than 0.5 times the maximum filter value. |
1918
1853
  +--------------+-----------------------------------------------------+
1919
- | 'Laplace' | Like 'Sobel' but with a laplace filter. |
1854
+ | Laplace | Like 'Sobel', but with a Laplace filter. |
1920
1855
  +--------------+-----------------------------------------------------+
1921
- | 'Minimum' | Like 'Sobel' and 'Laplace' but with a spherical |
1856
+ | Minimum | Like 'Sobel' and 'Laplace' but with a spherical |
1922
1857
  | | minimum filter on the lower density bound. |
1923
1858
  +--------------+-----------------------------------------------------+
1924
1859
 
@@ -1930,15 +1865,11 @@ class Density:
1930
1865
  Returns
1931
1866
  -------
1932
1867
  NDArray
1933
- An array of surface coordinates with shape (number_of_points, dimensions).
1868
+ An array of surface coordinates with shape (points, dimensions).
1934
1869
 
1935
1870
  References
1936
1871
  ----------
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
1872
+ .. [1] Cragnolini T, et al. (2021) Acta Crys Sect D Struct Biol
1942
1873
 
1943
1874
  See Also
1944
1875
  --------
@@ -1948,12 +1879,12 @@ class Density:
1948
1879
  :py:class:`tme.matching_optimization.Envelope`
1949
1880
  :py:class:`tme.matching_optimization.Chamfer`
1950
1881
  """
1951
- available_methods = ["ConvexHull", "Weight", "Sobel", "Laplace", "Minimum"]
1882
+ _available_methods = ["ConvexHull", "Weight", "Sobel", "Laplace", "Minimum"]
1952
1883
 
1953
- if method not in available_methods:
1884
+ if method not in _available_methods:
1954
1885
  raise ValueError(
1955
1886
  "Argument method has to be one of the following: %s"
1956
- % ", ".join(available_methods)
1887
+ % ", ".join(_available_methods)
1957
1888
  )
1958
1889
 
1959
1890
  lower_bound, upper_bound = density_boundaries
@@ -2001,7 +1932,7 @@ class Density:
2001
1932
  def normal_vectors(self, coordinates: NDArray) -> NDArray:
2002
1933
  """
2003
1934
  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
1935
+ of the class instance. If the normal vector to a given coordinate
2005
1936
  can not be computed, the zero vector is returned instead. The output of this
2006
1937
  function can e.g. be used in
2007
1938
  :py:class:`tme.matching_optimization.NormalVectorScore`.
@@ -2051,12 +1982,7 @@ class Density:
2051
1982
  in_box = np.logical_and(
2052
1983
  coordinates < np.array(self.shape), coordinates >= 0
2053
1984
  ).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
-
1985
+ coordinates = coordinates[in_box, :]
2060
1986
  for index in range(coordinates.shape[0]):
2061
1987
  point = coordinates[index, :]
2062
1988
  start = np.maximum(point - 1, 0)
@@ -2076,22 +2002,16 @@ class Density:
2076
2002
 
2077
2003
  def core_mask(self) -> NDArray:
2078
2004
  """
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.
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]_.
2088
2010
 
2089
2011
  Returns
2090
2012
  -------
2091
2013
  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.
2014
+ Core-weighted mask with shape of :py:attr:`Density.data`.
2095
2015
 
2096
2016
  References
2097
2017
  ----------
@@ -2126,19 +2046,7 @@ class Density:
2126
2046
  NDArray
2127
2047
  Center of mass with shape (arr.ndim).
2128
2048
  """
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
2049
+ return NumpyFFTWBackend().center_of_mass(arr, cutoff)
2142
2050
 
2143
2051
  @classmethod
2144
2052
  def match_densities(
@@ -2187,21 +2095,20 @@ class Density:
2187
2095
  -----
2188
2096
  No densities below cutoff_template are present in the returned Density object.
2189
2097
  """
2190
- from .matching_exhaustive import normalize_under_mask
2098
+ from .matching_utils import normalize_template
2191
2099
  from .matching_optimization import optimize_match, create_score_object
2192
2100
 
2193
2101
  template_mask = template.empty
2194
- template_mask.data[:] = 1
2102
+ template_mask.data.fill(1)
2195
2103
 
2196
- normalize_under_mask(
2104
+ normalize_template(
2197
2105
  template=template.data,
2198
2106
  mask=template_mask.data,
2199
- mask_intensity=template_mask.data.sum(),
2107
+ n_observations=template_mask.data.sum(),
2200
2108
  )
2201
2109
 
2202
2110
  target_sampling_rate = np.array(target.sampling_rate)
2203
2111
  template_sampling_rate = np.array(template.sampling_rate)
2204
-
2205
2112
  target_sampling_rate = np.repeat(
2206
2113
  target_sampling_rate, target.data.ndim // target_sampling_rate.size
2207
2114
  )
@@ -2340,49 +2247,6 @@ class Density:
2340
2247
 
2341
2248
  return out, final_translation, rotation_matrix
2342
2249
 
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
2250
  @staticmethod
2387
2251
  def fourier_shell_correlation(density1: "Density", density2: "Density") -> NDArray:
2388
2252
  """
@@ -2439,3 +2303,9 @@ class Density:
2439
2303
  qidx = np.where(qbins < qx.max())
2440
2304
 
2441
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"