Skip to content

Commit d180efa

Browse files
Renaming observable and condition hint (#52)
* only prompt the "do you want to rename" in case the to be changed observable also exists in the measurement table * Same renaming/adding schema for condition ids
1 parent 2d63e21 commit d180efa

3 files changed

Lines changed: 91 additions & 19 deletions

File tree

src/petab_gui/controllers/mother_controller.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from functools import partial
2+
13
from PySide6.QtWidgets import QMessageBox, QFileDialog, QLineEdit, QWidget, \
24
QHBoxLayout, QToolButton, QTableView
35
from PySide6.QtGui import QAction
@@ -115,11 +117,25 @@ def setup_connections(self):
115117
"""
116118
# Rename Observable
117119
self.observable_controller.observable_2be_renamed.connect(
118-
self.measurement_controller.rename_observable
120+
partial(
121+
self.measurement_controller.rename_value,
122+
column_names = "observableId"
123+
)
124+
)
125+
# Rename Condition
126+
self.condition_controller.condition_2be_renamed.connect(
127+
partial(
128+
self.measurement_controller.rename_value,
129+
column_names = ["simulationConditionId",
130+
"preequilibrationConditionId"]
131+
)
119132
)
120-
# Add new observable
121-
self.model.measurement.observable_id_changed.connect(
122-
self.observable_controller.maybe_add_observable
133+
# Add new condition or observable
134+
self.model.measurement.relevant_id_changed.connect(
135+
lambda x, y, z: self.observable_controller.maybe_add_observable(
136+
x, y) if z == "observable" else
137+
self.condition_controller.maybe_add_condition(
138+
x, y) if z == "condition" else None
123139
)
124140
# Maybe Move to a Plot Model
125141
self.view.measurement_dock.table_view.selectionModel().selectionChanged.connect(

src/petab_gui/controllers/table_controllers.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def check_petab_lint(self, row_data):
270270
observable_df=observable_df,
271271
)
272272

273-
def rename_observable(self, old_id: str, new_id: str):
273+
def rename_value(self, old_id: str, new_id: str, column_names: str | list[str]):
274274
"""Rename the observables in the measurement_df.
275275
276276
Triggered by changes in the original observable_df id.
@@ -282,10 +282,13 @@ def rename_observable(self, old_id: str, new_id: str):
282282
new_id:
283283
The new observable_id.
284284
"""
285+
if not isinstance(column_names, list):
286+
column_names = [column_names]
285287
rows = self.model.get_df().shape[0]
286288
for row in range(rows):
287-
if self.model._data_frame.at[row, "observableId"] == old_id:
288-
self.model._data_frame.at[row, "observableId"] = new_id
289+
for column_name in column_names:
290+
if self.model._data_frame.at[row, column_name] == old_id:
291+
self.model._data_frame.at[row, column_name] = new_id
289292
self.model.something_changed.emit(True)
290293
self.model.layoutChanged.emit()
291294

@@ -480,6 +483,16 @@ def setup_completers(self):
480483

481484
class ConditionController(TableController):
482485
"""Controller of the Condition table."""
486+
condition_2be_renamed = Signal(str, str) # Signal to mother controller
487+
488+
def setup_connections_specific(self):
489+
"""Setup connections specific to the condition controller.
490+
491+
Only handles connections from within the table controllers.
492+
"""
493+
self.model.relevant_id_changed.connect(
494+
self.maybe_rename_condition
495+
)
483496

484497
def check_petab_lint(self, row_data):
485498
"""Check a single row of the model with petablint."""
@@ -491,16 +504,43 @@ def check_petab_lint(self, row_data):
491504
model=sbml_model,
492505
)
493506

494-
def maybe_add_condition(self, condition_id):
507+
def maybe_rename_condition(self, new_id, old_id):
508+
"""Potentially rename condition_ids in measurement_df.
509+
510+
Opens a dialog to ask the user if they want to rename the conditions.
511+
If so, emits a signal to rename the conditions in the measurement_df.
512+
"""
513+
if old_id not in self.mother_controller.measurement_controller.model.get_df()["simulationConditionId"].values:
514+
return
515+
reply = QMessageBox.question(
516+
self.view, 'Rename Condition',
517+
f'Do you want to rename condition "{old_id}" to "{new_id}" '
518+
f'in all measurements?',
519+
QMessageBox.Yes | QMessageBox.No,
520+
QMessageBox.No
521+
)
522+
if reply == QMessageBox.Yes:
523+
self.logger.log_message(
524+
f"Renaming condition '{old_id}' to '{new_id}' in all "
525+
f"measurements",
526+
color="green"
527+
)
528+
self.condition_2be_renamed.emit(old_id, new_id)
529+
530+
531+
def maybe_add_condition(self, condition_id, old_id=None):
495532
"""Add a condition to the condition table if it does not exist yet."""
496-
if condition_id in self.model.get_df()["conditionId"].values:
533+
if condition_id in self.model.get_df().index:
497534
return
498535
# add a row
499536
self.model.insertRows(position=None, rows=1)
500537
self.model.fill_row(
501538
self.model.get_df().shape[0] - 1,
502539
data={"conditionId": condition_id}
503540
)
541+
self.model.cell_needs_validation.emit(
542+
self.model.get_df().shape[0] - 1, 0
543+
)
504544
self.logger.log_message(
505545
f"Automatically added condition '{condition_id}' to the condition "
506546
f"table.",
@@ -589,7 +629,7 @@ def setup_connections_specific(self):
589629
590630
Only handles connections from within the table controllers.
591631
"""
592-
self.model.observable_id_changed.connect(
632+
self.model.relevant_id_changed.connect(
593633
self.maybe_rename_observable
594634
)
595635

@@ -603,6 +643,8 @@ def maybe_rename_observable(self, new_id, old_id):
603643
Opens a dialog to ask the user if they want to rename the observables.
604644
If so, emits a signal to rename the observables in the measurement_df.
605645
"""
646+
if old_id not in self.mother_controller.measurement_controller.model.get_df()["observableId"].values:
647+
return
606648
reply = QMessageBox.question(
607649
self.view, 'Rename Observable',
608650
f'Do you want to rename observable "{old_id}" to "{new_id}" '

src/petab_gui/models/pandas_table_model.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pandas as pd
12
from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex, Signal, QSortFilterProxyModel
23
from PySide6.QtGui import QColor
34

@@ -8,7 +9,7 @@
89
class PandasTableModel(QAbstractTableModel):
910
"""Basic table model for a pandas DataFrame."""
1011
# Signals
11-
observable_id_changed = Signal(str, str) # new_id, old_id
12+
relevant_id_changed = Signal(str, str, str) # new_id, old_id, type
1213
new_log_message = Signal(str, str) # message, color
1314
cell_needs_validation = Signal(int, int) # row, column
1415
something_changed = Signal(bool)
@@ -148,11 +149,17 @@ def setData(self, index, value, role=Qt.EditRole):
148149
if column_name == "observableId":
149150
self._data_frame.iloc[row, column - col_setoff] = value
150151
self.dataChanged.emit(index, index, [Qt.DisplayRole])
151-
self.observable_id_changed.emit(value, old_value)
152+
self.relevant_id_changed.emit(value, old_value, "observable")
153+
self.cell_needs_validation.emit(row, column)
154+
self.something_changed.emit(True)
155+
return True
156+
if column_name in ["conditionId", "simulationConditionId", "preequilibrationConditionId"]:
157+
self._data_frame.iloc[row, column - col_setoff] = value
158+
self.dataChanged.emit(index, index, [Qt.DisplayRole])
159+
self.relevant_id_changed.emit(value, old_value, "condition")
152160
self.cell_needs_validation.emit(row, column)
153161
self.something_changed.emit(True)
154162
return True
155-
# Maybe TODO: same for conditionId?
156163

157164
# Validate data based on expected type
158165
expected_type = self._allowed_columns.get(column_name)["type"]
@@ -165,7 +172,7 @@ def setData(self, index, value, role=Qt.EditRole):
165172
self.new_log_message.emit(
166173
f"Column '{column_name}' expects a value of "
167174
f"type {expected_type}, but got '{tried_value}'",
168-
color="red"
175+
"red"
169176
)
170177
return False
171178
# Set the new value
@@ -256,6 +263,7 @@ def clear_table(self):
256263

257264
class IndexedPandasTableModel(PandasTableModel):
258265
"""Table model for tables with named index."""
266+
condition_2be_renamed = Signal(str, str) # Signal to mother controller
259267
def __init__(self, data_frame, allowed_columns, table_type, parent=None):
260268
super().__init__(
261269
data_frame=data_frame,
@@ -274,20 +282,20 @@ def handle_named_index(self, index, value):
274282
if value in self._data_frame.index:
275283
self.new_log_message.emit(
276284
f"Duplicate index value '{value}'",
277-
color="red"
285+
"red"
278286
)
279287
return False
280288
try:
281289
self._data_frame.rename(index={old_value: value}, inplace=True)
282290
self.dataChanged.emit(index, index, [Qt.DisplayRole])
283-
self.observable_id_changed.emit(value, old_value)
291+
self.relevant_id_changed.emit(value, old_value, self.table_type)
284292
self.cell_needs_validation.emit(row, 0)
285293
self.something_changed.emit(True)
286294
return True
287295
except Exception as e:
288296
self.new_log_message.emit(
289297
f"Error renaming index value '{old_value}' to '{value}': {e}",
290-
color="red"
298+
"red"
291299
)
292300
return False
293301

@@ -397,10 +405,11 @@ def fill_row(self, row_position: int, data: dict):
397405
data_to_add.update(data)
398406
# Maybe add default values for missing columns?
399407
new_index = self._data_frame.index.tolist()
408+
index_name = self._data_frame.index.name
400409
new_index[row_position] = data_to_add.pop(
401410
"observableId"
402411
)
403-
self._data_frame.index = new_index
412+
self._data_frame.index = pd.Index(new_index, name=index_name)
404413
self._data_frame.iloc[row_position] = data_to_add
405414

406415

@@ -440,7 +449,12 @@ def fill_row(self, row_position: int, data: dict):
440449
column_name: "" for column_name in self._data_frame.columns
441450
}
442451
data_to_add.update(data)
443-
self._data_frame.index[row_position] = data_to_add.pop("conditionId")
452+
new_index = self._data_frame.index.tolist()
453+
index_name = self._data_frame.index.name
454+
new_index[row_position] = data_to_add.pop(
455+
"conditionId"
456+
)
457+
self._data_frame.index = pd.Index(new_index, name=index_name)
444458
self._data_frame.iloc[row_position] = data_to_add
445459

446460

0 commit comments

Comments
 (0)