Skip to content

Commit 2d23b57

Browse files
Copilott0mdavid-m
andcommitted
Fix tool_instance_name: resolve instance names to real tool names in save_parameters, add support in input_TOPP, run_topp, and get_topp_parameters
Co-authored-by: t0mdavid-m <57191390+t0mdavid-m@users.noreply.github.com>
1 parent 8dab8cf commit 2d23b57

4 files changed

Lines changed: 321 additions & 20 deletions

File tree

src/workflow/CommandExecutor.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def read_stderr():
216216
stdout_thread.join()
217217
stderr_thread.join()
218218

219-
def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}) -> bool:
219+
def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}, tool_instance_name: str = None) -> bool:
220220
"""
221221
Constructs and executes commands for the specified tool OpenMS TOPP tool based on the given
222222
input and output configurations. Ensures that all input/output file lists
@@ -234,6 +234,9 @@ def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}) -> b
234234
tool (str): The executable name or path of the tool.
235235
input_output (dict): A dictionary specifying the input/output parameter names (as key) and their corresponding file paths (as value).
236236
custom_params (dict): A dictionary of custom parameters to pass to the tool.
237+
tool_instance_name (str, optional): A unique instance name for this tool
238+
invocation, used for parameter lookup when multiple instances of the
239+
same tool exist. If not provided, defaults to the tool name.
237240
238241
Returns:
239242
bool: True if all commands succeeded, False if any failed.
@@ -242,6 +245,8 @@ def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}) -> b
242245
ValueError: If the lengths of input/output file lists are inconsistent,
243246
except for single string inputs.
244247
"""
248+
# Use tool_instance_name for parameter lookup, fall back to tool name
249+
params_key = tool_instance_name if tool_instance_name else tool
245250
# check input: any input lists must be same length, other items can be a single string
246251
# e.g. input_mzML : [list of n mzML files], output_featureXML : [list of n featureXML files], input_database : database.tsv
247252
io_lengths = [len(v) for v in input_output.values() if len(v) > 1]
@@ -281,9 +286,9 @@ def run_topp(self, tool: str, input_output: dict, custom_params: dict = {}) -> b
281286
# standard case, files was a list of strings, take the file name at index
282287
else:
283288
command += [value[i]]
284-
# Add non-default TOPP tool parameters
285-
if tool in params.keys():
286-
for k, v in params[tool].items():
289+
# Add non-default TOPP tool parameters (use params_key for lookup)
290+
if params_key in params.keys():
291+
for k, v in params[params_key].items():
287292
command += [f"-{k}"]
288293
# Skip only empty strings (pass flag with no value)
289294
# Note: 0 and 0.0 are valid values, so use explicit check

src/workflow/ParameterManager.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def save_parameters(self) -> None:
6565
# Advanced parameters are only in session state if the view is active
6666
json_params = self.get_parameters_from_json() | json_params
6767

68-
# get a list of TOPP tools which are in session state
68+
# get a list of TOPP tools (or tool instance names) which are in session state
6969
current_topp_tools = list(
7070
set(
7171
[
@@ -75,12 +75,16 @@ def save_parameters(self) -> None:
7575
]
7676
)
7777
)
78-
# for each TOPP tool, open the ini file
78+
# Retrieve the instance-name → real-tool-name mapping (set by input_TOPP)
79+
tool_instance_map = st.session_state.get("_topp_tool_instance_map", {})
80+
# for each TOPP tool (or instance name), open the ini file
7981
for tool in current_topp_tools:
80-
if not self.create_ini(tool):
82+
# Resolve instance name to real tool name for create_ini / ini loading
83+
real_tool = tool_instance_map.get(tool, tool)
84+
if not self.create_ini(real_tool):
8185
# Could not create ini file - skip this tool
8286
continue
83-
ini_path = Path(self.ini_dir, f"{tool}.ini")
87+
ini_path = Path(self.ini_dir, f"{real_tool}.ini")
8488
if tool not in json_params:
8589
json_params[tool] = {}
8690
# load the param object
@@ -92,8 +96,11 @@ def save_parameters(self) -> None:
9296
# Skip display keys used by multiselect widgets
9397
if key.endswith("_display"):
9498
continue
95-
# get ini_key
96-
ini_key = key.replace(self.topp_param_prefix, "").encode()
99+
# get ini_key – map instance name back to real tool name
100+
ini_key = key.replace(self.topp_param_prefix, "")
101+
if tool != real_tool:
102+
ini_key = ini_key.replace(f"{tool}:1:", f"{real_tool}:1:", 1)
103+
ini_key = ini_key.encode()
97104
# get ini (default) value by ini_key
98105
ini_value = param.getValue(ini_key)
99106
is_list_param = isinstance(ini_value, list)
@@ -130,17 +137,20 @@ def get_parameters_from_json(self) -> dict:
130137
st.error("**ERROR**: Attempting to load an invalid JSON parameter file. Reset to defaults.")
131138
return {}
132139

