From c6f6e6903a863be15919a6db497cfefdfb1330c9 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 19 Sep 2025 14:27:24 +0200 Subject: [PATCH 1/4] Add .pre-commit-config.yaml * Add .pre-commit-config.yaml * Fix some ruff issues * Add some ruff ignores -- to be fixed later --- .pre-commit-config.yaml | 24 ++++++++++++++++++++ pyproject.toml | 14 ++++++++++++ src/petab_gui/controllers/sbml_controller.py | 4 +--- src/petab_gui/models/sbml_model.py | 4 ++-- src/petab_gui/views/simple_plot_view.py | 2 +- 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ea5ae4c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.24.1 + hooks: + - id: validate-pyproject + # Optional extra validations from SchemaStore: + additional_dependencies: [ "validate-pyproject-schema-store[all]" ] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.13.1 + hooks: + - id: ruff-check + types_or: [ python, pyi ] + args: [ --fix ] + - id: ruff-format + types_or: [ python, pyi ] diff --git a/pyproject.toml b/pyproject.toml index d8a044a..5b3683f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,16 @@ lint.ignore = [ "D100", # Ignore missing docstring in public modules "D101", # Ignore missing docstring in public classes "F401", + # FIXME: those are ignored for now, should be fixed eventually + "E501", # Ignore line too long + "ERA001", # Found commented-out code + "RET504", # Unnecessary assignment before `return` + "T201", # `print` found" + "SIM105", # Use `contextlib.suppress` + "S110", # `try`-`except`-`pass` detected, consider logging the exception + "A002", # Function argument shadowing a Python builtin + "E701", # Multiple statements on one line (colon) + "E741", # Ambiguous variable name ] [tool.ruff.lint.per-file-ignores] "*/__init__.py" = [ @@ -91,5 +101,9 @@ lint.ignore = [ "D400", "D205", ] +"docs/source/conf.py" = [ + "A001", # Variable `copyright` is shadowing a Python builtin + +] [tool.ruff.lint.pydocstyle] convention = "pep257" diff --git a/src/petab_gui/controllers/sbml_controller.py b/src/petab_gui/controllers/sbml_controller.py index 54b62b0..d2292e6 100644 --- a/src/petab_gui/controllers/sbml_controller.py +++ b/src/petab_gui/controllers/sbml_controller.py @@ -151,12 +151,10 @@ def _overwrite_sbml_with_model(self, sbml_model: SbmlModel): "SBML model successfully overwritten.", color="green" ) - def clear_model(self): - """Clear the model in case the user wants to start a new problem""" + """Clear the model in case the user wants to start a new problem.""" self.model.antimony_text = DEFAULT_ANTIMONY_TEXT self.model.convert_antimony_to_sbml() self.view.sbml_text_edit.setPlainText(self.model.sbml_text) self.view.antimony_text_edit.setPlainText(self.model.antimony_text) self.overwritten_model.emit() - diff --git a/src/petab_gui/models/sbml_model.py b/src/petab_gui/models/sbml_model.py index 6b8b656..5cd8ac4 100644 --- a/src/petab_gui/models/sbml_model.py +++ b/src/petab_gui/models/sbml_model.py @@ -33,7 +33,7 @@ def __init__(self, sbml_model: petab.models.Model, parent=None): self.antimony_text = sbmlToAntimony(self.sbml_text) else: self.antimony_text = DEFAULT_ANTIMONY_TEXT - with QSignalBlocker(self) as blocker: + with QSignalBlocker(self): self.convert_antimony_to_sbml() self.model_id = self._get_model_id() @@ -67,7 +67,7 @@ def get_current_sbml_model(self): def _get_model_id(self): """Extract the model ID from the SBML text.""" - document = libsbml.readSBMLFromString(self.sbml_text) + document = libsbml.readSBMLFromString(self.sbml_text) model = document.getModel() model_id = model.getIdAttribute() or "New_File" return model_id diff --git a/src/petab_gui/views/simple_plot_view.py b/src/petab_gui/views/simple_plot_view.py index a3e0759..e94d629 100644 --- a/src/petab_gui/views/simple_plot_view.py +++ b/src/petab_gui/views/simple_plot_view.py @@ -390,7 +390,7 @@ class ToolbarOptionManager(QObject): def __new__(cls): if cls._instance is None: - cls._instance = super(ToolbarOptionManager, cls).__new__(cls) + cls._instance = super().__new__(cls) return cls._instance def __init__(self): From 19d5d17d61b731045a7c423aa096f0cacac7a0dc Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 22 Sep 2025 10:22:21 +0200 Subject: [PATCH 2/4] pre-commit on GHA --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..24cff02 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI +on: + push: + pull_request: + workflow_dispatch: + merge_group: + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Prepare Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install pre-commit + + - name: Run pre-commit hooks + run: pre-commit run --all-files From eb0553a275ff7d3307445a421ce2297096b95cda Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 22 Sep 2025 10:25:27 +0200 Subject: [PATCH 3/4] ignore --- src/petab_gui/controllers/table_controllers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/petab_gui/controllers/table_controllers.py b/src/petab_gui/controllers/table_controllers.py index aadd597..e949eff 100644 --- a/src/petab_gui/controllers/table_controllers.py +++ b/src/petab_gui/controllers/table_controllers.py @@ -765,7 +765,8 @@ def _rank_dose_candidates(self, df) -> list[str]: """Lightweight ranking of dose-like columns (regex + numeric + cardinality).""" patt = re.compile(r"\b(dose|conc|concentration|drug|compound|stim|input|u\d+)\b", re.IGNORECASE) scores = {} - for col in df.columns: + # FIXME: https://github.com/PaulJonasJost/PEtab_GUI/issues/159 + for col in df.columns: # noqa: B007 s = 0.0 if patt.search(col or ""): s += 2.0 try: From 2510af2158a3d417d15b7da18cdd7d6cefc9e825 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 22 Sep 2025 14:04:22 +0200 Subject: [PATCH 4/4] autofix --- .zenodo.json | 2 +- README.md | 20 +- docs/source/conf.py | 27 +- example/data_matrix.csv | 2 +- example/dose_response.csv | 2 +- example/meas.tsv | 2 +- example/problem.yaml | 2 +- src/petab_gui/C.py | 2 +- src/petab_gui/app.py | 10 +- src/petab_gui/commands.py | 11 +- .../controllers/logger_controller.py | 4 +- .../controllers/mother_controller.py | 98 +- .../controllers/table_controllers.py | 89 +- src/petab_gui/controllers/utils.py | 83 +- src/petab_gui/models/pandas_table_model.py | 4 +- src/petab_gui/models/petab_model.py | 3 +- src/petab_gui/models/tooltips.py | 30 +- src/petab_gui/settings_manager.py | 20 +- src/petab_gui/stylesheet.css | 1 - src/petab_gui/views/logger.py | 2 +- src/petab_gui/views/other_views.py | 21 +- src/petab_gui/views/simple_plot_view.py | 79 +- src/petab_gui/views/utils.py | 7 +- src/petab_gui/views/whats_this.py | 859 +++++++++--------- 24 files changed, 734 insertions(+), 646 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index a5f6745..6c92c5b 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -20,4 +20,4 @@ "Biological Modeling", "Parameter Estimation" ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index fd0d127..05e684b 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,17 @@ The PEtabGUI provides a Python-based graphical user interface that simplifies the creation, editing, and validation of PEtab parameter estimation problems. - **Unified Environment** - - Integrates all PEtab components (SBML/PySB models, conditions, observables, - measurements, parameters, and visualization files). - - Supports drag-and-drop import of YAML or individual component files. - - Automatically resolves mismatches and converts matrix-format experimental data + - Integrates all PEtab components (SBML/PySB models, conditions, observables, + measurements, parameters, and visualization files). + - Supports drag-and-drop import of YAML or individual component files. + - Automatically resolves mismatches and converts matrix-format experimental data into valid PEtab format. - **Interactive and Intuitive Editing** - - Dockable, resizable, and movable table widgets for each PEtab file. - - Context-aware editing with combo-boxes, drop-downs, and multi-cell editing. - - Automatic generation of missing observables/conditions with customizable defaults. - - Real-time validation and plausibility checks with PEtab linting tools. - - SBML view in both XML and human-readable Antimony syntax. + - Dockable, resizable, and movable table widgets for each PEtab file. + - Context-aware editing with combo-boxes, drop-downs, and multi-cell editing. + - Automatic generation of missing observables/conditions with customizable defaults. + - Real-time validation and plausibility checks with PEtab linting tools. + - SBML view in both XML and human-readable Antimony syntax. - **Visualization and Simulation** - Interactive plots linking measurement data with model simulations. - Bidirectional highlighting between plots and tables. @@ -71,6 +71,6 @@ the creation, editing, and validation of PEtab parameter estimation problems. - Intelligent defaults for visualization with optional user customization. - Ability to disable plotting for large models to maintain responsiveness. - **Archiving and Export** - - Export individual tables, the SBML model, or complete PEtab problems. + - Export individual tables, the SBML model, or complete PEtab problems. - Save as directory structures or [COMBINE archives](https://combinearchive.org) for reproducibility diff --git a/docs/source/conf.py b/docs/source/conf.py index d810715..517e4d4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,30 +10,29 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'PEtab GUI' -copyright = '2025, Paul Jonas Jost, Frank T. Bergmann' -author = 'Paul Jonas Jost, Frank T. Bergmann' -release = '0.1.3' +project = "PEtab GUI" +copyright = "2025, Paul Jonas Jost, Frank T. Bergmann" +author = "Paul Jonas Jost, Frank T. Bergmann" +release = "0.1.3" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.napoleon', # For Google/Numpy style docstrings - 'sphinx.ext.viewcode', # Add links to highlighted source code - 'sphinx.ext.githubpages', # For publishing on GitHub Pages - 'sphinx.ext.todo', # Support todo items - 'sphinx.ext.mathjax', # For LaTeX math rendering - 'myst_parser', # For Markdown support - 'sphinx_copybutton', # To allow copying code snippets - 'sphinx_design', # For better design elements + "sphinx.ext.napoleon", # For Google/Numpy style docstrings + "sphinx.ext.viewcode", # Add links to highlighted source code + "sphinx.ext.githubpages", # For publishing on GitHub Pages + "sphinx.ext.todo", # Support todo items + "sphinx.ext.mathjax", # For LaTeX math rendering + "myst_parser", # For Markdown support + "sphinx_copybutton", # To allow copying code snippets + "sphinx_design", # For better design elements ] -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = [] - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/example/data_matrix.csv b/example/data_matrix.csv index d5a6f79..02a40d6 100644 --- a/example/data_matrix.csv +++ b/example/data_matrix.csv @@ -1,4 +1,4 @@ Time,obsA,obsB,obsC 3, 1, 2, 3 5, 4, 5, 6 -7, 7, 8, 9 \ No newline at end of file +7, 7, 8, 9 diff --git a/example/dose_response.csv b/example/dose_response.csv index e9170a4..c56d803 100644 --- a/example/dose_response.csv +++ b/example/dose_response.csv @@ -1,4 +1,4 @@ pApB,obsA,obsB,obsC 3, 1, 2, 3 5, 4, 5, 6 -7, 7, 8, 9 \ No newline at end of file +7, 7, 8, 9 diff --git a/example/meas.tsv b/example/meas.tsv index 22446c1..814cdb1 100644 --- a/example/meas.tsv +++ b/example/meas.tsv @@ -46,4 +46,4 @@ rSTAT5A_rel model1_data1 41.0627332148332 100.0 sd_rSTAT5A_rel model1_data1_rS rSTAT5A_rel model1_data1 39.2358300289377 120.0 sd_rSTAT5A_rel model1_data1_rSTAT5A_rel rSTAT5A_rel model1_data1 36.6194605442418 160.0 sd_rSTAT5A_rel model1_data1_rSTAT5A_rel rSTAT5A_rel model1_data1 34.8937144010561 200.0 sd_rSTAT5A_rel model1_data1_rSTAT5A_rel -rSTAT5A_rel model1_data1 32.2110771608676 240.0 sd_rSTAT5A_rel model1_data1_rSTAT5A_rel \ No newline at end of file +rSTAT5A_rel model1_data1 32.2110771608676 240.0 sd_rSTAT5A_rel model1_data1_rSTAT5A_rel diff --git a/example/problem.yaml b/example/problem.yaml index 26f179c..b24d457 100644 --- a/example/problem.yaml +++ b/example/problem.yaml @@ -8,4 +8,4 @@ problems: observable_files: - obs.tsv sbml_files: - - sbml_model.xml \ No newline at end of file + - sbml_model.xml diff --git a/src/petab_gui/C.py b/src/petab_gui/C.py index 9be7243..01ebaec 100644 --- a/src/petab_gui/C.py +++ b/src/petab_gui/C.py @@ -71,7 +71,7 @@ "yLabel": {"type": np.object_, "optional": True}, "yScale": {"type": np.object_, "optional": True}, "legendEntry": {"type": np.object_, "optional": True}, - } + }, } CONFIG = { diff --git a/src/petab_gui/app.py b/src/petab_gui/app.py index 8d25239..9d18fd2 100644 --- a/src/petab_gui/app.py +++ b/src/petab_gui/app.py @@ -1,8 +1,8 @@ import argparse import os import sys +from importlib.metadata import PackageNotFoundError, version from importlib.resources import files -from importlib.metadata import version, PackageNotFoundError from pathlib import Path from PySide6.QtCore import QEvent @@ -48,7 +48,7 @@ class PEtabGuiApp(QApplication): Inherits from QApplication and sets up the MVC components. """ - def __init__(self, file: str|Path = None): + def __init__(self, file: str | Path = None): """Initialize the PEtab GUI application. Sets up the model, view, and controller components. @@ -125,12 +125,10 @@ def main(): "--version", action="version", version=f"%(prog)s {pkg_version}", - help="Show version number and exit" + help="Show version number and exit", ) parser.add_argument( - "petab_yaml", - nargs="?", - help="Path to the PEtab YAML file" + "petab_yaml", nargs="?", help="Path to the PEtab YAML file" ) args = parser.parse_args() diff --git a/src/petab_gui/commands.py b/src/petab_gui/commands.py index e3f75cc..621409c 100644 --- a/src/petab_gui/commands.py +++ b/src/petab_gui/commands.py @@ -141,7 +141,9 @@ def redo(self): df = self.model._data_frame if self.add_mode: - position = 0 if df.empty else df.shape[0] - 1 # insert *before* the auto-row + position = ( + 0 if df.empty else df.shape[0] - 1 + ) # insert *before* the auto-row self.model.beginInsertRows( QModelIndex(), position, position + len(self.row_indices) - 1 ) @@ -264,12 +266,9 @@ def _apply_changes(self, use_new: bool): else: df[col] = df[col].astype(dtype) - rows = [ - df.index.get_loc(row_key) for (row_key, _) in self.changes - ] + rows = [df.index.get_loc(row_key) for (row_key, _) in self.changes] cols = [ - df.columns.get_loc(col) + col_offset - for (_, col) in self.changes + df.columns.get_loc(col) + col_offset for (_, col) in self.changes ] top_left = self.model.index(min(rows), min(cols)) diff --git a/src/petab_gui/controllers/logger_controller.py b/src/petab_gui/controllers/logger_controller.py index e50fd0a..c091f96 100644 --- a/src/petab_gui/controllers/logger_controller.py +++ b/src/petab_gui/controllers/logger_controller.py @@ -18,8 +18,8 @@ def __init__(self, views): self.logger_level = 1 self.log_message("Welcome to PEtab-GUI!", color="green") self.log_message( - 'If you need help, click Help in the menu,' - ' enter the Help Mode (click question mark in toolbar) or visit ' + "If you need help, click Help in the menu," + " enter the Help Mode (click question mark in toolbar) or visit " 'the documentation.', color="green", diff --git a/src/petab_gui/controllers/mother_controller.py b/src/petab_gui/controllers/mother_controller.py index 3417193..6ad0def 100644 --- a/src/petab_gui/controllers/mother_controller.py +++ b/src/petab_gui/controllers/mother_controller.py @@ -261,9 +261,7 @@ def setup_connections(self): self.visualization_controller, self.simulation_controller, ]: - controller.overwritten_df.connect( - self._schedule_plot_update - ) + controller.overwritten_df.connect(self._schedule_plot_update) def setup_actions(self): """Setup actions for the main controller.""" @@ -302,9 +300,7 @@ def setup_actions(self): actions["save_single_table"] = QAction( qta.icon("mdi6.table-arrow-down"), "Save This Table", self.view ) - actions["save_single_table"].triggered.connect( - self.save_single_table - ) + actions["save_single_table"].triggered.connect(self.save_single_table) # Find + Replace actions["find"] = QAction(qta.icon("mdi6.magnify"), "Find", self.view) actions["find"].setShortcut(QKeySequence.Find) @@ -341,8 +337,9 @@ def setup_actions(self): actions["delete_row"].triggered.connect(self.delete_rows) # add/delete column actions["add_column"] = QAction( - qta.icon("mdi6.table-column-plus-after"), "Add Column...", - self.view + qta.icon("mdi6.table-column-plus-after"), + "Add Column...", + self.view, ) actions["add_column"].triggered.connect(self.add_column) actions["delete_column"] = QAction( @@ -381,8 +378,14 @@ def setup_actions(self): filter_layout.addWidget(self.filter_input) for table_n, table_name in zip( ["m", "p", "o", "c", "v", "s"], - ["measurement", "parameter", "observable", "condition", - "visualization", "simulation"], + [ + "measurement", + "parameter", + "observable", + "condition", + "visualization", + "simulation", + ], strict=False, ): tool_button = QToolButton() @@ -405,8 +408,14 @@ def setup_actions(self): self.filter_input.textChanged.connect(self.filter_table) # show/hide elements - for element in ["measurement", "observable", "parameter", - "condition", "visualization", "simulation"]: + for element in [ + "measurement", + "observable", + "parameter", + "condition", + "visualization", + "simulation", + ]: actions[f"show_{element}"] = QAction( f"{element.capitalize()} Table", self.view ) @@ -790,21 +799,13 @@ def open_omex_and_load_files(self, omex_path=None): self.measurement_controller.overwrite_df( combine_archive.measurement_df ) - self.observable_controller.overwrite_df( - combine_archive.observable_df - ) - self.condition_controller.overwrite_df( - combine_archive.condition_df - ) - self.parameter_controller.overwrite_df( - combine_archive.parameter_df - ) + self.observable_controller.overwrite_df(combine_archive.observable_df) + self.condition_controller.overwrite_df(combine_archive.condition_df) + self.parameter_controller.overwrite_df(combine_archive.parameter_df) self.visualization_controller.overwrite_df( combine_archive.visualization_df ) - self.sbml_controller.overwrite_sbml( - sbml_model = combine_archive.model - ) + self.sbml_controller.overwrite_sbml(sbml_model=combine_archive.model) def new_file(self): """Empty all tables. In case of unsaved changes, ask to save.""" @@ -981,7 +982,6 @@ def open_settings(self): settings_dialog = SettingsDialog(table_columns, self.view) settings_dialog.exec() - def find(self): """Create a find replace bar if it is non existent.""" if self.view.find_replace_bar is None: @@ -1001,7 +1001,7 @@ def init_plotter(self): self.simulation_controller.proxy_model, self.condition_controller.proxy_model, self.visualization_controller.proxy_model, - self.model + self.model, ) self.plotter = self.view.plot_dock self.plotter.highlighter.click_callback = self._on_plot_point_clicked @@ -1021,10 +1021,7 @@ def _on_plot_point_clicked(self, x, y, label, data_type): def column_index(name): for col in range(proxy.columnCount()): - if ( - proxy.headerData(col, Qt.Horizontal) - == name - ): + if proxy.headerData(col, Qt.Horizontal) == name: return col raise ValueError(f"Column '{name}' not found.") @@ -1052,11 +1049,13 @@ def _on_table_selection_changed(self, selected, deselected): self.plotter.highlight_from_selection(selected_rows) def _on_simulation_selection_changed(self, selected, deselected): - selected_rows = get_selected(self.simulation_controller.view.table_view) + selected_rows = get_selected( + self.simulation_controller.view.table_view + ) self.plotter.highlight_from_selection( selected_rows, proxy=self.simulation_controller.proxy_model, - y_axis_col="simulation" + y_axis_col="simulation", ) def simulate(self): @@ -1069,16 +1068,21 @@ def simulate(self): from basico.petab import PetabSimulator # report current basico / COPASI version - self.logger.log_message(f"Simulate with basico: {basico.__version__}, COPASI: {basico.COPASI.__version__}", color="green") + self.logger.log_message( + f"Simulate with basico: {basico.__version__}, COPASI: {basico.COPASI.__version__}", + color="green", + ) import tempfile # create temp directory in temp folder: with tempfile.TemporaryDirectory() as temp_dir: # settings is only current solution statistic for now: - settings = {'method' : {'name': basico.PE.CURRENT_SOLUTION}} + settings = {"method": {"name": basico.PE.CURRENT_SOLUTION}} # create simulator - simulator = PetabSimulator(petab_problem, settings=settings, working_dir=temp_dir) + simulator = PetabSimulator( + petab_problem, settings=settings, working_dir=temp_dir + ) # simulate sim_df = simulator.simulate() @@ -1093,6 +1097,7 @@ def _schedule_plot_update(self): def _toggle_whats_this_mode(self, on: bool): """Enable/disable click-to-help mode by installing/removing the global filter. + On enter: show a short instruction bubble. """ app = QApplication.instance() @@ -1105,17 +1110,14 @@ def _toggle_whats_this_mode(self, on: bool): except Exception: pass app.removeEventFilter(self._whats_this_filter) - self.logger.log_message( - "Enden the Help mode.", - color="blue" - ) + self.logger.log_message("Enden the Help mode.", color="blue") return # install filter app.installEventFilter(self._whats_this_filter) QApplication.setOverrideCursor(Qt.WhatsThisCursor) self.logger.log_message( "Started the Help mode. Click on any widget to see its help.", - color="blue" + color="blue", ) self._show_help_welcome() @@ -1129,15 +1131,15 @@ def _show_help_welcome(self): msg.setWindowTitle("Help mode") msg.setTextFormat(Qt.RichText) msg.setText( - "Welcome to help mode
" - "
    " - "
  • Click any widget, tab, or column header to see its help.
  • " - "
  • Click the same item again or press Esc to close the bubble.
  • " - "
  • Press Esc with no bubble, or toggle the ? button, to exit.
  • " - "
