From: illysam Date: Mon, 1 Mar 2010 17:30:27 +0000 (+0000) Subject: Hooke(GUI) X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=c67a6a2eaaa8fecde12f0d7216daa61d8196f106;p=hooke.git Hooke(GUI) hooke.py - added check for off-screen window position on startup - added note panel - added check for existing plugin commands - fixed access error on close (forgot to deregister a listener) - added option to hide file extension from playlist and plot title - added 'do_copylog' - added general preferences - modifed UpdatePlot to use individual scale formatter and legends for subplots - added default settings for scale formatters and legends - added general preference 'use_zero' to dispaly '0' on axes rather than e.g. '0.00' hooke configspec.ini/hooke.ini - added mfp1d driver - added mfp3d driver - removed curvetools plugin drivers/mfp1dexport.py - corrected spelling drivers/picoforce.py - removed detriggerize drivers/mfp1d.py - added driver for direct import of MFP-1D files drivers/mfp3d.py - added driver for direct import of MFP-3D files lib/clickedpoint.py - added object ClickedPoint to lib (was in libhooke before) lib/curve.py - added properties decimals, legend and multiplier to Curve - added classes Data, Decimals, Multiplier and PrefixFormatter lib/delta.py - added object Delta to lib lib/file.py - rename self.notes to self.note lib/libhooke.py - added fit_interval_nm (was in curvetools before) - added pickup_contact_point(was in curvetools before) - added remove_extension lib/playlist.py - added note support panels/ploy.py - added 'x' and 'y' to status text panels/note.py - added note panel plugins/autopeak.ini and autopeak.py - added options: delta_force, plot_linewidth, plot_size and plot_style plugins/core.ini - added 'preferences'options: hide_curve_extension, legend, x_decimals, x_multiplier, y_decimals, y_multiplier and use_zero - added 'copylog' options: destination and use_LVDT_folder plugins/export.ini and export.py - added 'notes' options: filename, folder, prefix and use_playlist_filename - added do_notes plugins/flatfilts.ini and flatfilts.py - corrected some default values - added warning about not applying the flatten plotmanipulator plugins/generalvclamp.py - updated copyright information plugins/plot.ini and plot.py - corrected some default values - added support for options: decimals, legend and multiplier plugins/procplots.ini and procplots.py - corrected some default values - added support for options: decimals, legend and multiplier to do_fft --- diff --git a/config/hooke configspec.ini b/config/hooke configspec.ini index f1cd81e..1f02453 100644 --- a/config/hooke configspec.ini +++ b/config/hooke configspec.ini @@ -7,7 +7,9 @@ csvdriver = boolean(default = False) hemingclamp = boolean(default = False) jpk = boolean(default = False) mcs = boolean(default = False) +mfp1d = boolean(default = True) mfp1dexport = boolean(default = True) +mfp3d = boolean(default = True) picoforce = boolean(default = True) picoforcealt = boolean(default = False) tutorialdriver = boolean(default = False) @@ -27,7 +29,6 @@ active = string(default = Default) [plugins] autopeak = boolean(default = True) -curvetools = boolean(default = True) export = boolean(default = True) fit = boolean(default = True) flatfilts = boolean(default = True) diff --git a/config/hooke.ini b/config/hooke.ini index 4c0871c..f7cb6e1 100644 --- a/config/hooke.ini +++ b/config/hooke.ini @@ -16,6 +16,8 @@ csvdriver = False hemingclamp = False jpk = False mcs = False +mfp1d = True +mfp3d = True mfp1dexport = True picoforce = True picoforcealt = False @@ -39,7 +41,6 @@ default = Default #this section defines which plugins have to be loaded by Hooke [plugins] autopeak = True -curvetools = True export = True fit = True flatfilts = True diff --git a/drivers/mfp1d.py b/drivers/mfp1d.py new file mode 100644 index 0000000..833eaa0 --- /dev/null +++ b/drivers/mfp1d.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +''' +mfp1d.py + +Driver for MFP-1D files. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) +This driver is based on the work of R. Naud and A. Seeholzer (see below) +to read Igor binary waves. Code used with permission. + +This program is released under the GNU General Public License version 2. +''' + +# DEFINITION: +# Reads Igor's (Wavemetric) binary wave format, .ibw, files. +# +# ALGORITHM: +# Parsing proper to version 2, 3, or version 5 (see Technical notes TN003.ifn: +# http://mirror.optus.net.au/pub/wavemetrics/IgorPro/Technical_Notes/) and data +# type 2 or 4 (non complex, single or double precision vector, real values). +# +# AUTHORS: +# Matlab version: R. Naud August 2008 (http://lcn.epfl.ch/~naud/Home.html) +# Python port: A. Seeholzer October 2008 +# +# VERSION: 0.1 +# +# COMMENTS: +# Only tested for version 2 Igor files for now, testing for 3 and 5 remains to be done. +# More header data could be passed back if wished. For significance of ignored bytes see +# the technical notes linked above. + +import numpy +import os.path +import struct + +import lib.driver +import lib.curve +import lib.plot + +__version__='0.0.0.20100225' + +class mfp1dDriver(lib.driver.Driver): + + def __init__(self, filename): + ''' + This is a driver to import Asylum Research MFP-1D data. + Status: experimental + ''' + self.data = [] + self.note = [] + self.retract_velocity = None + self.spring_constant = None + self.filename = filename + + self.filedata = open(filename,'rU') + self.lines = list(self.filedata.readlines()) + self.filedata.close() + + self.filetype = 'mfp1d' + self.experiment = 'smfs' + + def _load_from_file(self, filename, extract_note=False): + data = None + f = open(filename, 'rb') + ####################### ORDERING + # machine format for IEEE floating point with big-endian + # byte ordering + # MacIgor use the Motorola big-endian 'b' + # WinIgor use Intel little-endian 'l' + # If the first byte in the file is non-zero, then the file is a WinIgor + firstbyte = struct.unpack('b', f.read(1))[0] + if firstbyte == 0: + format = '>' + else: + format = '<' + ####################### CHECK VERSION + f.seek(0) + version = struct.unpack(format+'h', f.read(2))[0] + ####################### READ DATA AND ACCOMPANYING INFO + if version == 2 or version == 3: + # pre header + wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding. + noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text. + if version==3: + formulaSize = struct.unpack(format+'i', f.read(4))[0] + pictSize = struct.unpack(format+'i', f.read(4))[0] # Reserved. Write zero. Ignore on read. + checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header. + # wave header + dtype = struct.unpack(format+'h', f.read(2))[0] + if dtype == 2: + dtype = numpy.float32(.0).dtype + elif dtype == 4: + dtype = numpy.double(.0).dtype + else: + assert False, "Wave is of type '%i', not supported" % dtype + dtype = dtype.newbyteorder(format) + + ignore = f.read(4) # 1 uint32 + bname = self._flatten(struct.unpack(format+'20c', f.read(20))) + ignore = f.read(4) # 2 int16 + ignore = f.read(4) # 1 uint32 + dUnits = self._flatten(struct.unpack(format+'4c', f.read(4))) + xUnits = self._flatten(struct.unpack(format+'4c', f.read(4))) + npnts = struct.unpack(format+'i', f.read(4))[0] + amod = struct.unpack(format+'h', f.read(2))[0] + dx = struct.unpack(format+'d', f.read(8))[0] + x0 = struct.unpack(format+'d', f.read(8))[0] + ignore = f.read(4) # 2 int16 + fsValid = struct.unpack(format+'h', f.read(2))[0] + topFullScale = struct.unpack(format+'d', f.read(8))[0] + botFullScale = struct.unpack(format+'d', f.read(8))[0] + ignore = f.read(16) # 16 int8 + modDate = struct.unpack(format+'I', f.read(4))[0] + ignore = f.read(4) # 1 uint32 + # Numpy algorithm works a lot faster than struct.unpack + data = numpy.fromfile(f, dtype, npnts) + + elif version == 5: + # pre header + checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header. + wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding. + formulaSize = struct.unpack(format+'i', f.read(4))[0] + noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text. + dataEUnitsSize = struct.unpack(format+'i', f.read(4))[0] + dimEUnitsSize = struct.unpack(format+'4i', f.read(16)) + dimLabelsSize = struct.unpack(format+'4i', f.read(16)) + sIndicesSize = struct.unpack(format+'i', f.read(4))[0] + optionSize1 = struct.unpack(format+'i', f.read(4))[0] + optionSize2 = struct.unpack(format+'i', f.read(4))[0] + + # header + ignore = f.read(4) + CreationDate = struct.unpack(format+'I',f.read(4))[0] + modData = struct.unpack(format+'I',f.read(4))[0] + npnts = struct.unpack(format+'i',f.read(4))[0] + # wave header + dtype = struct.unpack(format+'h',f.read(2))[0] + if dtype == 2: + dtype = numpy.float32(.0).dtype + elif dtype == 4: + dtype = numpy.double(.0).dtype + else: + assert False, "Wave is of type '%i', not supported" % dtype + dtype = dtype.newbyteorder(format) + + ignore = f.read(2) # 1 int16 + ignore = f.read(6) # 6 schar, SCHAR = SIGNED CHAR? ignore = fread(fid,6,'schar'); # + ignore = f.read(2) # 1 int16 + bname = self._flatten(struct.unpack(format+'32c',f.read(32))) + ignore = f.read(4) # 1 int32 + ignore = f.read(4) # 1 int32 + ndims = struct.unpack(format+'4i',f.read(16)) # Number of of items in a dimension -- 0 means no data. + sfA = struct.unpack(format+'4d',f.read(32)) + sfB = struct.unpack(format+'4d',f.read(32)) + dUnits = self._flatten(struct.unpack(format+'4c',f.read(4))) + xUnits = self._flatten(struct.unpack(format+'16c',f.read(16))) + fsValid = struct.unpack(format+'h',f.read(2)) + whpad3 = struct.unpack(format+'h',f.read(2)) + ignore = f.read(16) # 2 double + ignore = f.read(40) # 10 int32 + ignore = f.read(64) # 16 int32 + ignore = f.read(6) # 3 int16 + ignore = f.read(2) # 2 char + ignore = f.read(4) # 1 int32 + ignore = f.read(4) # 2 int16 + ignore = f.read(4) # 1 int32 + ignore = f.read(8) # 2 int32 + + data = numpy.fromfile(f, dtype, npnts) + note_str = f.read(noteSize) + if extract_note: + note_lines = note_str.split('\r') + self.note = {} + for line in note_lines: + if ':' in line: + key, value = line.split(':', 1) + self.note[key] = value + self.retract_velocity = float(self.note['RetractVelocity']) + self.spring_constant = float(self.note['SpringC']) + else: + assert False, "Fileversion is of type '%i', not supported" % dtype + data = [] + + f.close() + if len(data) > 0: + data_list = data.tolist() + count = len(data_list) / 2 + return data_list[:count - 1], data_list[count:] + else: + return None + + def _flatten(self, tup): + out = '' + for ch in tup: + out += ch + return out + + def _read_columns(self): + extension = lib.curve.Data() + retraction = lib.curve.Data() + + extension.y, retraction.y = self._load_from_file(self.filename, extract_note=True) + filename = self.filename.replace('deflection', 'LVDT', 1) + path, name = os.path.split(filename) + filename = os.path.join(path, 'lvdt', name) + extension.x, retraction.x = self._load_from_file(filename, extract_note=False) + return [[extension.x, extension.y], [retraction.x, retraction.y]] + + def close_all(self): + self.filedata.close() + + def is_me(self): + if len(self.lines) < 34: + return False + + name, extension = os.path.splitext(self.filename) + #PullDist, PullDistSign, FastSamplingFrequency, SlowSamplingFrequency, FastDecimationFactor + #SlowDecimationFactor, IsDualPull, InitRetDist, RelaxDist, SlowTrigger, RelativeTrigger, + #EndOfNote + if extension == '.ibw' and 'deflection' in name: + if 'EndOfNote' in self.lines: + return True + else: + return False + else: + return False + + def default_plots(self): + ''' + loads the curve data + ''' + defl_ext, defl_ret = self.deflection() + + extension = lib.curve.Curve() + retraction = lib.curve.Curve() + + extension.color = 'red' + extension.label = 'extension' + extension.style = 'plot' + extension.title = 'Force curve' + extension.units.x = 'm' + extension.units.y = 'N' + extension.x = self.data[0][0] + extension.y = [i * self.spring_constant for i in defl_ext] + retraction.color = 'blue' + retraction.label = 'retraction' + retraction.style = 'plot' + retraction.title = 'Force curve' + retraction.units.x = 'm' + retraction.units.y = 'N' + retraction.x = self.data[1][0] + retraction.y = [i * self.spring_constant for i in defl_ret] + + plot = lib.plot.Plot() + plot.title = os.path.basename(self.filename) + plot.curves.append(extension) + plot.curves.append(retraction) + + plot.normalize() + return plot + + def deflection(self): + if not self.data: + self.data = self._read_columns() + return self.data[0][1], self.data[1][1] diff --git a/drivers/mfp1dexport.py b/drivers/mfp1dexport.py index 0214866..a2bb8cf 100644 --- a/drivers/mfp1dexport.py +++ b/drivers/mfp1dexport.py @@ -3,7 +3,7 @@ ''' mfp1dexport.py -Driver for text-exported MFP 1D files. +Driver for text-exported MFP-1D files. Copyright 2009 by Massimo Sandal with modifications by Dr. Rolf Schmidt (Concordia University, Canada) @@ -23,7 +23,7 @@ class mfp1dexportDriver(lib.driver.Driver): def __init__(self, filename): ''' - This is a driver to import Asylum Research MFP 1D data. + This is a driver to import Asylum Research MFP-1D data. Status: experimental ''' self.filename = filename diff --git a/drivers/mfp3d.py b/drivers/mfp3d.py new file mode 100644 index 0000000..5988c3f --- /dev/null +++ b/drivers/mfp3d.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +''' +mfp3d.py + +Driver for MFP-3D files. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) +This driver is based on the work of R. Naud and A. Seeholzer (see below) +to read Igor binary waves. Code used with permission. + +This program is released under the GNU General Public License version 2. +''' + +# DEFINITION: +# Reads Igor's (Wavemetric) binary wave format, .ibw, files. +# +# ALGORITHM: +# Parsing proper to version 2, 3, or version 5 (see Technical notes TN003.ifn: +# http://mirror.optus.net.au/pub/wavemetrics/IgorPro/Technical_Notes/) and data +# type 2 or 4 (non complex, single or double precision vector, real values). +# +# AUTHORS: +# Matlab version: R. Naud August 2008 (http://lcn.epfl.ch/~naud/Home.html) +# Python port: A. Seeholzer October 2008 +# +# VERSION: 0.1 +# +# COMMENTS: +# Only tested for version 2 Igor files for now, testing for 3 and 5 remains to be done. +# More header data could be passed back if wished. For significance of ignored bytes see +# the technical notes linked above. + +import numpy +import os.path +import struct + +import lib.driver +import lib.curve +import lib.plot + +__version__='0.0.0.20100225' + +class mfp3dDriver(lib.driver.Driver): + + def __init__(self, filename): + ''' + This is a driver to import Asylum Research MFP-3D data. + Status: experimental + ''' + self.data = [] + self.note = [] + self.retract_velocity = None + self.spring_constant = None + self.filename = filename + + self.filedata = open(filename,'rU') + self.lines = list(self.filedata.readlines()) + self.filedata.close() + + self.filetype = 'mfp3d' + self.experiment = 'smfs' + + def _load_from_file(self, filename, extract_note=False): + data = None + f = open(filename, 'rb') + ####################### ORDERING + # machine format for IEEE floating point with big-endian + # byte ordering + # MacIgor use the Motorola big-endian 'b' + # WinIgor use Intel little-endian 'l' + # If the first byte in the file is non-zero, then the file is a WinIgor + firstbyte = struct.unpack('b', f.read(1))[0] + if firstbyte == 0: + format = '>' + else: + format = '<' + ####################### CHECK VERSION + f.seek(0) + version = struct.unpack(format+'h', f.read(2))[0] + ####################### READ DATA AND ACCOMPANYING INFO + if version == 2 or version == 3: + # pre header + wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding. + noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text. + if version==3: + formulaSize = struct.unpack(format+'i', f.read(4))[0] + pictSize = struct.unpack(format+'i', f.read(4))[0] # Reserved. Write zero. Ignore on read. + checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header. + # wave header + dtype = struct.unpack(format+'h', f.read(2))[0] + if dtype == 2: + dtype = numpy.float32(.0).dtype + elif dtype == 4: + dtype = numpy.double(.0).dtype + else: + assert False, "Wave is of type '%i', not supported" % dtype + dtype = dtype.newbyteorder(format) + + ignore = f.read(4) # 1 uint32 + bname = self._flatten(struct.unpack(format+'20c', f.read(20))) + ignore = f.read(4) # 2 int16 + ignore = f.read(4) # 1 uint32 + dUnits = self._flatten(struct.unpack(format+'4c', f.read(4))) + xUnits = self._flatten(struct.unpack(format+'4c', f.read(4))) + npnts = struct.unpack(format+'i', f.read(4))[0] + amod = struct.unpack(format+'h', f.read(2))[0] + dx = struct.unpack(format+'d', f.read(8))[0] + x0 = struct.unpack(format+'d', f.read(8))[0] + ignore = f.read(4) # 2 int16 + fsValid = struct.unpack(format+'h', f.read(2))[0] + topFullScale = struct.unpack(format+'d', f.read(8))[0] + botFullScale = struct.unpack(format+'d', f.read(8))[0] + ignore = f.read(16) # 16 int8 + modDate = struct.unpack(format+'I', f.read(4))[0] + ignore = f.read(4) # 1 uint32 + # Numpy algorithm works a lot faster than struct.unpack + data = numpy.fromfile(f, dtype, npnts) + + elif version == 5: + # pre header + checksum = struct.unpack(format+'H', f.read(2))[0] # Checksum over this header and the wave header. + wfmSize = struct.unpack(format+'i', f.read(4))[0] # The size of the WaveHeader2 data structure plus the wave data plus 16 bytes of padding. + formulaSize = struct.unpack(format+'i', f.read(4))[0] + noteSize = struct.unpack(format+'i', f.read(4))[0] # The size of the note text. + dataEUnitsSize = struct.unpack(format+'i', f.read(4))[0] + dimEUnitsSize = struct.unpack(format+'4i', f.read(16)) + dimLabelsSize = struct.unpack(format+'4i', f.read(16)) + sIndicesSize = struct.unpack(format+'i', f.read(4))[0] + optionSize1 = struct.unpack(format+'i', f.read(4))[0] + optionSize2 = struct.unpack(format+'i', f.read(4))[0] + + # header + ignore = f.read(4) + CreationDate = struct.unpack(format+'I',f.read(4))[0] + modData = struct.unpack(format+'I',f.read(4))[0] + npnts = struct.unpack(format+'i',f.read(4))[0] + # wave header + dtype = struct.unpack(format+'h',f.read(2))[0] + if dtype == 2: + dtype = numpy.float32(.0).dtype + elif dtype == 4: + dtype = numpy.double(.0).dtype + else: + assert False, "Wave is of type '%i', not supported" % dtype + dtype = dtype.newbyteorder(format) + + ignore = f.read(2) # 1 int16 + ignore = f.read(6) # 6 schar, SCHAR = SIGNED CHAR? ignore = fread(fid,6,'schar'); # + ignore = f.read(2) # 1 int16 + bname = self._flatten(struct.unpack(format+'32c',f.read(32))) + ignore = f.read(4) # 1 int32 + ignore = f.read(4) # 1 int32 + ndims = struct.unpack(format+'4i',f.read(16)) # Number of of items in a dimension -- 0 means no data. + sfA = struct.unpack(format+'4d',f.read(32)) + sfB = struct.unpack(format+'4d',f.read(32)) + dUnits = self._flatten(struct.unpack(format+'4c',f.read(4))) + xUnits = self._flatten(struct.unpack(format+'16c',f.read(16))) + fsValid = struct.unpack(format+'h',f.read(2)) + whpad3 = struct.unpack(format+'h',f.read(2)) + ignore = f.read(16) # 2 double + ignore = f.read(40) # 10 int32 + ignore = f.read(64) # 16 int32 + ignore = f.read(6) # 3 int16 + ignore = f.read(2) # 2 char + ignore = f.read(4) # 1 int32 + ignore = f.read(4) # 2 int16 + ignore = f.read(4) # 1 int32 + ignore = f.read(8) # 2 int32 + + data = numpy.fromfile(f, dtype, npnts) + note_str = f.read(noteSize) + if extract_note: + note_lines = note_str.split('\r') + self.note = {} + for line in note_lines: + if ':' in line: + key, value = line.split(':', 1) + self.note[key] = value + self.retract_velocity = float(self.note['RetractVelocity']) + self.spring_constant = float(self.note['SpringConstant']) + else: + assert False, "Fileversion is of type '%i', not supported" % dtype + data = [] + + f.close() + if len(data) > 0: + #we have 3 columns: deflection, LVDT, raw + count = npnts / 3 + deflection = data[:count].tolist() + lvdt = data[count:2 * count].tolist() + #every column contains data for extension and retraction + #we assume the same number of points for each + #we could possibly extract this info from the note + count = npnts / 6 + extension = lib.curve.Data() + retraction = lib.curve.Data() + extension.x = deflection[:count] + extension.y = lvdt[:count] + retraction.x = deflection[count:] + retraction.y = lvdt[count:] + + return extension, retraction + else: + return None + + def _flatten(self, tup): + out = '' + for ch in tup: + out += ch + return out + + def _read_columns(self): + extension, retraction = self._load_from_file(self.filename, extract_note=True) + return [[extension.x, extension.y], [retraction.x, retraction.y]] + + def close_all(self): + self.filedata.close() + + def is_me(self): + if len(self.lines) < 34: + return False + + name, extension = os.path.splitext(self.filename) + if extension == '.ibw': + return True + else: + return False + + def default_plots(self): + ''' + loads the curve data + ''' + defl_ext, defl_ret = self.deflection() + + extension = lib.curve.Curve() + retraction = lib.curve.Curve() + + extension.color = 'red' + extension.label = 'extension' + extension.style = 'plot' + extension.title = 'Force curve' + extension.units.x = 'm' + extension.units.y = 'N' + extension.x = self.data[0][0] + extension.y = [i * self.spring_constant for i in defl_ext] + retraction.color = 'blue' + retraction.label = 'retraction' + retraction.style = 'plot' + retraction.title = 'Force curve' + retraction.units.x = 'm' + retraction.units.y = 'N' + retraction.x = self.data[1][0] + retraction.y = [i * self.spring_constant for i in defl_ret] + + plot = lib.plot.Plot() + plot.title = os.path.basename(self.filename) + plot.curves.append(extension) + plot.curves.append(retraction) + + plot.normalize() + return plot + + def deflection(self): + if not self.data: + self.data = self._read_columns() + return self.data[0][1], self.data[1][1] diff --git a/drivers/picoforce.py b/drivers/picoforce.py index 5fe17ba..15969a3 100755 --- a/drivers/picoforce.py +++ b/drivers/picoforce.py @@ -507,23 +507,6 @@ class picoforceDriver(lib.driver.Driver): ''' raise "Not implemented yet." - def detriggerize(self, forcext): - ''' - Cuts away the trigger-induced s**t on the extension curve. - DEPRECATED - cutindex=2 - startvalue=forcext[0] - - for index in range(len(forcext)-1,2,-2): - if forcext[index]>startvalue: - cutindex=index - else: - break - - return cutindex - ''' - return 0 - def is_me(self): ''' self-identification of file type magic diff --git a/hooke.py b/hooke.py index 8c2167b..7033ed6 100644 --- a/hooke.py +++ b/hooke.py @@ -17,8 +17,9 @@ from configobj import ConfigObj import copy import os.path import platform +import shutil import time -#import wx + import wx.html import wx.lib.agw.aui as aui import wx.lib.evtmgr as evtmgr @@ -40,11 +41,14 @@ except ImportError: # if it's not there locally, try the wxPython lib. lh.hookeDir = os.path.abspath(os.path.dirname(__file__)) from config.config import config import drivers +import lib.clickedpoint +import lib.curve import lib.delta import lib.playlist import lib.plotmanipulator import lib.prettyformat import panels.commands +import panels.note import panels.perspectives import panels.playlist import panels.plot @@ -62,9 +66,6 @@ __codename__ = lh.HOOKE_VERSION[1] __releasedate__ = lh.HOOKE_VERSION[2] __release_name__ = lh.HOOKE_VERSION[1] -#TODO: add general preferences to Hooke -#this might be useful -#ID_Config = wx.NewId() ID_About = wx.NewId() ID_Next = wx.NewId() ID_Previous = wx.NewId() @@ -72,6 +73,7 @@ ID_Previous = wx.NewId() ID_ViewAssistant = wx.NewId() ID_ViewCommands = wx.NewId() ID_ViewFolders = wx.NewId() +ID_ViewNote = wx.NewId() ID_ViewOutput = wx.NewId() ID_ViewPlaylists = wx.NewId() ID_ViewProperties = wx.NewId() @@ -90,8 +92,21 @@ class Hooke(wx.App): self.SetAppName('Hooke') self.SetVendorName('') - windowPosition = (config['main']['left'], config['main']['top']) - windowSize = (config['main']['width'], config['main']['height']) + window_height = config['main']['height'] + window_left= config['main']['left'] + window_top = config['main']['top'] + window_width = config['main']['width'] + + #sometimes, the ini file gets confused and sets 'left' + #and 'top' to large negative numbers + #let's catch and fix this + #keep small negative numbers, the user might want those + if window_left < -window_width: + window_left = 0 + if window_top < -window_height: + window_top = 0 + window_position = (window_left, window_top) + window_size = (window_width, window_height) #setup the splashscreen if config['splashscreen']['show']: @@ -132,7 +147,7 @@ class Hooke(wx.App): def make_command_class(*bases): #create metaclass with plugins and plotmanipulators return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {}) - frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=windowPosition, size=windowSize) + frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=window_position, size=window_size) frame.Show(True) self.SetTopWindow(frame) @@ -187,14 +202,15 @@ class HookeFrame(wx.Frame): self.panelFolders = self.CreatePanelFolders() self.panelPlaylists = self.CreatePanelPlaylists() self.panelProperties = self.CreatePanelProperties() + self.panelNote = self.CreatePanelNote() self.panelOutput = self.CreatePanelOutput() self.panelResults = self.CreatePanelResults() self.plotNotebook = self.CreateNotebook() - #self.textCtrlCommandLine=self.CreateCommandLine() # add panes self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False)) + self._mgr.AddPane(self.panelNote, aui.AuiPaneInfo().Name('Note').Caption('Note').Left().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False)) self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False)) @@ -245,6 +261,8 @@ class HookeFrame(wx.Frame): plugin_config = ConfigObj(ini_path) #self.config.merge(plugin_config) self.configs['core'] = plugin_config + #existing_commands contains: {command: plugin} + existing_commands = {} #make sure we execute _plug_init() for every command line plugin we import for plugin in self.config['plugins']: if self.config['plugins'][plugin]: @@ -265,9 +283,18 @@ class HookeFrame(wx.Frame): #add to plugins commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)') #keep only commands (ie names that start with 'do_') - #TODO: check for existing commands and warn the user! commands = [command for command in commands if command.startswith('do_')] if commands: + for command in commands: + if existing_commands.has_key(command): + message_str = 'Adding "' + command + '" in plugin "' + plugin + '".\n\n' + message_str += '"' + command + '" already exists in "' + str(existing_commands[command]) + '".\n\n' + message_str += 'Only "' + command + '" in "' + str(existing_commands[command]) + '" will work.\n\n' + message_str += 'Please rename one of the commands in the source code and restart Hooke or disable one of the plugins.' + dialog = wx.MessageDialog(self, message_str, 'Warning', wx.OK|wx.ICON_WARNING|wx.CENTER) + dialog.ShowModal() + dialog.Destroy() + existing_commands[command] = plugin self.plugins[plugin] = commands try: #initialize the plugin @@ -276,11 +303,12 @@ class HookeFrame(wx.Frame): pass except ImportError: pass - #initialize the commands tree + #add commands from hooke.py i.e. 'core' commands commands = dir(HookeFrame) commands = [command for command in commands if command.startswith('do_')] if commands: self.plugins['core'] = commands + #initialize the commands tree self.panelCommands.Initialize(self.plugins) for command in dir(self): if command.startswith('plotmanip_'): @@ -288,7 +316,6 @@ class HookeFrame(wx.Frame): #load default list, if possible self.do_loadlist(self.config['core']['list']) - #self.do_loadlist() def _BindEvents(self): #TODO: figure out if we can use the eventManager for menu ranges @@ -324,6 +351,7 @@ class HookeFrame(wx.Frame): evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self.panelCommands.ExecuteButton) evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree) evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self.panelCommands.CommandsTree) + evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton) #property editor self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged) #results panel @@ -373,6 +401,8 @@ class HookeFrame(wx.Frame): self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown()) if pane.name == 'Commands': self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown()) + if pane.name == 'Note': + self.MenuBar.FindItemById(ID_ViewNote).Check(pane.window.IsShown()) if pane.name == 'Properties': self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown()) if pane.name == 'Output': @@ -397,6 +427,8 @@ class HookeFrame(wx.Frame): #commands tree evtmgr.eventManager.DeregisterListener(self.OnExecute) evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged) + evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlItemActivated) + evtmgr.eventManager.DeregisterListener(self.OnUpdateNote) def AddPlaylist(self, playlist=None, name='Untitled'): if playlist and playlist.count > 0: @@ -421,10 +453,11 @@ class HookeFrame(wx.Frame): playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0) #add all files to the Playlist tree # files = {} + hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension') for index, file_to_add in enumerate(playlist.files): - #TODO: optionally remove the extension from the name of the curve - #item_text, extension = os.path.splitext(curve.name) - #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1) + #optionally remove the extension from the name of the curve + if hide_curve_extension: + file_to_add.name = lh.remove_extension(file_to_add.name) file_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1) if index == playlist.index: self.panelPlaylists.PlaylistsTree.SelectItem(file_ID) @@ -438,6 +471,7 @@ class HookeFrame(wx.Frame): #self.playlists[playlist.name] = [playlist, figure] self.panelPlaylists.PlaylistsTree.Expand(playlist_root) self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() self.UpdatePlot() def AppendToOutput(self, text): @@ -485,6 +519,9 @@ class HookeFrame(wx.Frame): folder = self.config['core']['workdir'] return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index) + def CreatePanelNote(self): + return panels.note.Note(self) + def CreatePanelOutput(self): return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) @@ -531,8 +568,8 @@ class HookeFrame(wx.Frame): view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9') view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10') view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11') + view_menu.AppendCheckItem(ID_ViewNote, 'Note\tF12') #perspectives -# perspectives_menu = self.CreatePerspectivesMenu() perspectives_menu = wx.Menu() #help @@ -540,7 +577,6 @@ class HookeFrame(wx.Frame): help_menu.Append(wx.ID_ABOUT, 'About Hooke') #put it all together menu_bar.Append(file_menu, 'File') -# menu_bar.Append(edit_menu, 'Edit') menu_bar.Append(view_menu, 'View') menu_bar.Append(perspectives_menu, "Perspectives") self.UpdatePerspectivesMenu() @@ -834,6 +870,7 @@ class HookeFrame(wx.Frame): if playlist.count > 1: playlist.next() self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() self.UpdatePlot() def OnNotebookPageClose(self, event): @@ -866,6 +903,7 @@ class HookeFrame(wx.Frame): playlist = self.GetActivePlaylist() playlist.index = index self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() self.UpdatePlot() #if you uncomment the following line, the tree will collapse/expand as well #event.Skip() @@ -909,6 +947,7 @@ class HookeFrame(wx.Frame): if playlist.count > 1: playlist.previous() self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() self.UpdatePlot() def OnPropGridChanged (self, event): @@ -927,29 +966,8 @@ class HookeFrame(wx.Frame): def OnRestorePerspective(self, event): name = self.MenuBar.FindItemById(event.GetId()).GetLabel() self._RestorePerspective(name) -# self._mgr.LoadPerspective(self._perspectives[name]) -# self.config['perspectives']['active'] = name -# self._mgr.Update() -# all_panes = self._mgr.GetAllPanes() -# for pane in all_panes: -# if not pane.name.startswith('toolbar'): -# if pane.name == 'Assistant': -# self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown()) -# if pane.name == 'Folders': -# self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown()) -# if pane.name == 'Playlists': -# self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown()) -# if pane.name == 'Commands': -# self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown()) -# if pane.name == 'Properties': -# self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown()) -# if pane.name == 'Output': -# self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown()) -# if pane.name == 'Results': -# self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown()) def OnResultsCheck(self, index, flag): - #TODO: fix for multiple results results = self.GetActivePlot().results if results.has_key(self.results_str): results[self.results_str].results[index].visible = flag @@ -1060,6 +1078,13 @@ class HookeFrame(wx.Frame): def OnTreeCtrlItemActivated(self, event): self.OnExecute(event) + def OnUpdateNote(self, event): + ''' + Saves the note to the active file. + ''' + active_file = self.GetActiveFile() + active_file.note = self.panelNote.Editor.GetValue() + def OnView(self, event): menu_id = event.GetId() menu_item = self.MenuBar.FindItemById(menu_id) @@ -1075,9 +1100,9 @@ class HookeFrame(wx.Frame): def _clickize(self, xvector, yvector, index): ''' - returns a ClickedPoint() object from an index and vectors of x, y coordinates + Returns a ClickedPoint() object from an index and vectors of x, y coordinates ''' - point = lh.ClickedPoint() + point = lib.clickedpoint.ClickedPoint() point.index = index point.absolute_coords = xvector[index], yvector[index] point.find_graph_coords(xvector, yvector) @@ -1085,7 +1110,7 @@ class HookeFrame(wx.Frame): def _delta(self, message='Click 2 points', whatset=lh.RETRACTION): ''' - calculates the difference between two clicked points + Calculates the difference between two clicked points ''' clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset) @@ -1107,7 +1132,7 @@ class HookeFrame(wx.Frame): General helper function for N-points measurements By default, measurements are done on the retraction ''' - if message != '': + if message: dialog = wx.MessageDialog(None, message, 'Info', wx.OK) dialog.ShowModal() @@ -1120,7 +1145,7 @@ class HookeFrame(wx.Frame): points = [] for clicked_point in clicked_points: - point = lh.ClickedPoint() + point = lib.clickedpoint.ClickedPoint() point.absolute_coords = clicked_point[0], clicked_point[1] point.dest = 0 #TODO: make this optional? @@ -1131,6 +1156,29 @@ class HookeFrame(wx.Frame): points.append(point) return points + def do_copylog(self): + ''' + Copies all files in the current playlist that have a note to the destination folder. + destination: select folder where you want the files to be copied + use_LVDT_folder: when checked, the files will be copied to a folder called 'LVDT' in the destination folder (for MFP-1D files only) + ''' + playlist = self.GetActivePlaylist() + if playlist is not None: + destination = self.GetStringFromConfig('core', 'copylog', 'destination') + if not os.path.isdir(destination): + os.makedirs(destination) + for current_file in playlist.files: + if current_file.note: + shutil.copy(current_file.filename, destination) + if current_file.driver.filetype == 'mfp1d': + filename = current_file.filename.replace('deflection', 'LVDT', 1) + path, name = os.path.split(filename) + filename = os.path.join(path, 'lvdt', name) + use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder') + if use_LVDT_folder: + destination = os.path.join(destination, 'LVDT') + shutil.copy(filename, destination) + def do_plotmanipulators(self): ''' Please select the plotmanipulators you would like to use @@ -1140,6 +1188,14 @@ class HookeFrame(wx.Frame): ''' self.UpdatePlot() + def do_preferences(self): + ''' + Please set general preferences for Hooke here. + hide_curve_extension: hides the extension of the force curve files. + not recommended for 'picoforce' files + ''' + pass + def do_test(self): ''' Use this command for testing purposes. You find do_test in hooke.py. @@ -1162,9 +1218,21 @@ class HookeFrame(wx.Frame): self.AppendToOutput('NumPy version: ' + numpy_version) self.AppendToOutput('---') self.AppendToOutput('Platform: ' + str(platform.uname())) - #TODO: adapt to 'new' config - #self.AppendToOutput('---') - #self.AppendToOutput('Loaded plugins:', self.config['loaded_plugins']) + self.AppendToOutput('******************************') + self.AppendToOutput('Loaded plugins') + self.AppendToOutput('---') + + #sort the plugins into alphabetical order + plugins_list = [key for key, value in self.plugins.iteritems()] + plugins_list.sort() + for plugin in plugins_list: + self.AppendToOutput(plugin) + + def UpdateNote(self): + #update the note for the active file + active_file = self.GetActiveFile() + if active_file is not None: + self.panelNote.Editor.SetValue(active_file.note) def UpdatePerspectivesMenu(self): #add perspectives to menubar and _perspectives @@ -1178,7 +1246,7 @@ class HookeFrame(wx.Frame): perspectiveFile = open(filename, 'rU') perspective = perspectiveFile.readline() perspectiveFile.close() - if perspective != '': + if perspective: name, extension = os.path.splitext(perspectiveFilename) if extension == '.txt': self._perspectives[name] = perspective @@ -1219,42 +1287,46 @@ class HookeFrame(wx.Frame): if playlist is not None: if playlist.index >= 0: self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() self.UpdatePlot() def UpdatePlot(self, plot=None): def add_to_plot(curve): if curve.visible and curve.x and curve.y: + #get the index of the subplot to use as destination destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1 + #set all parameters for the plot axes_list[destination].set_title(curve.title) - axes_list[destination].set_xlabel(multiplier_x + curve.units.x) - axes_list[destination].set_ylabel(multiplier_y + curve.units.y) + axes_list[destination].set_xlabel(curve.multiplier.x + curve.units.x) + axes_list[destination].set_ylabel(curve.multiplier.y + curve.units.y) + #set the formatting details for the scale + formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.multiplier.x, use_zero) + formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.multiplier.y, use_zero) + axes_list[destination].xaxis.set_major_formatter(formatter_x) + axes_list[destination].yaxis.set_major_formatter(formatter_y) if curve.style == 'plot': axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1) if curve.style == 'scatter': axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2) - - def get_format_x(x, pos): - 'The two args are the value and tick position' - multiplier = lib.prettyformat.get_exponent(multiplier_x) - decimals_str = '%.' + str(decimals_x) + 'f' - return decimals_str % (x/(10 ** multiplier)) - - def get_format_y(x, pos): - 'The two args are the value and tick position' - multiplier = lib.prettyformat.get_exponent(multiplier_y) - decimals_str = '%.' + str(decimals_y) + 'f' - return decimals_str % (x/(10 ** multiplier)) - - decimals_x = self.GetIntFromConfig('plot', 'x_decimals') - decimals_y = self.GetIntFromConfig('plot', 'y_decimals') - multiplier_x = self.GetStringFromConfig('plot', 'x_multiplier') - multiplier_y = self.GetStringFromConfig('plot', 'y_multiplier') + #add the legend if necessary + if curve.legend: + axes_list[destination].legend() if plot is None: active_file = self.GetActiveFile() if not active_file.driver: + #the first time we identify a file, the following need to be set active_file.identify(self.drivers) + for curve in active_file.plot.curves: + curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals') + curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals') + curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend') + curve.multiplier.x = self.GetStringFromConfig('core', 'preferences', 'x_multiplier') + curve.multiplier.y = self.GetStringFromConfig('core', 'preferences', 'y_multiplier') + if active_file.driver is None: + self.AppendToOutput('Invalid file: ' + active_file.filename) + return self.displayed_plot = copy.deepcopy(active_file.plot) #add raw curves to plot self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves) @@ -1267,31 +1339,29 @@ class HookeFrame(wx.Frame): self.displayed_plot = copy.deepcopy(plot) figure = self.GetActiveFigure() - figure.clear() - figure.suptitle(self.displayed_plot.title, fontsize=14) - + #use '0' instead of e.g. '0.00' for scales + use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero') + #optionally remove the extension from the title of the plot + hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension') + if hide_curve_extension: + title = lh.remove_extension(self.displayed_plot.title) + else: + title = self.displayed_plot.title + figure.suptitle(title, fontsize=14) + #create the list of all axes necessary (rows and columns) axes_list =[] - number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves]) number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves]) - for index in range(number_of_rows * number_of_columns): axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1)) - - for axes in axes_list: - formatter_x = FuncFormatter(get_format_x) - formatter_y = FuncFormatter(get_format_y) - axes.xaxis.set_major_formatter(formatter_x) - axes.yaxis.set_major_formatter(formatter_y) - + #add all curves to the corresponding plots for curve in self.displayed_plot.curves: add_to_plot(curve) #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot' figure.subplots_adjust(hspace=0.3) - #TODO: add multiple results support to fit in curve.results: #display results self.panelResults.ClearResults() if self.displayed_plot.results.has_key(self.results_str): @@ -1300,11 +1370,7 @@ class HookeFrame(wx.Frame): self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str]) else: self.panelResults.ClearResults() - - legend = self.GetBoolFromConfig('plot', 'legend') - for axes in axes_list: - if legend: - axes.legend() + #refresh the plot figure.canvas.draw() if __name__ == '__main__': @@ -1320,5 +1386,3 @@ if __name__ == '__main__': app = Hooke(redirect=redirect) app.MainLoop() - - diff --git a/lib/clickedpoint.py b/lib/clickedpoint.py new file mode 100644 index 0000000..377ff2a --- /dev/null +++ b/lib/clickedpoint.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +''' +clickedpoint.py + +ClickedPoint class for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +from scipy import arange + +class ClickedPoint(object): + ''' + This class defines what a clicked point on the curve plot is. + ''' + def __init__(self): + + self.is_marker = None #boolean ; decides if it is a marker + self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused) + self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph + self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point + self.index = None #integer ; the index of the clicked point with respect to the vector selected + self.dest = None #0 or 1 ; 0=top plot 1=bottom plot + + def find_graph_coords(self, xvector, yvector): + ''' + Given a clicked point on the plot, finds the nearest point in the dataset (in X) that + corresponds to the clicked point. + ''' + dists = [] + for index in arange(1, len(xvector), 1): + dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2)) + + self.index=dists.index(min(dists)) + self.graph_coords=(xvector[self.index], yvector[self.index]) diff --git a/lib/curve.py b/lib/curve.py index 0d81a96..a6143c9 100644 --- a/lib/curve.py +++ b/lib/curve.py @@ -10,13 +10,19 @@ Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) This program is released under the GNU General Public License version 2. ''' +from matplotlib.ticker import Formatter +import lib.prettyformat + class Curve(object): def __init__(self): self.color = 'blue' + self.decimals = Decimals() self.destination = Destination() self.label = '' + self.legend = False self.linewidth = 1 + self.multiplier = Multiplier() self.size = 0.5 self.style = 'plot' self.title = '' @@ -26,6 +32,20 @@ class Curve(object): self.y = [] +class Data(object): + + def __init__(self): + self.x = [] + self.y = [] + + +class Decimals(object): + + def __init__(self): + self.x = 2 + self.y = 2 + + class Destination(object): def __init__(self): @@ -33,6 +53,32 @@ class Destination(object): self.row = 1 +class Multiplier(object): + + def __init__(self): + self.x = 'n' + self.y = 'p' + + +class PrefixFormatter(Formatter): + ''' + Formatter (matplotlib) class that uses power prefixes. + ''' + def __init__(self, decimals=2, multiplier='n', use_zero=True): + self.decimals = decimals + self.multiplier = multiplier + self.use_zero = use_zero + + def __call__(self, x, pos=None): + 'Return the format for tick val *x* at position *pos*' + if self.use_zero: + if x == 0: + return '0' + multiplier = lib.prettyformat.get_exponent(self.multiplier) + decimals_str = '%.' + str(self.decimals) + 'f' + return decimals_str % (x / (10 ** multiplier)) + + class Units(object): def __init__(self): diff --git a/lib/delta.py b/lib/delta.py new file mode 100644 index 0000000..7996daa --- /dev/null +++ b/lib/delta.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +''' +delta.py + +Delta class for Hooke to describe differences between 2 points. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' + +from lib.curve import Units + +class Point(object): + + def __init__(self): + self.x = 0 + self.y = 0 + +class Delta(object): + + def __init__(self): + self.point1 = Point() + self.point2 = Point() + self.units = Units() + + def get_delta_x(self): + return self.point1.x - self.point2.x + + def get_delta_y(self): + return self.point1.y - self.point2.y + + diff --git a/lib/file.py b/lib/file.py index c7ff7d3..6af41db 100644 --- a/lib/file.py +++ b/lib/file.py @@ -17,7 +17,7 @@ class File(object): def __init__(self, filename=None, drivers=None): self.driver = None - self.notes = '' + self.note = '' self.plot = lib.plot.Plot() if filename is None: self.filename = None diff --git a/lib/libhooke.py b/lib/libhooke.py index 11d8565..c377ed0 100644 --- a/lib/libhooke.py +++ b/lib/libhooke.py @@ -26,6 +26,12 @@ hookeDir='' EXTENSION = 0 RETRACTION = 1 +def coth(z): + ''' + Hyperbolic cotangent. + ''' + return (numpy.exp(2 * z) + 1) / (numpy.exp(2 * z) - 1) + def delete_empty_lines_from_xmlfile(filename): #the following 3 lines are needed to strip newlines. #Otherwise, since newlines are XML elements too, the parser would read them @@ -35,56 +41,53 @@ def delete_empty_lines_from_xmlfile(filename): aFile=''.join(aFile) return aFile +def fit_interval_nm(start_index, x_vect, nm, backwards): + ''' + Calculates the number of points to fit, given a fit interval in nm + start_index: index of point + plot: plot to use + backwards: if true, finds a point backwards. + ''' + c = 0 + i = start_index + maxlen=len(x_vect) + while abs(x_vect[i] - x_vect[start_index]) * (10**9) < nm: + if i == 0 or i == maxlen-1: #we reached boundaries of vector! + return c + if backwards: + i -= 1 + else: + i += 1 + c += 1 + return c + def get_file_path(filename, folders = []): if os.path.dirname(filename) == '' or os.path.isabs(filename) == False: path = '' for folder in folders: path = os.path.join(path, folder) filename = os.path.join(hookeDir, path, filename) - return filename -def coth(z): +def pickup_contact_point(filename=''): ''' - hyperbolic cotangent + Picks up the contact point by left-clicking. ''' - return (numpy.exp(2 * z) + 1) / (numpy.exp(2 * z) - 1) - -class ClickedPoint(object): + contact_point = self._measure_N_points(N=1, message='Please click on the contact point.')[0] + contact_point_index = contact_point.index + self.wlccontact_point = contact_point + self.wlccontact_index = contact_point.index + self.wlccurrent = filename + return contact_point, contact_point_index + +def remove_extension(filename): ''' - this class defines what a clicked point on the curve plot is + Removes the extension from a filename. ''' - def __init__(self): - - self.is_marker = None #boolean ; decides if it is a marker - self.is_line_edge = None #boolean ; decides if it is the edge of a line (unused) - self.absolute_coords = (None, None) #(float,float) ; the absolute coordinates of the clicked point on the graph - self.graph_coords = (None, None) #(float,float) ; the coordinates of the plot that are nearest in X to the clicked point - self.index = None #integer ; the index of the clicked point with respect to the vector selected - self.dest = None #0 or 1 ; 0=top plot 1=bottom plot - - def find_graph_coords(self, xvector, yvector): - ''' - Given a clicked point on the plot, finds the nearest point in the dataset (in X) that - corresponds to the clicked point. - ''' - dists = [] - for index in scipy.arange(1, len(xvector), 1): - dists.append(((self.absolute_coords[0] - xvector[index]) ** 2)+((self.absolute_coords[1] - yvector[index]) ** 2)) - - self.index=dists.index(min(dists)) - self.graph_coords=(xvector[self.index], yvector[self.index]) + name, extension = os.path.splitext(filename) + return name #CSV-HELPING FUNCTIONS -def transposed2(lists, defval=0): - ''' - transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing - elements - (by Zoran Isailovski on the Python Cookbook online) - ''' - if not lists: return [] - return map(lambda *row: [elem or defval for elem in row], *lists) - def csv_write_dictionary(f, data, sorting='COLUMNS'): ''' Writes a CSV file from a dictionary, with keys as first column or row @@ -105,3 +108,13 @@ def csv_write_dictionary(f, data, sorting='COLUMNS'): if sorting=='ROWS': print 'Not implemented!' #FIXME: implement it. + +def transposed2(lists, defval=0): + ''' + transposes a list of lists, i.e. from [[a,b,c],[x,y,z]] to [[a,x],[b,y],[c,z]] without losing + elements + (by Zoran Isailovski on the Python Cookbook online) + ''' + if not lists: return [] + return map(lambda *row: [elem or defval for elem in row], *lists) + diff --git a/lib/playlist.py b/lib/playlist.py index 92b9629..90a730f 100644 --- a/lib/playlist.py +++ b/lib/playlist.py @@ -91,11 +91,19 @@ class Playlist(object): #rebuild a data structure from the xml attributes #the next two lines are here for backwards compatibility, newer playlist files use 'filename' instead of 'path' if element.hasAttribute('path'): + #path, name = os.path.split(element.getAttribute('path')) + #path = path.split(os.sep) + #filename = lib.libhooke.get_file_path(name, path) filename = element.getAttribute('path') if element.hasAttribute('filename'): + #path, name = os.path.split(element.getAttribute('filename')) + #path = path.split(os.sep) + #filename = lib.libhooke.get_file_path(name, path) filename = element.getAttribute('filename') if os.path.isfile(filename): data_file = lib.file.File(filename) + if element.hasAttribute('note'): + data_file.note = element.getAttribute('note') self.files.append(data_file) self.count = len(self.files) if self.count > 0: diff --git a/lib/prettyformat.py b/lib/prettyformat.py index bbcbcf0..5ace11b 100644 --- a/lib/prettyformat.py +++ b/lib/prettyformat.py @@ -36,7 +36,7 @@ def pretty_format(value, unit='', decimals=-1, multiplier=0, leading_spaces=Fals if multiplier == 0: multiplier=get_multiplier(value) unit_str = '' - if unit != '': + if unit: unit_str = ' ' + get_prefix(multiplier) + unit if decimals >= 0: format_str = '% ' + repr(leading_spaces_int + decimals) + '.' + repr(decimals) + 'f' diff --git a/panels/note.py b/panels/note.py new file mode 100644 index 0000000..2257d88 --- /dev/null +++ b/panels/note.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +''' +note.py + +Note panel for Hooke. + +Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada) + +This program is released under the GNU General Public License version 2. +''' +import wx + +class Note(wx.Panel): + + def __init__(self, parent): + wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200)) + + self.Editor = wx.TextCtrl(self, style=wx.TE_MULTILINE) + + self.UpdateButton = wx.Button(self, -1, 'Update note') + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.Editor, 1, wx.EXPAND) + sizer.Add(self.UpdateButton, 0, wx.EXPAND) + + self.SetSizer(sizer) + self.SetAutoLayout(True) diff --git a/panels/plot.py b/panels/plot.py index 4ea3243..7a1515e 100644 --- a/panels/plot.py +++ b/panels/plot.py @@ -145,7 +145,7 @@ class PlotPanel(wx.Panel): #line.figure.canvas.draw() if self.display_coordinates: - coordinateString = ''.join([str(event.xdata), ' ', str(event.ydata)]) + coordinateString = ''.join(['x: ', str(event.xdata), ' y: ', str(event.ydata)]) #TODO: pretty format self.SetStatusText(coordinateString) diff --git a/panels/results.py b/panels/results.py index 4b10f83..c53de11 100644 --- a/panels/results.py +++ b/panels/results.py @@ -39,7 +39,7 @@ class Results(wx.Panel): #Returns the width of a string in pixels #Unfortunately, it does not work terribly well (although it should). #Thus, we have to add a bit afterwards. - #Annoys the heck out of me (me being Rolf). + #Annoys the heck out of me (illysam). font = self.results_list.GetFont() dc = wx.WindowDC(self.results_list) dc.SetFont(font) diff --git a/plugins/autopeak.ini b/plugins/autopeak.ini index 88fb6c0..726252a 100644 --- a/plugins/autopeak.ini +++ b/plugins/autopeak.ini @@ -52,6 +52,12 @@ type = color value = "(255,0,128)" + [[delta_force]] + default = 10 + minimum = 0 + type = integer + value = 10 + [[fit_function]] default = wlc elements = wlc, fjc, fjcPEG @@ -72,7 +78,27 @@ default = 0.35e-9 minimum = 0 type = float - value = 0.175 + value = 0.35 + + [[plot_linewidth]] + default = 1 + maximum = 10000 + minimum = 1 + type = integer + value = 2 + + [[plot_size]] + default = 20 + maximum = 10000 + minimum = 1 + type = integer + value = 4 + + [[plot_style]] + default = scatter + elements = plot, scatter + type = enum + value = plot [[rebase]] default = False diff --git a/plugins/autopeak.py b/plugins/autopeak.py index 67a85af..56d132e 100644 --- a/plugins/autopeak.py +++ b/plugins/autopeak.py @@ -92,6 +92,8 @@ class autopeakCommands: outside of which the peak is automatically discarded (in nm) auto_min_p: Minimum persistence length (if using WLC) or Kuhn length (if using FJC) outside of which the peak is automatically discarded (in nm) + delta_force: defines the force window in points to locate the peak minimum (default: 10) + do not change unless you know what you are doing ''' #default variables @@ -102,11 +104,16 @@ class autopeakCommands: auto_right_baseline = self.GetFloatFromConfig('autopeak', 'auto_right_baseline') baseline_clicks = self.GetStringFromConfig('autopeak', 'baseline_clicks') color = self.GetColorFromConfig('autopeak', 'color') + delta_force = self.GetIntFromConfig('autopeak', 'delta_force') fit_function = self.GetStringFromConfig('autopeak', 'fit_function') fit_points = self.GetIntFromConfig('autopeak', 'auto_fit_points') noauto = self.GetBoolFromConfig('autopeak', 'noauto') + #persistence_length has to be given in nm persistence_length = self.GetFloatFromConfig('autopeak', 'persistence_length') #rebase: redefine the baseline + plot_linewidth = self.GetIntFromConfig('autopeak', 'plot_linewidth') + plot_size = self.GetIntFromConfig('autopeak', 'plot_size') + plot_style = self.GetStringFromConfig('autopeak', 'plot_style') rebase = self.GetBoolFromConfig('autopeak', 'rebase') reclick = self.GetBoolFromConfig('autopeak', 'reclick') slope_span = self.GetIntFromConfig('autopeak', 'auto_slope_span') @@ -115,7 +122,7 @@ class autopeakCommands: if not usepl: pl_value = None else: - pl_value = persistence_length / 10 ** 9 + pl_value = persistence_length usepoints = self.GetBoolFromConfig('autopeak', 'usepoints') whatset_str = self.GetStringFromConfig('autopeak', 'whatset') if whatset_str == 'extension': @@ -123,9 +130,6 @@ class autopeakCommands: if whatset_str == 'retraction': whatset = lh.RETRACTION - #TODO: should this be variable? - delta_force = 10 - #setup header column labels for results if fit_function == 'wlc': fit_results = lib.results.ResultsWLC() @@ -162,10 +166,10 @@ class autopeakCommands: #--Contact point arguments if reclick: - contact_point, contact_point_index = self.pickup_contact_point(filename=filename) + contact_point, contact_point_index = lh.pickup_contact_point(filename=filename) elif noauto: if self.wlccontact_index is None or self.wlccurrent != filename: - contact_point, contact_point_index = self.pickup_contact_point(filename=filename) + contact_point, contact_point_index = lh.pickup_contact_point(filename=filename) else: contact_point = self.wlccontact_point contact_point_index = self.wlccontact_index @@ -191,13 +195,13 @@ class autopeakCommands: if rebase or (self.basecurrent != filename) or self.basepoints is None: if baseline_clicks == 'automatic': self.basepoints = [] - base_index_0 = peak_location[-1] + self.fit_interval_nm(peak_location[-1], retraction.x, auto_right_baseline, False) + base_index_0 = peak_location[-1] + lh.fit_interval_nm(peak_location[-1], retraction.x, auto_right_baseline, False) self.basepoints.append(self._clickize(retraction.x, retraction.y, base_index_0)) - base_index_1 = self.basepoints[0].index + self.fit_interval_nm(self.basepoints[0].index, retraction.x, auto_left_baseline, False) + base_index_1 = self.basepoints[0].index + lh.fit_interval_nm(self.basepoints[0].index, retraction.x, auto_left_baseline, False) self.basepoints.append(self._clickize(retraction.x, retraction.y, base_index_1)) if baseline_clicks == '1 point': self.basepoints=self._measure_N_points(N=1, message='Click on 1 point to select the baseline.', whatset=whatset) - base_index_1 = self.basepoints[0].index + self.fit_interval_nm(self.basepoints[0].index, retraction.x, auto_left_baseline, False) + base_index_1 = self.basepoints[0].index + lh.fit_interval_nm(self.basepoints[0].index, retraction.x, auto_left_baseline, False) self.basepoints.append(self._clickize(retraction.x, retraction.y, base_index_1)) if baseline_clicks == '2 points': self.basepoints=self._measure_N_points(N=2, message='Click on 2 points to select the baseline.', whatset=whatset) @@ -212,7 +216,7 @@ class autopeakCommands: #WLC FITTING #define fit interval if not usepoints: - fit_points = self.fit_interval_nm(peak, retraction.x, auto_fit_nm, True) + fit_points = lh.fit_interval_nm(peak, retraction.x, auto_fit_nm, True) peak_point = self._clickize(retraction.x, retraction.y, peak) other_fit_point=self._clickize(retraction.x, retraction.y, peak - fit_points) @@ -279,6 +283,9 @@ class autopeakCommands: if len(fit_result.result) > 0: fit_result.color = color fit_result.label = fit_function + '_' + str(index) + fit_result.linewidth = plot_linewidth + fit_result.size = plot_size + fit_result.style = plot_style fit_result.title = retraction.title fit_result.units.x = retraction.units.x fit_result.units.y = retraction.units.y diff --git a/plugins/core.ini b/plugins/core.ini index bcfeb5c..763b1a6 100644 --- a/plugins/core.ini +++ b/plugins/core.ini @@ -55,3 +55,56 @@ default = False type = boolean value = False + +[preferences] + [[hide_curve_extension]] + default = False + type = boolean + value = False + + [[legend]] + default = False + type = boolean + value = False + + [[x_decimals]] + default = 2 + maximum = 10 + minimum = 0 + type = integer + value = 2 + + [[x_multiplier]] + default = n + elements = n, p + type = enum + value = n + + [[y_decimals]] + default = 2 + maximum = 10 + minimum = 0 + type = integer + value = 2 + + [[y_multiplier]] + default = n + elements = n, p + type = enum + value = n + + [[use_zero]] + default = True + type = boolean + value = True + +[copylog] + [[destination]] + default = "" + type = folder + value = + + [[use_LVDT_folder]] + default = False + type = boolean + value = False diff --git a/plugins/export.ini b/plugins/export.ini index a46f0bb..9598aa4 100644 --- a/plugins/export.ini +++ b/plugins/export.ini @@ -46,6 +46,27 @@ type = string value = _ +[notes] + [[filename]] + default = notes + type = string + value = notes + + [[folder]] + default = "" + type = folder + value = + + [[prefix]] + default = notes_ + type = string + value = notes_ + + [[use_playlist_filename]] + default = False + type = boolean + value = False + [results] [[append]] default = True diff --git a/plugins/export.py b/plugins/export.py index 171f943..3623caa 100644 --- a/plugins/export.py +++ b/plugins/export.py @@ -31,7 +31,6 @@ class exportCommands(object): ''' Exports all fitting results (if available) in a columnar ASCII format. ''' - ext = self.GetStringFromConfig('export', 'fits', 'ext') folder = self.GetStringFromConfig('export', 'fits', 'folder') prefix = self.GetStringFromConfig('export', 'fits', 'prefix') @@ -111,6 +110,33 @@ class exportCommands(object): output_file.write('\n'.join(output)) output_file.close + def do_notes(self): + ''' + Exports the note for all the files in a playlist. + ''' + filename = self.GetStringFromConfig('export', 'notes', 'filename') + folder = self.GetStringFromConfig('export', 'notes', 'folder') + prefix = self.GetStringFromConfig('export', 'notes', 'prefix') + use_playlist_filename = self.GetBoolFromConfig('export', 'notes', 'use_playlist_filename') + + playlist = self.GetActivePlaylist() + output_str = '' + for current_file in playlist.files: + output_str = ''.join([output_str, current_file.filename, ' | ', current_file.note, '\n']) + if output_str: + output_str = ''.join(['Notes taken at ', time.asctime(), '\n', playlist.filename, '\n', output_str]) + if use_playlist_filename: + path, filename = os.path.split(playlist.filename) + filename = lh.remove_extension(filename) + filename = ''.join([prefix, filename, '.txt']) + filename = os.path.join(folder, filename) + output_file = open(filename, 'w') + output_file.write(output_str) + output_file.close + else: + dialog = wx.MessageDialog(None, 'No notes found, file not saved.', 'Info', wx.OK) + dialog.ShowModal() + def do_overlay(self): ''' Exports all retraction files in a playlist with the same scale. @@ -161,7 +187,7 @@ class exportCommands(object): for row_index, row in enumerate(new_x): output_str += ''.join([str(new_x[row_index]), ', ', str(new_y[row_index]), '\n']) - if output_str != '': + if output_str: filename = ''.join([filename_prefix, current_file.name]) filename = current_file.filename.replace(current_file.name, filename) output_file = open(filename, 'w') @@ -203,15 +229,15 @@ class exportCommands(object): line_str = current_file.plot.results[key].get_result_as_string(index) line_str = ''.join([line_str, separator, current_file.filename]) output_str = ''.join([output_str, line_str, '\n']) - if output_str != '': + if output_str: output_str = ''.join(['Analysis started ', time.asctime(), '\n', output_str]) if append and os.path.isfile(filename): - output_file = open(filename,'a') - else: - output_file = open(filename, 'w') - output_file.write(output_str) - output_file.close + output_file = open(filename,'a') + else: + output_file = open(filename, 'w') + output_file.write(output_str) + output_file.close else: dialog = wx.MessageDialog(None, 'No results found, file not saved.', 'Info', wx.OK) dialog.ShowModal() diff --git a/plugins/fit.py b/plugins/fit.py index dda328f..0ba16a4 100644 --- a/plugins/fit.py +++ b/plugins/fit.py @@ -24,6 +24,8 @@ import numpy as np import scipy.stats import scipy.odr +import lib.clickedpoint + class fitCommands(object): ''' Do not use any of the following commands directly: @@ -610,7 +612,7 @@ class fitCommands(object): contact_point_index=self.wlccontact_index else: cindex=self.find_contact_point() - contact_point=lh.ClickedPoint() + contact_point = lib.clickedpoint.ClickedPoint() contact_point.absolute_coords=displayed_plot.vectors[1][0][cindex], displayed_plot.vectors[1][1][cindex] contact_point.find_graph_coords(displayed_plot.vectors[1][0], displayed_plot.vectors[1][1]) contact_point.is_marker=True @@ -796,7 +798,7 @@ class fitCommands(object): return xext,ysub,contact #now, exploit a ClickedPoint instance to calculate index... - dummy=lh.ClickedPoint() + dummy=lib.clickedpoint.ClickedPoint() dummy.absolute_coords=(x_intercept,y_intercept) dummy.find_graph_coords(xret2,yret) diff --git a/plugins/flatfilts.ini b/plugins/flatfilts.ini index 3a0168b..110e565 100644 --- a/plugins/flatfilts.ini +++ b/plugins/flatfilts.ini @@ -15,7 +15,6 @@ [[convolution]] default = "[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]" type = string - #value = '[6.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]' value = "[6.0,-1.0,-1.0,-1.0,-1.0,-1.0,-1.0]" [[maxcut]] @@ -31,7 +30,7 @@ maximum = 100 minimum = 0 type = integer - value = 2 + value = 7 [[mindeviation]] default = 5 @@ -79,24 +78,24 @@ maximum = 10000 minimum = 1 type = integer - value = 7 + value = 4 [[min_deviation]] default = 9 maximum = 100 minimum = 1 type = float - value = 10 + value = 9 [peaks] [[color]] default = black type = color - value = "(128,128,128)" + value = "(0,0,0)" [[size]] default = 20 maximum = 10000 minimum = 1 type = integer - value = 50 + value = 20 diff --git a/plugins/flatfilts.py b/plugins/flatfilts.py index 9b540fd..351dada 100644 --- a/plugins/flatfilts.py +++ b/plugins/flatfilts.py @@ -132,9 +132,6 @@ class flatfiltsCommands: def has_peaks(self, plot=None, plugin=None): ''' Finds peak position in a force curve. - FIXME: should be moved to peakspot.py - #TODO: should this really be moved? this is obviously tied into flatfilts/convfilt - #flatfilts.py is where 'has_peaks' belongs ''' if plugin is None: @@ -284,7 +281,14 @@ class flatfiltsCommands: current_file.peak_size = peak_size features.append(file_index - 1) - #TODO: warn when flatten is not applied? + #Warn that no flattening had been done. + if not self.HasPlotmanipulator('plotmanip_flatten'): + self.AppendToOutput('Flatten manipulator was not found. Processing was done without flattening.') + else: + if not self.AppliesPlotmanipulator('flatten'): + self.AppendToOutput('Flatten manipulator was not applied.') + self.AppendToOutput('Try to enable the flatten plotmanipulator for better results.') + if not features: self.AppendToOutput('Found nothing interesting. Check the playlist, could be a bug or criteria could be too stringent.') else: diff --git a/plugins/generalvclamp.py b/plugins/generalvclamp.py index 749b9ef..c0b5750 100644 --- a/plugins/generalvclamp.py +++ b/plugins/generalvclamp.py @@ -5,7 +5,8 @@ generalvclamp.py Plugin regarding general velocity clamp measurements -Copyright 2008 by Massimo Sandal (University of Bologna, Italy) +Copyright 2008 by Massimo Sandal, Fabrizio Benedetti, Marco Brucale, Bruno Samori (University of Bologna, Italy), +and Alberto Gomez-Casado (University of Twente) with modifications by Dr. Rolf Schmidt (Concordia University, Canada) This program is released under the GNU General Public License version 2. diff --git a/plugins/plot.ini b/plugins/plot.ini index f84bba6..0c0264e 100644 --- a/plugins/plot.ini +++ b/plugins/plot.ini @@ -9,7 +9,7 @@ maximum = 10 minimum = 0 type = integer - value = 1 + value = 2 [[x_multiplier]] default = n diff --git a/plugins/plot.py b/plugins/plot.py index 0e8db7a..fca35ff 100644 --- a/plugins/plot.py +++ b/plugins/plot.py @@ -13,4 +13,12 @@ This program is released under the GNU General Public License version 2. class plotCommands: def do_plot(self): + active_file = self.GetActiveFile() + for curve in active_file.plot.curves: + curve.decimals.x = self.GetIntFromConfig('plot', 'x_decimals') + curve.decimals.y = self.GetIntFromConfig('plot', 'y_decimals') + curve.legend = self.GetBoolFromConfig('plot', 'legend') + curve.multiplier.x = self.GetStringFromConfig('plot', 'x_multiplier') + curve.multiplier.y = self.GetStringFromConfig('plot', 'y_multiplier') + self.UpdatePlot(); diff --git a/plugins/procplots.ini b/plugins/procplots.ini index 90c11e1..42e3aa5 100644 --- a/plugins/procplots.ini +++ b/plugins/procplots.ini @@ -74,7 +74,7 @@ default = retraction elements = extension, retraction, both type = enum - value = both + value = retraction [procplots] [[centerzero]] diff --git a/plugins/procplots.py b/plugins/procplots.py index 222caed..ac36993 100644 --- a/plugins/procplots.py +++ b/plugins/procplots.py @@ -20,6 +20,7 @@ from numpy import arange, diff, fft, median from scipy.signal import medfilt from lib.peakspot import conv_dx +import lib.prettyformat class procplotsCommands: @@ -69,7 +70,6 @@ class procplotsCommands: self.UpdatePlot(plot) - def do_derivplot(self): ''' Plots the discrete differentiation of the currently displayed force curve. @@ -117,7 +117,6 @@ class procplotsCommands: ------- Syntax: subtplot ''' - #TODO: what is sub_filter supposed to do? #TODO: add option to keep previous subtplot plot = self.GetDisplayedPlotCorrected() @@ -273,10 +272,17 @@ class procplotsCommands: for index in whatset: fft_curve = self.fft_plot(copy.deepcopy(plot.curves[index]), boundaries) + fft_curve.decimals.x = 3 + fft_curve.decimals.y = 0 fft_curve.destination.column = column fft_curve.destination.row = row + fft_curve.label = plot.curves[index].label + fft_curve.legend = True + fft_curve.multiplier.x = lib.prettyformat.get_prefix(max(fft_curve.x)) + fft_curve.multiplier.y = lib.prettyformat.get_prefix(max(fft_curve.y)) + #fft_curve.multiplier.y = '' fft_curve.title = 'FFT' - fft_curve.units.x = 'frequency' + fft_curve.units.x = 'Hz' fft_curve.units.y = 'power' plot.curves.append(fft_curve)