Skip to content

Commit 1de0e8a

Browse files
Allow multiple reconstruction recipes for sxt (#830)
This moves the SXT processing into a rabbitmq message, and adds support for more recipes. Recipes in the machine config are detected and all of these are used to send processing if the collection type is SXT. This will allow for using imod and aretomo. Links to DiamondLightSource/cryoem-services#258
1 parent df053f5 commit 1de0e8a

6 files changed

Lines changed: 144 additions & 59 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ TomographyMetadataContext = "murfey.client.contexts.tomo_metadata:TomographyMeta
126126
"spa.ctf_estimated" = "murfey.workflows.spa.ctf_estimation:ctf_estimated"
127127
"spa.flush_spa_preprocess" = "murfey.workflows.spa.flush_spa_preprocess:flush_spa_preprocess"
128128
"spa.motion_corrected" = "murfey.workflows.spa.motion_correction:motion_corrected"
129+
"sxt.process_tilt_series" = "murfey.workflows.sxt.process_sxt_tilt_series:run"
129130

130131
[tool.setuptools]
131132
package-dir = {"" = "src"}

src/murfey/client/contexts/sxt.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def _find_reference(txrm_file: Path) -> Path | None:
5252
)[0]
5353
)
5454
if mosaic_size == 0:
55-
logger.info(f"Found reference {ref_option}")
55+
logger.info(f"Found reference {ref_option.name}")
5656
return Path(ref_option.full_path)
5757
logger.warning(f"No reference found for {txrm_file}")
5858
return None
@@ -125,9 +125,7 @@ def register_sxt_data_collection(
125125
data=dc_data,
126126
)
127127