" - ) + "Welcome to help mode
" + "
    " + "
  • Click any widget, tab, or column header to see its help.
  • " + "
  • Click the same item again or press Esc to close the bubble.
  • " + "
  • Press Esc with no bubble, or toggle the ? button, to exit.
  • " + "
" + ) dont = QCheckBox("Don't show again") msg.setCheckBox(dont) msg.exec() if dont.isChecked(): - settings.setValue("help_mode/welcome_disabled", True) + settings.setValue("help_mode/welcome_disabled", True) diff --git a/src/petab_gui/controllers/table_controllers.py b/src/petab_gui/controllers/table_controllers.py index e949eff..0c99b0c 100644 --- a/src/petab_gui/controllers/table_controllers.py +++ b/src/petab_gui/controllers/table_controllers.py @@ -158,7 +158,9 @@ def open_table(self, file_path=None, separator=None, mode="overwrite"): return try: if self.model.table_type in [ - "measurement", "visualization", "simulation" + "measurement", + "visualization", + "simulation", ]: new_df = pd.read_csv(file_path, sep=separator) else: @@ -570,7 +572,9 @@ def save_table(self, file_name): if not file_name.endswith((".tsv", ".csv")): file_name += ".tsv" try: - save_petab_table(self.model.get_df(), file_name, self.model.table_type) + save_petab_table( + self.model.get_df(), file_name, self.model.table_type + ) except Exception as e: QMessageBox.critical( self.view, @@ -709,30 +713,34 @@ def process_data_matrix_file(self, file_name, mode, separator=None): if cond_dialog.exec(): conditions = cond_dialog.get_inputs() condition_id = conditions.get("simulationConditionId", "") - preeq_id = conditions.get("preequilibrationConditionId", "") + preeq_id = conditions.get( + "preequilibrationConditionId", "" + ) else: return else: dose_col_sel, time_choice, preeq_id = ( - self._resolve_dose_and_time(data_matrix)) + self._resolve_dose_and_time(data_matrix) + ) if not dose_col_sel or time_choice is None: self.logger.log_message( "While uploading file as a data matrix: time column " "found and no dose/time selection made.", - color="red" + color="red", ) return df_proc = data_matrix.copy() - if (isinstance(time_choice, str) - and time_choice.strip().lower() == "inf"): - df_proc["time"] = "inf" + if ( + isinstance(time_choice, str) + and time_choice.strip().lower() == "inf" + ): + df_proc["time"] = "inf" else: try: - df_proc["time"] = float(time_choice) + df_proc["time"] = float(time_choice) except Exception: self.logger.log_message( - f"Invalid time value: {time_choice}", - color="red" + f"Invalid time value: {time_choice}", color="red" ) return # No fixed condition_id in dose-response; it's built per-row @@ -763,25 +771,39 @@ def _detect_time_column(self, df) -> str | None: def _rank_dose_candidates(self, df) -> list[str]: """Lightweight ranking of dose-like columns (regex + numeric + cardinality).""" - patt = re.compile(r"\b(dose|conc|concentration|drug|compound|stim|input|u\d+)\b", re.IGNORECASE) + patt = re.compile( + r"\b(dose|conc|concentration|drug|compound|stim|input|u\d+)\b", + re.IGNORECASE, + ) scores = {} # FIXME: https://github.com/PaulJonasJost/PEtab_GUI/issues/159 for col in df.columns: # noqa: B007 s = 0.0 - if patt.search(col or ""): s += 2.0 + if patt.search(col or ""): + s += 2.0 try: - if df[col].dtype.kind in "if": s += 1.0 + if df[col].dtype.kind in "if": + s += 1.0 uniq = df[col].nunique(dropna=True) - if 2 <= uniq <= 30: s += 0.8 - if np.all(pd.to_numeric(df[col], errors="coerce").fillna(0) >= 0): s += 0.3 + if 2 <= uniq <= 30: + s += 0.8 + if np.all(pd.to_numeric(df[col], errors="coerce").fillna(0) >= 0): + s += 0.3 ser = pd.to_numeric(df[col], errors="coerce").dropna() if len(ser) >= 5: diffs = np.diff(ser.values) - if np.mean(diffs >= 0) >= 0.7: s += 0.2 + if np.mean(diffs >= 0) >= 0.7: + s += 0.2 except Exception: pass scores[col] = s - return [c for c, _ in sorted(scores.items(), key=lambda x: (-x[1], df[x[0]].nunique(dropna=True)))] + return [ + c + for c, _ in sorted( + scores.items(), + key=lambda x: (-x[1], df[x[0]].nunique(dropna=True)), + ) + ] def _resolve_dose_and_time(self, df) -> tuple[str | None, str | None, str]: """Open dialog with ranked dose suggestions and time choices (incl. steady state).""" @@ -791,11 +813,11 @@ def _resolve_dose_and_time(self, df) -> tuple[str | None, str | None, str]: last_dose = settings.value(f"dose/last_choice/{header_key}", "", str) suggested = self._rank_dose_candidates(df) if last_dose and last_dose in df.columns: - suggested = [last_dose] + [s for s in suggested if s != last_dose] + suggested = [last_dose] + [s for s in suggested if s != last_dose] dlg = DoseTimeDialog( columns=list(df.columns), dose_suggested=suggested, - parent=self.view if hasattr(self, "view") else None + parent=self.view if hasattr(self, "view") else None, ) if dlg.exec(): dose_col, time_text, preeq_id = dlg.get_result() @@ -815,8 +837,11 @@ def _format_dose_value(self, v) -> str: return str(v).strip().replace(" ", "_") def populate_tables_from_data_matrix( - self, data_matrix, - condition_id, preeq_id: str = "", dose_col: str | None = None + self, + data_matrix, + condition_id, + preeq_id: str = "", + dose_col: str | None = None, ): """Populate the measurement table from the data matrix.""" # Build per-row condition IDs if dose_col provided @@ -832,13 +857,15 @@ def populate_tables_from_data_matrix( if col == "time": continue if dose_col and col == dose_col: - continue + continue observable_id = col self.model.relevant_id_changed.emit( observable_id, "", "observable" ) if condition_ids is None: - self.model.relevant_id_changed.emit(condition_id, "", "condition") + self.model.relevant_id_changed.emit( + condition_id, "", "condition" + ) if preeq_id: self.model.relevant_id_changed.emit(preeq_id, "", "condition") self.add_measurement_rows( @@ -854,9 +881,9 @@ def add_measurement_rows( data_matrix, observable_id, condition_id: str = "", - preeq_id: str = "", - condition_ids: Sequence[str] | None = None, - ): + preeq_id: str = "", + condition_ids: Sequence[str] | None = None, + ): """Adds multiple rows to the measurement table.""" # check number of rows and signal row insertion rows = data_matrix.shape[0] @@ -867,7 +894,11 @@ def add_measurement_rows( ) # Fills the table with empty rows top_left = self.model.createIndex(current_rows, 0) for i_row, (_, row) in enumerate(data_matrix.iterrows()): - cid = condition_ids[i_row] if condition_ids is not None else condition_id + cid = ( + condition_ids[i_row] + if condition_ids is not None + else condition_id + ) self.model.fill_row( i_row + current_rows, data={ @@ -1282,5 +1313,5 @@ def __init__( model=model, logger=logger, undo_stack=undo_stack, - mother_controller=mother_controller + mother_controller=mother_controller, ) diff --git a/src/petab_gui/controllers/utils.py b/src/petab_gui/controllers/utils.py index d93f663..e5f1920 100644 --- a/src/petab_gui/controllers/utils.py +++ b/src/petab_gui/controllers/utils.py @@ -37,8 +37,10 @@ class _WhatsThisClickHelp(QObject): def __init__(self, action): super().__init__() self.action = action - self._has_bubble = False # whether a bubble is currently visible - self._last = None # ("kind", widget, extra) to toggle on repeated clicks + self._has_bubble = False # whether a bubble is currently visible + self._last = ( + None # ("kind", widget, extra) to toggle on repeated clicks + ) def _exit_mode(self): """Uncheck action and remove filter.""" @@ -66,8 +68,10 @@ def eventFilter(self, _obj, ev): return False # Left-click: toggle/show help at the clicked target - if ev.type() == QEvent.MouseButtonPress and (ev.buttons() & Qt.LeftButton): - QWhatsThis.hideText() # always close any previous bubble + if ev.type() == QEvent.MouseButtonPress and ( + ev.buttons() & Qt.LeftButton + ): + QWhatsThis.hideText() # always close any previous bubble w = QApplication.widgetAt(QCursor.pos()) # Clicking the toolbar "?" button itself -> exit help mode @@ -94,7 +98,11 @@ def eventFilter(self, _obj, ev): if self._last == ("tab", tabbar, i): self._has_bubble = False return True - text = tabbar.tabToolTip(i) or tabbar.tabText(i) or "No help available." + text = ( + tabbar.tabToolTip(i) + or tabbar.tabText(i) + or "No help available." + ) QWhatsThis.showText(QCursor.pos(), text, tabbar) self._last = ("tab", tabbar, i) self._has_bubble = True @@ -115,10 +123,18 @@ def eventFilter(self, _obj, ev): if self._last == ("header", hdr, sec): self._has_bubble = False return True - text = (view.model().headerData(sec, hdr.orientation(), Qt.WhatsThisRole) - or view.model().headerData(sec, hdr.orientation(), Qt.ToolTipRole) - or view.model().headerData(sec, hdr.orientation(), Qt.DisplayRole) - or "No help available.") + text = ( + view.model().headerData( + sec, hdr.orientation(), Qt.WhatsThisRole + ) + or view.model().headerData( + sec, hdr.orientation(), Qt.ToolTipRole + ) + or view.model().headerData( + sec, hdr.orientation(), Qt.DisplayRole + ) + or "No help available." + ) QWhatsThis.showText(QCursor.pos(), text, hdr) self._last = ("header", hdr, sec) self._has_bubble = True @@ -135,7 +151,11 @@ def eventFilter(self, _obj, ev): if self._last == ("cell", view, (idx.row(), idx.column())): self._has_bubble = False return True - text = (idx.data(Qt.WhatsThisRole) or idx.data(Qt.ToolTipRole) or "No help available.") + text = ( + idx.data(Qt.WhatsThisRole) + or idx.data(Qt.ToolTipRole) + or "No help available." + ) QWhatsThis.showText(QCursor.pos(), text, w) self._last = ("cell", view, (idx.row(), idx.column())) self._has_bubble = True @@ -171,24 +191,26 @@ def wrapper( except Exception as e: err_msg = filtered_error(e) err_msg = html.escape(err_msg) - if (additional_error_check and "Missing parameter(s)" in - err_msg): - match = re.search(r"\{(.+?)}", err_msg) - missing_params = { - s.strip(" '") for s in match.group(1).split(",") - } - remain = { - p - for p in missing_params - if p not in self.model._data_frame.index - } - if not remain: - return True - err_msg = re.sub( - r"\{.*?}", - "{" + ", ".join(sorted(remain)) + "}", - err_msg, - ) + if ( + additional_error_check + and "Missing parameter(s)" in err_msg + ): + match = re.search(r"\{(.+?)}", err_msg) + missing_params = { + s.strip(" '") for s in match.group(1).split(",") + } + remain = { + p + for p in missing_params + if p not in self.model._data_frame.index + } + if not remain: + return True + err_msg = re.sub( + r"\{.*?}", + "{" + ", ".join(sorted(remain)) + "}", + err_msg, + ) msg = "PEtab linter failed" if row_name is not None and col_name is not None: msg = f"{msg} at ({row_name}, {col_name}): {err_msg}" @@ -308,9 +330,8 @@ def clear_recent_files(self): self.save_recent_files() self.update_tool_bar_menu() -def save_petab_table( - df: pd.DataFrame, filename: str | Path, table_type: str -): + +def save_petab_table(df: pd.DataFrame, filename: str | Path, table_type: str): """Save a PEtab table to a file. Function used based on table type.""" if table_type == "condition": petab.write_condition_df(df, filename) diff --git a/src/petab_gui/models/pandas_table_model.py b/src/petab_gui/models/pandas_table_model.py index 32124c2..4ba208f 100644 --- a/src/petab_gui/models/pandas_table_model.py +++ b/src/petab_gui/models/pandas_table_model.py @@ -207,7 +207,9 @@ def headerData(self, section, orientation, role=Qt.DisplayRole): if section == 0 and self._has_named_index: col_label = self._data_frame.index.name else: - col_label = self._data_frame.columns[section - self.column_offset] + col_label = self._data_frame.columns[ + section - self.column_offset + ] if role == Qt.ToolTipRole: tooltip_header = header_tip(self.table_type, col_label) return tooltip_header diff --git a/src/petab_gui/models/petab_model.py b/src/petab_gui/models/petab_model.py index e9bb685..06a1702 100644 --- a/src/petab_gui/models/petab_model.py +++ b/src/petab_gui/models/petab_model.py @@ -140,8 +140,7 @@ def save(self, directory: str | Path): self.current_petab_problem.to_files_generic(prefix_path=directory) def save_as_omex(self, file_name: str): - """Save the PEtab model as an OMEX file. - """ + """Save the PEtab model as an OMEX file.""" with tempfile.TemporaryDirectory() as temp_dir: self.save(temp_dir) petab.create_combine_archive( diff --git a/src/petab_gui/models/tooltips.py b/src/petab_gui/models/tooltips.py index ecdf775..396b3ea 100644 --- a/src/petab_gui/models/tooltips.py +++ b/src/petab_gui/models/tooltips.py @@ -1,4 +1,5 @@ """Tooltips for table view columns.""" + import numpy as np # Tooltips @@ -6,8 +7,11 @@ _CELL_TIPS: dict[str, dict[str, str]] = {} -def register_tips(table: str, header: dict[str, str] | None = None, - cell: dict[str, str] | None = None) -> None: +def register_tips( + table: str, + header: dict[str, str] | None = None, + cell: dict[str, str] | None = None, +) -> None: """Register tooltips for a given table.""" if header: _HEADER_TIPS.setdefault(table, {}).update(header) @@ -16,7 +20,8 @@ def register_tips(table: str, header: dict[str, str] | None = None, # Measurement Tooltips -register_tips("measurement", +register_tips( + "measurement", header={ "observableId": "ID from Observables; the output being measured.", "preequilibrationConditionId": "Condition for preequilibration; empty = none.", @@ -42,7 +47,8 @@ def register_tips(table: str, header: dict[str, str] | None = None, ) # Observable Tooltips -register_tips("observable", +register_tips( + "observable", header={ "observableId": "Unique ID; letters, digits, underscores; not starting with digit. Referenced in Measurements.", "observableName": "Optional display name; not used for identification.", @@ -62,7 +68,8 @@ def register_tips(table: str, header: dict[str, str] | None = None, ) # Parameter Tooltips -register_tips("parameter", +register_tips( + "parameter", header={ "parameterId": "Unique ID; must match SBML parameter, condition override, or observable/noise parameter.", "parameterName": "Optional label for plotting; may differ from SBML name.", @@ -92,7 +99,8 @@ def register_tips(table: str, header: dict[str, str] | None = None, ) # Condition Tooltips -register_tips("condition", +register_tips( + "condition", header={ "conditionId": "Unique ID; letters/digits/underscores; not starting with digit. Referenced by Measurements.", "conditionName": "Optional human-readable name for reports/plots.", @@ -102,12 +110,13 @@ def register_tips(table: str, header: dict[str, str] | None = None, "conditionId": "Enter a valid, unique identifier.", "conditionName": "Optional label.", "*": "User-defined column. Provide numeric value or SBML/parameter ID. " - "Species IDs = initial amount/concentration (NaN = keep preeq/initial). " - "Compartment IDs = initial size.", + "Species IDs = initial amount/concentration (NaN = keep preeq/initial). " + "Compartment IDs = initial size.", }, ) -register_tips("visualization", +register_tips( + "visualization", header={ "plotId": "Plot ID; datasets with same ID share axes.", "plotName": "Optional plot display name.", @@ -142,7 +151,7 @@ def register_tips(table: str, header: dict[str, str] | None = None, }, ) -_default_tip = "User-defined column; no specific structure enforced.", +_default_tip = ("User-defined column; no specific structure enforced.",) def header_tip(table: str, column: str) -> str: @@ -240,4 +249,3 @@ def cell_tip(table: str, column: str) -> str: "• Edit SBML (XML) and Antimony side-by-side.
" "• Use Forward Changes buttons to sync; see logger for errors." ) - diff --git a/src/petab_gui/settings_manager.py b/src/petab_gui/settings_manager.py index 5c5bd1b..d20c542 100644 --- a/src/petab_gui/settings_manager.py +++ b/src/petab_gui/settings_manager.py @@ -62,27 +62,29 @@ def set_value(self, key, value): def load_ui_settings(self, main_window): """Load UI-related settings such as main window and dock states.""" # Restore main window geometry and state - main_window.restoreGeometry(self.get_value( - "main_window/geometry", main_window.saveGeometry() - )) + main_window.restoreGeometry( + self.get_value("main_window/geometry", main_window.saveGeometry()) + ) main_window.restoreState( self.get_value("main_window/state", main_window.saveState()) ) # Restore dock widget visibility for dock, _ in main_window.dock_visibility.items(): - dock.setVisible(self.get_value( - f"docks/{dock.objectName()}", True, value_type=bool - )) + dock.setVisible( + self.get_value( + f"docks/{dock.objectName()}", True, value_type=bool + ) + ) main_window.data_tab.restoreGeometry( self.get_value( "data_tab/geometry", main_window.data_tab.saveGeometry() ) ) - main_window.data_tab.restoreState(self.get_value( - "data_tab/state", main_window.data_tab.saveState() - )) + main_window.data_tab.restoreState( + self.get_value("data_tab/state", main_window.data_tab.saveState()) + ) def save_ui_settings(self, main_window): """Save UI-related settings such as main window and dock states.""" diff --git a/src/petab_gui/stylesheet.css b/src/petab_gui/stylesheet.css index 7b5bab8..96557ef 100644 --- a/src/petab_gui/stylesheet.css +++ b/src/petab_gui/stylesheet.css @@ -138,4 +138,3 @@ QDockWidget::close-button:hover { background-color: #ffcccc; /* Light red */ border-radius: 5px; } - diff --git a/src/petab_gui/views/logger.py b/src/petab_gui/views/logger.py index 46eb717..d89afe0 100644 --- a/src/petab_gui/views/logger.py +++ b/src/petab_gui/views/logger.py @@ -3,8 +3,8 @@ Contains logger widget as well as two helper buttons. """ -from PySide6.QtWidgets import QHBoxLayout, QTextBrowser, QWidget from PySide6.QtCore import Qt +from PySide6.QtWidgets import QHBoxLayout, QTextBrowser, QWidget class Logger(QWidget): diff --git a/src/petab_gui/views/other_views.py b/src/petab_gui/views/other_views.py index eac486a..cb5480b 100644 --- a/src/petab_gui/views/other_views.py +++ b/src/petab_gui/views/other_views.py @@ -1,4 +1,5 @@ """Collection of other views aside from the main ones.""" + from PySide6.QtWidgets import ( QComboBox, QDialog, @@ -13,19 +14,29 @@ class DoseTimeDialog(QDialog): """Pick dose and time (or steady state).""" - def __init__(self, columns: list[str], dose_suggested: list[str], parent=None): + def __init__( + self, columns: list[str], dose_suggested: list[str], parent=None + ): super().__init__(parent) self.setWindowTitle("Select Dose and Time") - order = [c for c in dose_suggested if c in columns] + [c for c in columns if c not in dose_suggested] + order = [c for c in dose_suggested if c in columns] + [ + c for c in columns if c not in dose_suggested + ] self._dose = QComboBox(self) self._dose.addItems(order) self._time = QLineEdit(self) - self._time.setPlaceholderText("Enter constant time (e.g. 0, 5, 12.5). Use 'inf' for steady state") + self._time.setPlaceholderText( + "Enter constant time (e.g. 0, 5, 12.5). Use 'inf' for steady state" + ) self._preeq_edit = QLineEdit(self) - self._preeq_edit.setPlaceholderText("Optional preequilibrationConditionId") + self._preeq_edit.setPlaceholderText( + "Optional preequilibrationConditionId" + ) self._dose_lbl = QLabel("Dose column:", self) self._time_lbl = QLabel("Time:", self) - self._preeq_lbl = QLabel("Preequilibration condition (optional):", self) + self._preeq_lbl = QLabel( + "Preequilibration condition (optional):", self + ) ok = QPushButton("OK", self) ok.clicked.connect(self.accept) cancel = QPushButton("Cancel", self) diff --git a/src/petab_gui/views/simple_plot_view.py b/src/petab_gui/views/simple_plot_view.py index e94d629..fcce794 100644 --- a/src/petab_gui/views/simple_plot_view.py +++ b/src/petab_gui/views/simple_plot_view.py @@ -77,8 +77,7 @@ def __init__(self, parent=None): self.observable_to_subplot = {} def initialize( - self, meas_proxy, sim_proxy, cond_proxy, vis_proxy, - petab_model + self, meas_proxy, sim_proxy, cond_proxy, vis_proxy, petab_model ): self.meas_proxy = meas_proxy self.cond_proxy = cond_proxy @@ -125,44 +124,60 @@ def plot_it(self): conditions_df, measurements_df, simulations_df, - group_by + group_by, ) worker.signals.finished.connect(self._render_on_main_thread) QThreadPool.globalInstance().start(worker) def _render_on_main_thread(self, payload): import petab.v1.visualize as petab_vis + # GUI-thread plotting - plt.close('all') - meas_df = payload.get('meas_df') - cond_df = payload.get('cond_df') - if meas_df is None or meas_df.empty or cond_df is None or cond_df.empty: + plt.close("all") + meas_df = payload.get("meas_df") + cond_df = payload.get("cond_df") + if ( + meas_df is None + or meas_df.empty + or cond_df is None + or cond_df.empty + ): self._update_tabs(None) return - sim_df = payload.get('sim_df') - group_by = payload.get('group_by') - if group_by == 'vis_df': - vis_df = payload.get('vis_df') + sim_df = payload.get("sim_df") + group_by = payload.get("group_by") + if group_by == "vis_df": + vis_df = payload.get("vis_df") if vis_df is not None and not vis_df.empty: try: - petab_vis.plot_with_vis_spec(vis_df, cond_df, meas_df, sim_df) + petab_vis.plot_with_vis_spec( + vis_df, cond_df, meas_df, sim_df + ) fig = plt.gcf() self._update_tabs(fig) return except Exception as e: - print(f'Invalid Visualisation DF: {e}') + print(f"Invalid Visualisation DF: {e}") # fallback to observable grouping - plt.close('all') + plt.close("all") petab_vis.plot_without_vis_spec( - cond_df, measurements_df=meas_df, simulations_df=sim_df, group_by='observable' + cond_df, + measurements_df=meas_df, + simulations_df=sim_df, + group_by="observable", ) else: - plt.close('all') + plt.close("all") petab_vis.plot_without_vis_spec( - cond_df, measurements_df=meas_df, simulations_df=sim_df, group_by=group_by + cond_df, + measurements_df=meas_df, + simulations_df=sim_df, + group_by=group_by, ) fig = plt.gcf() - fig.subplots_adjust(left=0.12, bottom=0.15, right=0.95, top=0.9, wspace=0.3, hspace=0.4) + fig.subplots_adjust( + left=0.12, bottom=0.15, right=0.95, top=0.9, wspace=0.3, hspace=0.4 + ) self._update_tabs(fig) def _update_tabs(self, fig: plt.Figure): @@ -239,7 +254,9 @@ def _update_tabs(self, fig: plt.Figure): # Plot residuals if necessary self.plot_residuals() - def highlight_from_selection(self, selected_rows: list[int], proxy=None, y_axis_col="measurement"): + def highlight_from_selection( + self, selected_rows: list[int], proxy=None, y_axis_col="measurement" + ): proxy = proxy or self.meas_proxy if not proxy: return @@ -296,15 +313,15 @@ def plot_residuals(self): plot_goodness_of_fit, plot_residuals_vs_simulation, ) + fig_res, axes = plt.subplots( - 1, 2, - sharey=True, constrained_layout=True, width_ratios=[2, 1] + 1, 2, sharey=True, constrained_layout=True, width_ratios=[2, 1] ) try: plot_residuals_vs_simulation( problem, simulations_df, - axes = axes, + axes=axes, ) create_plot_tab(fig_res, self, "Residuals vs Simulation") except ValueError as e: @@ -314,7 +331,7 @@ def plot_residuals(self): plot_goodness_of_fit( problem, simulations_df, - ax = axes_fit, + ax=axes_fit, ) # fig_fit.tight_layout() create_plot_tab(fig_fit, self, "Goodness of Fit") @@ -322,8 +339,10 @@ def plot_residuals(self): class MeasurementHighlighter: def __init__(self): - self.highlight_scatters = defaultdict(list) # (subplot index) → scatter artist - self.point_index_map = {} # (subplot index, observableId, x, y) → row index + self.highlight_scatters = defaultdict( + list + ) # (subplot index) → scatter artist + self.point_index_map = {} # (subplot index, observableId, x, y) → row index self.click_callback = None def clear_highlight(self): @@ -331,7 +350,7 @@ def clear_highlight(self): def register_subplot(self, ax, subplot_idx): scatter = ax.scatter( - [], [], s=80, edgecolors='black', facecolors='none', zorder=5 + [], [], s=80, edgecolors="black", facecolors="none", zorder=5 ) self.highlight_scatters[subplot_idx].append(scatter) @@ -427,7 +446,9 @@ def __init__(self, canvas, parent): ) for grp, action in self.groupy_by_options.items(): action.setCheckable(True) - action.triggered.connect(lambda _, grp=grp: self.manager.set_option(grp)) + action.triggered.connect( + lambda _, grp=grp: self.manager.set_option(grp) + ) self.settings_menu.addAction(action) self.manager.option_changed.connect(self.update_checked_state) self.update_checked_state(self.manager.get_option()) @@ -441,9 +462,7 @@ def update_checked_state(self, selected_option): def create_plot_tab( - figure, - plotter: MeasurementPlotter, - plot_title: str = "New Plot" + figure, plotter: MeasurementPlotter, plot_title: str = "New Plot" ) -> FigureCanvas: """Create a new tab with the given figure and plotter.""" canvas = FigureCanvas(figure) diff --git a/src/petab_gui/views/utils.py b/src/petab_gui/views/utils.py index 2b052d3..6752c3e 100644 --- a/src/petab_gui/views/utils.py +++ b/src/petab_gui/views/utils.py @@ -9,11 +9,8 @@ def proxy_to_dataframe(proxy_model): headers = [proxy_model.headerData(c, Qt.Horizontal) for c in range(cols)] data = [] - for r in range(rows-1): - row = { - headers[c]: proxy_model.index(r, c).data() - for c in range(cols) - } + for r in range(rows - 1): + row = {headers[c]: proxy_model.index(r, c).data() for c in range(cols)} for key, value in row.items(): if isinstance(value, str) and value == "": row[key] = None diff --git a/src/petab_gui/views/whats_this.py b/src/petab_gui/views/whats_this.py index 00ce136..4a5d7ee 100644 --- a/src/petab_gui/views/whats_this.py +++ b/src/petab_gui/views/whats_this.py @@ -1,439 +1,440 @@ """What's This? Dialog desciptions of the widgets.""" + WHATS_THIS = { - "tabs": { - "data_tables": ( - "Data Tables
" - "
    " - "
  • Edit PEtab tables: Measurement, Observable, Parameter, Condition, Visualization.
  • " - "
  • Hover headers for definitions; right-click for context actions.
  • " - "
  • Keep IDs consistent across tables (e.g., observableId, condition IDs).
  • " - "
  • Import/export to manage files; validation highlights issues.
  • " - "
