Skip to content

Initial implementation of reprocessing xia2.multiplex jobs via SynchWeb - #384

Draft
amyjaynethompson wants to merge 4 commits into
mainfrom
multiplex_reprocessing
Draft

Initial implementation of reprocessing xia2.multiplex jobs via SynchWeb#384
amyjaynethompson wants to merge 4 commits into
mainfrom
multiplex_reprocessing

Conversation

@amyjaynethompson

Copy link
Copy Markdown
Contributor

This PR provides an initial implementation to reprocess xia2.multiplex jobs. In a first instance, it might make sense to limit this feature to reprocessing existing jobs with different command line parameters. This is to keep the bookkeeping in SynchWeb as clear as possible.

This PR introduces a separate trigger function, because much of the main multiplex trigger function is unnecessary for a reprocessing job under this specification.

Examples of features of main trigger functions that don't work in this context:

  • loop through all sample groups associated with a DCID (for reprocessing, really only want to do a single sample group)
  • trigger every dataset vs at end of sample group (not really a consideration for reprocessing)
  • wait for datasets to complete (we know they've completed because it is being reprocessed)
  • check for space group compatibility, min number datasets, etc (we know this is fine because it has a parent job)
  • find files from xia2-dials jobs (we can get this directly from the parent job)

Instead, an ISPyB query can get the information required based on the parent job.

In SynchWeb, the new job is assigned a new processingJobId in ISPyB, and user defined parameters are given processingJobParameterIds. One of the processingJobParameters is the autoProcScalingId of the original multiplex job. From here, it is straightforward to query the database to find the DCIDs contained in the original job (as well as which sample group, and the data paths included).

This has been tested using ispyb-dev-1 which now has a multiplex reprocessing button. Using this interface submits the parameters to the ISPyB database. A new multiplex recipe is then called with zocalo.go -n -r trigger-multiplex -s ispyb_process=<processingJobId> where the processingJobId is the new job registered by SynchWeb (and linked to the user defined parameters).

@amyjaynethompson
amyjaynethompson requested a review from pblowey July 6, 2026 14:54
cchalf_filtering_method: Optional[str] = None
sd_cutoff: Optional[float] = None
image_group_size: Optional[int] = None
scaling_id: Optional[int] = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scaling_id should be a required parameter as reprocessing will not work without it

Comment on lines +2345 to +2405
# First, use the input scaling ID to find the DCIDs of all multiplex jobs as well as the sample (group) id value

query = (
session.query(ProcessingJobImageSweep, ProcessingJobParameter)
.join(
AutoProcProgram,
AutoProcProgram.processingJobId
== ProcessingJobImageSweep.processingJobId,
)
.join(
ProcessingJobParameter,
ProcessingJobParameter.processingJobId
== AutoProcProgram.processingJobId,
)
.join(
AutoProc,
AutoProc.autoProcProgramId == AutoProcProgram.autoProcProgramId,
)
.join(AutoProcScaling, AutoProcScaling.autoProcId == AutoProc.autoProcId)
.where(AutoProcScaling.autoProcScalingId == parameters.scaling_id)
.options(
Load(ProcessingJobImageSweep).load_only(
ProcessingJobImageSweep.dataCollectionId,
raiseload=True,
),
Load(ProcessingJobParameter).load_only(
ProcessingJobParameter.parameterKey,
ProcessingJobParameter.parameterValue,
raiseload=True,
),
)
)

return {"success": True, "return_value": jobids}
found_dcids = set()
group = None
found_data_files = set()

for dc, dc_params in query.all():
found_dcids.add(dc.dataCollectionId)
if "sample_id" == dc_params.parameterKey:
group = ("sample_id", dc_params.parameterValue)
elif "sample_group_id" == dc_params.parameterKey:
group = ("sample_group_id", dc_params.parameterValue)
elif "data" == dc_params.parameterKey:
found_data_files.add(dc_params.parameterValue)

# Set parameters

dcids = list(found_dcids)
data_files = list(found_data_files)

self.log.info(f"Found {dcids} corresponding to {group}")

self.log.debug(f"Found data files {data_files}")

job_parameters: list[tuple[str, str]] = [
("data", files) for files in data_files
]
if group:
job_parameters.append(group)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be simplified a lot by splitting into two queries. The issue with the single query is that you effectively get a duplicate of the processingJobParameters for each dcid. If you split it into two queries, the handling of the parameters becomes much easier.

Suggested change
# First, use the input scaling ID to find the DCIDs of all multiplex jobs as well as the sample (group) id value
query = (
session.query(ProcessingJobImageSweep, ProcessingJobParameter)
.join(
AutoProcProgram,
AutoProcProgram.processingJobId
== ProcessingJobImageSweep.processingJobId,
)
.join(
ProcessingJobParameter,
ProcessingJobParameter.processingJobId
== AutoProcProgram.processingJobId,
)
.join(
AutoProc,
AutoProc.autoProcProgramId == AutoProcProgram.autoProcProgramId,
)
.join(AutoProcScaling, AutoProcScaling.autoProcId == AutoProc.autoProcId)
.where(AutoProcScaling.autoProcScalingId == parameters.scaling_id)
.options(
Load(ProcessingJobImageSweep).load_only(
ProcessingJobImageSweep.dataCollectionId,
raiseload=True,
),
Load(ProcessingJobParameter).load_only(
ProcessingJobParameter.parameterKey,
ProcessingJobParameter.parameterValue,
raiseload=True,
),
)
)
return {"success": True, "return_value": jobids}
found_dcids = set()
group = None
found_data_files = set()
for dc, dc_params in query.all():
found_dcids.add(dc.dataCollectionId)
if "sample_id" == dc_params.parameterKey:
group = ("sample_id", dc_params.parameterValue)
elif "sample_group_id" == dc_params.parameterKey:
group = ("sample_group_id", dc_params.parameterValue)
elif "data" == dc_params.parameterKey:
found_data_files.add(dc_params.parameterValue)
# Set parameters
dcids = list(found_dcids)
data_files = list(found_data_files)
self.log.info(f"Found {dcids} corresponding to {group}")
self.log.debug(f"Found data files {data_files}")
job_parameters: list[tuple[str, str]] = [
("data", files) for files in data_files
]
if group:
job_parameters.append(group)
# Get the job parameters from the existing multiplex job.
job_parameters: list[tuple[str, str]] = (
session.query(ProcessingJobParameter.parameterKey, ProcessingJobParameter.parameterValue)
.select_from(AutoProcProgram)
.join(
ProcessingJobParameter,
ProcessingJobParameter.processingJobId
== AutoProcProgram.processingJobId,
)
.join(
AutoProc,
AutoProc.autoProcProgramId == AutoProcProgram.autoProcProgramId,
)
.join(AutoProcScaling, AutoProcScaling.autoProcId == AutoProc.autoProcId)
.where(AutoProcScaling.autoProcScalingId == parameters.scaling_id)
).all()
# Get the dataCollectionIds used in the existing multiplex job
parent_job_dcids: list[tuple[int]] = (
session.query(ProcessingJobImageSweep.dataCollectionId)
.join(
AutoProcProgram,
AutoProcProgram.processingJobId
== ProcessingJobImageSweep.processingJobId,
)
.join(
ProcessingJobParameter,
ProcessingJobParameter.processingJobId
== AutoProcProgram.processingJobId,
)
.join(
AutoProc,
AutoProc.autoProcProgramId == AutoProcProgram.autoProcProgramId,
)
.join(AutoProcScaling, AutoProcScaling.autoProcId == AutoProc.autoProcId)
.where(AutoProcScaling.autoProcScalingId == parameters.scaling_id)
).all()
dcids: list[int] = [row[0] for row in parent_job_dcids]

Comment on lines +2406 to +2445
if parameters.spacegroup:
job_parameters.append(("spacegroup", parameters.spacegroup))

if parameters.d_min:
job_parameters.append(("d_min", str(parameters.d_min)))

if (
parameters.diffraction_plan_info
and parameters.diffraction_plan_info.anomalousScatterer
):
job_parameters.extend(
[
("anomalous", "true"),
("absorption_level", "high"),
]
)
if output_clusters:
job_parameters.extend(
[
("clustering.method", "coordinate"),
("clustering.output_clusters", "true"),
]
)

# Unlike regular multiplex, filtering allowed regardless of beamline

if parameters.apply_cchalf_filtering:
job_parameters.append(("filtering.method", "deltacchalf"))
if parameters.cchalf_filtering_method:
job_parameters.append(
("deltacchalf.mode", parameters.cchalf_filtering_method)
)
if parameters.image_group_size:
job_parameters.append(
("deltacchalf.group_size", str(parameters.image_group_size))
)
if parameters.sd_cutoff:
job_parameters.append(
("deltacchalf.stdcutoff", str(parameters.sd_cutoff))
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this code, to append new parameters to the job parameters feels quite repetitive. It's not a huge issue but, particularly for the more simple additions, you could try doing this inside a loop to avoid having multiple if statements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants