PYME-extra 1.0.4.post0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PYMEcs/Acquire/Actions/__init__.py +0 -0
- PYMEcs/Acquire/Actions/custom.py +167 -0
- PYMEcs/Acquire/Hardware/LPthreadedSimple.py +248 -0
- PYMEcs/Acquire/Hardware/LPthreadedSimpleSim.py +246 -0
- PYMEcs/Acquire/Hardware/NikonTiFlaskServer.py +45 -0
- PYMEcs/Acquire/Hardware/NikonTiFlaskServerT.py +59 -0
- PYMEcs/Acquire/Hardware/NikonTiRESTClient.py +73 -0
- PYMEcs/Acquire/Hardware/NikonTiSim.py +35 -0
- PYMEcs/Acquire/Hardware/__init__.py +0 -0
- PYMEcs/Acquire/Hardware/driftTrackGUI.py +329 -0
- PYMEcs/Acquire/Hardware/driftTrackGUI_n.py +472 -0
- PYMEcs/Acquire/Hardware/driftTracking.py +424 -0
- PYMEcs/Acquire/Hardware/driftTracking_n.py +433 -0
- PYMEcs/Acquire/Hardware/fakeCamX.py +15 -0
- PYMEcs/Acquire/Hardware/offsetPiezoRESTCorrelLog.py +38 -0
- PYMEcs/Acquire/__init__.py +0 -0
- PYMEcs/Analysis/MBMcollection.py +552 -0
- PYMEcs/Analysis/MINFLUX.py +280 -0
- PYMEcs/Analysis/MapUtils.py +77 -0
- PYMEcs/Analysis/NPC.py +1176 -0
- PYMEcs/Analysis/Paraflux.py +218 -0
- PYMEcs/Analysis/Simpler.py +81 -0
- PYMEcs/Analysis/Sofi.py +140 -0
- PYMEcs/Analysis/__init__.py +0 -0
- PYMEcs/Analysis/decSofi.py +211 -0
- PYMEcs/Analysis/eventProperties.py +50 -0
- PYMEcs/Analysis/fitDarkTimes.py +569 -0
- PYMEcs/Analysis/objectVolumes.py +20 -0
- PYMEcs/Analysis/offlineTracker.py +130 -0
- PYMEcs/Analysis/stackTracker.py +180 -0
- PYMEcs/Analysis/timeSeries.py +63 -0
- PYMEcs/Analysis/trackFiducials.py +186 -0
- PYMEcs/Analysis/zerocross.py +91 -0
- PYMEcs/IO/MINFLUX.py +851 -0
- PYMEcs/IO/NPC.py +117 -0
- PYMEcs/IO/__init__.py +0 -0
- PYMEcs/IO/darkTimes.py +19 -0
- PYMEcs/IO/picasso.py +219 -0
- PYMEcs/IO/tabular.py +11 -0
- PYMEcs/__init__.py +0 -0
- PYMEcs/experimental/CalcZfactor.py +51 -0
- PYMEcs/experimental/FRC.py +338 -0
- PYMEcs/experimental/ImageJROItools.py +49 -0
- PYMEcs/experimental/MINFLUX.py +1537 -0
- PYMEcs/experimental/NPCcalcLM.py +560 -0
- PYMEcs/experimental/Simpler.py +369 -0
- PYMEcs/experimental/Sofi.py +78 -0
- PYMEcs/experimental/__init__.py +0 -0
- PYMEcs/experimental/binEventProperty.py +187 -0
- PYMEcs/experimental/chaining.py +23 -0
- PYMEcs/experimental/clusterTrack.py +179 -0
- PYMEcs/experimental/combine_maps.py +104 -0
- PYMEcs/experimental/eventProcessing.py +93 -0
- PYMEcs/experimental/fiducials.py +323 -0
- PYMEcs/experimental/fiducialsNew.py +402 -0
- PYMEcs/experimental/mapTools.py +271 -0
- PYMEcs/experimental/meas2DplotDh5view.py +107 -0
- PYMEcs/experimental/mortensen.py +131 -0
- PYMEcs/experimental/ncsDenoise.py +158 -0
- PYMEcs/experimental/onTimes.py +295 -0
- PYMEcs/experimental/procPoints.py +77 -0
- PYMEcs/experimental/pyme2caml.py +73 -0
- PYMEcs/experimental/qPAINT.py +965 -0
- PYMEcs/experimental/randMap.py +188 -0
- PYMEcs/experimental/regExtraCmaps.py +11 -0
- PYMEcs/experimental/selectROIfilterTable.py +72 -0
- PYMEcs/experimental/showErrs.py +51 -0
- PYMEcs/experimental/showErrsDh5view.py +58 -0
- PYMEcs/experimental/showShiftMap.py +56 -0
- PYMEcs/experimental/snrEvents.py +188 -0
- PYMEcs/experimental/specLabeling.py +51 -0
- PYMEcs/experimental/splitRender.py +246 -0
- PYMEcs/experimental/testChannelByName.py +36 -0
- PYMEcs/experimental/timedSpecies.py +28 -0
- PYMEcs/experimental/utils.py +31 -0
- PYMEcs/misc/ExtraCmaps.py +177 -0
- PYMEcs/misc/__init__.py +0 -0
- PYMEcs/misc/configUtils.py +169 -0
- PYMEcs/misc/guiMsgBoxes.py +27 -0
- PYMEcs/misc/mapUtils.py +230 -0
- PYMEcs/misc/matplotlib.py +136 -0
- PYMEcs/misc/rectsFromSVG.py +182 -0
- PYMEcs/misc/shellutils.py +1110 -0
- PYMEcs/misc/utils.py +205 -0
- PYMEcs/misc/versionCheck.py +20 -0
- PYMEcs/misc/zcInfo.py +90 -0
- PYMEcs/pyme_warnings.py +4 -0
- PYMEcs/recipes/__init__.py +0 -0
- PYMEcs/recipes/base.py +75 -0
- PYMEcs/recipes/localisations.py +2380 -0
- PYMEcs/recipes/manipulate_yaml.py +83 -0
- PYMEcs/recipes/output.py +177 -0
- PYMEcs/recipes/processing.py +247 -0
- PYMEcs/recipes/simpler.py +290 -0
- PYMEcs/version.py +2 -0
- pyme_extra-1.0.4.post0.dist-info/METADATA +114 -0
- pyme_extra-1.0.4.post0.dist-info/RECORD +101 -0
- pyme_extra-1.0.4.post0.dist-info/WHEEL +5 -0
- pyme_extra-1.0.4.post0.dist-info/entry_points.txt +3 -0
- pyme_extra-1.0.4.post0.dist-info/licenses/LICENSE +674 -0
- pyme_extra-1.0.4.post0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from traits.api import HasTraits, Str, Int, CStr, List, Enum, Float
|
|
2
|
+
|
|
3
|
+
class clusterParam(HasTraits):
|
|
4
|
+
Ryrminsize = Float(1.0)
|
|
5
|
+
|
|
6
|
+
class Meas2DPlotter:
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, dsviewer):
|
|
11
|
+
self.dsviewer = dsviewer
|
|
12
|
+
dsviewer.AddMenuItem('Experimental>Meas2D', 'Plot cluster measurements', self.OnPlotClust)
|
|
13
|
+
dsviewer.AddMenuItem('Experimental>Meas2D', 'Plot per cluster colocalisation', self.OnClusterColoc)
|
|
14
|
+
self.clusterPar = clusterParam() # initialise parameter selector
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def OnPlotClust(self, event=None):
|
|
18
|
+
nodata = False
|
|
19
|
+
try:
|
|
20
|
+
pipeline = self.dsviewer.pipeline
|
|
21
|
+
except AttributeError:
|
|
22
|
+
nodata = True
|
|
23
|
+
else:
|
|
24
|
+
if 'area' not in pipeline.keys():
|
|
25
|
+
nodata = True
|
|
26
|
+
if nodata:
|
|
27
|
+
print('no area column found - returning')
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
if not self.clusterPar.configure_traits(kind='modal'):
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
mdh = self.dsviewer.image.mdh
|
|
34
|
+
vx = 1e3*mdh['voxelsize.x']
|
|
35
|
+
vy = 1e3*mdh['voxelsize.y']
|
|
36
|
+
RyRsz = 900.0 # RyR footprint in nm
|
|
37
|
+
ryrmin = self.clusterPar.Ryrminsize # minimal size of cluster to check
|
|
38
|
+
|
|
39
|
+
ryrsall = pipeline['area']*vx*vy/RyRsz
|
|
40
|
+
ryrs = ryrsall[ryrsall > ryrmin]
|
|
41
|
+
ryrmean = ryrs.mean()
|
|
42
|
+
|
|
43
|
+
import matplotlib.pyplot as plt
|
|
44
|
+
# plot data and fitted curves
|
|
45
|
+
plt.figure()
|
|
46
|
+
plt.hist(ryrs, bins=20)
|
|
47
|
+
plt.xlabel('RyRs')
|
|
48
|
+
plt.ylabel('Frequency')
|
|
49
|
+
plt.title('RyR size distribution (mean %.1f RyRs, %d clusters)' % (ryrmean,ryrs.shape[0]))
|
|
50
|
+
plt.show()
|
|
51
|
+
|
|
52
|
+
def OnClusterColoc(self, event=None):
|
|
53
|
+
nodata = False
|
|
54
|
+
try:
|
|
55
|
+
pipeline = self.dsviewer.pipeline
|
|
56
|
+
except AttributeError:
|
|
57
|
+
nodata = True
|
|
58
|
+
else:
|
|
59
|
+
if 'area' not in pipeline.keys():
|
|
60
|
+
nodata = True
|
|
61
|
+
if 'mean_intensity' not in pipeline.keys():
|
|
62
|
+
nodata = True
|
|
63
|
+
if nodata:
|
|
64
|
+
print('no area column found - returning')
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
if not self.clusterPar.configure_traits(kind='modal'):
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
mdh = self.dsviewer.image.mdh
|
|
71
|
+
vx = 1e3*mdh['voxelsize.x']
|
|
72
|
+
vy = 1e3*mdh['voxelsize.y']
|
|
73
|
+
RyRsz = 900.0 # RyR footprint in nm
|
|
74
|
+
ryrmin = self.clusterPar.Ryrminsize # minimal size of cluster to check
|
|
75
|
+
|
|
76
|
+
ryrs = pipeline['area']*vx*vy/RyRsz
|
|
77
|
+
ryrgtmin = ryrs > ryrmin
|
|
78
|
+
|
|
79
|
+
import matplotlib.pyplot as plt
|
|
80
|
+
import numpy as np
|
|
81
|
+
plt.figure()
|
|
82
|
+
plt.scatter(ryrs[ryrgtmin],pipeline['mean_intensity'][ryrgtmin],
|
|
83
|
+
facecolors='lightgray', edgecolors='black')
|
|
84
|
+
plt.xlabel('RyRs')
|
|
85
|
+
plt.ylabel('fractional coloc')
|
|
86
|
+
plt.title('area fraction of IP3R in RyR clusters')
|
|
87
|
+
plt.ylim(0,1)
|
|
88
|
+
|
|
89
|
+
fracs1 = pipeline['mean_intensity'][ryrgtmin]
|
|
90
|
+
meanclc = fracs1.mean()
|
|
91
|
+
#print('mean fraction colocalising per cluster (RyRs>5): %.2f' % meanclc)
|
|
92
|
+
|
|
93
|
+
positive1 = (fracs1 > 0.05).sum()/float(fracs1.shape[0])
|
|
94
|
+
#print('fraction with positive staining: %.2f' % positive1)
|
|
95
|
+
|
|
96
|
+
plt.figure()
|
|
97
|
+
#mbins = np.arange(0,10,1.0)/10.0
|
|
98
|
+
h0 = plt.hist(pipeline['mean_intensity'][ryrgtmin],bins=10)
|
|
99
|
+
plt.title('mean coloc frac %.2f, cluster frac positive %.2f' % (meanclc,positive1))
|
|
100
|
+
plt.xlabel('coloc fraction per cluster')
|
|
101
|
+
plt.ylabel('frequency')
|
|
102
|
+
plt.show()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def Plug(dsviewer):
|
|
106
|
+
"""Plugs this module into the gui"""
|
|
107
|
+
dsviewer.meas2Dplt = Meas2DPlotter(dsviewer)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
# you may need a lot more imports depending what functionality your require in your plugin
|
|
3
|
+
|
|
4
|
+
from traits.api import HasTraits, Str, Int, CStr, List, Enum, Float
|
|
5
|
+
#from traitsui.api import View, Item, Group
|
|
6
|
+
#from traitsui.menu import OKButton, CancelButton, OKCancelButtons
|
|
7
|
+
|
|
8
|
+
from PYMEcs.misc.guiMsgBoxes import Warn
|
|
9
|
+
import PYMEcs.misc.shellutils as su
|
|
10
|
+
|
|
11
|
+
class PlotOptions(HasTraits):
|
|
12
|
+
plotMode = Enum(('Compare with and without background',
|
|
13
|
+
'Colour errors by photon number',
|
|
14
|
+
'Scatter Density Plot',
|
|
15
|
+
'Plot z errors (colour by photon number)'))
|
|
16
|
+
|
|
17
|
+
# We use the formula (S30) from Mortensen et al, 2010 [1] which provides a nice closed-form expression for the localisation error;
|
|
18
|
+
# note that this is likely a lower bound on actual error due to simplifying model assumptions (no read noise etc)
|
|
19
|
+
|
|
20
|
+
# Reference
|
|
21
|
+
|
|
22
|
+
# 1. Optimized localization analysis for single-molecule tracking and super-resolution microscopy.
|
|
23
|
+
# Kim I Mortensen, L Stirling Churchman, James A Spudich, and Henrik Flyvbjerg.
|
|
24
|
+
# Nat Meth, 2017 vol. 18 (5) pp. 377-381.
|
|
25
|
+
# http://www.nature.com/doifinder/10.1038/nmeth.1447
|
|
26
|
+
|
|
27
|
+
class MortensenFormula:
|
|
28
|
+
"""
|
|
29
|
+
A plugin that calculates errors according to the Mortensen formula using photon number and background
|
|
30
|
+
estimates that event analysis has provided.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, visFr):
|
|
33
|
+
self.visFr = visFr
|
|
34
|
+
self.pipeline = visFr.pipeline
|
|
35
|
+
self.plotOptions = PlotOptions()
|
|
36
|
+
|
|
37
|
+
visFr.AddMenuItem('Experimental>ExtraColumns>Errors', 'Add Mortensen Formula', self.OnAddMort,
|
|
38
|
+
helpText='Add an event property that provides an estimate by the Mortensen Formula (from background and amplitude)')
|
|
39
|
+
visFr.AddMenuItem('Experimental>ExtraColumns>Errors', 'Plot Mortensen Error', self.OnPlotMort,
|
|
40
|
+
helpText='Scatterplot estimate by the Mortensen Formula')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def OnAddMort(self, event=None):
|
|
44
|
+
"""
|
|
45
|
+
this function adds a 'mortensenError' property to events - there could be some discussion how that is actually best calculated
|
|
46
|
+
"""
|
|
47
|
+
import math
|
|
48
|
+
mdh = self.pipeline.mdh
|
|
49
|
+
# the formula below is very adhoc
|
|
50
|
+
# I am not even sure this is remotely correct nor the best way
|
|
51
|
+
# so use only as a basis for experimentation and/or better plugins
|
|
52
|
+
N = self.pipeline['nPhotons']
|
|
53
|
+
# we think we do not need to subtract the camera offset
|
|
54
|
+
Nb = mdh['Camera.ElectronsPerCount'] * np.maximum(0,self.pipeline['fitResults_background'] / mdh['Camera.TrueEMGain'])
|
|
55
|
+
a = 1e3*mdh['voxelsize.x']
|
|
56
|
+
siga = np.sqrt(self.pipeline['sig']**2+a*a/12.0)
|
|
57
|
+
|
|
58
|
+
emort = siga /np.sqrt(N) * np.sqrt(16.0/9.0 + 8*math.pi*siga*siga*Nb/(N*a*a))
|
|
59
|
+
emort_nobg = siga /np.sqrt(N) * np.sqrt(16.0/9.0)
|
|
60
|
+
|
|
61
|
+
cb = 1.0*Nb/N
|
|
62
|
+
self.pipeline.addColumn('cb_estimate', cb)
|
|
63
|
+
self.pipeline.addColumn('mortensenError',emort)
|
|
64
|
+
self.pipeline.addColumn('mortensenErrorNoBG',emort_nobg)
|
|
65
|
+
self.pipeline.addColumn('backgroundPhotons',Nb)
|
|
66
|
+
|
|
67
|
+
self.pipeline.Rebuild()
|
|
68
|
+
self.visFr.CreateFoldPanel() # to make, for example, new columns show up in filter column selections
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def OnPlotMort(self, event=None):
|
|
72
|
+
import matplotlib.pyplot as plt
|
|
73
|
+
pipeline = self.pipeline
|
|
74
|
+
|
|
75
|
+
err = pipeline['mortensenError']
|
|
76
|
+
err1 = np.percentile(err,1)
|
|
77
|
+
err99 = np.percentile(err,99)
|
|
78
|
+
|
|
79
|
+
errnbg = pipeline['mortensenErrorNoBG']
|
|
80
|
+
errnbg1 = np.percentile(errnbg,1)
|
|
81
|
+
errnbg99 = np.percentile(errnbg,99)
|
|
82
|
+
|
|
83
|
+
popt = self.plotOptions
|
|
84
|
+
if popt.configure_traits(kind='modal'):
|
|
85
|
+
if popt.plotMode == 'Compare with and without background':
|
|
86
|
+
plt.figure()
|
|
87
|
+
ebg = plt.scatter(pipeline['error_x'],pipeline['mortensenError'],
|
|
88
|
+
c='g',alpha=0.5)
|
|
89
|
+
enobg = plt.scatter(pipeline['error_x'],pipeline['mortensenErrorNoBG'],
|
|
90
|
+
c='r',alpha=0.5)
|
|
91
|
+
plt.legend((ebg,enobg),('error with bg','error assuming zero bg'))
|
|
92
|
+
plt.plot([errnbg1,err99],[errnbg1,err99])
|
|
93
|
+
plt.xlabel('Fit error x')
|
|
94
|
+
plt.ylabel('Error from Mortensen Formula')
|
|
95
|
+
elif popt.plotMode == 'Colour errors by photon number':
|
|
96
|
+
nph = pipeline['nPhotons']
|
|
97
|
+
nph5 = np.percentile(nph,5)
|
|
98
|
+
nph95 = np.percentile(nph,95)
|
|
99
|
+
plt.figure()
|
|
100
|
+
plt.scatter(pipeline['error_x'],pipeline['mortensenError'],
|
|
101
|
+
c=nph,vmin=nph5,vmax=nph95,cmap=plt.cm.jet)
|
|
102
|
+
plt.plot([err1,err99],[err1,err99])
|
|
103
|
+
plt.xlabel('Fit error x')
|
|
104
|
+
plt.ylabel('Error from Mortensen Formula')
|
|
105
|
+
plt.title('error coloured with nPhotons')
|
|
106
|
+
plt.colorbar()
|
|
107
|
+
elif popt.plotMode == 'Scatter Density Plot':
|
|
108
|
+
plt.figure()
|
|
109
|
+
su.scatterdens(pipeline['error_x'],pipeline['mortensenError'],
|
|
110
|
+
subsample=0.2, xlabel='Fit error x',
|
|
111
|
+
ylabel='Error from Mortensen Formula',s=20)
|
|
112
|
+
plt.plot([err1,err99],[err1,err99])
|
|
113
|
+
elif popt.plotMode == 'Plot z errors (colour by photon number)':
|
|
114
|
+
if 'fitError_z0' not in pipeline.keys():
|
|
115
|
+
Warn('No z error - works only with fitError_z0 property')
|
|
116
|
+
return
|
|
117
|
+
nph = pipeline['nPhotons']
|
|
118
|
+
nph5 = np.percentile(nph,5)
|
|
119
|
+
nph95 = np.percentile(nph,95)
|
|
120
|
+
plt.figure()
|
|
121
|
+
plt.scatter(pipeline['fitError_z0'],pipeline['mortensenError'],
|
|
122
|
+
c=nph,vmin=nph5,vmax=nph95,cmap=plt.cm.jet)
|
|
123
|
+
plt.plot([err1,err99],[err1,err99])
|
|
124
|
+
plt.xlabel('Fit error z')
|
|
125
|
+
plt.ylabel('Error from Mortensen Formula')
|
|
126
|
+
plt.title('error coloured with nPhotons')
|
|
127
|
+
plt.colorbar()
|
|
128
|
+
|
|
129
|
+
def Plug(visFr):
|
|
130
|
+
"""Plugs this module into the gui"""
|
|
131
|
+
visFr.mortForm = MortensenFormula(visFr)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from PYME.DSView import ViewIm3D
|
|
2
|
+
from PYME.localization.remFitBuf import CameraInfoManager
|
|
3
|
+
|
|
4
|
+
# the NCS functionality needs the pyNCS in the path, either linked
|
|
5
|
+
# into the PYMEcs.Analysis directory (e.g. via symlink) or as pyNCS in the PYTHONPATH
|
|
6
|
+
# the code can be obtained from https://github.com/HuanglabPurdue/NCS,
|
|
7
|
+
# in the python3-6 directory
|
|
8
|
+
# my testing shows that the implementation runs fine under python-2.7
|
|
9
|
+
try:
|
|
10
|
+
import PYMEcs.Analysis.pyNCS.denoisetools as ncs
|
|
11
|
+
except ImportError:
|
|
12
|
+
try:
|
|
13
|
+
import pyNCS.denoisetools as ncs
|
|
14
|
+
except ImportError:
|
|
15
|
+
ncs = None
|
|
16
|
+
|
|
17
|
+
from traits.api import HasTraits, Str, Int, CStr, List, Enum, Float
|
|
18
|
+
from traitsui.api import View, Item, Group
|
|
19
|
+
from traitsui.menu import OKButton, CancelButton, OKCancelButtons
|
|
20
|
+
|
|
21
|
+
from PYMEcs.misc.guiMsgBoxes import Warn
|
|
22
|
+
|
|
23
|
+
from PYMEcs.misc.mapUtils import check_mapexists
|
|
24
|
+
|
|
25
|
+
class ncsSelect(HasTraits):
|
|
26
|
+
winSize = Enum(64,128,256)
|
|
27
|
+
Rs = Int(8) # IMPORTANT: looks like the pyncs code assumes Rs is a divider of the imgsz (see imagsz2 below)!!
|
|
28
|
+
Lambda_nm = Float(690) # emission wavelength
|
|
29
|
+
NA = Float(1.49) # objective NA
|
|
30
|
+
iterations = Int(15)
|
|
31
|
+
alpha = Float(0.2)
|
|
32
|
+
|
|
33
|
+
class ncsDenoiser:
|
|
34
|
+
"""
|
|
35
|
+
GUI class to supply various map tools
|
|
36
|
+
"""
|
|
37
|
+
def __init__(self, dsviewer):
|
|
38
|
+
self.dsviewer = dsviewer
|
|
39
|
+
self.do = dsviewer.do
|
|
40
|
+
self.image = dsviewer.image
|
|
41
|
+
self.ncsSel = ncsSelect() # by making it part of the object we retain parameters across invocations
|
|
42
|
+
self.ci = CameraInfoManager()
|
|
43
|
+
|
|
44
|
+
dsviewer.AddMenuItem('Experimental',
|
|
45
|
+
'NCS denoising of small square ROI',
|
|
46
|
+
self.OnNCSDenoise)
|
|
47
|
+
|
|
48
|
+
def OnNCSDenoise(self, event=None):
|
|
49
|
+
ncsSel = self.ncsSel
|
|
50
|
+
if not ncsSel.configure_traits(kind='modal'):
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
mdh = self.image.mdh
|
|
54
|
+
img = self.image.data[:,:, self.do.zp, 0].squeeze() # current frame
|
|
55
|
+
|
|
56
|
+
# code below from pyncs.addnoise
|
|
57
|
+
# I is presumably average photon number; bg is background photons/pixel
|
|
58
|
+
# idealimg = np.abs(normimg)*I+bg
|
|
59
|
+
# poissonimg = np.random.poisson(idealimg)
|
|
60
|
+
|
|
61
|
+
# UNITS: scmosimg (ADUs) = electrons * [gain-as-ADU/electrons / flatfield] + readnoise(ADUs) + offset
|
|
62
|
+
# from which follows gain_for_pyNCS = [gain-as-ADU/electrons / flatfield]
|
|
63
|
+
# scmosimg = poissonimg*gainmap + np.sqrt(varmap)*np.random.randn(R,R)
|
|
64
|
+
# scmosimg += offset
|
|
65
|
+
|
|
66
|
+
if check_mapexists(mdh,'dark') is None:
|
|
67
|
+
Warn(None, 'ncsimage: no dark map found')
|
|
68
|
+
return
|
|
69
|
+
else:
|
|
70
|
+
dark = self.ci.getDarkMap(mdh)
|
|
71
|
+
|
|
72
|
+
if check_mapexists(mdh,'variance') is None:
|
|
73
|
+
Warn(None, 'ncsimage: no variance map found')
|
|
74
|
+
return
|
|
75
|
+
else:
|
|
76
|
+
# we need to convert to units of ADU^2
|
|
77
|
+
variance = self.ci.getVarianceMap(mdh)/(mdh['Camera.ElectronsPerCount']**2)
|
|
78
|
+
|
|
79
|
+
if check_mapexists(mdh,'flatfield') is None:
|
|
80
|
+
gain = 1.0/mdh['Camera.ElectronsPerCount']*np.ones_like(variance)
|
|
81
|
+
else:
|
|
82
|
+
# code above argues we need to divide by flatfield
|
|
83
|
+
gain = 1.0/(mdh['Camera.ElectronsPerCount']*self.ci.getFlatfieldMap(mdh))
|
|
84
|
+
|
|
85
|
+
# the slice code needs a little bit of further checking
|
|
86
|
+
isz = ncsSel.winSize
|
|
87
|
+
imshape = img.shape
|
|
88
|
+
xstart = self.do.xp - isz/2
|
|
89
|
+
if xstart < 0:
|
|
90
|
+
xstart = imshape[0]/2 - isz/2
|
|
91
|
+
ystart = self.do.yp - isz/2
|
|
92
|
+
if ystart < 0:
|
|
93
|
+
ystart = imshape[1]/2 - isz/2
|
|
94
|
+
|
|
95
|
+
sliceSquare = np.s_[xstart:xstart+isz,ystart:ystart+isz] # either on crosshairs or in the centre
|
|
96
|
+
roi = [[xstart,xstart+isz],[ystart,ystart+isz],[self.do.zp,self.do.zp]]
|
|
97
|
+
|
|
98
|
+
var_sl = variance[sliceSquare]
|
|
99
|
+
dark_sl = dark[sliceSquare]
|
|
100
|
+
gain_sl = gain[sliceSquare]
|
|
101
|
+
|
|
102
|
+
# next few lines from NCSdemo_experiment which show how raw cmos data is pre-corrected
|
|
103
|
+
# apply gain and offset correction
|
|
104
|
+
# N = subims.shape[0]
|
|
105
|
+
# imsd = (subims-np.tile(suboffset,(N,1,1)))/np.tile(subgain,(N,1,1))
|
|
106
|
+
# imsd[imsd<=0] = 1e-6
|
|
107
|
+
|
|
108
|
+
# therefore this needs to be in photoelectrons
|
|
109
|
+
imgcorr = mdh['Camera.ElectronsPerCount']*self.ci.correctImage(mdh, img)
|
|
110
|
+
imgc_sl = imgcorr[sliceSquare].squeeze()
|
|
111
|
+
|
|
112
|
+
imgc_sl_T = imgc_sl[:,:,None].transpose((2,0,1))
|
|
113
|
+
ret = np.clip(imgc_sl_T,1e-6,None,out=imgc_sl_T) # clip inplace at 1e-6
|
|
114
|
+
|
|
115
|
+
Rs = ncsSel.Rs # IMPORTANT: looks like the pyncs code assumes Rs is a divider of the imgsz (see imagsz2 below)!!
|
|
116
|
+
Pixelsize = mdh['voxelsize.x']
|
|
117
|
+
Lambda = ncsSel.Lambda_nm / 1e3 # emission wavelength
|
|
118
|
+
NA = ncsSel.NA # objective NA
|
|
119
|
+
iterationN = ncsSel.iterations
|
|
120
|
+
alpha = ncsSel.alpha
|
|
121
|
+
|
|
122
|
+
if ncs is not None:
|
|
123
|
+
out = ncs.reducenoise(Rs,imgc_sl_T,var_sl,gain_sl,isz,Pixelsize,NA,Lambda,alpha,iterationN)
|
|
124
|
+
else:
|
|
125
|
+
out = imgc_sl_T # no reduction performed
|
|
126
|
+
|
|
127
|
+
# now we need code to show this image and make it possible to save that
|
|
128
|
+
disp_img = np.dstack([imgc_sl_T.squeeze(), out.squeeze()])
|
|
129
|
+
im = ImageStack(disp_img, titleStub = '%d pixel crop of Frame %d denoised' % (isz,self.do.zp))
|
|
130
|
+
im.mdh.copyEntriesFrom(mdh)
|
|
131
|
+
|
|
132
|
+
# NCS parameters and crop info
|
|
133
|
+
im.mdh['Parent'] = self.image.filename
|
|
134
|
+
im.mdh['Processing.Units'] = 'PhotoElectrons'
|
|
135
|
+
im.mdh['Processing.Type'] = 'NCS Denoising'
|
|
136
|
+
im.mdh['Processing.NCS.alpha'] = ncsSel.alpha
|
|
137
|
+
im.mdh['Processing.NCS.iterations'] = ncsSel.iterations
|
|
138
|
+
im.mdh['Processing.NCS.Rs'] = ncsSel.Rs
|
|
139
|
+
im.mdh['Processing.NCS.LambdaNM'] = ncsSel.Lambda_nm
|
|
140
|
+
im.mdh['Processing.NCS.NA'] = ncsSel.NA
|
|
141
|
+
im.mdh['Processing.CropROI'] = roi
|
|
142
|
+
im.mdh['Processing.Comment'] = 'First frame: original (photoelectrons), second frame: denoised'
|
|
143
|
+
|
|
144
|
+
vx, vy, vz = self.image.voxelsize
|
|
145
|
+
ox, oy, oz = self.image.origin
|
|
146
|
+
im.mdh['Origin.x'] = ox + roi[0][0]*vx
|
|
147
|
+
im.mdh['Origin.y'] = oy + roi[1][0]*vy
|
|
148
|
+
im.mdh['Origin.z'] = oz
|
|
149
|
+
|
|
150
|
+
if self.dsviewer.mode == 'visGUI':
|
|
151
|
+
mode = 'visGUI'
|
|
152
|
+
else:
|
|
153
|
+
mode = 'lite'
|
|
154
|
+
|
|
155
|
+
dv = ViewIm3D(im, mode=mode, glCanvas=self.dsviewer.glCanvas, parent=wx.GetTopLevelParent(self.dsviewer))
|
|
156
|
+
|
|
157
|
+
def Plug(dsviewer):
|
|
158
|
+
dsviewer.ncsDenoiser = ncsDenoiser(dsviewer)
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import wx
|
|
2
|
+
import numpy as np
|
|
3
|
+
import sys
|
|
4
|
+
from scipy import ndimage
|
|
5
|
+
from PYMEcs.misc.guiMsgBoxes import Warn, Info
|
|
6
|
+
|
|
7
|
+
from traits.api import HasTraits, Str, Int, CStr, List, Enum, Float
|
|
8
|
+
from traitsui.api import View, Item, Group
|
|
9
|
+
from traitsui.menu import OKButton, CancelButton, OKCancelButtons
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
logger = logging.getLogger(__file__)
|
|
14
|
+
|
|
15
|
+
class propertyChoice(HasTraits):
|
|
16
|
+
clist = List([])
|
|
17
|
+
EventProperty = Enum(values='clist')
|
|
18
|
+
|
|
19
|
+
traits_view = View(Group(Item(name = 'EventProperty'),
|
|
20
|
+
show_border = True),
|
|
21
|
+
buttons = OKCancelButtons)
|
|
22
|
+
|
|
23
|
+
def add_channels(self,chans):
|
|
24
|
+
for chan in chans:
|
|
25
|
+
if chan not in self.clist:
|
|
26
|
+
self.clist.append(chan)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class onTimer:
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
def __init__(self, visFr):
|
|
34
|
+
self.visFr = visFr
|
|
35
|
+
self.pipeline = visFr.pipeline
|
|
36
|
+
|
|
37
|
+
visFr.AddMenuItem('Experimental>Event Processing',
|
|
38
|
+
"onTimes from selected coalesced events",
|
|
39
|
+
self.OnTimes,
|
|
40
|
+
helpText='analyse the on time distribution of events in a region - needs the coalesced data source with nFrames event property')
|
|
41
|
+
visFr.AddMenuItem('Experimental>Event Processing',
|
|
42
|
+
"plot time series of clumps",
|
|
43
|
+
self.OnPlotClumps,
|
|
44
|
+
helpText='plots the time course of the selected events in a "single channel trace" - needs the clumpIndex event property')
|
|
45
|
+
visFr.AddMenuItem('Experimental>Event Processing',
|
|
46
|
+
"plot time series of event property",
|
|
47
|
+
self.OnPlotProperty,
|
|
48
|
+
helpText='plots a time series of the selected events in a "single channel trace" - using a user chosen event property')
|
|
49
|
+
visFr.AddMenuItem('Experimental>Event Processing',
|
|
50
|
+
"plot time gating from selected events\tCtrl+G",
|
|
51
|
+
self.OnPlotTser,
|
|
52
|
+
helpText='plots the time series of gating of detected molecules from the selected group of events')
|
|
53
|
+
visFr.AddMenuItem('Experimental>Event Processing',
|
|
54
|
+
"Event density in ROI\tCtrl+D",
|
|
55
|
+
self.OnEvtDensity,
|
|
56
|
+
helpText='calculate the event density (events/unit area) in the current ROI/image area')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def OnEvtDensity(self,event):
|
|
60
|
+
from PYMEcs.Analysis.eventProperties import evtDensity, getarea
|
|
61
|
+
visFr = self.visFr
|
|
62
|
+
pipeline = visFr.pipeline
|
|
63
|
+
|
|
64
|
+
dens, intens1, intens2, trange = evtDensity(pipeline)
|
|
65
|
+
area = getarea(pipeline)
|
|
66
|
+
|
|
67
|
+
if dens is None:
|
|
68
|
+
Warn(visFr,'area too small (%.2f um^2)' % (area))
|
|
69
|
+
else:
|
|
70
|
+
infostr = \
|
|
71
|
+
"""
|
|
72
|
+
Event density: %.1f events/um^2
|
|
73
|
+
Norm. intens.: %.1f events/um^2/5K-fr
|
|
74
|
+
Norm. intens.: %.1f events/(20um)^2/fr
|
|
75
|
+
Number of Events: %d events
|
|
76
|
+
Area probed: %.2f um^2
|
|
77
|
+
Time range: %.2fK frames""" % (dens, intens1, intens2, dens*area, area, trange/1e3)
|
|
78
|
+
Info(visFr,infostr)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def OnPlotTser(self,event):
|
|
82
|
+
from PYMEcs.misc.shellutils import plotserpipeline
|
|
83
|
+
|
|
84
|
+
visFr = self.visFr
|
|
85
|
+
pipeline = visFr.pipeline
|
|
86
|
+
mdh = pipeline.mdh
|
|
87
|
+
|
|
88
|
+
t = pipeline['t']
|
|
89
|
+
maxPts = 1e4
|
|
90
|
+
if len(t) > maxPts:
|
|
91
|
+
Warn(None,'aborting darktime analysis: too many events, current max is %d' % maxPts)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
p = pipeline
|
|
95
|
+
|
|
96
|
+
# if we have coalesced events use this info
|
|
97
|
+
if ('tmin' in pipeline.keys()) and ('tmax' in pipeline.keys()):
|
|
98
|
+
tmin = pipeline['tmin']
|
|
99
|
+
tmax = pipeline['tmax']
|
|
100
|
+
tc = np.arange(tmin[0],tmax[0]+1)
|
|
101
|
+
for i in range(1,tmin.shape[0]):
|
|
102
|
+
tc = np.append(tc,np.arange(tmin[i],tmax[i]+1))
|
|
103
|
+
tc.sort()
|
|
104
|
+
else:
|
|
105
|
+
tc = t
|
|
106
|
+
|
|
107
|
+
startn = pipeline.selectedDataSource['t'].min()
|
|
108
|
+
endn = pipeline.selectedDataSource['t'].max()
|
|
109
|
+
|
|
110
|
+
tt, v = plotserpipeline(tc, np.ones_like(tc))
|
|
111
|
+
|
|
112
|
+
# add points to start from beginning of series
|
|
113
|
+
tt2 = np.append([startn,tt[0],tt[0]],tt)
|
|
114
|
+
v2 = np.append([0,0,1],v)
|
|
115
|
+
# add points to go to end of series
|
|
116
|
+
tt2 = np.append(tt2,[tt[-1],tt[-1],endn])
|
|
117
|
+
v2 = np.append(v2,[1,0,0])
|
|
118
|
+
|
|
119
|
+
import matplotlib.pyplot as plt
|
|
120
|
+
# plot data and fitted curves
|
|
121
|
+
plt.figure()
|
|
122
|
+
plt.plot(tt2,v2)
|
|
123
|
+
plt.show()
|
|
124
|
+
plt.xlim(startn,endn)
|
|
125
|
+
|
|
126
|
+
def OnTimes(self,event):
|
|
127
|
+
import StringIO
|
|
128
|
+
from PYMEcs.Analysis import fitDarkTimes
|
|
129
|
+
|
|
130
|
+
visFr = self.visFr
|
|
131
|
+
pipeline = visFr.pipeline
|
|
132
|
+
mdh = pipeline.mdh
|
|
133
|
+
|
|
134
|
+
NTMIN = 5
|
|
135
|
+
maxPts = 1e5
|
|
136
|
+
t = pipeline['t']
|
|
137
|
+
if len(t) > maxPts:
|
|
138
|
+
Warn(None,'aborting analysis: too many events, current max is %d' % maxPts)
|
|
139
|
+
return
|
|
140
|
+
x = pipeline['x']
|
|
141
|
+
y = pipeline['y']
|
|
142
|
+
|
|
143
|
+
# determine darktime from gaps and reject zeros (no real gaps)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
dtg = pipeline['nFrames']
|
|
147
|
+
except KeyError:
|
|
148
|
+
Warn(None,'aborting analysis: could not find "nFrames" property, needs "Coalesced" Data Source','Error')
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
nts = dtg.shape[0]
|
|
152
|
+
|
|
153
|
+
if nts > NTMIN:
|
|
154
|
+
# now make a cumulative histogram from these
|
|
155
|
+
cumux,cumuy = fitDarkTimes.cumuhist(dtg)
|
|
156
|
+
binctrs,hc,binctrsg,hcg = fitDarkTimes.cumuhistBinned(dtg)
|
|
157
|
+
|
|
158
|
+
bbx = (x.min(),x.max())
|
|
159
|
+
bby = (y.min(),y.max())
|
|
160
|
+
voxx = 1e3*mdh['voxelsize.x']
|
|
161
|
+
voxy = 1e3*mdh['voxelsize.y']
|
|
162
|
+
bbszx = bbx[1]-bbx[0]
|
|
163
|
+
bbszy = bby[1]-bby[0]
|
|
164
|
+
|
|
165
|
+
# fit theoretical distributions
|
|
166
|
+
popth,pcovh,popt,pcov = (None,None,None,None)
|
|
167
|
+
if nts > NTMIN:
|
|
168
|
+
from scipy.optimize import curve_fit
|
|
169
|
+
|
|
170
|
+
idx = (np.abs(hcg - 0.63)).argmin()
|
|
171
|
+
tauesth = binctrsg[idx]
|
|
172
|
+
popth,pcovh,infodicth,errmsgh,ierrh = curve_fit(fitDarkTimes.cumuexpfit,binctrs,hc, p0=(tauesth),full_output=True)
|
|
173
|
+
chisqredh = ((hc - infodicth['fvec'])**2).sum()/(hc.shape[0]-1)
|
|
174
|
+
idx = (np.abs(cumuy - 0.63)).argmin()
|
|
175
|
+
tauest = cumux[idx]
|
|
176
|
+
popt,pcov,infodict,errmsg,ierr = curve_fit(fitDarkTimes.cumuexpfit,cumux,cumuy, p0=(tauest),full_output=True)
|
|
177
|
+
chisqred = ((cumuy - infodict['fvec'])**2).sum()/(nts-1)
|
|
178
|
+
|
|
179
|
+
import matplotlib.pyplot as plt
|
|
180
|
+
# plot data and fitted curves
|
|
181
|
+
plt.figure()
|
|
182
|
+
plt.subplot(211)
|
|
183
|
+
plt.plot(cumux,cumuy,'o')
|
|
184
|
+
plt.plot(cumux,fitDarkTimes.cumuexpfit(cumux,popt[0]))
|
|
185
|
+
plt.plot(binctrs,hc,'o')
|
|
186
|
+
plt.plot(binctrs,fitDarkTimes.cumuexpfit(binctrs,popth[0]))
|
|
187
|
+
plt.ylim(-0.2,1.2)
|
|
188
|
+
plt.subplot(212)
|
|
189
|
+
plt.semilogx(cumux,cumuy,'o')
|
|
190
|
+
plt.semilogx(cumux,fitDarkTimes.cumuexpfit(cumux,popt[0]))
|
|
191
|
+
plt.semilogx(binctrs,hc,'o')
|
|
192
|
+
plt.semilogx(binctrs,fitDarkTimes.cumuexpfit(binctrs,popth[0]))
|
|
193
|
+
plt.ylim(-0.2,1.2)
|
|
194
|
+
plt.show()
|
|
195
|
+
|
|
196
|
+
outstr = StringIO.StringIO()
|
|
197
|
+
|
|
198
|
+
analysis = {
|
|
199
|
+
'Nevents' : t.shape[0],
|
|
200
|
+
'Ndarktimes' : nts,
|
|
201
|
+
'filterKeys' : pipeline.filterKeys.copy(),
|
|
202
|
+
'darktimes' : (popt[0],popth[0]),
|
|
203
|
+
'darktimeErrors' : (np.sqrt(pcov[0][0]),np.sqrt(pcovh[0][0]))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#if not hasattr(self.visFr,'analysisrecord'):
|
|
207
|
+
# self.visFr.analysisrecord = []
|
|
208
|
+
# self.visFr.analysisrecord.append(analysis)
|
|
209
|
+
|
|
210
|
+
print >>outstr, "events: %d, on times: %d" % (t.shape[0],nts)
|
|
211
|
+
print >>outstr, "region: %d x %d nm (%d x %d pixel)" % (bbszx,bbszy,bbszx/voxx,bbszy/voxy)
|
|
212
|
+
print >>outstr, "centered at %d,%d (%d,%d pixels)" % (x.mean(),y.mean(),x.mean()/voxx,y.mean()/voxy)
|
|
213
|
+
print >>outstr, "ontime: %.1f+-%d (%.1f+-%d) frames - chisqr %.2f (%.2f)" % (popt[0],np.sqrt(pcov[0][0]),
|
|
214
|
+
popth[0],np.sqrt(pcovh[0][0]),
|
|
215
|
+
chisqred,chisqredh)
|
|
216
|
+
print >>outstr, "ontime: starting estimates: %.1f (%.1f)" % (tauest,tauesth)
|
|
217
|
+
print >>outstr, "qunits: %.2f (%.2f), mean on time: %.2f" % (100.0/popt[0], 100.0/popth[0], dtg.mean())
|
|
218
|
+
|
|
219
|
+
labelstr = outstr.getvalue()
|
|
220
|
+
plt.annotate(labelstr, xy=(0.5, 0.1), xycoords='axes fraction',
|
|
221
|
+
fontsize=10)
|
|
222
|
+
else:
|
|
223
|
+
Warn(None, 'not enough data points, only found %d on times (need at least %d)' % (nts,NTMIN), 'Error')
|
|
224
|
+
|
|
225
|
+
def OnPlotClumps(self,event):
|
|
226
|
+
import PYMEcs.Analysis.timeSeries as ts
|
|
227
|
+
try:
|
|
228
|
+
ci = self.pipeline['clumpIndex']
|
|
229
|
+
except KeyError:
|
|
230
|
+
Warn(None,'aborting analysis: could not find "clumpIndex" property','Error')
|
|
231
|
+
return
|
|
232
|
+
t = self.pipeline['t']
|
|
233
|
+
|
|
234
|
+
maxPts = 1e4
|
|
235
|
+
if len(t) > maxPts:
|
|
236
|
+
Warn(None,'aborting analysis: too many events, current max is %d' % maxPts)
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
ts.plotClumpSeries(t, ci)
|
|
240
|
+
|
|
241
|
+
def tserFromPipeline(self,key,base=0):
|
|
242
|
+
# t is on integer times assumed (from pipeline)
|
|
243
|
+
# val is the corresponding value
|
|
244
|
+
t = self.pipeline['t']
|
|
245
|
+
val = self.pipeline[key]
|
|
246
|
+
|
|
247
|
+
tmin = t.min()
|
|
248
|
+
tmax = t.max()
|
|
249
|
+
tstart = tmin-1
|
|
250
|
+
tend = tmax + 1
|
|
251
|
+
tlen = tend-tstart+1
|
|
252
|
+
|
|
253
|
+
to = t-tstart
|
|
254
|
+
tidx = np.argsort(to)
|
|
255
|
+
tos = to[tidx]
|
|
256
|
+
vs = val[tidx]
|
|
257
|
+
|
|
258
|
+
tvalid = np.zeros((tlen))
|
|
259
|
+
tvalid[tos] = 1
|
|
260
|
+
|
|
261
|
+
vals = np.zeros((tlen))
|
|
262
|
+
vals[tos] = vs
|
|
263
|
+
|
|
264
|
+
tup = tos[1:][(tos[1:]-tos[:-1] > 1)]
|
|
265
|
+
tvalid[tup-1] = 1
|
|
266
|
+
vals[tup-1] = base
|
|
267
|
+
|
|
268
|
+
tdown = tos[:-1][(tos[1:]-tos[:-1] > 1)]
|
|
269
|
+
tvalid[tdown+1] = 1
|
|
270
|
+
vals[tdown+1] = base
|
|
271
|
+
|
|
272
|
+
tplot = tvalid.nonzero()[0]
|
|
273
|
+
vplot = vals[tplot]
|
|
274
|
+
|
|
275
|
+
return (tplot+tstart,vplot)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def OnPlotProperty(self,event):
|
|
279
|
+
pChoice = propertyChoice()
|
|
280
|
+
pChoice.add_channels(sorted(self.pipeline.keys()))
|
|
281
|
+
if pChoice.configure_traits(kind='modal'):
|
|
282
|
+
t,prop = self.tserFromPipeline(pChoice.EventProperty)
|
|
283
|
+
if t.shape[0] > 2e4:
|
|
284
|
+
Warn(None,'aborting analysis: too many events, current max is %d' % 2e4)
|
|
285
|
+
return
|
|
286
|
+
import matplotlib.pyplot as plt
|
|
287
|
+
plt.figure()
|
|
288
|
+
plt.plot(t,prop)
|
|
289
|
+
plt.xlabel('Frame Number')
|
|
290
|
+
plt.ylabel(pChoice.EventProperty)
|
|
291
|
+
plt.show()
|
|
292
|
+
|
|
293
|
+
def Plug(visFr):
|
|
294
|
+
"""Plugs this module into the gui"""
|
|
295
|
+
visFr.onTimer = onTimer(visFr)
|