" - ), - "sbml_model": ( - "SBML Model
" - "
    " - "
  • Edit SBML (XML) and Antimony side-by-side.
  • " - "
  • Use the Forward Changes buttons to convert/sync between views.
  • " - "
  • Errors and warnings appear in the logger panel below.
  • " - "
  • Some constructs may not round-trip perfectly; keep the canonical SBML copy.
  • " - "
" - ), - }, - "sbml_view": { - "sbml_editor": ( - "SBML editor (XML)
" - "
    " - "
  • Paste or edit valid SBML (L2/L3); keep namespaces intact.
  • " - "
  • Click Forward → Antimony to generate Antimony.
  • " - "
  • Prefer this pane for full SBML feature coverage.
  • " - "
  • Conversion issues are reported in the logger.
  • " - "
" - ), - "antimony_editor": ( - "Antimony editor
" - "
    " - "
  • Human-readable model syntax compiled to SBML.
  • " - "
  • Click Forward → SBML to regenerate XML.
  • " - "
  • Use for quick edits; verify semantics after conversion.
  • " - "
  • Syntax/convert errors will appear in the logger.
  • " - "
" - ), - }, - "tables": { - "measurement": { - "table": ( - "Measurement table
" - "
    " - "
  • Each row is a data point with time, value, and linked IDs.
  • " - "
  • observableId must exist in Observable; " - "condition IDs must exist in Condition.
  • " - "
  • Use 'inf' for steady-state times.
  • " - "
  • Override placeholders observableParameter{n}_{observableId} " - "and noise parameters noiseParameter{n}_{observableId} when defined.
  • " - "
" - ), - "columns": { - "observableId": ( - "
    " - "
  • Reference to an observable defined in the Observable table.
  • " - "
  • Must match an existing observableId.
  • " - "
" - ), - "preequilibrationConditionId": ( - "
    " - "
  • Condition used for pre-equilibration; empty = none.
  • " - "
  • Must be a valid condition ID if provided.
  • " - "
" - ), - "simulationConditionId": ( - "
    " - "
  • Condition used for simulation parameters (required).
  • " - "
  • Must be a valid condition ID.
  • " - "
" - ), - "time": ( - "
    " - "
  • Numeric time in SBML units, or 'inf' for steady state.
  • " - "
  • Use a consistent unit system across data and model.
  • " - "
" - ), - "measurement": ( - "
    " - "
  • Observed numeric value in the same scale/units as the model output.
  • " - "
  • Leave blank for missing values if supported by your workflow.
  • " - "
" - ), - "observableParameters": ( - "
    " - "
  • Overrides for placeholders defined in the observable formula.
  • " - "
  • Provide n semicolon-separated values/names for " - "observableParameter{n}_{observableId}; empty if none.
  • " - "
" - ), - "noiseParameters": ( - "
    " - "
  • Noise std-dev (or parameter names); NaN if σ is a model parameter.
  • " - "
  • Same rules as observableParameters for lists and naming.
  • " - "