133-
def get_topp_parameters(self, tool: str) -> dict:
140+
def get_topp_parameters(self, tool: str, tool_instance_name: str = None) -> dict:
134141
"""
135142
Get all parameters for a TOPP tool, merging defaults with user values.
136143
137144
Args:
138-
tool: Name of the TOPP tool (e.g., "CometAdapter")
145+
tool: Name of the TOPP tool executable (e.g., "CometAdapter")
146+
tool_instance_name: Optional instance name used for parameter storage
147+
(e.g., "IDFilter_step1"). If not provided, defaults to tool name.
139148
140149
Returns:
141150
Dict with parameter names as keys (without tool prefix) and their values.
142151
Returns empty dict if ini file doesn't exist.
143152
"""
153+
instance_name = tool_instance_name or tool
144154
ini_path = Path(self.ini_dir, f"{tool}.ini")
145155
if not ini_path.exists():
146156
return {}
@@ -158,8 +168,8 @@ def get_topp_parameters(self, tool: str) -> dict:
158168
short_key = key_str.split(prefix, 1)[1]
159169
full_params[short_key] = param.getValue(key)
160170

161-
# Override with user-modified values from JSON
162-
user_params = self.get_parameters_from_json().get(tool, {})
171+
# Override with user-modified values from JSON (keyed by instance name)
172+
user_params = self.get_parameters_from_json().get(instance_name, {})
163173
full_params.update(user_params)
164174

165175
return full_params

src/workflow/StreamlitUI.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ def input_TOPP(
616616
display_subsections: bool = True,
617617
display_subsection_tabs: bool = False,
618618
custom_defaults: dict = {},
619+
tool_instance_name: str = None,
619620
) -> None:
620621
"""
621622
Generates input widgets for TOPP tool parameters dynamically based on the tool's
@@ -631,7 +632,21 @@ def input_TOPP(
631632
display_subsections (bool, optional): Whether to split parameters into subsections based on the prefix. Defaults to True.
632633
display_subsection_tabs (bool, optional): Whether to display main subsections in separate tabs (if more than one main section). Defaults to False.
633634
custom_defaults (dict, optional): Dictionary of custom defaults to use. Defaults to an empty dict.
635+
tool_instance_name (str, optional): A unique instance name for this tool
636+
invocation. Allows multiple instances of the same TOPP tool with
637+
independent parameters (e.g., two IDFilter calls). If not provided,
638+
defaults to topp_tool_name. The instance name is used for session
639+
state keys and parameter storage, while topp_tool_name is used for
640+
the actual tool executable and ini file creation.
634641
"""
642+
# Default instance name to the tool name when not provided
643+
if tool_instance_name is None:
644+
tool_instance_name = topp_tool_name
645+
646+
# Register instance-name → real-tool-name mapping in session state
647+
if "_topp_tool_instance_map" not in st.session_state:
648+
st.session_state["_topp_tool_instance_map"] = {}
649+
st.session_state["_topp_tool_instance_map"][tool_instance_name] = topp_tool_name
635650

636651
if not display_subsections:
637652
display_subsection_tabs = False
@@ -735,9 +750,9 @@ def _matches_parameter(pattern: str, key: bytes) -> bool:
735750
# else check if the parameter is already in self.params, if yes take the value from self.params
736751
for p in params:
737752
name = p["key"].decode().split(":1:")[1]
738-
if topp_tool_name in self.params:
739-
if name in self.params[topp_tool_name]:
740-
p["value"] = self.params[topp_tool_name][name]
753+
if tool_instance_name in self.params:
754+
if name in self.params[tool_instance_name]:
755+
p["value"] = self.params[tool_instance_name][name]
741756
elif name in custom_defaults:
742757
p["value"] = custom_defaults[name]
743758
elif name in custom_defaults:
@@ -775,7 +790,7 @@ def _matches_parameter(pattern: str, key: bytes) -> bool:
775790

776791
# Display tool name if required
777792
if display_tool_name:
778-
st.markdown(f"**{topp_tool_name}**")
793+
st.markdown(f"**{tool_instance_name}**")
779794

780795
tab_names = [k for k in param_sections.keys() if ":" not in k]
781796
tabs = None
@@ -803,8 +818,11 @@ def display_TOPP_params(params: dict, num_cols):
803818
cols = st.columns(num_cols)
804819
i = 0
805820
for p in params:
806-
# get key and name
807-
key = f"{self.parameter_manager.topp_param_prefix}{p['key'].decode()}"
821+
# get key and name – use tool_instance_name in session state key
822+
key_str = p['key'].decode()
823+
if tool_instance_name != topp_tool_name:
824+
key_str = key_str.replace(f"{topp_tool_name}:1:", f"{tool_instance_name}:1:", 1)
825+
key = f"{self.parameter_manager.topp_param_prefix}{key_str}"
808826
name = p["name"]
809827
try:
810828
# sometimes strings with newline, handle as list

0 commit comments

Comments
 (0)