diff --git a/bin/config_tab.py b/bin/config_tab.py index 518744b..fa98690 100644 --- a/bin/config_tab.py +++ b/bin/config_tab.py @@ -54,6 +54,8 @@ def __init__(self, studio_flag): self.xml_root = None + self.sync_output = True + qlineedit_style = """ QLineEdit: disabled { background-color:#ff0000; @@ -318,6 +320,7 @@ def __init__(self, studio_flag): self.svg_interval = QLineEdit() self.svg_interval.setFixedWidth(100) self.svg_interval.setValidator(QtGui.QDoubleValidator()) + self.svg_interval.textChanged.connect(self.svg_interval_changed) icol += 1 # self.config_tab_layout.addWidget(self.svg_interval, idx_row,icol,1,1) # w, row, column, rowspan, colspan hbox.addWidget(self.svg_interval) @@ -345,6 +348,7 @@ def __init__(self, studio_flag): self.full_interval = QLineEdit() self.full_interval.setFixedWidth(100) self.full_interval.setValidator(QtGui.QDoubleValidator()) + self.full_interval.textChanged.connect(self.full_interval_changed) icol += 1 # self.config_tab_layout.addWidget(self.full_interval, idx_row,icol,1,1) # w, row, column, rowspan, colspan hbox.addWidget(self.full_interval) @@ -354,6 +358,17 @@ def __init__(self, studio_flag): # self.config_tab_layout.addWidget(label, idx_row,icol,1,1) # w, row, column, rowspan, colspan hbox.addWidget(label) + #------ + label = QLabel(" ") + hbox.addWidget(label) + + self.sync_svg_mat = QCheckBox_custom("Sync") + self.sync_svg_mat.setFixedWidth(cbox_width) + self.sync_svg_mat.setChecked(self.sync_output) + self.sync_svg_mat.clicked.connect(self.sync_clicked) + hbox.addWidget(self.sync_svg_mat) + + #------ hbox.addStretch() vbox.addLayout(hbox) @@ -484,6 +499,11 @@ def svg_clicked(self, bval): else: self.svg_interval.setStyleSheet("background-color: lightgray; color: black") + def svg_interval_changed(self, val): + # print("svg_interval_changed(): val=",val) + if self.sync_output: + self.full_interval.setText(val) + def full_clicked(self, bval): self.full_interval.setEnabled(bval) if bval: @@ -491,6 +511,20 @@ def full_clicked(self, bval): else: self.full_interval.setStyleSheet("background-color: lightgray; color: black") + def full_interval_changed(self, val): + # print("full_interval_changed(): val=",val) + if self.sync_output: + self.svg_interval.setText(val) + + def sync_clicked(self, bval): + self.sync_output = bval + + if bval: + if self.save_svg.isChecked(): + self.full_interval.setText(self.svg_interval.text()) + else: + self.svg_interval.setText(self.full_interval.text()) + def cells_csv_clicked(self, bval): self.csv_folder.setEnabled(bval) if self.nanohub_flag: diff --git a/bin/ics_tab.py b/bin/ics_tab.py index b8a357a..0b7b413 100644 --- a/bin/ics_tab.py +++ b/bin/ics_tab.py @@ -11,6 +11,7 @@ import os import logging import time +import csv # import xml.etree.ElementTree as ET # https://docs.python.org/2/library/xml.etree.elementtree.html from pathlib import Path @@ -79,6 +80,8 @@ def __init__(self, config_tab, celldef_tab): super().__init__() # global self.config_params + self.create_point = False + self.celldef_tab = celldef_tab self.config_tab = config_tab @@ -89,7 +92,7 @@ def __init__(self, config_tab, celldef_tab): self.color_by_celltype = ['gray','red','green','yellow','cyan','magenta','blue','brown','black','orange','seagreen','gold'] self.alpha_value = 1.0 - self.csv_array = np.empty([1,4]) # default floats + self.csv_array = np.empty([1,4]) # default floats (x,y,z,cell_type_index) self.csv_array = np.delete(self.csv_array,0,0) # print("--------------- csv_array= ",self.csv_array) @@ -118,10 +121,15 @@ def __init__(self, config_tab, celldef_tab): self.x0_value = 0. self.y0_value = 0. self.z0_value = 0. + self.r1_value = 50. self.r2_value = 100. self.r3_value = 0. + self.o1_value = 0. + self.o2_value = 360. + self.odel_value = 0. + # self.config_file = "mymodel.xml" # self.reset_model_flag = True # self.xmin = -80 @@ -199,6 +207,7 @@ def __init__(self, config_tab, celldef_tab): hbox.addWidget(label) self.celltype_combobox = QComboBox() + self.celltype_combobox.currentIndexChanged.connect(self.celltype_combobox_changed_cb) # self.celltype_combobox.setStyleSheet(self.combobox_stylesheet) self.celltype_combobox.setFixedWidth(200) # how wide is sufficient? hbox.addWidget(self.celltype_combobox) @@ -213,6 +222,8 @@ def __init__(self, config_tab, celldef_tab): self.geom_combobox.addItem("annulus/disk") # self.geom_combobox.addItem("annulus(2D)/shell(3D)") self.geom_combobox.addItem("box") + self.geom_combobox.addItem("ring") + self.geom_combobox.addItem("point") self.geom_combobox.setFixedWidth(180) self.geom_combobox.currentIndexChanged.connect(self.geom_combobox_changed_cb) hbox.addWidget(self.geom_combobox) @@ -371,9 +382,69 @@ def __init__(self, config_tab, celldef_tab): hbox.addStretch(1) # not sure about this, but keeps buttons shoved to left self.vbox.addLayout(hbox) + #---- + hbox = QHBoxLayout() + + cvalue_width = 70 + label = QLabel("o1") # omega 1: starting angle for a "ring" of cells + label.setFixedWidth(30) + # label.setFixedWidth(label_width) + label.setAlignment(QtCore.Qt.AlignRight) + hbox.addWidget(label) + + self.o1val = QLineEdit() + self.o1val.setFixedWidth(fixed_width_value) + self.o1val.setEnabled(True) + self.o1val.setText(str(self.o1_value)) + # self.cmin.textChanged.connect(self.change_plot_range) + # self.r1val.returnPressed.connect(self.rval_cb) + self.o1val.textChanged.connect(self.oval_cb) + # self.r1val.setFixedWidth(cvalue_width) + self.o1val.setValidator(QtGui.QDoubleValidator(-360.,360.,2)) + hbox.addWidget(self.o1val) + + #------ + label = QLabel("o2") + label.setFixedWidth(30) + label.setAlignment(QtCore.Qt.AlignRight) + hbox.addWidget(label) + + self.o2val = QLineEdit() + self.o2val.setFixedWidth(fixed_width_value) + self.o2val.setEnabled(True) + self.o2val.setText(str(self.o2_value)) + # self.r2val.returnPressed.connect(self.rval_cb) + self.o2val.textChanged.connect(self.oval_cb) + # self.r2val.setFixedWidth(cvalue_width) + self.o2val.setValidator(QtGui.QDoubleValidator(-360.,360.,2)) + hbox.addWidget(self.o2val) + + #------ + label = QLabel("mod") + label.setFixedWidth(30) + label.setAlignment(QtCore.Qt.AlignRight) + hbox.addWidget(label) + + self.odelw = QLineEdit() + self.odelw.setFixedWidth(fixed_width_value) + # self.r3val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + self.odelw.setText("1") + # self.r3val.returnPressed.connect(self.rval_cb) + self.odelw.textChanged.connect(self.odelw_cb) + # self.r3val.setFixedWidth(cvalue_width) + self.odelw.setValidator(QtGui.QIntValidator(1,99)) + hbox.addWidget(self.odelw) + + hbox.addStretch(1) # not sure about this, but keeps buttons shoved to left + self.vbox.addLayout(hbox) + + + #---- self.enable_3Dwidgets(False) + self.enable_ring_params(False) #---- + hbox = QHBoxLayout() btn_width = 80 self.clear_button = QPushButton("Clear all") @@ -400,6 +471,27 @@ def __init__(self, config_tab, celldef_tab): self.vbox.addLayout(hbox) + #--------------------- + # self.vbox.addWidget(QHLine()) + + # hbox = QHBoxLayout() + self.import_button = QPushButton("Import (cell type name syntax only)") + self.import_button.setFixedWidth(230) + self.import_button.setStyleSheet("QPushButton {background-color: lightgreen; color: black;}") + self.import_button.clicked.connect(self.import_cb) + self.vbox.addWidget(self.import_button) + + # self.plot_button2 = QPushButton("Plot") + # self.plot_button2.setFixedWidth(btn_width) + # self.plot_button2.setStyleSheet("QPushButton {background-color: lightgreen; color: black;}") + # self.plot_button2.clicked.connect(self.plot_import_cb) + # hbox.addWidget(self.plot_button2) + + # hbox.addStretch(1) # not sure about this, but keeps buttons shoved to left + + # self.vbox.addLayout(hbox) + + #--------------------- # Maybe later provide a cute diagram explaining D1,D2 # diagram = QLabel("pic") # pixmap = QPixmap('physicell_logo_200px.png') # in root by default @@ -412,7 +504,8 @@ def __init__(self, config_tab, celldef_tab): hbox = QHBoxLayout() self.save_button = QPushButton("Save") self.save_button.setFixedWidth(btn_width) - self.save_button.setStyleSheet("QPushButton {background-color: lightgreen; color: black;}") + # self.save_button.setStyleSheet("QPushButton {background-color: lightgreen; color: black;}") + self.save_button.setStyleSheet("QPushButton {background-color: yellow; color: black;}") # self.plot_button.clicked.connect(self.uniform_random_pts_annulus_cb) self.save_button.clicked.connect(self.save_cb) hbox.addWidget(self.save_button) @@ -550,6 +643,36 @@ def rval_cb(self): self.r2_value = float(self.r2val.text()) self.r3_value = float(self.r3val.text()) # print(self.r1_value, self.r2_value) + + # delta_theta = np.arcsin(self.cell_radius / self.r2_value) + # print("rval_cb(): delta_theta= ",delta_theta) + # self.odelw.setText(f'{delta_theta:.6f}') + except: + pass + # self.update_plots() + + def odelw_cb(self): + print("odelw_cb(): ",self.odelw.text()) + try: + if int(self.odelw.text()) < 1: + self.odelw.setText("1") + except: + # self.odelw.setText("1") + pass + + def oval_cb(self): + # print("----- oval_cb:") + rval = self.cell_radius + R = self.r2_value + # delta_theta = np.arcsin(rval / R) + # print("oval_cb(): delta_theta= ",delta_theta) + # self.odelw.setText(f'{delta_theta:.6f}') + + try: # due to the initial callback + self.o1_value = float(self.o1val.text()) + self.o2_value = float(self.o2val.text()) + # self.odel_value = float(self.odelw.text()) + # print(self.r1_value, self.r2_value) except: pass # self.update_plots() @@ -602,13 +725,45 @@ def fill_cell_types_combobox(self, substrate_list): # print('field_min_max= ',self.field_min_max) # self.substrates_combobox.setCurrentIndex(2) # not working; gets reset to oxygen somehow after a Run - # def celltype_combobox_changed_cb(self,idx): - # print("----- celltype_combobox_changed_cb: idx = ",idx) + def celltype_combobox_changed_cb(self,idx): + try: + # print("----- celltype_combobox_changed_cb: idx = ",idx) + cdef = self.celltype_combobox.currentText() + volume = float(self.celldef_tab.param_d[cdef]["volume_total"]) + self.cell_radius = (volume * 0.75 / np.pi) ** (1./3) + # print("self.cell_radius= ",self.cell_radius) + except: + pass # self.update_plots() + def enable_ring_params(self, bval): + self.o1val.setEnabled(bval) + self.o2val.setEnabled(bval) + self.odelw.setEnabled(bval) + + # for now, until 3D even possible + # self.odelw.setEnabled(False) + # self.odelw.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + + if bval: + self.o1val.setStyleSheet("QLineEdit {background-color: white; color: black;}") + self.o2val.setStyleSheet("QLineEdit {background-color: white; color: black;}") + self.odelw.setStyleSheet("QLineEdit {background-color: white; color: black;}") + # rval = self.cell_radius + # R = self.r2_value + # delta_theta = np.arcsin(rval / R) + # print("delta_theta= ",delta_theta) + # self.odelw.setText(f'{delta_theta:.6f}') + else: + self.o1val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + self.o2val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + self.odelw.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + def enable_3Dwidgets(self, bval): print("----- enable_3Dwidgets: bval = ",bval) self.z0val.setEnabled(bval) + # self.odelw.setEnabled(bval) + if self.geom_combobox.currentText() == "box": self.r3val.setEnabled(False) else: @@ -624,6 +779,7 @@ def enable_3Dwidgets(self, bval): print("----- enable_3Dwidgets: bval = ",bval) self.z0val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") self.r3val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + self.odelw.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") def set_to_origin(self): self.x0_value = self.y0_value = self.z0_value = 0. @@ -642,13 +798,45 @@ def zeq0_cb(self,bval): def geom_combobox_changed_cb(self,idx): # print("----- geom_combobox_changed_cb: idx = ",idx) - if not self.zeq0.isChecked(): - if self.geom_combobox.currentText() == "box": - self.r3val.setEnabled(True) - self.r3val.setStyleSheet("QLineEdit {background-color: white; color: black;}") - else: - self.r3val.setEnabled(False) - self.r3val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + # print("----- geom_combobox_changed_cb: geom_combobox.currentText() = ",self.geom_combobox.currentText()) + sel_str = self.geom_combobox.currentText() + # if not self.zeq0.isChecked(): + # self.create_point = False + # if sel_str == "point": + # self.create_point = True + # print("------- setting 3D point") + # return + # elif sel_str == "box": + # self.r3val.setEnabled(True) + # self.r3val.setStyleSheet("QLineEdit {background-color: white; color: black;}") + # self.enable_ring_params(False) + # elif sel_str == "ring": + # self.enable_ring_params(True) + # # self.r3val.setEnabled(True) + # # self.r3val.setStyleSheet("QLineEdit {background-color: white; color: black;}") + # else: + # self.r3val.setEnabled(False) + # self.r3val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + # self.enable_ring_params(False) + + self.create_point = False + if self.geom_combobox.currentText().find("point") >= 0: + self.create_point = True + # print("------- setting point") + return + if self.geom_combobox.currentText().find("ring") >= 0: + self.enable_ring_params(True) + # print("got ring!") + self.num_cells.setEnabled(False) + self.fill_combobox.setEnabled(False) + self.r1val.setEnabled(False) + self.r1val.setStyleSheet("QLineEdit {background-color: rgb(200,200,200); color: black;}") + return + else: + self.enable_ring_params(False) + self.fill_combobox.setEnabled(True) + self.r1val.setEnabled(True) + self.r1val.setStyleSheet("QLineEdit {background-color: white; color: black;}") if "hex" in self.fill_combobox.currentText(): self.num_cells.setEnabled(False) @@ -701,10 +889,67 @@ def reset_info(self): self.canvas.draw() + def button_press(self,event): + if self.create_point: + # print("------ button_press()") + xval = event.xdata # or "None" if outside plot domain + yval = event.ydata + zval = 0.0 + # print("event.inaxes", event.inaxes) # Axes(0.125,0.146794;0.775x0.696412) + # print("x", event.x) # e.g.,750 + # print("y", event.y) # e.g., 562 + # print("size=",self.canvas.size()) + + if self.create_point and (xval is not None): + xlist = deque() + ylist = deque() + rlist = deque() + rgba_list = deque() + # cdef = self.celltype_combobox.currentText() + # volume = float(self.celldef_tab.param_d[cdef]["volume_total"]) + # self.cell_radius = (volume * 0.75 / np.pi) ** (1./3) + rval = self.cell_radius + + # print("event.xdata (xval)=", xval) + # print("event.ydata (yval)=", yval) + cell_type_index = self.celltype_combobox.currentIndex() + xlist.append(xval) + ylist.append(yval) + # self.csv_array = np.append(self.csv_array,[[xval_offset,yval,zval, cell_type_index]],axis=0) + self.csv_array = np.append(self.csv_array,[[xval,yval,zval, cell_type_index]],axis=0) + rlist.append(rval) + self.cell_radii.append(self.cell_radius) + + self.numcells_l.append(1) + + xvals = np.array(xlist) + yvals = np.array(ylist) + rvals = np.array(rlist) + # rgbas = np.array(rgba_list) + + if (self.cells_edge_checked_flag): + try: + self.circles(xvals,yvals, s=rvals, color=self.color_by_celltype[cell_type_index], edgecolor='black', linewidth=0.5, alpha=self.alpha_value) + except (ValueError): + pass + else: + self.circles(xvals,yvals, s=rvals, color=self.color_by_celltype[cell_type_index], alpha=self.alpha_value) + + self.ax0.set_aspect(1.0) + + self.ax0.set_xlim(self.plot_xmin, self.plot_xmax) + self.ax0.set_ylim(self.plot_ymin, self.plot_ymax) + + # self.update_plots() + self.canvas.update() + self.canvas.draw() + + def create_figure(self): # print("\nics_tab: --------- create_figure(): ------- creating figure, canvas, ax0") self.figure = plt.figure() self.canvas = FigureCanvasQTAgg(self.figure) + self.canvas.mpl_connect("button_press_event", self.button_press) self.canvas.setStyleSheet("background-color:transparent;") self.ax0 = self.figure.add_subplot(111, adjustable='box') @@ -825,11 +1070,28 @@ def annulus_error(self): def plot_cb(self): self.reset_plot_range() # self.cell_radii = [] + # print("len(self.numcells_l)= ",len(self.numcells_l)) + # print("plot_cb(): self.numcells_l= ",self.numcells_l) + + # if self.create_point: + # print("------- plot list of single points") + # return + + ncells = int(self.num_cells.text()) + if ncells <= 0: + msg = "Invalid # of cells." + print(msg) + msgBox = QMessageBox() + msgBox.setIcon(QMessageBox.Information) + msgBox.setText(msg) + msgBox.setStandardButtons(QMessageBox.Ok) + returnValue = msgBox.exec() + return - cdef = self.celltype_combobox.currentText() - volume = float(self.celldef_tab.param_d[cdef]["volume_total"]) - self.cell_radius = (volume * 0.75 / np.pi) ** (1./3) - logging.debug(f'ics_tab.py: volume= {volume}, radius= {self.cell_radius}') + # cdef = self.celltype_combobox.currentText() + # volume = float(self.celldef_tab.param_d[cdef]["volume_total"]) + # self.cell_radius = (volume * 0.75 / np.pi) ** (1./3) + # logging.debug(f'ics_tab.py: volume= {volume}, radius= {self.cell_radius}') if "annulus" in self.geom_combobox.currentText(): if self.r2_value <= self.r1_value: @@ -840,17 +1102,31 @@ def plot_cb(self): elif "hex" in self.fill_combobox.currentText(): self.hex_pts_annulus() - else: # box + elif "box" in self.geom_combobox.currentText(): if "random" in self.fill_combobox.currentText(): self.uniform_random_pts_box() elif "hex" in self.fill_combobox.currentText(): self.hex_pts_box() + elif "ring" in self.geom_combobox.currentText(): + self.ring() + + #------------------------------------------------------------ + # def plot_import_cb(self): + # self.reset_plot_range() + # # self.cell_radii = [] + # print("len(self.numcells_l)= ",len(self.numcells_l)) + # print("self.numcells_l= ",self.numcells_l) + #------------------------------------------------------------ def undo_cb(self): # print("----- undo_cb(): self.numcells_l = ",self.numcells_l) # nlast = self.numcells_l[-1] - nlast = self.numcells_l.pop() + try: + nlast = self.numcells_l.pop() + except: + print("Error self.numcells_l.pop()") + return ntotal = len(self.csv_array) self.reset_plot_range() @@ -935,15 +1211,19 @@ def hex_pts_box(self): for xval in np.arange(x_min,x_max, x_spacing): # xval_offset = xval + (y_idx%2) * self.cell_radius xval_offset = self.x0_value + xval + (y_idx%2) * self.cell_radius - - xlist.append(xval_offset) yval_offset = yval + self.y0_value - ylist.append(yval_offset) - # self.csv_array = np.append(self.csv_array,[[xval,yval,zval, cell_type_index]],axis=0) - self.csv_array = np.append(self.csv_array,[[xval_offset,yval_offset, zval, cell_type_index]],axis=0) - rlist.append(rval) - self.cell_radii.append(self.cell_radius) - count+=1 + + if xval_offset < self.plot_xmin or xval_offset > self.plot_xmax \ + or yval_offset < self.plot_ymin or yval_offset > self.plot_ymax: + continue + else: + xlist.append(xval_offset) + ylist.append(yval_offset) + # self.csv_array = np.append(self.csv_array,[[xval,yval,zval, cell_type_index]],axis=0) + self.csv_array = np.append(self.csv_array,[[xval_offset,yval_offset, zval, cell_type_index]],axis=0) + rlist.append(rval) + self.cell_radii.append(self.cell_radius) + count+=1 else: # 3D # for z in np.arange(z_min, z_max, z_spacing): @@ -1072,14 +1352,19 @@ def hex_pts_annulus(self): # # plt.plot(xval_offset,yval,'ro',markersize=30) xval_offset += self.x0_value - xlist.append(xval_offset) yval_offset = yval + self.y0_value - ylist.append(yval_offset) - # self.csv_array = np.append(self.csv_array,[[xval_offset,yval,zval, cell_type_index]],axis=0) - self.csv_array = np.append(self.csv_array,[[xval_offset,yval_offset,zval, cell_type_index]],axis=0) - rlist.append(rval) - self.cell_radii.append(self.cell_radius) - count+=1 + + if xval_offset < self.plot_xmin or xval_offset > self.plot_xmax \ + or yval_offset < self.plot_ymin or yval_offset > self.plot_ymax: + continue + else: + xlist.append(xval_offset) + ylist.append(yval_offset) + # self.csv_array = np.append(self.csv_array,[[xval_offset,yval,zval, cell_type_index]],axis=0) + self.csv_array = np.append(self.csv_array,[[xval_offset,yval_offset,zval, cell_type_index]],axis=0) + rlist.append(rval) + self.cell_radii.append(self.cell_radius) + count+=1 self.numcells_l.append(count) @@ -1357,6 +1642,93 @@ def uniform_random_pts_annulus(self): self.canvas.update() self.canvas.draw() + #---------------------------------- + def ring(self): + xlist = deque() + ylist = deque() + rlist = deque() + rgba_list = deque() + + + colors = np.empty((0,4)) + count = 0 + zval = 0.0 + cell_type_index = self.celltype_combobox.currentIndex() + # ncells = int(self.num_cells.text()) + # print("self.r1_value= ", self.r1_value) + + rval = self.cell_radius + R = self.r2_value + delta_theta = np.arcsin(rval / R) + + try: + rmod = int(self.odelw.text()) + except: + msg = "Invalid ring modulo value" + print(msg) + msgBox = QMessageBox() + msgBox.setIcon(QMessageBox.Information) + msgBox.setText(msg) + msgBox.setStandardButtons(QMessageBox.Ok) + returnValue = msgBox.exec() + return + print("ring(): rmod= ",rmod) + + cells_x = np.array([]) + cells_y = np.array([]) + + xctr = 0.0 + yctr = 0.0 + + y_idx = 0 + start_radians = self.o1_value * np.pi/180. + end_radians = self.o2_value * np.pi/180. + # delta_radians = self.o2_value * np.pi/180. + + # for theta in np.arange(0, max_radians, np.pi/10.): + # for theta in np.arange(0, max_radians, delta_theta * np.pi/180): + # for theta in np.arange(start_radians, end_radians, delta_theta): + for theta in np.arange(start_radians, end_radians, rmod*2*delta_theta): + # print("theta= ",theta) + xval = self.x0_value + R * np.cos(theta) + yval = self.y0_value + R * np.sin(theta) + + if xval < self.plot_xmin or xval > self.plot_xmax \ + or yval < self.plot_ymin or yval > self.plot_ymax: + continue + else: + xlist.append(xval) + ylist.append(yval) + # self.csv_array = np.append(self.csv_array,[[xval_offset,yval,zval, cell_type_index]],axis=0) + self.csv_array = np.append(self.csv_array,[[xval,yval,zval, cell_type_index]],axis=0) + rlist.append(rval) + self.cell_radii.append(self.cell_radius) + count+=1 + + self.numcells_l.append(count) + + xvals = np.array(xlist) + yvals = np.array(ylist) + rvals = np.array(rlist) + # rgbas = np.array(rgba_list) + + if (self.cells_edge_checked_flag): + try: + self.circles(xvals,yvals, s=rvals, color=self.color_by_celltype[cell_type_index], edgecolor='black', linewidth=0.5, alpha=self.alpha_value) + except (ValueError): + pass + else: + self.circles(xvals,yvals, s=rvals, color=self.color_by_celltype[cell_type_index], alpha=self.alpha_value) + + self.ax0.set_aspect(1.0) + + self.ax0.set_xlim(self.plot_xmin, self.plot_xmax) + self.ax0.set_ylim(self.plot_ymin, self.plot_ymax) + + # self.update_plots() + self.canvas.update() + self.canvas.draw() + #------------------------------------------------ def clear_cb(self): # self.ax0.clear() @@ -1431,4 +1803,112 @@ def save_cb(self): else: print("----- Writing v1 (with cell indices) .csv file for cells") print("----- full_fname=",full_fname) - np.savetxt(full_fname, self.csv_array, delimiter=',') \ No newline at end of file + np.savetxt(full_fname, self.csv_array, delimiter=',') + + #-------------------------------------------------- + def import_cb(self): + # filePath = QFileDialog.getOpenFileName(self,'',".",'*.xml') + filePath = QFileDialog.getOpenFileName(self,'',".") + full_path_rules_name = filePath[0] + # logging.debug(f'\nimport_cb(): full_path_rules_name ={full_path_rules_name}') + print(f'\nimport_cb(): full_path_rules_name ={full_path_rules_name}') + basename = os.path.basename(full_path_rules_name) + print(f'import_cb(): basename ={basename}') + dirname = os.path.dirname(full_path_rules_name) + print(f'import_cb(): dirname ={dirname}') + # if (len(full_path_rules_name) > 0) and Path(full_path_rules_name): + if (len(full_path_rules_name) > 0) and Path(full_path_rules_name).is_file(): + print("import_cb(): filePath is valid") + # logging.debug(f' filePath is valid') + print("len(full_path_rules_name) = ", len(full_path_rules_name) ) + + cell_types_l = [self.celltype_combobox.itemText(i) for i in range(self.celltype_combobox.count())] + print(cell_types_l) + + xlist = deque() + ylist = deque() + rlist = deque() + rgba_list = deque() + + rval = self.cell_radius + + colors = np.empty((0,4)) + count = 0 + # zval = 0.0 + # cell_type_index = self.celltype_combobox.currentIndex() + # cell_type_name = self.celltype_combobox.currentText() + + # x,y,z,type,volume,cycle entry,custom:GFP,custom:sample + # -49.52373671227464,-85.42875790157267,0.0,acell + zval = 0.0 # only doing 2D now + with open(full_path_rules_name, newline='') as csvfile: + states_reader = csv.reader(csvfile, delimiter=',') + count = 0 + for row in states_reader: + # print(count,row) + if count == 0: + # print("skipping header row") + # print("row[0]=",row[0]) + if row[0] != 'x': + msgBox = QMessageBox() + msgBox.setIcon(QMessageBox.Information) + msgBox.setText("Invalid first row. Expecting 'x' in 1st column.") + msgBox.setStandardButtons(QMessageBox.Ok) + returnValue = msgBox.exec() + return + # count+=1 + else: + xval = float(row[0]) + yval = float(row[1]) + # zval = float(row[2]) + cell_type_name = row[3] + try: + cell_type_index = cell_types_l.index(cell_type_name) + except: + msgBox = QMessageBox() + msgBox.setIcon(QMessageBox.Information) + msgBox.setText(f"Invalid cell type name: {cell_type_name}") + msgBox.setStandardButtons(QMessageBox.Ok) + returnValue = msgBox.exec() + return + + # print('xval,yval=',xval,yval) + # volume = float(self.celldef_tab.param_d[cdef]["volume_total"]) + # volume = float(self.celldef_tab.param_d[cell_type_name]["volume_total"]) + # rval = (volume * 0.75 / np.pi) ** (1./3) + rval = self.cell_radius + + self.csv_array = np.append(self.csv_array,[[xval,yval, zval, cell_type_index]],axis=0) + # rlist.append(rval) + self.cell_radii.append(rval) + # count+=1 + + if (self.cells_edge_checked_flag): + try: + # print(f"calling self.circles with {xval}, {yval}") + self.circles(xval,yval, s=rval, color=self.color_by_celltype[cell_type_index], edgecolor='black', linewidth=0.5, alpha=self.alpha_value) + except (ValueError): + print("Exception: self.circles") + pass + else: + self.circles(xval,yval, s=rval, color=self.color_by_celltype[cell_type_index], alpha=self.alpha_value) + + self.numcells_l.append(1) + + count+=1 + # self.numcells_l.append(count) + + self.ax0.set_aspect(1.0) + + self.ax0.set_xlim(self.plot_xmin, self.plot_xmax) + self.ax0.set_ylim(self.plot_ymin, self.plot_ymax) + + self.canvas.update() + self.canvas.draw() + + else: + print("import_cb(): full_path_model_name is NOT valid") + + def fill_gui(self): + self.csv_folder.setText(self.config_tab.csv_folder.text()) + self.output_file.setText(self.config_tab.csv_file.text()) \ No newline at end of file diff --git a/bin/microenv_tab.py b/bin/microenv_tab.py index 2084837..10db307 100644 --- a/bin/microenv_tab.py +++ b/bin/microenv_tab.py @@ -63,6 +63,8 @@ def __init__(self, config_tab): self.default_rate_units = "1/min" self.dirichlet_units = "mmHG" + self.rules_tab = None # update in studio.py + # self.stacked_w = QStackedWidget() # self.stack_w = [] # self.stack_w.append(QStackedWidget()) @@ -750,6 +752,11 @@ def delete_substrate(self): # rwh: BEWARE of mutating the dict? del self.param_d[self.current_substrate] + # do *after* removing from param_d keys. + if self.rules_tab: + # self.rules_tab.delete_substrate(item_idx) + self.rules_tab.delete_substrate(self.current_substrate) + # may need to make a copy instead?? # new_dict = {key:val for key, val in self.param_d.items() if key != 'Mani'} # self.param_d = new_dict diff --git a/bin/rules_tab.py b/bin/rules_tab.py index 006140c..ad15fdd 100644 --- a/bin/rules_tab.py +++ b/bin/rules_tab.py @@ -886,6 +886,14 @@ def substrate_rename(self,idx,old_name,new_name): self.fill_signals_widget() self.fill_responses_widget() + #----------------------------------------------------------- + def delete_substrate(self,name): + # print(f"-------- rules_tab.py: delete_substrate(), self.substrates = {self.substrates}") + # print(f" name = {name}") + self.substrates.remove(name) + self.fill_signals_widget() + self.fill_responses_widget() + #----------------------------------------------------------- def add_new_substrate(self,name): # print("rules_tab: add_new_substrate(): name= ",name) @@ -946,7 +954,7 @@ def celltype_combobox_changed_cb(self, idx): # self.response_l += ["apoptosis","necrosis","migration speed","migration bias","migration persistence time"] def update_base_value(self): - print("\n-------update_base_value(self)") + # print("\n-------update_base_value(self)") behavior = self.response_combobox.currentText() # key0 = self.celltype_name key0 = self.celltype_combobox.currentText() @@ -956,8 +964,32 @@ def update_base_value(self): return base_val = '??' - if btokens[0] in self.substrates: - print(f"{btokens[0]} is a substrate") + if btokens[0] in ["cycle", "exit"]: + cycle_model_idx = self.celldef_tab.param_d[key0]['cycle_choice_idx'] + # print(behavior, cycle_model_idx ) + #{0:"live", 1:"basic Ki67", 2:"advanced Ki67", 3:"flow cytometry", 4:"Flow cytometry model (separated)", 5:"cycling quiescent"} + if (behavior == 'cycle entry' or behavior == 'exit from cycle phase 0'): + if cycle_model_idx == 0 : base_val = self.celldef_tab.param_d[key0]['cycle_live_trate00'] + elif cycle_model_idx == 1 : base_val = self.celldef_tab.param_d[key0]['cycle_Ki67_trate01'] + elif cycle_model_idx == 2 : base_val = self.celldef_tab.param_d[key0]['cycle_advancedKi67_trate01'] + elif cycle_model_idx == 3 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcyto_trate01'] + elif cycle_model_idx == 4 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcytosep_trate01'] + elif cycle_model_idx == 5 : base_val = self.celldef_tab.param_d[key0]['cycle_quiescent_trate01'] + elif (behavior == 'exit from cycle phase 1'): + if cycle_model_idx == 1 : base_val = self.celldef_tab.param_d[key0]['cycle_Ki67_trate10'] + elif cycle_model_idx == 2 : base_val = self.celldef_tab.param_d[key0]['cycle_advancedKi67_trate12'] + elif cycle_model_idx == 3 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcyto_trate12'] + elif cycle_model_idx == 4 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcytosep_trate12'] + elif cycle_model_idx == 5 : base_val = self.celldef_tab.param_d[key0]['cycle_quiescent_trate10'] + elif (behavior == 'exit from cycle phase 2'): + if cycle_model_idx == 2 : base_val = self.celldef_tab.param_d[key0]['cycle_advancedKi67_trate20'] + elif cycle_model_idx == 3 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcyto_trate20'] + elif cycle_model_idx == 4 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcytosep_trate23'] + elif (behavior == 'exit from cycle phase 3'): + if cycle_model_idx == 4 : base_val = self.celldef_tab.param_d[key0]['cycle_flowcytosep_trate30'] + + elif btokens[0] in self.substrates: + print(f"update_base_value(): {btokens[0]} is a substrate") # key1 = btokens[0] key1 = 'secretion' key2 = btokens[0] @@ -974,13 +1006,11 @@ def update_base_value(self): # print("\n---key1= ",self.celldef_tab.param_d[key0][key1]) # print("\n---key2= ",self.celldef_tab.param_d[key0][key1][key2]) base_val = self.celldef_tab.param_d[key0][key1][key2][key3] - print("------- base_val= ",base_val) + print("update_base_value(): ------- base_val= ",base_val) except: - print("---- got exception") + print("update_base_value(): ---- got exception") return elif btokens[0] == 'apoptosis': - print("---- update_base_value(): key0=",key0) - print("\n self.celldef_tab.param_d[key0]",self.celldef_tab.param_d[key0]) base_val = self.celldef_tab.param_d[key0]['apoptosis_death_rate'] elif btokens[0] == 'necrosis': base_val = self.celldef_tab.param_d[key0]['necrosis_death_rate'] @@ -1035,19 +1065,27 @@ def update_base_value(self): elif behavior[0:len("transform to")] == "transform to": cell_type = behavior[len("transform to")+1:] base_val = self.celldef_tab.param_d[key0]['transformation_rate'][cell_type] - + elif behavior == "damage rate": + base_val = self.celldef_tab.param_d[key0]["damage_rate"] + elif "custom:" in btokens[0]: + custom_data_name = btokens[0].split(':')[-1] # return string after colon + print(custom_data_name, self.celldef_tab.param_d[key0]['custom_data'][custom_data_name]) + base_val = self.celldef_tab.param_d[key0]['custom_data'][custom_data_name][0] #--------------------- # Set the base value self.rule_base_val.setText(base_val) - # Compute/set the saturation value + # Compute/set the saturation value if base_val == '??': - saturation_val = 1.0 + if "decreases" in self.up_down_combobox.currentText(): saturation_val = 0.0 + else: saturation_val = 1.0 else: - saturation_val = self.scale_base_for_max * float(base_val) - if abs(saturation_val) < 1.e-9: - saturation_val = 1.0 + if "decreases" in self.up_down_combobox.currentText(): saturation_val = self.scale_base_for_min * float(base_val) + else: saturation_val = self.scale_base_for_max * float(base_val) + # behaviors with max response + if ( behavior == 'migration bias' and saturation_val > 1 ): saturation_val = 1.0 + if ( behavior == 'is_movable' and saturation_val > 1 ): saturation_val = 1.0 self.rule_max_val.setText(str(saturation_val)) # print(self.celldef_tab.param_d.keys()) diff --git a/bin/studio.py b/bin/studio.py index 5e9ae31..ec06e11 100644 --- a/bin/studio.py +++ b/bin/studio.py @@ -412,6 +412,7 @@ def __init__(self, config_file, studio_flag, skip_validate_flag, rules_flag, mod # self.rules_tab.fill_gui() self.tabWidget.addTab(self.rules_tab,"Rules") self.rules_tab.xml_root = self.xml_root + self.microenv_tab.rules_tab = self.rules_tab self.celldef_tab.rules_tab = self.rules_tab if self.nanohub_flag: self.rules_tab.absolute_data_dir = self.absolute_data_dir @@ -576,6 +577,7 @@ def tab_change_cb(self,index: int): self.microenv_tab.update_3D() elif self.rules_tab_index and (index == self.rules_tab_index): + self.rules_tab.update_base_value() if self.rules_tab.update_rules_for_custom_data: print("studio.py: need to update Rules comboboxes for changed custom data") self.rules_tab.fill_signals_widget()