" - ), - "datasetId": ( - "
    " - "
  • Grouping key for plotting (datasets share style/axes).
  • " - "
  • Optional; defaults to per-row if omitted.
  • " - "
" - ), - "replicateId": ( - "
    " - "
  • Label to distinguish replicates within a dataset.
  • " - "
  • Enables error bars/replicate plotting modes.
  • " - "
" - ), - }, - }, - "simulation": { - "table": ( - "Simulation table
" - "
    " - "
  • Holds simulated outputs aligned to measurement definitions.
  • " - "
  • Same IDs as Measurement (observable/conditions/time) for comparison.
  • " - "
  • Populated by simulator/export; typically read-only.
  • " - "
" - ), - "columns": { - "observableId": ( - "
    " - "
  • Observable whose simulation is reported.
  • " - "
  • Must match an observableId in Observable.
  • " - "
" - ), - "preequilibrationConditionId": ( - "
    " - "
  • Preequilibration condition used during simulation; empty = none.
  • " - "
  • Must be a valid condition ID if set.
  • " - "
" - ), - "simulationConditionId": ( - "
    " - "
  • Condition used to set simulation parameters (required).
  • " - "
  • Must be a valid condition ID.
  • " - "
" - ), - "time": ( - "
    " - "
  • Time point for the simulated value (numeric or 'inf').
  • " - "
  • Use same units as the model.
  • " - "
