Skip to content

Commit 195f44c

Browse files
committed
Always write an optics block
1 parent 7091078 commit 195f44c

File tree

4 files changed

+46
-24
lines changed

4 files changed

+46
-24
lines changed

src/aspire/source/image.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,31 +1311,26 @@ def _prepare_relion_optics_blocks(metadata):
13111311
"_rlnImageDimensionality",
13121312
]
13131313

1314-
# We only write a data_optics block when every required field is present.
1315-
required_fields = [
1316-
"_rlnSphericalAberration",
1317-
"_rlnVoltage",
1318-
"_rlnAmplitudeContrast",
1319-
"_rlnImageSize",
1320-
"_rlnImageDimensionality",
1321-
]
1322-
pixel_fields = ["_rlnImagePixelSize", "_rlnMicrographPixelSize"]
1314+
# Some optics group fields might not always be present, but are necessary
1315+
# for reading the file in Relion. We ensure these fields exist and populate
1316+
# with a dummy value if not.
1317+
n_rows = len(metadata["_rlnImageName"])
13231318

1324-
has_required = all(field in metadata for field in required_fields)
1325-
has_pixel_field = any(field in metadata for field in pixel_fields)
1319+
def _ensure_column(field, value):
1320+
if field not in metadata:
1321+
logger.warning(
1322+
f"Optics field {field} not found, populating with default value {value}"
1323+
)
1324+
metadata[field] = np.full(n_rows, value)
13261325

1327-
if not (has_required and has_pixel_field):
1328-
# Optics metadata incomplete, fall back to legacy single block.
1329-
logger.warning(
1330-
"Optics metadata incomplete, writing only data_particles block."
1331-
)
1332-
return None, metadata
1326+
_ensure_column("_rlnSphericalAberration", 2.0)
1327+
_ensure_column("_rlnVoltage", 300)
1328+
_ensure_column("_rlnAmplitudeContrast", 0.1)
13331329

13341330
# Restrict to the optics columns that are actually present on this source.
13351331
optics_value_fields = [
13361332
field for field in all_optics_fields if field in metadata
13371333
]
1338-
n_rows = len(metadata["_rlnImageName"])
13391334

13401335
# Map each unique optics tuple to a 1-based group ID in order encountered.
13411336
group_lookup = OrderedDict()

src/aspire/source/relion.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ def __init__(
168168
f"Found partially populated CTF Params."
169169
f" To automatically populate CTFFilters provide {CTF_params}"
170170
)
171-
171+
self.unique_filters = [IdentityFilter()]
172+
self.filter_indices = np.zeros(self.n, dtype=int)
172173
# If no CTF info in STAR, we initialize the filter values of metadata with default values
173174
else:
174175
self.unique_filters = [IdentityFilter()]

tests/test_coordinate_source.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ def testSave(self):
526526
# load saved particle stack
527527
saved_star = StarFile(star_path)
528528
# we want to read the saved mrcs file from the STAR file
529-
image_name_column = saved_star.get_block_by_index(0)["_rlnImageName"]
529+
image_name_column = saved_star.get_block_by_index(1)["_rlnImageName"]
530530
# we're reading a string of the form 0000X@mrcs_path.mrcs
531531
_particle, mrcs_path = image_name_column[0].split("@")
532532
saved_mrcs_stack = mrcfile.open(os.path.join(self.data_folder, mrcs_path)).data
@@ -537,15 +537,28 @@ def testSave(self):
537537
self.assertEqual(
538538
list(saved_star["particles"].keys()),
539539
[
540-
"_rlnImagePixelSize",
540+
"_rlnOpticsGroup",
541541
"_rlnSymmetryGroup",
542542
"_rlnImageName",
543543
"_rlnCoordinateX",
544544
"_rlnCoordinateY",
545+
],
546+
)
547+
548+
self.assertEqual(
549+
list(saved_star["optics"].keys()),
550+
[
551+
"_rlnOpticsGroup",
552+
"_rlnOpticsGroupName",
553+
"_rlnImagePixelSize",
554+
"_rlnSphericalAberration",
555+
"_rlnVoltage",
556+
"_rlnAmplitudeContrast",
545557
"_rlnImageSize",
546558
"_rlnImageDimensionality",
547559
],
548560
)
561+
549562
# assert that all the correct coordinates were saved
550563
for i in range(10):
551564
self.assertEqual(

tests/test_relion_source.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def test_prepare_relion_optics_blocks_warns(caplog):
7070
"_rlnImagePixelSize": np.array([1.234]),
7171
"_rlnImageSize": np.array([32]),
7272
"_rlnImageDimensionality": np.array([2]),
73+
"_rlnImageName": np.array(["[email protected]"]),
7374
}
7475

7576
caplog.clear()
@@ -78,9 +79,21 @@ def test_prepare_relion_optics_blocks_warns(caplog):
7879
metadata.copy()
7980
)
8081

81-
assert optics_block is None
82-
assert particle_block == metadata
83-
assert "Optics metadata incomplete" in caplog.text
82+
# We should get and optics block
83+
assert optics_block is not None
84+
85+
# Verify defaults were injected.
86+
np.testing.assert_allclose(optics_block["_rlnImagePixelSize"], [1.234])
87+
np.testing.assert_array_equal(optics_block["_rlnImageSize"], [32])
88+
np.testing.assert_array_equal(optics_block["_rlnImageDimensionality"], [2])
89+
np.testing.assert_allclose(optics_block["_rlnVoltage"], [300.0])
90+
np.testing.assert_allclose(optics_block["_rlnSphericalAberration"], [2.0])
91+
np.testing.assert_allclose(optics_block["_rlnAmplitudeContrast"], [0.1])
92+
93+
# Caplog should contain the warnings about the three missing fields.
94+
assert "Optics field _rlnSphericalAberration not found" in caplog.text
95+
assert "Optics field _rlnVoltage not found" in caplog.text
96+
assert "Optics field _rlnAmplitudeContrast not found" in caplog.text
8497

8598

8699
def test_pixel_size(caplog):

0 commit comments

Comments
 (0)