mirror of
https://github.com/Athemis/PyDSF.git
synced 2025-04-04 22:36:02 +00:00
First implementation of parameter dependency analysis
in additions, averaging over rows was added
This commit is contained in:
parent
455f715e37
commit
b8c67a13ed
6 changed files with 4357 additions and 136 deletions
|
@ -11,6 +11,8 @@ class AnalytikJenaqTower2:
|
|||
self.name = "Analytik Jena qTower 2.0/2.2"
|
||||
self.providesTempRange = False
|
||||
self.providesDeltaT = False
|
||||
self.wells_horizontal = 12
|
||||
self.wells_vertical = 8
|
||||
|
||||
def loadData(self, filename, reads, wells):
|
||||
with open(filename, 'r') as f:
|
||||
|
|
331
pydsf.py
331
pydsf.py
|
@ -47,7 +47,12 @@ class Well:
|
|||
Owned by an object of type 'Plate'.
|
||||
"""
|
||||
|
||||
def __init__(self, owner, name=None):
|
||||
def __init__(self,
|
||||
owner,
|
||||
name=None,
|
||||
concentration=None,
|
||||
well_id=None,
|
||||
empty=False):
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
self.raw = np.zeros(self.owner.reads, dtype=np.float)
|
||||
|
@ -58,6 +63,10 @@ class Well:
|
|||
self.tm_sd = np.NaN
|
||||
self.baseline_correction = owner.baseline_correction
|
||||
self.baseline = None
|
||||
self.concentration = concentration
|
||||
self.id = well_id
|
||||
self.empty = empty
|
||||
self.denatured = True
|
||||
|
||||
def filter_raw(self):
|
||||
"""
|
||||
|
@ -110,7 +119,7 @@ class Well:
|
|||
Checks if a given value is within the defined temperature cutoff.
|
||||
"""
|
||||
if (value >= self.owner.tm_cutoff_low and
|
||||
value <= self.owner.tm_cutoff_high):
|
||||
value <= self.owner.tm_cutoff_high):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
@ -123,11 +132,13 @@ class Well:
|
|||
try:
|
||||
# If tm is within cutoff, perform the interpolation
|
||||
if (self.temp_within_cutoff(tm)):
|
||||
tm = round(peakutils.interpolate(x, y,
|
||||
tm = round(peakutils.interpolate(x,
|
||||
y,
|
||||
width=3,
|
||||
ind=[max_i])[0], 2)
|
||||
ind=[max_i])[0],
|
||||
2)
|
||||
# Remove the denatured flag
|
||||
self.owner.denatured_wells.remove(self)
|
||||
self.denatured = False
|
||||
return tm # and return the Tm
|
||||
else:
|
||||
return np.NaN # otherwise, return NaN
|
||||
|
@ -139,11 +150,15 @@ class Well:
|
|||
Calculate the Tm of the well. Returns either the Tm or 'np.NaN'.
|
||||
"""
|
||||
# Check if the well has already been flagged as denatured
|
||||
if self in self.owner.denatured_wells:
|
||||
# if self.denatured:
|
||||
# return np.NaN # Return 'NaN' if true
|
||||
|
||||
# Check if the well is empty
|
||||
if self.empty:
|
||||
return np.NaN # Return 'NaN' if true
|
||||
|
||||
# First assume that the well is denatured
|
||||
self.owner.denatured_wells.append(self)
|
||||
self.denatured = True
|
||||
|
||||
# Use the whole temperature range for x. We'll check the cutoff later
|
||||
x = self.owner.temprange
|
||||
|
@ -174,6 +189,7 @@ class Well:
|
|||
# melting temperature
|
||||
if max_y and max_i:
|
||||
tm = x[max_i]
|
||||
self.denatured = False
|
||||
return self.interpolate_tm(x, y, max_i, tm)
|
||||
# if no maximum is found, return NaN
|
||||
else:
|
||||
|
@ -188,21 +204,18 @@ class Well:
|
|||
already flagged as denatured, no Tm was found, or if the initial
|
||||
signal intensity is above a user definded threshold.
|
||||
"""
|
||||
denatured = True # Assumption is that the well is denatured
|
||||
|
||||
if self in self.owner.denatured_wells:
|
||||
# check if the well is already flagged as denatured
|
||||
return denatured # return true if it is
|
||||
if self.denatured:
|
||||
return self.denatured
|
||||
|
||||
if self.tm and (self.tm <= self.owner.tm_cutoff_low or
|
||||
self.tm >= self.owner.tm_cutoff_high):
|
||||
denatured = True
|
||||
return denatured
|
||||
self.tm >= self.owner.tm_cutoff_high):
|
||||
self.denatured = True
|
||||
return self.denatured
|
||||
|
||||
for i in self.derivatives[1]:
|
||||
# Iterate over all points in the first derivative
|
||||
if i > 0: # If a positive slope is found
|
||||
denatured = False # set denatured flag to False
|
||||
self.denatured = False # set denatured flag to False
|
||||
|
||||
reads = int(round(self.owner.reads / 10))
|
||||
# How many values should be checked against the signal threshold:
|
||||
|
@ -210,47 +223,48 @@ class Well:
|
|||
read = 0
|
||||
# Initialize running variable representing the current data point
|
||||
|
||||
if not denatured:
|
||||
if not self.denatured:
|
||||
for j in self.filtered: # Iterate over the filtered data
|
||||
if self.owner.signal_threshold:
|
||||
# If a signal threshold was defined
|
||||
if j > self.owner.signal_threshold and read <= reads:
|
||||
# iterate over 1/10 of all data points
|
||||
# and check for values larger than the threshold.
|
||||
denatured = True
|
||||
self.denatured = True
|
||||
# Set flag to True if a match is found
|
||||
print("{}: {}".format(self.name, j))
|
||||
return denatured # and return
|
||||
return self.denatured # and return
|
||||
read += 1
|
||||
|
||||
return denatured
|
||||
return self.denatured
|
||||
|
||||
def analyze(self):
|
||||
"""
|
||||
Analyse data of the well. Takes care of the calculation of derivatives,
|
||||
fitting of splines to derivatives and calculation of melting point.
|
||||
"""
|
||||
# apply signal filter to raw data to filter out some noise
|
||||
self.filter_raw()
|
||||
# fit a spline to unfiltered and filtered raw data
|
||||
self.splines["raw"] = self.calc_spline(self.raw)
|
||||
self.splines["filtered"] = self.calc_spline(self.filtered)
|
||||
# calculate derivatives of filtered data
|
||||
self.calc_derivatives()
|
||||
# if baseline correction is requested, calculate baseline
|
||||
if self.baseline_correction:
|
||||
self.baseline = self.calc_baseline(self.derivatives[1])
|
||||
# do an initial check if data suggest denaturation
|
||||
if self.is_denatured():
|
||||
# if appropriate, append well to denatured wells of the plate
|
||||
self.owner.denatured_wells.append(self)
|
||||
# fit a spline to the first derivative
|
||||
self.splines["derivative1"] = self.calc_spline(self.derivatives[1])
|
||||
# calculate and set melting point
|
||||
self.tm = self.calc_tm()
|
||||
# fallback: set melting point to NaN
|
||||
if self.tm is None:
|
||||
self.tm = np.NaN
|
||||
if not self.empty:
|
||||
# apply signal filter to raw data to filter out some noise
|
||||
self.filter_raw()
|
||||
# fit a spline to unfiltered and filtered raw data
|
||||
self.splines["raw"] = self.calc_spline(self.raw)
|
||||
self.splines["filtered"] = self.calc_spline(self.filtered)
|
||||
# calculate derivatives of filtered data
|
||||
self.calc_derivatives()
|
||||
# if baseline correction is requested, calculate baseline
|
||||
if self.baseline_correction:
|
||||
self.baseline = self.calc_baseline(self.derivatives[1])
|
||||
# do an initial check if data suggest denaturation
|
||||
# if self.is_denatured():
|
||||
# if appropriate, append well to denatured wells of the plate
|
||||
# self.owner.denatured_wells.append(self)
|
||||
# fit a spline to the first derivative
|
||||
self.splines["derivative1"] = self.calc_spline(self.derivatives[1])
|
||||
# calculate and set melting point
|
||||
self.tm = self.calc_tm()
|
||||
# fallback: set melting point to NaN
|
||||
if self.tm is None:
|
||||
self.tm = np.NaN
|
||||
|
||||
|
||||
class Experiment:
|
||||
|
@ -268,7 +282,9 @@ class Experiment:
|
|||
cutoff_high=None,
|
||||
signal_threshold=None,
|
||||
color_range=None,
|
||||
baseline_correction=False):
|
||||
baseline_correction=False,
|
||||
concentrations=None,
|
||||
average_rows=None):
|
||||
self.replicates = replicates
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
|
@ -287,6 +303,8 @@ class Experiment:
|
|||
self.signal_threshold = signal_threshold
|
||||
self.avg_plate = None
|
||||
self.baseline_correction = baseline_correction
|
||||
self.concentrations = concentrations
|
||||
self.average_rows = average_rows
|
||||
# use cuttoff if provided, otherwise cut at edges
|
||||
if cutoff_low:
|
||||
self.tm_cutoff_low = cutoff_low
|
||||
|
@ -322,9 +340,10 @@ class Experiment:
|
|||
plate.id = i
|
||||
self.plates.append(plate)
|
||||
i += 1
|
||||
# if more than one file is provied, assume that those are replicates
|
||||
# and add a special plate representing the average results
|
||||
if len(files) > 1:
|
||||
# if more than one file is provided or average over rows is requested,
|
||||
# assume that those are replicates and add a special plate
|
||||
# representing the average results
|
||||
if len(files) > 1 or self.average_rows:
|
||||
self.avg_plate = Plate(owner=self,
|
||||
filename=None,
|
||||
t1=self.t1,
|
||||
|
@ -338,6 +357,70 @@ class Experiment:
|
|||
color_range=self.color_range)
|
||||
self.avg_plate.id = 'average'
|
||||
|
||||
def average_by_plates(self):
|
||||
# iterate over all wells in a plate
|
||||
for i in range(self.wellnum):
|
||||
tmp = []
|
||||
# iterate over all plates
|
||||
for plate in self.plates:
|
||||
tm = plate.wells[i].tm
|
||||
self.avg_plate.wells[i].name = plate.wells[i].name
|
||||
if not plate.wells[i].denatured:
|
||||
# if well is not denatured, add to collection of tm
|
||||
# values
|
||||
tmp.append(tm)
|
||||
if len(tmp) > 0:
|
||||
# if at least one tm is added, calculate average
|
||||
# and standard deviation
|
||||
self.avg_plate.wells[i].tm = np.nanmean(tmp)
|
||||
self.avg_plate.wells[i].tm_sd = np.nanstd(tmp)
|
||||
self.avg_plate.wells[
|
||||
i].concentration = plate.wells[i].concentration
|
||||
else:
|
||||
# otherwise add to denatured wells
|
||||
self.avg_plate.wells[i].denatured = True
|
||||
|
||||
def average_by_rows(self):
|
||||
for plate in self.plates:
|
||||
|
||||
for well in self.avg_plate.wells:
|
||||
well.empty = True
|
||||
|
||||
i = 0
|
||||
for column in range(plate.cols):
|
||||
equivalent_wells = []
|
||||
for row in range(plate.rows):
|
||||
w = row * plate.cols + column
|
||||
mod = (row + 1) % self.average_rows
|
||||
|
||||
print("Merging well {} with Tm of {}".format(
|
||||
plate.wells[w].id, plate.wells[w].tm))
|
||||
equivalent_wells.append(plate.wells[w].tm)
|
||||
|
||||
if mod == 0:
|
||||
print(equivalent_wells)
|
||||
mean = np.nanmean(equivalent_wells)
|
||||
std = np.nanstd(equivalent_wells)
|
||||
equivalent_wells = []
|
||||
self.avg_plate.wells[i].empty = False
|
||||
self.avg_plate.wells[i].tm = mean
|
||||
self.avg_plate.wells[i].tm_sd = std
|
||||
self.avg_plate.wells[i].name = plate.wells[i].name
|
||||
self.avg_plate.wells[
|
||||
i].concentration = plate.wells[i].concentration
|
||||
if np.isnan(mean):
|
||||
self.avg_plate.wells[i].denatured = True
|
||||
print("Well {} is denatured!".format(i))
|
||||
else:
|
||||
self.avg_plate.wells[i].denatured = False
|
||||
print(
|
||||
"Adding new well with ID {}, TM {}, SD {}".format(
|
||||
i, mean, std))
|
||||
|
||||
if len(equivalent_wells) == 0:
|
||||
# i = w
|
||||
i += 1
|
||||
|
||||
def analyze(self):
|
||||
"""
|
||||
Triggers analyzation of plates.
|
||||
|
@ -347,31 +430,15 @@ class Experiment:
|
|||
# if more than one plate is present, calculate average values for the
|
||||
# merged average plate
|
||||
if len(self.plates) > 1:
|
||||
# iterate over all wells in a plate
|
||||
for i in range(self.wellnum):
|
||||
tmp = []
|
||||
# iterate over all plates
|
||||
for plate in self.plates:
|
||||
tm = plate.wells[i].tm
|
||||
self.avg_plate.wells[i].name = plate.wells[i].name
|
||||
if plate.wells[i] not in plate.denatured_wells:
|
||||
# if well is not denatured, add to collection of tm
|
||||
# values
|
||||
tmp.append(tm)
|
||||
if len(tmp) > 0:
|
||||
# if at least one tm is added, calculate average
|
||||
# and standard deviation
|
||||
self.avg_plate.wells[i].tm = np.mean(tmp)
|
||||
self.avg_plate.wells[i].tm_sd = np.std(tmp)
|
||||
else:
|
||||
# otherwise add to denatured wells
|
||||
append_well = self.avg_plate.wells[i]
|
||||
self.avg_plate.denatured_wells.append(append_well)
|
||||
self.average_by_plates()
|
||||
elif self.average_rows:
|
||||
self.average_by_rows()
|
||||
|
||||
|
||||
class Plate:
|
||||
|
||||
def __init__(self, owner,
|
||||
def __init__(self,
|
||||
owner,
|
||||
plate_id=None,
|
||||
filename=None,
|
||||
replicates=None,
|
||||
|
@ -383,7 +450,8 @@ class Plate:
|
|||
cutoff_low=None,
|
||||
cutoff_high=None,
|
||||
signal_threshold=None,
|
||||
color_range=None):
|
||||
color_range=None,
|
||||
concentrations=None):
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
self.owner = owner
|
||||
|
@ -411,6 +479,10 @@ class Plate:
|
|||
self.signal_threshold = signal_threshold
|
||||
self.id = plate_id
|
||||
self.baseline_correction = owner.baseline_correction
|
||||
if concentrations is None:
|
||||
self.concentrations = self.owner.concentrations
|
||||
else:
|
||||
self.concentrations = concentrations
|
||||
if cutoff_low:
|
||||
self.tm_cutoff_low = cutoff_low
|
||||
else:
|
||||
|
@ -424,34 +496,21 @@ class Plate:
|
|||
else:
|
||||
self.color_range = None
|
||||
|
||||
self.denatured_wells = []
|
||||
self.tms = []
|
||||
|
||||
# TODO: Adapt for vertical concentrations
|
||||
conc_id = 0
|
||||
for i in range(self.wellnum):
|
||||
well = Well(owner=self)
|
||||
if self.concentrations:
|
||||
if conc_id == self.cols:
|
||||
conc_id = 0
|
||||
concentration = self.concentrations[conc_id]
|
||||
conc_id += 1
|
||||
else:
|
||||
concentration = None
|
||||
well = Well(owner=self, well_id=i, concentration=concentration)
|
||||
self.wells.append(well)
|
||||
|
||||
def analytikJena(self):
|
||||
"""
|
||||
Data processing for Analytik Jena qTower 2.0 export files
|
||||
"""
|
||||
with open(self.filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=';', quoting=csv.QUOTE_NONE)
|
||||
|
||||
i = 0
|
||||
for row in reader:
|
||||
temp = np.zeros(self.reads, dtype=float)
|
||||
for read in range(self.reads + 1):
|
||||
if read > 0:
|
||||
try:
|
||||
temp[read - 1] = row[read]
|
||||
except (IndexError, ValueError):
|
||||
temp[read - 1] = 0.0
|
||||
elif read == 0:
|
||||
self.wells[i].name = row[read]
|
||||
self.wells[i].raw = temp
|
||||
i += 1
|
||||
|
||||
def analyze(self):
|
||||
try:
|
||||
# Try to access data file in the given path
|
||||
|
@ -461,8 +520,7 @@ class Plate:
|
|||
# If the file is not found, or not accessible: abort
|
||||
print('Error accessing file: {}'.format(e))
|
||||
|
||||
self.wells = self.instrument.loadData(self.filename,
|
||||
self.reads,
|
||||
self.wells = self.instrument.loadData(self.filename, self.reads,
|
||||
self.wells)
|
||||
|
||||
for well in self.wells:
|
||||
|
@ -540,10 +598,12 @@ class PlotResults():
|
|||
c_values = [] # Array holding the color values aka Tm
|
||||
dx_values = []
|
||||
dy_values = []
|
||||
ex_values = []
|
||||
ey_values = []
|
||||
canvas = widget.canvas
|
||||
canvas.clear()
|
||||
for well in plate.wells: # Iterate over all wells
|
||||
if well not in plate.denatured_wells:
|
||||
if not well.denatured and not well.empty:
|
||||
# Check if well is denatured (no Tm found)
|
||||
c = well.tm # If not, set color to Tm
|
||||
if c < plate.tm_cutoff_low:
|
||||
|
@ -554,6 +614,9 @@ class PlotResults():
|
|||
# Check if Tm is higher that the cutoff
|
||||
c = plate.tm_cutoff_high
|
||||
# If it is, set color to cutoff
|
||||
elif well.empty:
|
||||
ex_values.append(x)
|
||||
ey_values.append(y)
|
||||
else: # If the plate is denatured
|
||||
c = plate.tm_cutoff_low
|
||||
# Set its color to the low cutoff
|
||||
|
@ -576,7 +639,8 @@ class PlotResults():
|
|||
# n rows
|
||||
if plate.color_range:
|
||||
# plot wells and color using the colormap
|
||||
cax = ax1.scatter(x_values, y_values,
|
||||
cax = ax1.scatter(x_values,
|
||||
y_values,
|
||||
s=305,
|
||||
c=c_values,
|
||||
marker='s',
|
||||
|
@ -584,20 +648,27 @@ class PlotResults():
|
|||
vmax=plate.color_range[1])
|
||||
else:
|
||||
# plot wells and color using the colormap
|
||||
cax = ax1.scatter(x_values, y_values,
|
||||
cax = ax1.scatter(x_values,
|
||||
y_values,
|
||||
s=305,
|
||||
c=c_values,
|
||||
marker='s')
|
||||
|
||||
ax1.scatter(dx_values, dy_values,
|
||||
ax1.scatter(dx_values, dy_values, s=305, c='white', marker='s')
|
||||
|
||||
ax1.scatter(dx_values,
|
||||
dy_values,
|
||||
s=80,
|
||||
c='white',
|
||||
c='red',
|
||||
marker='x',
|
||||
linewidths=(1.5, ))
|
||||
|
||||
ax1.scatter(ex_values, ey_values, s=305, c='white', marker='s')
|
||||
|
||||
ax1.invert_yaxis() # invert y axis to math plate layout
|
||||
cbar = fig1.colorbar(cax) # show colorbar
|
||||
ax1.set_xlabel(_translate('pydsf',
|
||||
'Columns')) # set axis and colorbar label
|
||||
# set axis and colorbar label
|
||||
ax1.set_xlabel(_translate('pydsf', 'Columns'))
|
||||
ax1.set_ylabel(_translate('pydsf', 'Rows'))
|
||||
|
||||
if str(plate.id) == 'average':
|
||||
|
@ -621,7 +692,8 @@ class PlotResults():
|
|||
fig.suptitle(title + str(plate.id) + ')')
|
||||
|
||||
grid = mpl_toolkits.axes_grid1.Grid(
|
||||
fig, 111,
|
||||
fig,
|
||||
111,
|
||||
nrows_ncols=(plate.rows, plate.cols),
|
||||
axes_pad=(0.15, 0.25),
|
||||
add_all=True,
|
||||
|
@ -641,22 +713,24 @@ class PlotResults():
|
|||
ax = grid[i]
|
||||
# set title of current subplot to well identifier
|
||||
ax.set_title(well.name, size=6)
|
||||
if well in plate.denatured_wells:
|
||||
if well.denatured:
|
||||
ax.patch.set_facecolor('#FFD6D6')
|
||||
# only show three tickmarks on both axes
|
||||
ax.xaxis.set_major_locator(ticker.MaxNLocator(4))
|
||||
ax.yaxis.set_major_locator(ticker.MaxNLocator(4))
|
||||
# check if well is denatured (without determined Tm)
|
||||
if well not in plate.denatured_wells:
|
||||
if not well.denatured:
|
||||
tm = well.tm # if not, grab its Tm
|
||||
else:
|
||||
tm = np.NaN # else set Tm to np.NaN
|
||||
if tm:
|
||||
ax.axvline(x=tm) # plot vertical line at the Tm
|
||||
ax.axvspan(plate.t1, plate.tm_cutoff_low,
|
||||
ax.axvspan(plate.t1,
|
||||
plate.tm_cutoff_low,
|
||||
facecolor='0.8',
|
||||
alpha=0.5) # shade lower cutoff area
|
||||
ax.axvspan(plate.tm_cutoff_high, plate.t2,
|
||||
ax.axvspan(plate.tm_cutoff_high,
|
||||
plate.t2,
|
||||
facecolor='0.8',
|
||||
alpha=0.5) # shade higher cutoff area
|
||||
# set fontsize for all tick labels to xx-small
|
||||
|
@ -681,7 +755,8 @@ class PlotResults():
|
|||
fig.suptitle(title + str(plate.id) + ')')
|
||||
|
||||
grid = mpl_toolkits.axes_grid1.Grid(
|
||||
fig, 111,
|
||||
fig,
|
||||
111,
|
||||
nrows_ncols=(plate.rows, plate.cols),
|
||||
axes_pad=(0.15, 0.25),
|
||||
add_all=True,
|
||||
|
@ -695,15 +770,17 @@ class PlotResults():
|
|||
ax = grid[i]
|
||||
# set title of current subplot to well identifier
|
||||
ax.set_title(well.name, size=6)
|
||||
if well in plate.denatured_wells:
|
||||
if well.denatured:
|
||||
ax.patch.set_facecolor('#FFD6D6')
|
||||
# only show three tickmarks on both axes
|
||||
ax.xaxis.set_major_locator(ticker.MaxNLocator(4))
|
||||
ax.yaxis.set_major_locator(ticker.MaxNLocator(4))
|
||||
ax.axvspan(plate.t1, plate.tm_cutoff_low,
|
||||
ax.axvspan(plate.t1,
|
||||
plate.tm_cutoff_low,
|
||||
facecolor='0.8',
|
||||
alpha=0.5) # shade lower cutoff area
|
||||
ax.axvspan(plate.tm_cutoff_high, plate.t2,
|
||||
ax.axvspan(plate.tm_cutoff_high,
|
||||
plate.t2,
|
||||
facecolor='0.8',
|
||||
alpha=0.5) # shade higher cutoff area
|
||||
# set fontsize for all tick labels to xx-small
|
||||
|
@ -712,3 +789,41 @@ class PlotResults():
|
|||
ax.plot(x, y)
|
||||
fig.tight_layout()
|
||||
canvas.draw()
|
||||
|
||||
def plot_concentration_dependency(self,
|
||||
plate,
|
||||
widget,
|
||||
direction='horizontal',
|
||||
parameter_label='Parameter [au]',
|
||||
error_bars=False):
|
||||
canvas = widget.canvas
|
||||
canvas.clear()
|
||||
|
||||
fig = canvas.fig
|
||||
title = _translate('pydsf', "Melting point vs Parameter (plate #")
|
||||
fig.suptitle(title + str(plate.id) + ')')
|
||||
|
||||
ax1 = fig.add_subplot(111)
|
||||
ax1.set_xlabel(parameter_label)
|
||||
ax1.set_ylabel(_translate('pydsf', 'Melting point [°C]'))
|
||||
|
||||
for row in range(plate.rows):
|
||||
x_values = []
|
||||
y_values = []
|
||||
if error_bars:
|
||||
errors = []
|
||||
for col in range(plate.cols):
|
||||
well = plate.wells[row * col - 1]
|
||||
x = well.concentration
|
||||
y = well.tm
|
||||
x_values.append(x)
|
||||
y_values.append(y)
|
||||
if error_bars:
|
||||
errors.append(well.tm_sd)
|
||||
if error_bars:
|
||||
ax1.errorbar(x_values, y_values, yerr=errors, fmt='o')
|
||||
else:
|
||||
ax1.plot(x_values, y_values, 'o')
|
||||
|
||||
fig.tight_layout()
|
||||
canvas.draw()
|
||||
|
|
3905
ui/Ui_mainwindow.py
3905
ui/Ui_mainwindow.py
File diff suppressed because it is too large
Load diff
|
@ -57,6 +57,15 @@ class Worker(QRunnable):
|
|||
files = []
|
||||
for item in items:
|
||||
files.append(item.text())
|
||||
|
||||
replicates = self.owner.groupBox_replicates.isChecked()
|
||||
row_replicates = self.owner.radioButton_rep_rows.isChecked()
|
||||
|
||||
if replicates and row_replicates:
|
||||
average_rows = self.owner.spinBox_avg_rows.value()
|
||||
else:
|
||||
average_rows = None
|
||||
|
||||
self.exp = Experiment(instrument=self.owner.instrument,
|
||||
files=files,
|
||||
t1=self.owner.doubleSpinBox_tmin.value(),
|
||||
|
@ -67,7 +76,9 @@ class Worker(QRunnable):
|
|||
cutoff_low=c_lower,
|
||||
cutoff_high=c_upper,
|
||||
signal_threshold=signal_threshold,
|
||||
color_range=cbar_range)
|
||||
color_range=cbar_range,
|
||||
concentrations=self.owner.concentrations,
|
||||
average_rows=average_rows)
|
||||
self.exp.analyze()
|
||||
self.signals.finished.emit()
|
||||
|
||||
|
@ -77,7 +88,6 @@ class TaskSignals(QObject):
|
|||
|
||||
|
||||
class Tasks(QObject):
|
||||
|
||||
def __init__(self):
|
||||
super(Tasks, self).__init__()
|
||||
|
||||
|
@ -116,7 +126,6 @@ class Tasks(QObject):
|
|||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
|
||||
"""
|
||||
Class documentation goes here.
|
||||
"""
|
||||
|
@ -141,10 +150,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
QDialogButtonBox.AcceptRole)
|
||||
self.tasks = Tasks()
|
||||
self.tasks.signals.finished.connect(self.on_processing_finished)
|
||||
self.lineEdit_conc.textChanged.connect(
|
||||
self.on_lineEdit_conc_textChanged)
|
||||
self.worker = None
|
||||
self.outputPath = None
|
||||
self.instrument = None
|
||||
self.concentrations = None
|
||||
self.populateInstrumentList()
|
||||
self.instrument = self.getSelectedInstrument()
|
||||
|
||||
def populateInstrumentList(self):
|
||||
self.instruments = [AnalytikJenaqTower2()]
|
||||
|
@ -152,6 +164,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
instrument = self.instruments[i]
|
||||
self.comboBox_instrument.setItemText(i, instrument.name)
|
||||
|
||||
@pyqtSlot()
|
||||
def getInstrumentFromName(self, name):
|
||||
for instrument in self.instruments:
|
||||
if instrument.name == name:
|
||||
|
@ -178,7 +191,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.groupBox_replicates.setChecked(True)
|
||||
self.radioButton_rep_files.setEnabled(True)
|
||||
elif button == self.buttonBox_open_reset.button(
|
||||
QDialogButtonBox.Reset):
|
||||
QDialogButtonBox.Reset):
|
||||
self.listWidget_data.clear()
|
||||
|
||||
@pyqtSlot("QAbstractButton*")
|
||||
|
@ -202,8 +215,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
else:
|
||||
self.groupBox_temp.setEnabled(True)
|
||||
|
||||
def generate_plot_tab(self, name):
|
||||
tab = MplWidget(parent=self.tabWidget)
|
||||
def generate_plot_tab(self, name, mouse_event=False):
|
||||
if mouse_event:
|
||||
tab = MplWidget(parent=self.tabWidget, mouse_event=True)
|
||||
else:
|
||||
tab = MplWidget(parent=self.tabWidget)
|
||||
tab.setObjectName(name)
|
||||
return tab
|
||||
|
||||
|
@ -220,7 +236,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
plotter = PlotResults()
|
||||
|
||||
if plate.id != 'average':
|
||||
tab = self.generate_plot_tab("tab_heatmap_{}".format(plate.id))
|
||||
tab = self.generate_plot_tab("tab_heatmap_{}".format(plate.id),
|
||||
mouse_event=True)
|
||||
title = _translate("MainWindow", "Heatmap #")
|
||||
self.tabWidget.addTab(tab, title + str(plate.id))
|
||||
plotter.plot_tm_heatmap_single(plate, tab)
|
||||
|
@ -243,8 +260,26 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
if self.checkBox_saveplots.isChecked():
|
||||
tab.canvas.save(
|
||||
"{}/derivatives_{}.svg".format(self.outputPath, plate.id))
|
||||
|
||||
if self.groupBox_conc.isChecked():
|
||||
tab = self.generate_plot_tab("tab_derivative_{}".format(
|
||||
plate.id))
|
||||
title = _translate("MainWindow", "Parameter Dependency #")
|
||||
self.tabWidget.addTab(tab, title + str(plate.id))
|
||||
if self.lineEdit_par_label.text():
|
||||
par_label = self.lineEdit_par_label.text()
|
||||
plotter.plot_concentration_dependency(
|
||||
plate,
|
||||
tab,
|
||||
parameter_label=par_label)
|
||||
else:
|
||||
plotter.plot_concentration_dependency(plate, tab)
|
||||
if self.checkBox_saveplots.isChecked():
|
||||
tab.canvas.save(
|
||||
"{}/para_{}.svg".format(self.outputPath, plate.id))
|
||||
else:
|
||||
tab = self.generate_plot_tab("tab_heatmap_{}".format(plate.id))
|
||||
tab = self.generate_plot_tab("tab_heatmap_{}".format(plate.id),
|
||||
mouse_event=True)
|
||||
title = _translate("MainWindow", "Heatmap ")
|
||||
self.tabWidget.addTab(tab, title + str(plate.id))
|
||||
plotter.plot_tm_heatmap_single(plate, tab)
|
||||
|
@ -252,6 +287,26 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
tab.canvas.save(
|
||||
"{}/heatmap_{}.svg".format(self.outputPath, plate.id))
|
||||
|
||||
if self.groupBox_conc.isChecked():
|
||||
tab = self.generate_plot_tab("tab_derivative_{}".format(
|
||||
plate.id))
|
||||
title = _translate("MainWindow", "Parameter Dependency #")
|
||||
self.tabWidget.addTab(tab, title + str(plate.id))
|
||||
if self.lineEdit_par_label.text():
|
||||
par_label = self.lineEdit_par_label.text()
|
||||
plotter.plot_concentration_dependency(
|
||||
plate,
|
||||
tab,
|
||||
parameter_label=par_label,
|
||||
error_bars=True)
|
||||
else:
|
||||
plotter.plot_concentration_dependency(plate,
|
||||
tab,
|
||||
error_bars=True)
|
||||
if self.checkBox_saveplots.isChecked():
|
||||
tab.canvas.save(
|
||||
"{}/para_{}.svg".format(self.outputPath, plate.id))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_buttonBox_process_accepted(self):
|
||||
"""
|
||||
|
@ -267,8 +322,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
_translate("MainWindow", "No data file loaded!"),
|
||||
QMessageBox.Close, QMessageBox.Close)
|
||||
return
|
||||
if self.groupBox_conc.isChecked():
|
||||
self.concentrations = self.lineEdit_conc.text().split(',')
|
||||
if (self.groupBox_output.isChecked() and
|
||||
self.lineEdit_output.text().strip() == ''):
|
||||
self.lineEdit_output.text().strip() == ''):
|
||||
QMessageBox.critical(
|
||||
self, _translate("MainWindow", "Error"),
|
||||
_translate("MainWindow", "No output path set!"),
|
||||
|
@ -323,8 +380,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
if self.checkBox_savetables.isChecked():
|
||||
for plate in exp.plates:
|
||||
plate.write_tm_table(
|
||||
'{}/plate_{}_tm.csv'.format(self.outputPath,
|
||||
str(plate.id)))
|
||||
'{}/plate_{}_tm.csv'.format(self.outputPath, str(
|
||||
plate.id)))
|
||||
plate.write_data_table(
|
||||
'{}/plate_{}_dI_dT.csv'.format(self.outputPath,
|
||||
str(plate.id)),
|
||||
|
@ -380,10 +437,25 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
dialog.ui.setupUi(dialog)
|
||||
dialog.exec_()
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def on_actionAbout_Qt_triggered(self):
|
||||
"""
|
||||
Slot documentation goes here.
|
||||
"""
|
||||
QApplication.aboutQt()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_lineEdit_conc_textChanged(self):
|
||||
"""
|
||||
Slot documentation goes here.
|
||||
"""
|
||||
num_conc = len(self.lineEdit_conc.text().split(','))
|
||||
self.spinBox_num_conc.setValue(num_conc)
|
||||
if self.comboBox_direction.currentIndex() == 0:
|
||||
max_wells = self.instrument.wells_horizontal
|
||||
else:
|
||||
max_wells = self.instrument.wells_vertical
|
||||
if num_conc > max_wells:
|
||||
self.spinBox_num_conc.setStyleSheet("QSpinBox { color : red; }")
|
||||
else:
|
||||
self.spinBox_num_conc.setStyleSheet("QSpinBox { color : black; }")
|
||||
|
|
134
ui/mainwindow.ui
134
ui/mainwindow.ui
|
@ -79,7 +79,7 @@
|
|||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_instrument">
|
||||
<property name="text">
|
||||
<string>Instrument</string>
|
||||
<string>I&nstrument</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>comboBox_instrument</cstring>
|
||||
|
@ -150,6 +150,13 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="radioButton_rep_rows">
|
||||
<property name="text">
|
||||
<string>Rows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="radioButton_rep_files">
|
||||
<property name="enabled">
|
||||
|
@ -163,6 +170,23 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="spinBox_avg_rows">
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_rows">
|
||||
<property name="text">
|
||||
<string>Rows to average</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -465,7 +489,7 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_output">
|
||||
<property name="title">
|
||||
<string>Sa&ve processing results</string>
|
||||
|
@ -477,14 +501,14 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_saveplots">
|
||||
<property name="text">
|
||||
<string>Save plots</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QDialogButtonBox" name="buttonBox_output">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
|
@ -500,14 +524,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLineEdit" name="lineEdit_output">
|
||||
<property name="toolTip">
|
||||
<string>Output results to this path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="checkBox_savetables">
|
||||
<property name="text">
|
||||
<string>Save tabular results</string>
|
||||
|
@ -517,6 +541,102 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox_conc">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Parameter Dependency</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_direction">
|
||||
<property name="text">
|
||||
<string>Direction</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBox_direction">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Horizontal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Vertical</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_conc">
|
||||
<property name="text">
|
||||
<string>Parameter Values</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_conc">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Comma-seperated list of concentrations. This has to match the number of wells in either horizontal or vertical dimension. If a well is unused, either leave blank or use &quot;NaN&quot; as input.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_conc_num">
|
||||
<property name="text">
|
||||
<string>Number of wells</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="spinBox_num_conc">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Displays the number of wells specified above.</p></body></html></string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::NoButtons</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_par">
|
||||
<property name="text">
|
||||
<string>Parameter Label</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_par_label">
|
||||
<property name="placeholderText">
|
||||
<string>Parameter [au]</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -601,7 +721,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>4095</width>
|
||||
<height>28</height>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="locale">
|
||||
|
|
|
@ -19,12 +19,6 @@ class MplCanvas(FigureCanvas):
|
|||
QtWidgets.QSizePolicy.Expanding)
|
||||
FigureCanvas.updateGeometry(self)
|
||||
|
||||
# override mouseMoveEvent with non-functional dummy
|
||||
# this will prevent the gui thread to hang while moving the mouse
|
||||
# while a large number of plots is shown simultaniously
|
||||
def mouseMoveEvent(self, event):
|
||||
pass
|
||||
|
||||
def clear(self):
|
||||
self.ax.clear()
|
||||
self.fig.clear()
|
||||
|
@ -40,6 +34,16 @@ class MplCanvas(FigureCanvas):
|
|||
QtWidgets.QMessageBox.Close, QtWidgets.QMessageBox.Close)
|
||||
|
||||
|
||||
class MplCanvasNoMouse(MplCanvas):
|
||||
|
||||
# override mouseMoveEvent with non-functional dummy
|
||||
# this will prevent the gui thread to hang while moving the mouse
|
||||
# while a large number of plots is shown simultaniously
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
pass
|
||||
|
||||
|
||||
class CustomNavigationToolbar(NavigationToolbar):
|
||||
|
||||
toolitems = (
|
||||
|
@ -60,9 +64,12 @@ class CustomNavigationToolbar(NavigationToolbar):
|
|||
|
||||
class MplWidget(QtWidgets.QGraphicsView):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None, mouse_event=False):
|
||||
QtWidgets.QGraphicsView.__init__(self, parent)
|
||||
self.canvas = MplCanvas()
|
||||
if mouse_event:
|
||||
self.canvas = MplCanvas()
|
||||
else:
|
||||
self.canvas = MplCanvasNoMouse()
|
||||
self.ntb = CustomNavigationToolbar(self.canvas, self,
|
||||
coordinates=False)
|
||||
self.vbl = QtWidgets.QVBoxLayout()
|
||||
|
|
Loading…
Add table
Reference in a new issue