" - ), - "simulation": ( - "
    " - "
  • Simulated numeric value (same scale/units as measurement).
  • " - "
  • Used for plotting and residuals.
  • " - "
" - ), - "observableParameters": ( - "
    " - "
  • Parameters used to evaluate observable placeholders, if applicable.
  • " - "
  • Semicolon-separated values/names; mirrors Measurement rules.
  • " - "
" - ), - "noiseParameters": ( - "
    " - "
  • Noise parameters applied for simulation/plotting modes.
  • " - "
  • Numeric values or names; may be empty.
  • " - "
" - ), - "datasetId": ( - "
    " - "
  • Dataset grouping to match plotted series.
  • " - "
  • Optional; align with Measurement for overlays.
  • " - "
" - ), - "replicateId": ( - "
    " - "
  • Replicate label, if simulations are per-replicate.
  • " - "
  • Usually empty unless replicates are simulated explicitly.
  • " - "
" - ), - }, - }, - "observable": { - "table": ( - "Observable table
" - "
    " - "
  • Defines how model states/expressions map to measured outputs.
  • " - "
  • May introduce placeholders observableParameter{n}_{observableId} " - "that are overridden per-measurement.
  • " - "
  • Noise model can be numeric σ or a formula; distribution optional.
  • " - "
" - ), - "columns": { - "observableId": ( - "
    " - "
  • Unique identifier (letters/digits/underscores; not starting with a digit).
  • " - "
  • Referenced by measurement.observableId.
  • " - "
" - ), - "observableName": ( - "
    " - "
  • Optional display name for reports/plots.
  • " - "
  • Not used for identification.
  • " - "
" - ), - "observableFormula": ( - "
    " - "
  • Expression using SBML symbols/parameters (e.g., species ID).
  • " - "
  • May define observableParameter{n}_{observableId} placeholders.
  • " - "
" - ), - "observableTransformation": ( - "
    " - "
  • Transformation for objective: lin, log, or log10.
  • " - "
  • Defaults to lin; data and outputs assumed linear if not set.
  • " - "
" - ), - "noiseFormula": ( - "
    " - "
  • Numeric σ (implies normal) or formula for complex noise.
  • " - "
  • May include noiseParameter{n}_{observableId}; " - "values provided in Measurement.
  • " - "
" - ), - "noiseDistribution": ( - "
    " - "
  • normal (σ = std dev) or laplace (σ = scale).
  • " - "
  • Log-variants via observableTransformation = log/log10.
  • " - "
" - ), - }, - }, - "parameter": { - "table": ( - "Parameter table
" - "
    " - "
  • Declares parameters, estimation flag, and bounds (linear space).
  • " - "
  • parameterId must match SBML or overrides used elsewhere.
  • " - "
  • Optional priors for initialization and/or objective.
  • " - "
" - ), - "columns": { - "parameterId": ( - "
    " - "
  • Must match an SBML parameter, a condition override, or names used in measurements.
  • " - "
  • Unique within this table.
  • " - "
" - ), - "parameterName": ( - "
    " - "
  • Optional display name for plots/reports.
  • " - "
  • May differ from SBML name.
  • " - "
" - ), - "parameterScale": ( - "
    " - "
  • Estimation scale: lin, log, or log10.
  • " - "
  • Affects optimization scaling, not storage format.
  • " - "
" - ), - "lowerBound": ( - "
    " - "
  • Numeric lower bound in linear space.
  • " - "
  • Optional if estimate==0.
  • " - "
" - ), - "upperBound": ( - "
    " - "
  • Numeric upper bound in linear space.
  • " - "
  • Optional if estimate==0.
  • " - "
" - ), - "nominalValue": ( - "
    " - "
  • Value used when fixed (estimate==0), in linear space.
  • " - "
  • Optional otherwise.
  • " - "
" - ), - "estimate": ( - "
    " - "
  • 1 = estimated; 0 = fixed to nominal value.
  • " - "
  • Controls inclusion in the optimization vector.
  • " - "
" + "tabs": { + "data_tables": ( + "Data Tables
" + "
    " + "
  • Edit PEtab tables: Measurement, Observable, Parameter, Condition, Visualization.
  • " + "
  • Hover headers for definitions; right-click for context actions.
  • " + "
  • Keep IDs consistent across tables (e.g., observableId, condition IDs).
  • " + "
  • Import/export to manage files; validation highlights issues.
  • " + "
" + ), + "sbml_model": ( + "SBML Model
" + "
    " + "
  • Edit SBML (XML) and Antimony side-by-side.
  • " + "
  • Use the Forward Changes buttons to convert/sync between views.
  • " + "
  • Errors and warnings appear in the logger panel below.
  • " + "
  • Some constructs may not round-trip perfectly; keep the canonical SBML copy.
  • " + "
" ), - "initializationPriorType": ( - "
    " - "
  • Prior for initial point sampling (e.g., uniform, normal, " - "parameterScaleUniform).
  • " - "
  • Defaults to parameterScaleUniform.
  • " - "
" - ), - "initializationPriorParameters": ( - "
    " - "
  • Semicolon-separated numeric parameters; default lowerBound;upperBound.
  • " - "
  • Linear scale unless using parameter-scale priors.
  • " - "
" - ), - "objectivePriorType": ( - "
    " - "
  • Prior contributing to the objective; same options as initialization prior.
  • " - "
  • Optional; omit for unregularized fits.
  • " - "
" - ), - "objectivePriorParameters": ( - "
    " - "
  • Semicolon-separated numeric parameters; see initialization prior for formats.
  • " - "
  • Scale rules mirror the chosen prior type.
  • " - "
" - ), - }, }, - "condition": { - "table": ( - "Condition table
" - "
    " - "
  • Defines simulation/experimental conditions referenced by other tables.
  • " - "
  • User-defined columns must be SBML IDs (parameter/species/compartment).
  • " - "
  • Species values act as initial conditions;
  • " - "
" - ), - "columns": { - "conditionId": ( - "
    " - "
  • Unique identifier (letters/digits/underscores; not starting with a digit).
  • " - "
  • Referenced by Measurement and Simulation.
  • " - "
" + "sbml_view": { + "sbml_editor": ( + "SBML editor (XML)
" + "
    " + "
  • Paste or edit valid SBML (L2/L3); keep namespaces intact.
  • " + "
  • Click Forward → Antimony to generate Antimony.
  • " + "
  • Prefer this pane for full SBML feature coverage.
  • " + "
  • Conversion issues are reported in the logger.
  • " + "
" + ), + "antimony_editor": ( + "Antimony editor
" + "
    " + "
  • Human-readable model syntax compiled to SBML.
  • " + "
  • Click Forward → SBML to regenerate XML.
  • " + "
  • Use for quick edits; verify semantics after conversion.
  • " + "
  • Syntax/convert errors will appear in the logger.
  • " + "
" ), - "conditionName": ( - "
    " - "
  • Optional human-readable name for reports/plots.
  • " - "
  • Does not affect model execution.
  • " - "
" - ), - "*": ( - "
    " - "
  • User-defined column. Must be an SBML ID: parameter, species, or compartment.
  • " - "
  • Numbers or IDs allowed; species = initial amount/concentration " - "(NaN keeps preeq/initial), compartments = initial size.
  • " - "
" - ), - }, }, - "visualization": { - "table": ( - "Visualization table
" - "
    " - "
  • Groups datasets into plots and configures axes/scales.
  • " - "
  • plotId collects series into the same axes.
  • " - "
  • Choose simulation/data types; set labels and offsets.
  • " - "
" - ), - "columns": { - "plotId": ( - "
    " - "
  • Plot grouping key; identical IDs share the same axes.
  • " - "
  • Required for multi-series plots.
  • " - "
" - ), - "plotName": ( - "
    " - "
  • Optional display name for the plot.
  • " - "
  • Used in UIs/exports; not an ID.
  • " - "
" - ), - "plotTypeSimulation": ( - "
    " - "
  • LinePlot | BarPlot | ScatterPlot.
  • " - "
  • Default is LinePlot.
  • " - "
" - ), - "plotTypeData": ( - "
    " - "
  • MeanAndSD | MeanAndSEM | replicate | provided.
  • " - "
  • Default is MeanAndSD.
  • " - "
" - ), - "datasetId": ( - "
    " - "
  • Includes datasets (from Measurement) in this plot.
  • " - "
  • Optional; multiple IDs → multiple series.
  • " - "
" - ), - "xValues": ( - "
    " - "
  • Independent variable: time (default) or parameter/state ID.
  • " - "
  • Values appear as x-axis ticks.
  • " - "
" - ), - "xOffset": ( - "
    " - "
  • Numeric offset applied to x values (default 0).
  • " - "
  • Use to disambiguate overlapping series.
  • " - "
" - ), - "xLabel": ( - "
    " - "
  • Custom x-axis label; defaults to xValues.
  • " - "
  • Use units where helpful.
  • " - "
" - ), - "xScale": ( - "
    " - "
  • lin | log | log10 | order (LinePlot only).
  • " - "
  • Default is lin; order places points equidistantly.
  • " - "
" - ), - "yValues": ( - "
    " - "
  • Observable ID to plot on the y-axis.
  • " - "
  • Must match measurement.observableId for overlays.
  • " - "
" - ), - "yOffset": ( - "
    " - "
  • Numeric offset applied to y values (default 0).
  • " - "
  • Use for stacked/shifted visuals.
  • " - "
" - ), - "yLabel": ( - "
    " - "
  • Custom y-axis label; defaults to yValues.
  • " - "
  • Include units where applicable.
  • " - "
" - ), - "yScale": ( - "
    " - "
  • lin | log | log10.
  • " - "
  • Default is lin.
  • " - "
" - ), - "legendEntry": ( - "
    " - "
  • Legend text; defaults to datasetId.
  • " - "
  • Use concise, descriptive names.
  • " - "
" - ), - }, + "tables": { + "measurement": { + "table": ( + "Measurement table
" + "
    " + "
  • Each row is a data point with time, value, and linked IDs.
  • " + "
  • observableId must exist in Observable; " + "condition IDs must exist in Condition.
  • " + "
  • Use 'inf' for steady-state times.
  • " + "
  • Override placeholders observableParameter{n}_{observableId} " + "and noise parameters noiseParameter{n}_{observableId} when defined.
  • " + "
" + ), + "columns": { + "observableId": ( + "
    " + "
  • Reference to an observable defined in the Observable table.
  • " + "
  • Must match an existing observableId.
  • " + "
" + ), + "preequilibrationConditionId": ( + "
    " + "
  • Condition used for pre-equilibration; empty = none.
  • " + "
  • Must be a valid condition ID if provided.
  • " + "
" + ), + "simulationConditionId": ( + "
    " + "
  • Condition used for simulation parameters (required).
  • " + "
  • Must be a valid condition ID.
  • " + "
" + ), + "time": ( + "
    " + "
  • Numeric time in SBML units, or 'inf' for steady state.
  • " + "
  • Use a consistent unit system across data and model.
  • " + "
" + ), + "measurement": ( + "
    " + "
  • Observed numeric value in the same scale/units as the model output.
  • " + "
  • Leave blank for missing values if supported by your workflow.
  • " + "
" + ), + "observableParameters": ( + "
    " + "
  • Overrides for placeholders defined in the observable formula.
  • " + "
  • Provide n semicolon-separated values/names for " + "observableParameter{n}_{observableId}; empty if none.
  • " + "
" + ), + "noiseParameters": ( + "
    " + "
  • Noise std-dev (or parameter names); NaN if σ is a model parameter.
  • " + "
  • Same rules as observableParameters for lists and naming.
  • " + "
" + ), + "datasetId": ( + "
    " + "
  • Grouping key for plotting (datasets share style/axes).
  • " + "
  • Optional; defaults to per-row if omitted.
  • " + "
