From a6ac5d989c2d971139b5aa116af82a6ca6ae65f9 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Wed, 24 Sep 2025 10:41:25 +0100 Subject: [PATCH 1/9] Track dlstbx_align_crystal processing status Removes the initial creation of the screening record from the wrapper. This will be performed in a separate recipe step along with the creation of an AutoProcProgram record, which will be linked to the screening record. The AutoProcProgram record will be updated to reflect the processing status of the pipeline. Two conditional ispyb commands have been added to the wrapper to update the processing status accordingly when the pipeline completes showing success if a solution is found and failure if no valid solution exists. If any of the steps in the pipeline fail with a non-zero return code, the processing status will be set to 'Failed' via a separate recipe step. --- src/dlstbx/wrapper/dlstbx_align_crystal.py | 55 ++++++++++++---------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/dlstbx/wrapper/dlstbx_align_crystal.py b/src/dlstbx/wrapper/dlstbx_align_crystal.py index 01a23cf49..f3ef6838d 100644 --- a/src/dlstbx/wrapper/dlstbx_align_crystal.py +++ b/src/dlstbx/wrapper/dlstbx_align_crystal.py @@ -62,18 +62,7 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): chi = "%.2f" % chi phi = "%.2f" % phi - # Step 1: Add new record to Screening table, keep the ScreeningId - d = { - "dcid": dcid, - "programversion": "dials.align_crystal", - "comments": settings_str, - "shortcomments": "dials.align_crystal %i" % solution_id, - "ispyb_command": "insert_screening", - "store_result": "ispyb_screening_id_%i" % solution_id, - } - ispyb_command_list.append(d) - - # Step 2: Store screeningOutput results, linked to the screeningId + # Step 1: Store screeningOutput results, linked to the screeningId # Keep the screeningOutputId d = { "program": "dials.align_crystal", @@ -81,17 +70,17 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): "strategysuccess": 1, "alignmentsuccess": 1, "ispyb_command": "insert_screening_output", - "screening_id": "$ispyb_screening_id_%i" % solution_id, - "store_result": "ispyb_screening_output_id_%i" % solution_id, + "screening_id": "$ispyb_screening_id", + "store_result": f"ispyb_screening_output_id_{solution_id}", } ispyb_command_list.append(d) - # Step 3: Store screeningOutputLattice results, linked to the screeningOutputId + # Step 2: Store screeningOutputLattice results, linked to the screeningOutputId # Keep the screeningOutputLatticeId d = { "ispyb_command": "insert_screening_output_lattice", - "screening_output_id": "$ispyb_screening_output_id_%i" % solution_id, - "store_result": "ispyb_screening_output_lattice_id_%i" % solution_id, + "screening_output_id": f"$ispyb_screening_output_id_{solution_id}", + "store_result": f"ispyb_screening_output_lattice_id_{solution_id}", } uc_params = crystal_symmetry.unit_cell().parameters() for i, p in enumerate(("a", "b", "c", "alpha", "beta", "gamma")): @@ -99,17 +88,17 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): d["spacegroup"] = crystal_symmetry.space_group_info().type().lookup_symbol() ispyb_command_list.append(d) - # Step 4: Store screeningStrategy results, linked to the screeningOutputId + # Step 3: Store screeningStrategy results, linked to the screeningOutputId # Keep the screeningStrategyId d = { - "program": "dials.align_crystal %i" % solution_id, + "program": "dials.align_crystal", "ispyb_command": "insert_screening_strategy", - "screening_output_id": "$ispyb_screening_output_id_%i" % solution_id, - "store_result": "ispyb_screening_strategy_id_%i" % solution_id, + "screening_output_id": f"$ispyb_screening_output_id_{solution_id}", + "store_result": f"ispyb_screening_strategy_id_{solution_id}", } ispyb_command_list.append(d) - # Step 5: Store screeningStrategyWedge results, linked to the screeningStrategyId + # Step 4: Store screeningStrategyWedge results, linked to the screeningStrategyId # Keep the screeningStrategyWedgeId d = { "wedgenumber": 1, @@ -117,18 +106,31 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): "chi": chi, "comments": settings_str, "ispyb_command": "insert_screening_strategy_wedge", - "screening_strategy_id": "$ispyb_screening_strategy_id_%i" - % solution_id, - "store_result": "ispyb_screening_strategy_wedge_id_%i" % solution_id, + "screening_strategy_id": f"$ispyb_screening_strategy_id_{solution_id}", + "store_result": f"ispyb_screening_strategy_wedge_id_{solution_id}", } ispyb_command_list.append(d) if ispyb_command_list: + d = { + "ispyb_command": "update_processing_status", + "program_id": "$ispyb_autoprocprogram_id", + "message": "Processing successful", + "status": "success", + } + ispyb_command_list.append(d) self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) else: - self.log.info("There is no valid dials.align_crystal strategy here") + d = { + "ispyb_command": "update_processing_status", + "program_id": "$ispyb_autoprocprogram_id", + "message": "No achievable alignment within range of goniometer", + "status": "failure", + } + self.recwrap.send_to("ispyb", {"ispyb_command_list": [d]}) + self.log.info("No achievable alignment within range of goniometer") def construct_commandline(self, params): """Construct dlstbx.align_crystal command line. @@ -232,5 +234,6 @@ def run(self): ) else: self.log.warning("Expected JSON output file missing") + return False return True From c0b19ab135cedd9b95b77225fe0b63e12fd8e484 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Wed, 24 Sep 2025 12:47:07 +0100 Subject: [PATCH 2/9] Track status of downstream dials.align_crystal --- src/dlstbx/wrapper/dials_align_crystal.py | 61 ++++++++++++----------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/dlstbx/wrapper/dials_align_crystal.py b/src/dlstbx/wrapper/dials_align_crystal.py index a1a403755..07a260f67 100644 --- a/src/dlstbx/wrapper/dials_align_crystal.py +++ b/src/dlstbx/wrapper/dials_align_crystal.py @@ -64,18 +64,7 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): chi = "%.2f" % chi phi = "%.2f" % phi - # Step 1: Add new record to Screening table, keep the ScreeningId - d = { - "dcid": dcid, - "programversion": "xia2-dials+dials.align_crystal", - "comments": settings_str, - "shortcomments": f"solution {solution_id}", - "ispyb_command": "insert_screening", - "store_result": "ispyb_screening_id_%i" % solution_id, - } - ispyb_command_list.append(d) - - # Step 2: Store screeningOutput results, linked to the screeningId + # Step 1: Store screeningOutput results, linked to the screeningId # Keep the screeningOutputId d = { "program": "dials.align_crystal", @@ -83,17 +72,17 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): "strategysuccess": 1, "alignmentsuccess": 1, "ispyb_command": "insert_screening_output", - "screening_id": "$ispyb_screening_id_%i" % solution_id, - "store_result": "ispyb_screening_output_id_%i" % solution_id, + "screening_id": "$ispyb_screening_id", + "store_result": f"ispyb_screening_output_id_{solution_id}", } ispyb_command_list.append(d) - # Step 3: Store screeningOutputLattice results, linked to the screeningOutputId + # Step 2: Store screeningOutputLattice results, linked to the screeningOutputId # Keep the screeningOutputLatticeId d = { "ispyb_command": "insert_screening_output_lattice", - "screening_output_id": "$ispyb_screening_output_id_%i" % solution_id, - "store_result": "ispyb_screening_output_lattice_id_%i" % solution_id, + "screening_output_id": f"$ispyb_screening_output_id_{solution_id}", + "store_result": f"ispyb_screening_output_lattice_id_{solution_id}", } uc_params = crystal_symmetry.unit_cell().parameters() for i, p in enumerate(("a", "b", "c", "alpha", "beta", "gamma")): @@ -101,17 +90,17 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): d["spacegroup"] = crystal_symmetry.space_group_info().type().lookup_symbol() ispyb_command_list.append(d) - # Step 4: Store screeningStrategy results, linked to the screeningOutputId + # Step 3: Store screeningStrategy results, linked to the screeningOutputId # Keep the screeningStrategyId d = { - "program": "dials.align_crystal %i" % solution_id, + "program": "dials.align_crystal", "ispyb_command": "insert_screening_strategy", - "screening_output_id": "$ispyb_screening_output_id_%i" % solution_id, - "store_result": "ispyb_screening_strategy_id_%i" % solution_id, + "screening_output_id": f"$ispyb_screening_output_id_{solution_id}", + "store_result": f"ispyb_screening_strategy_id_{solution_id}", } ispyb_command_list.append(d) - # Step 5: Store screeningStrategyWedge results, linked to the screeningStrategyId + # Step 4: Store screeningStrategyWedge results, linked to the screeningStrategyId # Keep the screeningStrategyWedgeId d = { "wedgenumber": 1, @@ -119,18 +108,32 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): "chi": chi, "comments": settings_str, "ispyb_command": "insert_screening_strategy_wedge", - "screening_strategy_id": "$ispyb_screening_strategy_id_%i" - % solution_id, - "store_result": "ispyb_screening_strategy_wedge_id_%i" % solution_id, + "screening_strategy_id": f"$ispyb_screening_strategy_id_{solution_id}", + "store_result": f"ispyb_screening_strategy_wedge_id_{solution_id}", } ispyb_command_list.append(d) if ispyb_command_list: - self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) - self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) - self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) + d = { + "ispyb_command": "update_processing_status", + "program_id": "$ispyb_autoprocprogram_id", + "message": "Processing successful", + "status": "success", + } + ispyb_command_list.append(d) else: - self.log.info("There is no valid dials.align_crystal strategy here") + d = { + "ispyb_command": "update_processing_status", + "program_id": "$ispyb_autoprocprogram_id", + "message": "No achievable alignment within range of goniometer", + "status": "failure", + } + ispyb_command_list.append(d) + self.log.info("No achievable alignment within range of goniometer") + + self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) + self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) + self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) def run(self): assert hasattr(self, "recwrap"), "No recipewrapper object found" From 70f00efe08e09ca36d381aa35bfe1f52756ba2c4 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Wed, 24 Sep 2025 15:14:29 +0100 Subject: [PATCH 3/9] Refactor wrapper --- src/dlstbx/wrapper/dlstbx_align_crystal.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dlstbx/wrapper/dlstbx_align_crystal.py b/src/dlstbx/wrapper/dlstbx_align_crystal.py index f3ef6838d..dbf2adf15 100644 --- a/src/dlstbx/wrapper/dlstbx_align_crystal.py +++ b/src/dlstbx/wrapper/dlstbx_align_crystal.py @@ -119,9 +119,7 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): "status": "success", } ispyb_command_list.append(d) - self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) - self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) - self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) + else: d = { "ispyb_command": "update_processing_status", @@ -129,9 +127,13 @@ def insert_dials_align_strategies(self, dcid, crystal_symmetry, results): "message": "No achievable alignment within range of goniometer", "status": "failure", } - self.recwrap.send_to("ispyb", {"ispyb_command_list": [d]}) + ispyb_command_list.append(d) self.log.info("No achievable alignment within range of goniometer") + self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) + self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) + self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) + def construct_commandline(self, params): """Construct dlstbx.align_crystal command line. Takes job parameter dictionary, returns array.""" From 8f05e90a3ef827fc040b64bfafb1bb8247a1d44f Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Wed, 24 Sep 2025 16:11:45 +0100 Subject: [PATCH 4/9] Set xoalign wrapper up for process status tracking Also removes beamline check from within the wrapper as this will cause the wrapper to succeed but when it hasn't really run. In the workflow a screening record will have already been created. If beamline restriction is still desired, this should be done in the trigger step and not in the wrapper. --- src/dlstbx/wrapper/xoalign.py | 63 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/dlstbx/wrapper/xoalign.py b/src/dlstbx/wrapper/xoalign.py index 38aab0157..0566a7693 100644 --- a/src/dlstbx/wrapper/xoalign.py +++ b/src/dlstbx/wrapper/xoalign.py @@ -23,11 +23,6 @@ def run(self): substitutions=self.recwrap.environment, ) - beamline = params["beamline"] - if beamline not in ("i03", "i04"): - # Only run XOalign on these beamlines - return True - if isinstance(params["experiment_file"], list): experiment_file = Path(params["experiment_file"][0]) else: @@ -62,6 +57,7 @@ def run(self): else: datum = "" + beamline = params["beamline"] subprocess_environment = os.environ.copy() subprocess_environment["XOALIGN_CALIB"] = ( f"/dls_sw/{beamline}/etc/xoalign_config.py" @@ -94,7 +90,7 @@ def run(self): with open(working_directory / "XOalign.log", "w") as log_file: log_file.write(result.stdout) - self.insertXOalignStrategies(params["dcid"], result.stdout) + self.insertXOalignStrategies(result.stdout) results_directory.mkdir(parents=True, exist_ok=True) @@ -110,7 +106,7 @@ def run(self): shutil.copy(path, results_directory) return success - def insertXOalignStrategies(self, dcid, xoalign_log): + def insertXOalignStrategies(self, xoalign_log): smargon = False found_solutions = False @@ -152,18 +148,7 @@ def insertXOalignStrategies(self, dcid, xoalign_log): chi = "%.2f" % chi phi = "%.2f" % phi - # Step 1: Add new record to Screening table, keep the ScreeningId - d = { - "dcid": dcid, - "programversion": "XOalign", - "comments": settings_str, - "shortcomments": "XOalign %i" % solution_id, - "ispyb_command": "insert_screening", - "store_result": "ispyb_screening_id_%i" % solution_id, - } - ispyb_command_list.append(d) - - # Step 2: Store screeningOutput results, linked to the screeningId + # Step 1: Store screeningOutput results, linked to the screeningId # Keep the screeningOutputId d = { "program": "XOalign", @@ -171,22 +156,22 @@ def insertXOalignStrategies(self, dcid, xoalign_log): "strategysuccess": 1, "alignmentsuccess": 1, "ispyb_command": "insert_screening_output", - "screening_id": "$ispyb_screening_id_%i" % solution_id, - "store_result": "ispyb_screening_output_id_%i" % solution_id, + "screening_id": "$ispyb_screening_id", + "store_result": f"ispyb_screening_output_id_{solution_id}", } ispyb_command_list.append(d) - # Step 3: Store screeningStrategy results, linked to the screeningOutputId + # Step 2: Store screeningStrategy results, linked to the screeningOutputId # Keep the screeningStrategyId d = { "program": "XOalign", "ispyb_command": "insert_screening_strategy", - "screening_output_id": "$ispyb_screening_output_id_%i" % solution_id, - "store_result": "ispyb_screening_strategy_id_%i" % solution_id, + "screening_output_id": f"$ispyb_screening_output_id_{solution_id}", + "store_result": f"ispyb_screening_strategy_id_{solution_id}", } ispyb_command_list.append(d) - # Step 4: Store screeningStrategyWedge results, linked to the screeningStrategyId + # Step 3: Store screeningStrategyWedge results, linked to the screeningStrategyId # Keep the screeningStrategyWedgeId d = { "wedgenumber": 1, @@ -194,15 +179,29 @@ def insertXOalignStrategies(self, dcid, xoalign_log): "chi": chi, "comments": settings_str, "ispyb_command": "insert_screening_strategy_wedge", - "screening_strategy_id": "$ispyb_screening_strategy_id_%i" - % solution_id, - "store_result": "ispyb_screening_strategy_wedge_id_%i" % solution_id, + "screening_strategy_id": f"$ispyb_screening_strategy_id_{solution_id}", + "store_result": f"ispyb_screening_strategy_wedge_id_{solution_id}", } ispyb_command_list.append(d) if ispyb_command_list: - self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) - self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) - self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) + d = { + "ispyb_command": "update_processing_status", + "program_id": "$ispyb_autoprocprogram_id", + "message": "Processing successful", + "status": "success", + } + ispyb_command_list.append(d) else: - self.log.info("There is no valid XOalign strategy here") + d = { + "ispyb_command": "update_processing_status", + "program_id": "$ispyb_autoprocprogram_id", + "message": "No achievable alignment within range of goniometer", + "status": "failure", + } + ispyb_command_list.append(d) + self.log.info("No achievable alignment within range of goniometer") + + self.log.debug("Sending %s", json.dumps(ispyb_command_list, indent=2)) + self.recwrap.send_to("ispyb", {"ispyb_command_list": ispyb_command_list}) + self.log.info("Sent %d commands to ISPyB", len(ispyb_command_list)) From 9fba1b718edb66382c147b28e215f3fc6f696386 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Fri, 10 Oct 2025 14:48:37 +0100 Subject: [PATCH 5/9] Pick up latest ispyb-api version --- requirements.conda.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.conda.txt b/requirements.conda.txt index 1aa1bb297..9877e1315 100644 --- a/requirements.conda.txt +++ b/requirements.conda.txt @@ -1,6 +1,6 @@ drmaa hdf5plugin -ispyb>=11.0.3 +ispyb>=11.1.0 junit-xml>=1.9 marshmallow-sqlalchemy minio>=7.1.0 From 273e102edf1e015ea4adfbd9d423ef65fb815988 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Fri, 10 Oct 2025 16:43:15 +0100 Subject: [PATCH 6/9] Update comment --- src/dlstbx/services/ispybsvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlstbx/services/ispybsvc.py b/src/dlstbx/services/ispybsvc.py index 096d65154..7f23a4a97 100644 --- a/src/dlstbx/services/ispybsvc.py +++ b/src/dlstbx/services/ispybsvc.py @@ -539,7 +539,7 @@ def do_store_per_image_analysis_results(self, parameters, **kwargs): def do_insert_screening(self, parameters, **kwargs): """Write entry to the Screening table.""" - # screening_params: ['id', 'dcgid', 'dcid', 'programversion', 'shortcomments', 'comments'] + # screening_params: ['id', 'dcgid', 'dcid', 'programversion', 'shortcomments', 'comments', 'autoprocprogramid'] screening_params = self.ispyb.mx_screening.get_screening_params() for k in screening_params.keys(): screening_params[k] = parameters(k) From 71024a873c799b2dedc6d4026a9191feb1d64f05 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Tue, 14 Oct 2025 11:55:23 +0100 Subject: [PATCH 7/9] Call strategies as ispyb jobs Also removes depracated strategy pipelines --- src/dlstbx/mimas/core.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/dlstbx/mimas/core.py b/src/dlstbx/mimas/core.py index 7a3697e9e..114cfc37f 100644 --- a/src/dlstbx/mimas/core.py +++ b/src/dlstbx/mimas/core.py @@ -163,11 +163,12 @@ def handle_eiger_screening( **kwargs, ) -> List[mimas.Invocation]: return [ - mimas.MimasRecipeInvocation(DCID=scenario.DCID, recipe=recipe) - for recipe in ( - "strategy-align-crystal", - "strategy-mosflm", - "strategy-edna-eiger", + mimas.MimasISPyBJobInvocation( + DCID=scenario.DCID, + autostart=True, + recipe="strategy-align-crystal", + source="automatic", + displayname="align_crystal", ) ] @@ -177,10 +178,15 @@ def handle_characterization( scenario: mimas.MimasScenario, **kwargs, ) -> List[mimas.Invocation]: - tasks: List[mimas.Invocation] = [ - mimas.MimasRecipeInvocation(DCID=scenario.DCID, recipe="strategy-align-crystal") + return [ + mimas.MimasISPyBJobInvocation( + DCID=scenario.DCID, + autostart=True, + recipe="strategy-align-crystal", + source="automatic", + displayname="align_crystal", + ) ] - return tasks @mimas.match_specification( @@ -191,10 +197,12 @@ def handle_pilatus_screening( **kwargs, ) -> List[mimas.Invocation]: return [ - mimas.MimasRecipeInvocation(DCID=scenario.DCID, recipe=recipe) - for recipe in ( - "strategy-mosflm", - "strategy-edna", + mimas.MimasISPyBJobInvocation( + DCID=scenario.DCID, + autostart=True, + recipe="strategy-align-crystal", + source="automatic", + displayname="align_crystal", ) ] From 205bfb2b7028c60828accfa44fd14ad05bd07783 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Tue, 14 Oct 2025 13:53:38 +0100 Subject: [PATCH 8/9] Readd legacy mosflm pipeline for now --- src/dlstbx/mimas/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dlstbx/mimas/core.py b/src/dlstbx/mimas/core.py index 114cfc37f..31e641ce9 100644 --- a/src/dlstbx/mimas/core.py +++ b/src/dlstbx/mimas/core.py @@ -169,7 +169,8 @@ def handle_eiger_screening( recipe="strategy-align-crystal", source="automatic", displayname="align_crystal", - ) + ), + mimas.MimasRecipeInvocation(DCID=scenario.DCID, recipe="strategy-mosflm"), ] @@ -185,7 +186,7 @@ def handle_characterization( recipe="strategy-align-crystal", source="automatic", displayname="align_crystal", - ) + ), ] @@ -203,7 +204,8 @@ def handle_pilatus_screening( recipe="strategy-align-crystal", source="automatic", displayname="align_crystal", - ) + ), + mimas.MimasRecipeInvocation(DCID=scenario.DCID, recipe="strategy-mosflm"), ] From 1d6e3d3f17b39c64199eeeadd06572aca1d860f1 Mon Sep 17 00:00:00 2001 From: Phil Blowey Date: Tue, 14 Oct 2025 14:28:36 +0100 Subject: [PATCH 9/9] Fix xoalign wrapper --- src/dlstbx/wrapper/xoalign.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dlstbx/wrapper/xoalign.py b/src/dlstbx/wrapper/xoalign.py index 0566a7693..5d056120c 100644 --- a/src/dlstbx/wrapper/xoalign.py +++ b/src/dlstbx/wrapper/xoalign.py @@ -40,7 +40,7 @@ def run(self): # Create working directory working_directory.mkdir(parents=True, exist_ok=True) - symlink = self.params.get("create_symlink") + symlink = params.get("create_symlink") if isinstance(symlink, list): symlink = symlink[0] if symlink: