Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EFM normalization button (#503) #504

Merged
merged 4 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cnapy/gui_elements/central_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ def __init__(self, parent):
self.mode_navigator.modeNavigatorClosed.connect(self.update)
self.mode_navigator.reaction_participation_button.clicked.connect(self.reaction_participation)

self.mode_normalization_reaction = ""

self.update()

def fit_mapview(self):
Expand Down Expand Up @@ -386,6 +388,12 @@ def update_mode(self):
mean = sum(abs(v) for v in values.values())/len(values)
for r,v in values.items():
values[r] = v/mean
if self.mode_normalization_reaction != "":
if self.mode_normalization_reaction in values.keys():
normalization_value = values[self.mode_normalization_reaction]
if normalization_value != 0.0:
for r,v in values.items():
values[r] = v/normalization_value

# set values
self.appdata.project.comp_values.clear()
Expand Down
80 changes: 78 additions & 2 deletions cnapy/gui_elements/mode_navigator.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import numpy
from random import randint
from copy import deepcopy
import matplotlib.pyplot as plt

from qtpy.QtCore import Qt, Signal, Slot, QStringListModel
from qtpy.QtGui import QIcon, QBrush, QColor
from qtpy.QtWidgets import (QFileDialog, QHBoxLayout, QLabel, QPushButton,
from qtpy.QtWidgets import (QDialog, QFileDialog, QHBoxLayout, QLabel, QPushButton,
QVBoxLayout, QWidget, QCompleter, QLineEdit, QMessageBox, QToolButton)


from cnapy.appdata import AppData
from cnapy.flux_vector_container import FluxVectorContainer

from cnapy.utils import QComplReceivLineEdit

class ModeNavigator(QWidget):
"""A navigator widget"""
Expand Down Expand Up @@ -39,6 +42,8 @@ def __init__(self, appdata, central_widget):
self.apply_button.setToolTip("Add interventions to current scenario")
self.reaction_participation_button = QPushButton("Reaction participation")
self.size_histogram_button = QPushButton("Size histogram")
self.normalization_button = QPushButton("Normalize to...")
self.normalization_button.setVisible(False)

l1 = QHBoxLayout()
self.title = QLabel("Mode Navigation")
Expand Down Expand Up @@ -67,6 +72,7 @@ def __init__(self, appdata, central_widget):
l2.addWidget(self.apply_button)
l2.addWidget(self.reaction_participation_button)
l2.addWidget(self.size_histogram_button)
l2.addWidget(self.normalization_button)

self.layout.addLayout(l1)
self.layout.addLayout(l2)
Expand All @@ -79,6 +85,7 @@ def __init__(self, appdata, central_widget):
self.selector.returnPressed.connect(self.apply_selection)
self.selector.findChild(QToolButton).triggered.connect(self.reset_selection) # findChild(QToolButton) retrieves the clear button
self.size_histogram_button.clicked.connect(self.size_histogram)
self.normalization_button.clicked.connect(self.normalization)
self.central_widget.broadcastReactionID.connect(self.selector.receive_input)

def update(self):
Expand Down Expand Up @@ -130,6 +137,7 @@ def update_completion_list(self):
self.completion_list.setStringList(reac_id+["!"+str(r) for r in reac_id])

def set_to_mcs(self):
self.central_widget.mode_normalization_reaction = ""
self.mode_type = 1
self.title.setText("MCS Navigation")
if self.save_button_connection is not None:
Expand All @@ -138,6 +146,7 @@ def set_to_mcs(self):
self.save_button.setToolTip("save minimal cut sets")
self.clear_button.setToolTip("clear minimal cut sets")
self.apply_button.setVisible(True)
self.normalization_button.setVisible(False)
self.select_all()
self.update_completion_list()

Expand All @@ -150,6 +159,7 @@ def set_to_efm(self):
self.save_button.setToolTip("save modes")
self.clear_button.setToolTip("clear modes")
self.apply_button.setVisible(False)
self.normalization_button.setVisible(True)
self.select_all()
self.update_completion_list()

Expand All @@ -166,6 +176,7 @@ def set_to_strain_design(self):
self.update_completion_list()

def clear(self):
self.central_widget.mode_normalization_reaction = ""
self.mode_type = 0 # EFM or some sort of flux vector
self.appdata.project.modes.clear()
self.appdata.recreate_scenario_from_history()
Expand Down Expand Up @@ -305,7 +316,12 @@ def size_histogram(self):
plt.hist(sizes, bins="auto")
plt.show()

def normalization(self):
dialog = NormalizationDialog(self.appdata, self)
dialog.exec_()

def __del__(self):
self.central_widget.mode_normalization_reaction = ""
self.appdata.project.modes.clear() # for proper deallocation when it is a FluxVectorMemmap

changedCurrentMode = Signal(int)
Expand Down Expand Up @@ -347,3 +363,63 @@ def pathFromIndex(self, index): # overrides Qcompleter method
def splitPath(self, path): # overrides Qcompleter method
path = str(path.split(',')[-1]).lstrip(' ')
return [path]


class NormalizationDialog(QDialog):
"""A dialog to select a reaction for normalization."""

def __init__(self, appdata: AppData, parent):
QDialog.__init__(self)
self.setWindowTitle("Flux optimization")

self.appdata = appdata
self.parent = parent

self.reac_ids = list(self.appdata.project.comp_values.keys()) #self.appdata.project.cobra_py_model.reactions.list_attr("id")
numr = len(self.reac_ids) # len(self.appdata.project.cobra_py_model.reactions)
if numr > 1:
r1 = self.reac_ids[randint(0, numr-1)]
else:
r1 = 'r_product'

self.layout = QVBoxLayout()
label = QLabel("Select reaction to which the Flux Mode shall be normalized:")
self.layout.addWidget(label)

flux_expr_layout = QVBoxLayout()
self.expr = QComplReceivLineEdit(self, self.reac_ids, check=True)
self.expr.setPlaceholderText(f"Reaction ID, e.g. {r1}")
flux_expr_layout.addWidget(self.expr)
self.layout.addItem(flux_expr_layout)

l3 = QHBoxLayout()
self.button = QPushButton("Normalize")
self.cancel = QPushButton("Close")
l3.addWidget(self.button)
l3.addWidget(self.cancel)
self.layout.addItem(l3)
self.setLayout(self.layout)

# Connecting the signal
self.expr.textCorrect.connect(self.validate_dialog)
self.cancel.clicked.connect(self.reject)
self.button.clicked.connect(self.normalize)

self.validate_dialog()

@Slot()
def validate_dialog(self):
if self.expr.is_valid:
self.button.setEnabled(True)
else:
self.button.setEnabled(False)
if self.expr.text().strip() not in self.reac_ids:
self.button.setEnabled(False)

@Slot()
def normalize(self):
self.setCursor(Qt.BusyCursor)
self.parent.central_widget.mode_normalization_reaction = self.expr.text().strip()
self.parent.central_widget.update_mode()
self.setCursor(Qt.ArrowCursor)
self.accept()
Loading