" + ), + "replicateId": ( + "
    " + "
  • Label to distinguish replicates within a dataset.
  • " + "
  • Enables error bars/replicate plotting modes.
  • " + "
" + ), + }, + }, + "simulation": { + "table": ( + "Simulation table
" + "
    " + "
  • Holds simulated outputs aligned to measurement definitions.
  • " + "
  • Same IDs as Measurement (observable/conditions/time) for comparison.
  • " + "
  • Populated by simulator/export; typically read-only.
  • " + "
" + ), + "columns": { + "observableId": ( + "
    " + "
  • Observable whose simulation is reported.
  • " + "
  • Must match an observableId in Observable.
  • " + "
" + ), + "preequilibrationConditionId": ( + "
    " + "
  • Preequilibration condition used during simulation; empty = none.
  • " + "
  • Must be a valid condition ID if set.
  • " + "
" + ), + "simulationConditionId": ( + "
    " + "
  • Condition used to set simulation parameters (required).
  • " + "
  • Must be a valid condition ID.
  • " + "
" + ), + "time": ( + "
    " + "
  • Time point for the simulated value (numeric or 'inf').
  • " + "
  • Use same units as the model.
  • " + "
" + ), + "simulation": ( + "
    " + "
  • Simulated numeric value (same scale/units as measurement).
  • " + "
  • Used for plotting and residuals.
  • " + "
" + ), + "observableParameters": ( + "
    " + "
  • Parameters used to evaluate observable placeholders, if applicable.
  • " + "
  • Semicolon-separated values/names; mirrors Measurement rules.
  • " + "
" + ), + "noiseParameters": ( + "
    " + "
  • Noise parameters applied for simulation/plotting modes.
  • " + "
  • Numeric values or names; may be empty.
  • " + "
" + ), + "datasetId": ( + "
    " + "
  • Dataset grouping to match plotted series.
  • " + "
  • Optional; align with Measurement for overlays.
  • " + "
" + ), + "replicateId": ( + "
    " + "
  • Replicate label, if simulations are per-replicate.
  • " + "
  • Usually empty unless replicates are simulated explicitly.
  • " + "
" + ), + }, + }, + "observable": { + "table": ( + "Observable table
" + "
    " + "
  • Defines how model states/expressions map to measured outputs.
  • " + "
  • May introduce placeholders observableParameter{n}_{observableId} " + "that are overridden per-measurement.
  • " + "
  • Noise model can be numeric σ or a formula; distribution optional.
  • " + "
" + ), + "columns": { + "observableId": ( + "
    " + "
  • Unique identifier (letters/digits/underscores; not starting with a digit).
  • " + "
  • Referenced by measurement.observableId.
  • " + "
" + ), + "observableName": ( + "
    " + "
  • Optional display name for reports/plots.
  • " + "
  • Not used for identification.
  • " + "
" + ), + "observableFormula": ( + "
    " + "
  • Expression using SBML symbols/parameters (e.g., species ID).
  • " + "
  • May define observableParameter{n}_{observableId} placeholders.
  • " + "
" + ), + "observableTransformation": ( + "
    " + "
  • Transformation for objective: lin, log, or log10.
  • " + "
  • Defaults to lin; data and outputs assumed linear if not set.
  • " + "
" + ), + "noiseFormula": ( + "
    " + "
  • Numeric σ (implies normal) or formula for complex noise.
  • " + "
  • May include noiseParameter{n}_{observableId}; " + "values provided in Measurement.
  • " + "
" + ), + "noiseDistribution": ( + "
    " + "
  • normal (σ = std dev) or laplace (σ = scale).
  • " + "
  • Log-variants via observableTransformation = log/log10.
  • " + "
" + ), + }, + }, + "parameter": { + "table": ( + "Parameter table
" + "
    " + "
  • Declares parameters, estimation flag, and bounds (linear space).
  • " + "
  • parameterId must match SBML or overrides used elsewhere.
  • " + "
  • Optional priors for initialization and/or objective.
  • " + "
" + ), + "columns": { + "parameterId": ( + "
    " + "
  • Must match an SBML parameter, a condition override, or names used in measurements.
  • " + "
  • Unique within this table.
  • " + "
" + ), + "parameterName": ( + "
    " + "
  • Optional display name for plots/reports.
  • " + "
  • May differ from SBML name.
  • " + "
" + ), + "parameterScale": ( + "
    " + "
  • Estimation scale: lin, log, or log10.
  • " + "
  • Affects optimization scaling, not storage format.
  • " + "
" + ), + "lowerBound": ( + "
    " + "
  • Numeric lower bound in linear space.
  • " + "
  • Optional if estimate==0.
  • " + "
" + ), + "upperBound": ( + "
    " + "
  • Numeric upper bound in linear space.
  • " + "
  • Optional if estimate==0.
  • " + "
" + ), + "nominalValue": ( + "
    " + "
  • Value used when fixed (estimate==0), in linear space.
  • " + "
  • Optional otherwise.
  • " + "
" + ), + "estimate": ( + "
    " + "
  • 1 = estimated; 0 = fixed to nominal value.
  • " + "
  • Controls inclusion in the optimization vector.
  • " + "
" + ), + "initializationPriorType": ( + "
    " + "
  • Prior for initial point sampling (e.g., uniform, normal, " + "parameterScaleUniform).
  • " + "
  • Defaults to parameterScaleUniform.
  • " + "
" + ), + "initializationPriorParameters": ( + "
    " + "
  • Semicolon-separated numeric parameters; default lowerBound;upperBound.
  • " + "
  • Linear scale unless using parameter-scale priors.
  • " + "
" + ), + "objectivePriorType": ( + "
    " + "
  • Prior contributing to the objective; same options as initialization prior.
  • " + "
  • Optional; omit for unregularized fits.
  • " + "
" + ), + "objectivePriorParameters": ( + "
    " + "
  • Semicolon-separated numeric parameters; see initialization prior for formats.
  • " + "
  • Scale rules mirror the chosen prior type.
  • " + "
" + ), + }, + }, + "condition": { + "table": ( + "Condition table
" + "
    " + "
  • Defines simulation/experimental conditions referenced by other tables.
  • " + "
  • User-defined columns must be SBML IDs (parameter/species/compartment).
  • " + "
  • Species values act as initial conditions;
  • " + "
" + ), + "columns": { + "conditionId": ( + "
    " + "
  • Unique identifier (letters/digits/underscores; not starting with a digit).
  • " + "
  • Referenced by Measurement and Simulation.
  • " + "
" + ), + "conditionName": ( + "
    " + "
  • Optional human-readable name for reports/plots.
  • " + "
  • Does not affect model execution.
  • " + "
" + ), + "*": ( + "
    " + "
  • User-defined column. Must be an SBML ID: parameter, species, or compartment.
  • " + "
  • Numbers or IDs allowed; species = initial amount/concentration " + "(NaN keeps preeq/initial), compartments = initial size.
  • " + "
" + ), + }, + }, + "visualization": { + "table": ( + "Visualization table
" + "
    " + "
  • Groups datasets into plots and configures axes/scales.
  • " + "
  • plotId collects series into the same axes.
  • " + "
  • Choose simulation/data types; set labels and offsets.
  • " + "
" + ), + "columns": { + "plotId": ( + "
    " + "
  • Plot grouping key; identical IDs share the same axes.
  • " + "
  • Required for multi-series plots.
  • " + "
" + ), + "plotName": ( + "
    " + "
  • Optional display name for the plot.
  • " + "
  • Used in UIs/exports; not an ID.
  • " + "
" + ), + "plotTypeSimulation": ( + "
    " + "
  • LinePlot | BarPlot | ScatterPlot.
  • " + "
  • Default is LinePlot.
  • " + "
" + ), + "plotTypeData": ( + "
    " + "
  • MeanAndSD | MeanAndSEM | replicate | provided.
  • " + "
  • Default is MeanAndSD.
  • " + "
" + ), + "datasetId": ( + "
    " + "
  • Includes datasets (from Measurement) in this plot.
  • " + "
  • Optional; multiple IDs → multiple series.
  • " + "
" + ), + "xValues": ( + "
    " + "
  • Independent variable: time (default) or parameter/state ID.
  • " + "
  • Values appear as x-axis ticks.
  • " + "
" + ), + "xOffset": ( + "
    " + "
  • Numeric offset applied to x values (default 0).
  • " + "
  • Use to disambiguate overlapping series.
  • " + "
" + ), + "xLabel": ( + "
    " + "
  • Custom x-axis label; defaults to xValues.
  • " + "
  • Use units where helpful.
  • " + "
" + ), + "xScale": ( + "
    " + "
  • lin | log | log10 | order (LinePlot only).
  • " + "
  • Default is lin; order places points equidistantly.
  • " + "
" + ), + "yValues": ( + "
    " + "
  • Observable ID to plot on the y-axis.
  • " + "
  • Must match measurement.observableId for overlays.
  • " + "
" + ), + "yOffset": ( + "
    " + "
  • Numeric offset applied to y values (default 0).
  • " + "
  • Use for stacked/shifted visuals.
  • " + "
" + ), + "yLabel": ( + "
    " + "
  • Custom y-axis label; defaults to yValues.
  • " + "
  • Include units where applicable.
  • " + "
" + ), + "yScale": ( + "
    " + "
  • lin | log | log10.
  • " + "
  • Default is lin.
  • " + "
" + ), + "legendEntry": ( + "
    " + "
  • Legend text; defaults to datasetId.
  • " + "
  • Use concise, descriptive names.
  • " + "
" + ), + }, + }, }, - }, }