128-
recipes_to_assign_pjids = [
129-
"sxt-aretomo",
130-
]
128+
recipes_to_assign_pjids = self._machine_config.get("recipes", {}).values()
131129
for recipe in recipes_to_assign_pjids:
132130
capture_post(
133131
base_url=str(environment.url.geturl()),

src/murfey/server/api/workflow.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@
8080
from murfey.util.tomo import midpoint
8181
from murfey.workflows.sxt.process_sxt_tilt_series import (
8282
SXTTiltSeriesInfo,
83-
process_sxt_tilt_series_workflow,
8483
)
8584
from murfey.workflows.tomo.tomo_metadata import register_search_map_in_database
8685

@@ -1086,9 +1085,16 @@ def process_sxt_tilt_series(
10861085
tilt_series_info: SXTTiltSeriesInfo,
10871086
db=murfey_db,
10881087
):
1089-
return process_sxt_tilt_series_workflow(
1090-
visit_name, session_id, tilt_series_info, db
1091-
)
1088+
if _transport_object:
1089+
_transport_object.send(
1090+
_transport_object.feedback_queue,
1091+
{
1092+
"register": "sxt.process_tilt_series",
1093+
"session_id": session_id,
1094+
"visit_name": visit_name,
1095+
"tilt_series_info": tilt_series_info.model_dump(mode="json"),
1096+
},
1097+
)
10921098

10931099

10941100
correlative_router = APIRouter(

src/murfey/workflows/sxt/process_sxt_tilt_series.py

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from pydantic import BaseModel
55
from sqlmodel import select
6+
from sqlmodel.orm.session import Session as SQLModelSession
67
from werkzeug.utils import secure_filename
78

89
from murfey.server import _transport_object
@@ -31,12 +32,12 @@ class SXTTiltSeriesInfo(BaseModel):
3132
xrm_reference: str | None
3233

3334

34-
def process_sxt_tilt_series_workflow(
35+
def process_sxt_tilt_series(
3536
visit_name: str,
3637
session_id: MurfeySessionID,
3738
tilt_series_info: SXTTiltSeriesInfo,
38-
murfey_db: Session,
39-
):
39+
murfey_db: SQLModelSession,
40+
) -> dict[str, bool]:
4041
tilt_series_query = murfey_db.exec(
4142
select(TiltSeries)
4243
.where(TiltSeries.session_id == session_id)
@@ -47,7 +48,7 @@ def process_sxt_tilt_series_workflow(
4748
tilt_series = tilt_series_query[0]
4849
if tilt_series.processing_requested:
4950
logger.info(f"Tilt series {tilt_series.tag} has already been processed")
50-
return
51+
return {"success": True}
5152
else:
5253
tilt_series = TiltSeries(
5354
session_id=session_id,
@@ -59,6 +60,7 @@ def process_sxt_tilt_series_workflow(
5960
murfey_db.add(tilt_series)
6061
murfey_db.commit()
6162

63+
# Find all processing jobs registered for this tilt series
6264
collected_ids = murfey_db.exec(
6365
select(DataCollectionGroup, DataCollection, ProcessingJob, AutoProcProgram)
6466
.where(DataCollectionGroup.session_id == session_id)
@@ -67,8 +69,11 @@ def process_sxt_tilt_series_workflow(
6769
.where(DataCollection.dcg_id == DataCollectionGroup.id)
6870
.where(ProcessingJob.dc_id == DataCollection.id)
6971
.where(AutoProcProgram.pj_id == ProcessingJob.id)
70-
.where(ProcessingJob.recipe == "sxt-aretomo")
71-
).one()
72+
).all()
73+
if len(collected_ids) == 0:
74+
logger.warning(f"No processing recipes found for {tilt_series.tag}")
75+
return {"success": False, "requeue": False}
76+
7277
instrument_name = (
7378
murfey_db.exec(select(Session).where(Session.id == session_id))
7479
.one()
@@ -78,46 +83,66 @@ def process_sxt_tilt_series_workflow(
7883
instrument_name
7984
]
8085

86+
# Find the visit folder and any subfolders needed
8187
parts = [secure_filename(p) for p in Path(tilt_series_info.txrm).parts]
8288
visit_idx = parts.index(visit_name)
8389
core = Path(*Path(tilt_series_info.txrm).parts[: visit_idx + 1])
8490
ppath = Path(
8591
"/".join(secure_filename(p) for p in Path(tilt_series_info.txrm).parts)
8692
)
87-
sub_dataset = "/".join(ppath.relative_to(core).parts[:-1])
88-
extra_path = machine_config.processed_extra_directory
89-
stack_file = (
90-
core
91-
/ machine_config.processed_directory_name
92-
/ sub_dataset
93-
/ extra_path
94-
/ "Tomograms"
95-
/ f"{tilt_series.tag}_stack.mrc"
96-
)
97-
stack_file.parent.mkdir(parents=True, exist_ok=True)
98-
zocalo_message = {
99-
"recipes": ["sxt-aretomo"],
100-
"parameters": {
101-
"txrm_file": tilt_series_info.txrm,
102-
"xrm_reference": tilt_series_info.xrm_reference or "",
103-
"dcid": collected_ids[1].id,
104-
"appid": collected_ids[3].id,
105-
"stack_file": str(stack_file),
106-
"tilt_axis": 0,
107-
"pixel_size": tilt_series_info.pixel_size,
108-
"manual_tilt_offset": -tilt_series_info.tilt_offset,
109-
"node_creator_queue": machine_config.node_creator_queue,
110-
},
111-
}
112-
if _transport_object:
113-
logger.info(
114-
f"Sending Zocalo message for processing: {sanitise(str(zocalo_message))}"
115-
)
116-
_transport_object.send("processing_recipe", zocalo_message, new_connection=True)
117-
else:
118-
logger.info(
119-
f"No transport object found. Zocalo message would be {sanitise(str(zocalo_message))}"
93+
sub_dataset = "/".join(ppath.relative_to(core).parts[1:-1])
94+
95+
# Loop over all processing jobs, and send the alignment recipe for it
96+
for recipe_ids in collected_ids:
97+
# Stack file path needs to contain both recipe name and tilt series name
98+
stack_file = (
99+
core
100+
/ machine_config.processed_directory_name
101+
/ machine_config.processed_extra_directory
102+
/ sub_dataset
103+
/ tilt_series.tag
104+
/ recipe_ids[2].recipe
105+
/ "Tomograms"
106+
/ f"{tilt_series.tag}_stack.mrc"
120107
)
108+
stack_file.parent.mkdir(parents=True, exist_ok=True)
109+
110+
# Send message to rabbitmq
111+
zocalo_message = {
112+
"recipes": [recipe_ids[2].recipe],
113+
"parameters": {
114+
"txrm_file": tilt_series_info.txrm,
115+
"xrm_reference": tilt_series_info.xrm_reference or "",
116+
"dcid": recipe_ids[1].id,
117+
"appid": recipe_ids[3].id,
118+
"stack_file": str(stack_file),
119+
"tilt_axis": 0,
120+
"pixel_size": tilt_series_info.pixel_size,
121+
"manual_tilt_offset": -tilt_series_info.tilt_offset,
122+
"node_creator_queue": machine_config.node_creator_queue,
123+
},
124+
}
125+
if _transport_object:
126+
logger.info(
127+
f"Sending Zocalo message for processing: {sanitise(str(zocalo_message))}"
128+
)
129+
_transport_object.send(
130+
"processing_recipe", zocalo_message, new_connection=True
131+
)
132+
else:
133+
logger.info(
134+
f"No transport object found. Zocalo message would be {sanitise(str(zocalo_message))}"
135+
)
121136
tilt_series.processing_requested = True
122137
murfey_db.add(tilt_series)
123138
murfey_db.commit()
139+
return {"success": True}
140+
141+
142+
def run(message: dict, murfey_db: SQLModelSession) -> dict[str, bool]:
143+
return process_sxt_tilt_series(
144+
message["visit_name"],
145+
message["session_id"],
146+
SXTTiltSeriesInfo(**message["tilt_series_info"]),
147+
murfey_db,
148+
)

tests/client/contexts/test_sxt.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ def test_sxt_context_txrm(mock_ole_file, mock_post, tmp_path):
6969
visit="cm12345-6",
7070
murfey_session=1,
7171
)
72-
context = SXTContext("zeiss", tmp_path / "cm12345-6/grid1", {}, "")
72+
context = SXTContext(
73+
"zeiss",
74+
tmp_path / "cm12345-6/grid1",
75+
{"recipes": {"aretomo": "sxt-aretomo"}},
76+
"",
77+
)
7378
context.post_transfer(
7479
tmp_path / "cm12345-6/grid1/example.txrm",
7580
required_position_files=[],
@@ -191,7 +196,12 @@ def test_sxt_context_txrm_external_ref(mock_ole_file, mock_post, tmp_path):
191196
visit="cm12345-6",
192197
murfey_session=1,
193198
)
194-
context = SXTContext("zeiss", tmp_path / "cm12345-6/grid1", {}, "")
199+
context = SXTContext(
200+
"zeiss",
201+
tmp_path / "cm12345-6/grid1",
202+
{"recipes": {"aretomo": "sxt-aretomo", "imod": "sxt-imod-patch-wbp"}},
203+
"",
204+
)
195205
context.post_transfer(
196206
tmp_path / "cm12345-6/grid1/example_-60to60@0.5.txrm",
197207
required_position_files=[],
@@ -204,7 +214,7 @@ def test_sxt_context_txrm_external_ref(mock_ole_file, mock_post, tmp_path):
204214
)
205215
mock_ole_file.assert_any_call(str(tmp_path / "cm12345-6/grid1/ref.xrm"))
206216

207-
assert mock_post.call_count == 5
217+
assert mock_post.call_count == 6
208218
mock_post.assert_any_call(
209219
"http://localhost:8000/workflow/visits/cm12345-6/sessions/1/register_data_collection_group",
210220
json={
@@ -245,6 +255,16 @@ def test_sxt_context_txrm_external_ref(mock_ole_file, mock_post, tmp_path):
245255
},
246256
headers={"Authorization": "Bearer "},
247257
)
258+
mock_post.assert_any_call(
259+
"http://localhost:8000/workflow/visits/cm12345-6/sessions/1/register_processing_job",
260+
json={
261+
"tag": "example",
262+
"source": f"{tmp_path}/cm12345-6/grid1",
263+
"recipe": "sxt-imod-patch-wbp",
264+
"experiment_type": "sxt",
265+
},
266+
headers={"Authorization": "Bearer "},
267+
)
248268
mock_post.assert_any_call(
249269
"http://localhost:8000/workflow/sxt/visits/cm12345-6/sessions/1/sxt_tilt_series",
250270
json={

tests/workflows/sxt/test_process_sxt_tilt_series.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def set_up_db(murfey_db_session: Session):
3333
"dcg_id": dcg_entry.id,
3434
},
3535
)
36-
processing_job_entry: ProcessingJob = get_or_create_db_entry(
36+
aretomo_pj_entry: ProcessingJob = get_or_create_db_entry(
3737
murfey_db_session,
3838
ProcessingJob,
3939
lookup_kwargs={
@@ -42,23 +42,40 @@ def set_up_db(murfey_db_session: Session):
4242
"dc_id": dc_entry.id,
4343
},
4444
)
45-
auto_proc_entry = get_or_create_db_entry(
45+
imod_pj_entry: ProcessingJob = get_or_create_db_entry(
46+
murfey_db_session,
47+
ProcessingJob,
48+
lookup_kwargs={
49+
"id": 2,
50+
"recipe": "sxt-imod-patch-wbp",
51+
"dc_id": dc_entry.id,
52+
},
53+
)
54+
aretomo_autoproc_entry = get_or_create_db_entry(
4655
murfey_db_session,
4756
AutoProcProgram,
4857
lookup_kwargs={
4958
"id": 0,
50-
"pj_id": processing_job_entry.id,
59+
"pj_id": aretomo_pj_entry.id,
5160
},
5261
)
53-
return dcg_entry.id, dc_entry.id, processing_job_entry.id, auto_proc_entry.id
62+
imod_autoproc_entry = get_or_create_db_entry(
63+
murfey_db_session,
64+
AutoProcProgram,
65+
lookup_kwargs={
66+
"id": 1,
67+
"pj_id": imod_pj_entry.id,
68+
},
69+
)
70+
return dcg_entry.id, dc_entry.id, aretomo_autoproc_entry.id, imod_autoproc_entry.id
5471

5572

5673
@mock.patch("murfey.workflows.sxt.process_sxt_tilt_series._transport_object")
5774
def test_process_new_sxt_tilt_series(
5875
mock_transport, murfey_db_session: Session, tmp_path
5976
):
6077
"""Run the picker feedback with less particles than needed for classification"""
61-
dcg_id, dc_id, pj_id, app_id = set_up_db(murfey_db_session)
78+
dcg_id, dc_id, app_id_aretomo, app_id_imod = set_up_db(murfey_db_session)
6279

6380
new_parameters = process_sxt_tilt_series.SXTTiltSeriesInfo(
6481
session_id=ExampleVisit.murfey_session_id,
@@ -72,7 +89,7 @@ def test_process_new_sxt_tilt_series(
7289
)
7390

7491
# Run the registration
75-
process_sxt_tilt_series.process_sxt_tilt_series_workflow(
92+
process_sxt_tilt_series.process_sxt_tilt_series(
7693
"cm12345-6",
7794
ExampleVisit.murfey_session_id,
7895
new_parameters,
@@ -83,18 +100,36 @@ def test_process_new_sxt_tilt_series(
83100
mock_transport.send.assert_any_call(
84101
"processing_recipe",
85102
{
103+
"recipes": ["sxt-aretomo"],
86104
"parameters": {
87105
"txrm_file": f"{tmp_path}/cm12345-6/raw/tomogram_tag.txrm",
88106
"xrm_reference": f"{tmp_path}/cm12345-6/raw/ref.xrm",
89107
"dcid": dc_id,
90-
"appid": app_id,
91-
"stack_file": f"{tmp_path}/cm12345-6/processed/raw/Tomograms/tomogram_tag_stack.mrc",
108+
"appid": app_id_aretomo,
109+
"stack_file": f"{tmp_path}/cm12345-6/processed/tomogram_tag/sxt-aretomo/Tomograms/tomogram_tag_stack.mrc",
110+
"tilt_axis": 0,
111+
"pixel_size": 100,
112+
"manual_tilt_offset": -1,
113+
"node_creator_queue": "node_creator",
114+
},
115+
},
116+
new_connection=True,
117+
)
118+
mock_transport.send.assert_any_call(
119+
"processing_recipe",
120+
{
121+
"recipes": ["sxt-imod-patch-wbp"],
122+
"parameters": {
123+
"txrm_file": f"{tmp_path}/cm12345-6/raw/tomogram_tag.txrm",
124+
"xrm_reference": f"{tmp_path}/cm12345-6/raw/ref.xrm",
125+
"dcid": dc_id,
126+
"appid": app_id_imod,
127+
"stack_file": f"{tmp_path}/cm12345-6/processed/tomogram_tag/sxt-imod-patch-wbp/Tomograms/tomogram_tag_stack.mrc",
92128
"tilt_axis": 0,
93129
"pixel_size": 100,
94130
"manual_tilt_offset": -1,
95131
"node_creator_queue": "node_creator",
96132
},
97-
"recipes": ["sxt-aretomo"],
98133
},
99134
new_connection=True,
100135
)

0 commit comments

Comments
 (0)