22from collections .abc import Callable
33from typing import Any , Literal , overload
44
5- import equinox as eqx
65import equinox .internal as eqxi
76import jax .numpy as jnp
87import mmdf
4241 FourierVoxelSplineVolume ,
4342 GaussianMixtureVolume ,
4443 IndependentAtomVolume ,
44+ RealVoxelCloudVolume ,
4545 RealVoxelGridVolume ,
4646)
4747
4848
4949identity_fn = eqxi .doc_repr (lambda x , _ : x , "identity_fn" )
5050
5151
52- def _maybe_invert_rotation (
53- pose : AbstractPose ,
54- volume : AbstractVolumeParametrization ,
55- rotation_convention : Literal ["object" , "frame" ],
56- ) -> AbstractPose :
57- jaxpr_fn = eqx .filter_make_jaxpr (lambda vol : vol .to_representation ())
58- _ , out_dynamic , out_static = jaxpr_fn (volume )
59- out_struct = eqx .combine (out_dynamic , out_static )
60- conventions = [rotation_convention , out_struct .rotation_convention ]
61- if conventions [0 ] != conventions [1 ]:
62- pose = pose .to_inverse_rotation ()
63-
64- return pose
65-
66-
6752@overload
6853def make_image_model (
6954 volume : AbstractVolumeParametrization ,
@@ -79,7 +64,6 @@ def make_image_model(
7964 signal_centering : Literal ["bg" , "mean" ] = "mean" ,
8065 translate_mode : Literal ["fft" , "atom" , "none" ] = "fft" ,
8166 quantity_mode : Literal ["none" ] = "none" ,
82- rotation_convention : Literal ["object" , "frame" ] = "object" ,
8367) -> ProjectionImageModel : ...
8468
8569
@@ -98,7 +82,6 @@ def make_image_model( # pyright: ignore[reportOverlappingOverload]
9882 signal_centering : Literal ["bg" , "mean" ] = "mean" ,
9983 translate_mode : Literal ["fft" , "atom" , "none" ] = "fft" ,
10084 quantity_mode : Literal ["none" ] = "none" ,
101- rotation_convention : Literal ["object" , "frame" ] = "object" ,
10285) -> LinearImageModel : ...
10386
10487
@@ -117,7 +100,6 @@ def make_image_model(
117100 signal_centering : Literal ["bg" , "mean" ] = "mean" ,
118101 translate_mode : Literal ["fft" , "atom" , "none" ] = "fft" ,
119102 quantity_mode : Literal ["contrast" ] = "contrast" ,
120- rotation_convention : Literal ["object" , "frame" ] = "object" ,
121103) -> ContrastImageModel : ...
122104
123105
@@ -136,7 +118,6 @@ def make_image_model(
136118 signal_centering : Literal ["bg" , "mean" ] = "mean" ,
137119 translate_mode : Literal ["fft" , "atom" , "none" ] = "fft" ,
138120 quantity_mode : Literal ["intensity" ] = "intensity" ,
139- rotation_convention : Literal ["object" , "frame" ] = "object" ,
140121) -> IntensityImageModel : ...
141122
142123
@@ -155,7 +136,6 @@ def make_image_model(
155136 signal_centering : Literal ["bg" , "mean" ] = "mean" ,
156137 translate_mode : Literal ["fft" , "atom" , "none" ] = "fft" ,
157138 quantity_mode : Literal ["counts" ] = "counts" ,
158- rotation_convention : Literal ["object" , "frame" ] = "object" ,
159139) -> ElectronCountsImageModel : ...
160140
161141
@@ -173,7 +153,6 @@ def make_image_model(
173153 signal_centering : Literal ["bg" , "mean" ] = "mean" ,
174154 translate_mode : Literal ["fft" , "atom" , "none" ] = "fft" ,
175155 quantity_mode : Literal ["contrast" , "intensity" , "counts" , "none" ] = "none" ,
176- rotation_convention : Literal ["object" , "frame" ] = "object" ,
177156) -> AbstractImageModel :
178157 """Construct an [`cryojax.simulator.AbstractImageModel`][] for
179158 most common use-cases.
@@ -281,20 +260,6 @@ def make_image_model(
281260 Uses the [`cryojax.simulator.ElectronCountsImageModel`][]
282261 to simulate electron counts.
283262 If this is passed, a `detector` must also be passed.
284- - `rotation_convention`:
285- If `'object'`, the rotation given by `pose` is of the object.
286- If `'frame'`, the rotation given by `pose` is of the frame. These
287- are related by transpose.
288-
289- !!! info
290- The `make_image_model` function enforces agreement between
291- rotation conventions of different volumes via the
292- `rotation_convention` argument. Lower level `cryojax` APIs
293- will not enforce this agreement, such as if the user instantiates
294- an [`cryojax.simulator.AbstractImageModel`][] directly.
295-
296- In these cases, agreement can be acheived with a manual transpose
297- via `pose.to_inverse_rotation()`.
298263
299264 **Returns:**
300265
@@ -309,13 +274,6 @@ def make_image_model(
309274 [`cryojax.simulator.ElectronCountsImageModel`][] depending on
310275 the value of `quantity_mode`.
311276 """
312- # Invert pose if
313- if rotation_convention not in ["object" , "frame" ]:
314- raise ValueError (
315- f"Found `rotation_convention = { rotation_convention } `, but valid "
316- "values are 'object' and 'frame'."
317- )
318- pose = _maybe_invert_rotation (pose , volume , rotation_convention )
319277 options = dict (
320278 normalizes_signal = normalizes_signal ,
321279 signal_centering = signal_centering ,
@@ -581,14 +539,31 @@ def render_voxel_volume(
581539) -> RealVoxelGridVolume : ...
582540
583541
542+ @overload
543+ def render_voxel_volume (
544+ atom_volume : AbstractAtomVolume ,
545+ render_fn : AbstractVolumeRenderFn ,
546+ * ,
547+ output_type : type [RealVoxelCloudVolume ] = RealVoxelCloudVolume ,
548+ ) -> RealVoxelCloudVolume : ...
549+
550+
584551def render_voxel_volume (
585552 atom_volume : AbstractAtomVolume ,
586553 render_fn : AbstractVolumeRenderFn ,
587554 * ,
588555 output_type : type [
589- FourierVoxelGridVolume | FourierVoxelSplineVolume | RealVoxelGridVolume
556+ FourierVoxelGridVolume
557+ | FourierVoxelSplineVolume
558+ | RealVoxelGridVolume
559+ | RealVoxelCloudVolume
590560 ] = FourierVoxelGridVolume ,
591- ) -> FourierVoxelGridVolume | FourierVoxelSplineVolume | RealVoxelGridVolume :
561+ ) -> (
562+ FourierVoxelGridVolume
563+ | FourierVoxelSplineVolume
564+ | RealVoxelGridVolume
565+ | RealVoxelCloudVolume
566+ ):
592567 """Render a voxel volume representation from an atomistic one.
593568
594569 !!! example "Simulate an image with Fourier slice extraction"
@@ -624,7 +599,8 @@ def render_voxel_volume(
624599 Either [`cryojax.simulator.FourierVoxelGridVolume`][] /
625600 [`cryojax.simulator.FourierVoxelSplineVolume`][] for
626601 fourier-space representations, or
627- [`cryojax.simulator.RealVoxelGridVolume`][] for real-space.
602+ [`cryojax.simulator.RealVoxelGridVolume`][] /
603+ [`cryojax.simulator.RealVoxelCloudVolume`][] for real-space.
628604
629605
630606 **Returns:**
@@ -650,14 +626,20 @@ def render_voxel_volume(
650626 else :
651627 spline_coefficients = compute_spline_coefficients (fourier_voxel_grid )
652628 return FourierVoxelSplineVolume (spline_coefficients , frequency_slice )
653- elif output_type == RealVoxelGridVolume :
629+ elif output_type == RealVoxelGridVolume or output_type == RealVoxelCloudVolume :
654630 coordinate_grid = make_coordinate_grid (render_fn .shape )
655631 real_voxel_grid = render_fn (atom_volume , outputs_real_space = True )
656- return RealVoxelGridVolume (real_voxel_grid , coordinate_grid )
632+ if output_type == RealVoxelGridVolume :
633+ return RealVoxelGridVolume (real_voxel_grid , coordinate_grid )
634+ else :
635+ return RealVoxelCloudVolume .from_real_voxel_grid (
636+ real_voxel_grid , coordinate_grid_in_pixels = coordinate_grid
637+ )
657638 else :
658639 raise ValueError (
659- "Only `output_type` equal to `FourierVoxelGridVolume`, "
660- "`FourierVoxelSplineVolume`, or `RealVoxelGridVolume` "
661- "are supported."
662- f"Got `output_type = { output_type } `."
640+ f"Got `output_type = { output_type } `, but this is "
641+ "not supported by `render_voxel_volume(..., output_type=...)`."
642+ "Valid values for `output_type` are `FourierVoxelGridVolume`, "
643+ "`FourierVoxelSplineVolume`, `RealVoxelGridVolume`, or "
644+ "`RealVoxelCloudVolume`."
663645 )
0 commit comments