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
|
File without changes
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from traits.api import HasTraits, Str, Int, CStr, List, Enum, Float, Bool
|
|
2
|
+
from traitsui.api import View, Item, Group
|
|
3
|
+
from traitsui.menu import OKButton, CancelButton, OKCancelButtons
|
|
4
|
+
|
|
5
|
+
from PYMEcs.misc.guiMsgBoxes import YesNo
|
|
6
|
+
from PYME.IO.FileUtils.nameUtils import numToAlpha
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# could be called in init file as:
|
|
10
|
+
|
|
11
|
+
# @init_gui('ROI Calibration')
|
|
12
|
+
# def roi_calibration(MainFrame, scope):
|
|
13
|
+
#
|
|
14
|
+
# def roi_action_callback(event=None):
|
|
15
|
+
# from PYMEcs.Acquire.Actions.custom import queue_calibration_series
|
|
16
|
+
# queue_calibration_series(scope)
|
|
17
|
+
|
|
18
|
+
# MainFrame.AddMenuItem('Calibration', 'Camera Maps>Sub ROIs', roi_action_callback)
|
|
19
|
+
|
|
20
|
+
class ChipROI(HasTraits):
|
|
21
|
+
roiSize = Int(256)
|
|
22
|
+
overlap = Int(20)
|
|
23
|
+
numberOfFrames = Int(500)
|
|
24
|
+
checkBeforeQueuingActions = Bool(True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# commenting out the below which was just a quick trial how to do this
|
|
28
|
+
# keeping in for reference right now
|
|
29
|
+
|
|
30
|
+
# def queue_roi_series(scope):
|
|
31
|
+
# cam = scope.cam
|
|
32
|
+
|
|
33
|
+
# args = {'state': {'Camera.ROI' : [50, 50, 200, 200]}}
|
|
34
|
+
# scope.actions.QueueAction('state.update', args)
|
|
35
|
+
# args = {'maxFrames': 500, 'stack': False}
|
|
36
|
+
# scope.actions.QueueAction('spoolController.StartSpooling', args)
|
|
37
|
+
# args = {'state': {'Camera.ROI' : [100, 100, 250, 250]}}
|
|
38
|
+
# scope.actions.QueueAction('state.update', args)
|
|
39
|
+
# args = {'maxFrames': 500, 'stack': False}
|
|
40
|
+
# scope.actions.QueueAction('spoolController.StartSpooling', args)
|
|
41
|
+
# args = {'state': {'Camera.ROI' : [0, 0, 256, 256]}}
|
|
42
|
+
# scope.actions.QueueAction('state.update', args)
|
|
43
|
+
|
|
44
|
+
# # in future we might code this as:
|
|
45
|
+
# #
|
|
46
|
+
# # calib = [actions.SpoolSeries(maxFrames=500, stack=False,
|
|
47
|
+
# # state={'Camera.ROI' : [50, 50, 200, 200]}),
|
|
48
|
+
# # actions.SpoolSeries(maxFrames=500, stack=False,
|
|
49
|
+
# # state={'Camera.ROI' : [100, 100, 250, 250]}),
|
|
50
|
+
# # ]
|
|
51
|
+
|
|
52
|
+
# # scope.actions.queue_actions(calib)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_roi(x0,y0,x1,y1,width=None, height=None):
|
|
56
|
+
if x0<0:
|
|
57
|
+
x0=0
|
|
58
|
+
if y0<0:
|
|
59
|
+
y0=0
|
|
60
|
+
if x1>(width):
|
|
61
|
+
x1 = width
|
|
62
|
+
if y1>(height):
|
|
63
|
+
y1 = height
|
|
64
|
+
return [x0,y0,x1,y1]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def spoolSeries(scope, maxFrames=500, stack=False, state=None,
|
|
68
|
+
method=None, subdirectory=None):
|
|
69
|
+
if state is not None:
|
|
70
|
+
args = {'state': state}
|
|
71
|
+
scope.actions.QueueAction('state.update', args)
|
|
72
|
+
|
|
73
|
+
args = {'settings': {'max_frames': maxFrames,
|
|
74
|
+
'stack': stack,
|
|
75
|
+
'subdirectory': subdirectory,
|
|
76
|
+
'method': method}}
|
|
77
|
+
scope.actions.QueueAction('spoolController.start_spooling', args)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def setState(scope,state):
|
|
81
|
+
args = {'state': state}
|
|
82
|
+
scope.actions.QueueAction('state.update', args)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_mapdir(scope):
|
|
87
|
+
import os
|
|
88
|
+
seriesCounter = 0
|
|
89
|
+
subdirectory = 'map_' + numToAlpha(seriesCounter)
|
|
90
|
+
while os.path.exists(scope.spoolController.get_dirname(subdirectory)):
|
|
91
|
+
seriesCounter += 1
|
|
92
|
+
subdirectory = 'map_' + numToAlpha(seriesCounter)
|
|
93
|
+
return subdirectory
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ToDo - refine implementation as action interface improves
|
|
97
|
+
# - add online help, using traits help infrastructure (i.e. in class ChipROI)
|
|
98
|
+
def camera_chip_calibration_series(scope):
|
|
99
|
+
import os
|
|
100
|
+
|
|
101
|
+
chipROI = ChipROI()
|
|
102
|
+
if not chipROI.configure_traits(kind='modal'):
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
cam = scope.cam
|
|
106
|
+
chipWidth = cam.GetCCDWidth()
|
|
107
|
+
chipHeight = cam.GetCCDHeight()
|
|
108
|
+
curROI = cam.GetROI()
|
|
109
|
+
|
|
110
|
+
stepsize = chipROI.roiSize - chipROI.overlap
|
|
111
|
+
rsz = chipROI.roiSize
|
|
112
|
+
|
|
113
|
+
xsteps = int(chipWidth / stepsize)
|
|
114
|
+
ysteps = int(chipHeight / stepsize)
|
|
115
|
+
|
|
116
|
+
rois = []
|
|
117
|
+
|
|
118
|
+
# note PYME cam ROI conventions: 1) chip origin starts at 0,0
|
|
119
|
+
# 2) ROI with coordinates [x0,y0,x1,y1] starts and includes point x0,y0
|
|
120
|
+
# but extends to and *excludes* point x1, y1
|
|
121
|
+
# This implies that the ROI width is x1-x0 and height is y1-y0 (used further below)
|
|
122
|
+
x0 = 0
|
|
123
|
+
y0 = 0
|
|
124
|
+
x1 = rsz # note *not* rsz-1, since it excludes x1, y1, see above
|
|
125
|
+
y1 = rsz
|
|
126
|
+
|
|
127
|
+
for iy in range(0,ysteps):
|
|
128
|
+
for ix in range(0,xsteps):
|
|
129
|
+
rois.append(check_roi(x0+ix*stepsize,y0+iy*stepsize,
|
|
130
|
+
x1+ix*stepsize,y1+iy*stepsize,
|
|
131
|
+
width = chipWidth, height=chipHeight))
|
|
132
|
+
|
|
133
|
+
# show tiling on chip
|
|
134
|
+
|
|
135
|
+
import matplotlib.pyplot as plt
|
|
136
|
+
import matplotlib.patches as patches
|
|
137
|
+
import numpy as np
|
|
138
|
+
|
|
139
|
+
cols = ['r','g']
|
|
140
|
+
|
|
141
|
+
fig,ax = plt.subplots(1)
|
|
142
|
+
ax.imshow(np.ones((chipWidth,chipHeight)))
|
|
143
|
+
for i, roi in enumerate(rois):
|
|
144
|
+
rect = patches.Rectangle((roi[0],roi[1]),
|
|
145
|
+
roi[2]-roi[0], # remember we get ROI width by difference of corner points
|
|
146
|
+
roi[3]-roi[1], # same for height
|
|
147
|
+
linewidth=1,edgecolor=cols[i %2],facecolor='none')
|
|
148
|
+
ax.add_patch(rect)
|
|
149
|
+
cx = 0.5*(roi[0]+roi[2])
|
|
150
|
+
cy = 0.5*(roi[1]+roi[3])
|
|
151
|
+
plt.text(cx,cy,'%d' % i,c='w') # plot in white to ensure we see on top of darkish chip plot
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if chipROI.checkBeforeQueuingActions:
|
|
155
|
+
if not YesNo(None, "Will use %d ROIS.\nProceed with running ROI actions?" % len(rois), caption='Proceed'):
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
mapdir = get_mapdir(scope)
|
|
159
|
+
# actually queue series
|
|
160
|
+
for roi in rois:
|
|
161
|
+
spoolSeries(scope, maxFrames=chipROI.numberOfFrames, stack=False,
|
|
162
|
+
state={'Camera.ROI' : roi}, subdirectory=mapdir,
|
|
163
|
+
method='FILE')
|
|
164
|
+
|
|
165
|
+
# set back to original ROI
|
|
166
|
+
setState(scope,state={'Camera.ROI' : curROI})
|
|
167
|
+
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from threading import Thread, Lock
|
|
2
|
+
from queue import Queue
|
|
3
|
+
import logging
|
|
4
|
+
from PYME.Acquire.Hardware.NikonTi import LightPath
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
|
|
7
|
+
# derived class of NikonTi.LightPath that provides "safe" methods that catch certain exceptions
|
|
8
|
+
# and return a status if the method invocation was successful
|
|
9
|
+
#
|
|
10
|
+
# in actual hardware version we would check for a com_error only, all other exceptions will not be
|
|
11
|
+
# caught, i.e.
|
|
12
|
+
#
|
|
13
|
+
from pywintypes import com_error
|
|
14
|
+
# ...
|
|
15
|
+
# except com_error:
|
|
16
|
+
# ...
|
|
17
|
+
|
|
18
|
+
# note that we use HTTP status codes as we use a REST server anyway and
|
|
19
|
+
# thus having status values we can directly pass back via HTTP responses are useful
|
|
20
|
+
class LPSafe(LightPath):
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__()
|
|
23
|
+
|
|
24
|
+
def GetNamesS(self):
|
|
25
|
+
try:
|
|
26
|
+
names = self.names
|
|
27
|
+
status = HTTPStatus.OK
|
|
28
|
+
except com_error:
|
|
29
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
30
|
+
names = None
|
|
31
|
+
return status, names
|
|
32
|
+
|
|
33
|
+
def GetPortS(self):
|
|
34
|
+
try:
|
|
35
|
+
port = self.GetPort()
|
|
36
|
+
status = HTTPStatus.OK
|
|
37
|
+
except com_error:
|
|
38
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
39
|
+
port = None
|
|
40
|
+
return status, port
|
|
41
|
+
|
|
42
|
+
# we check the input argument range and return a suitable status
|
|
43
|
+
# if an invalid argument was provided
|
|
44
|
+
def SetPortS(self,port):
|
|
45
|
+
if port not in self.names:
|
|
46
|
+
return HTTPStatus.BAD_REQUEST
|
|
47
|
+
try:
|
|
48
|
+
self.SetPort(port)
|
|
49
|
+
except com_error:
|
|
50
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
51
|
+
else:
|
|
52
|
+
status = HTTPStatus.OK
|
|
53
|
+
return status
|
|
54
|
+
|
|
55
|
+
def GetPositionS(self):
|
|
56
|
+
try:
|
|
57
|
+
pos = self.GetPosition()
|
|
58
|
+
status = HTTPStatus.OK
|
|
59
|
+
except com_error:
|
|
60
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
61
|
+
pos = None
|
|
62
|
+
return status, pos
|
|
63
|
+
|
|
64
|
+
def SetPositionS(self,pos):
|
|
65
|
+
if pos not in range(len(self.names)):
|
|
66
|
+
return HTTPStatus.BAD_REQUEST
|
|
67
|
+
try:
|
|
68
|
+
self.SetPosition(pos)
|
|
69
|
+
except com_error:
|
|
70
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
71
|
+
else:
|
|
72
|
+
status = HTTPStatus.OK
|
|
73
|
+
return status
|
|
74
|
+
|
|
75
|
+
# objects that are used to pass info to and from the
|
|
76
|
+
# threaded version of the LightPath object
|
|
77
|
+
class commandObject(object):
|
|
78
|
+
def __init__(self,cmd,*args):
|
|
79
|
+
self.cmd = cmd
|
|
80
|
+
self.args = args
|
|
81
|
+
|
|
82
|
+
def __repr__(self):
|
|
83
|
+
return 'CMDMSG : %s!' % (self.cmd) + \
|
|
84
|
+
'[' + ','.join(str(x) for x in self.args) + ']'
|
|
85
|
+
|
|
86
|
+
class returnObject(object):
|
|
87
|
+
def __init__(self,cmd,status,*args):
|
|
88
|
+
self.cmd = cmd
|
|
89
|
+
self.status = status
|
|
90
|
+
self.return_args = args
|
|
91
|
+
|
|
92
|
+
def __repr__(self):
|
|
93
|
+
return 'RETMSG : %s!%s!' % (self.cmd,self.status) + \
|
|
94
|
+
'[' + ','.join(str(x) for x in self.return_args) + ']'
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class LPThread(Thread):
|
|
98
|
+
def __init__(self, *args, **kwargs):
|
|
99
|
+
super().__init__(*args, **kwargs)
|
|
100
|
+
self.command_queue = Queue()
|
|
101
|
+
self.results_queue = Queue()
|
|
102
|
+
self.lp = LPSafe()
|
|
103
|
+
|
|
104
|
+
self.daemon = True
|
|
105
|
+
|
|
106
|
+
def run(self):
|
|
107
|
+
while True:
|
|
108
|
+
cmd = self.command_queue.get()
|
|
109
|
+
logging.debug('received command %s' % cmd)
|
|
110
|
+
result = self._parse_command_and_execute(cmd)
|
|
111
|
+
self.results_queue.put(result)
|
|
112
|
+
|
|
113
|
+
def _parse_command_and_execute(self,cmd):
|
|
114
|
+
if (not cmd.cmd) or (cmd.cmd is None):
|
|
115
|
+
return returnObject(None, HTTPStatus.NO_CONTENT, 'no command received')
|
|
116
|
+
logging.warn('no command received')
|
|
117
|
+
elif cmd.cmd == 'GetPort':
|
|
118
|
+
status, port = self.lp.GetPortS()
|
|
119
|
+
return returnObject(cmd.cmd, status, port)
|
|
120
|
+
elif cmd.cmd == 'SetPort':
|
|
121
|
+
status = self.lp.SetPortS(cmd.args[0])
|
|
122
|
+
return returnObject(cmd.cmd, status)
|
|
123
|
+
elif cmd.cmd == 'GetPosition':
|
|
124
|
+
status, pos = self.lp.GetPositionS()
|
|
125
|
+
return returnObject(cmd.cmd, status,pos)
|
|
126
|
+
elif cmd.cmd == 'SetPosition':
|
|
127
|
+
status = self.lp.SetPositionS(cmd.args[0])
|
|
128
|
+
return returnObject(cmd.cmd, status)
|
|
129
|
+
elif cmd.cmd == 'GetNames':
|
|
130
|
+
status, names = self.lp.GetNamesS()
|
|
131
|
+
return returnObject(cmd.cmd, status, names)
|
|
132
|
+
else:
|
|
133
|
+
return returnObject(cmd.cmd, HTTPStatus.NOT_IMPLEMENTED)
|
|
134
|
+
logging.warn('received unknown command %s' % cmd)
|
|
135
|
+
|
|
136
|
+
# primary external facing method to execute a command in the NikonTi LP thread
|
|
137
|
+
# and return in a form suitable for the excecute command (e.g. getter, setter, array getter)
|
|
138
|
+
def run_command(self,cmd,*args):
|
|
139
|
+
self.command_queue.put(commandObject(cmd,*args))
|
|
140
|
+
logging.debug('running command %s' % cmd)
|
|
141
|
+
ret = self.results_queue.get()
|
|
142
|
+
# setter command
|
|
143
|
+
if len(ret.return_args) == 0:
|
|
144
|
+
return ret.status
|
|
145
|
+
# getter command
|
|
146
|
+
elif len(ret.return_args) == 1:
|
|
147
|
+
return ret.status, ret.return_args[0]
|
|
148
|
+
# getter command that expects an array back
|
|
149
|
+
else:
|
|
150
|
+
return ret.status, ret.return_args
|
|
151
|
+
|
|
152
|
+
# below we replicate the normal LightPath interface and allow using the threaded
|
|
153
|
+
# version as a stand-in for the "plain" LightPath
|
|
154
|
+
def GetPort(self):
|
|
155
|
+
status, port = self.run_command('GetPort')
|
|
156
|
+
if status == HTTPStatus.OK:
|
|
157
|
+
return port
|
|
158
|
+
else:
|
|
159
|
+
raise RuntimeError('GetPort: received HTTP status %s' % status)
|
|
160
|
+
|
|
161
|
+
def SetPort(self,port):
|
|
162
|
+
status = self.run_command('SetPort',port)
|
|
163
|
+
if status != HTTPStatus.OK:
|
|
164
|
+
raise RuntimeError('SetPort: received HTTP status %s' % status)
|
|
165
|
+
self.lastPosition = self.GetPosition()
|
|
166
|
+
self.OnChange()
|
|
167
|
+
|
|
168
|
+
def GetNames(self):
|
|
169
|
+
status, names = self.run_command('GetNames')
|
|
170
|
+
if status == HTTPStatus.OK:
|
|
171
|
+
return names
|
|
172
|
+
else:
|
|
173
|
+
raise RuntimeError('GetPort: received HTTP status %s' % status)
|
|
174
|
+
|
|
175
|
+
def GetPosition(self):
|
|
176
|
+
status, pos = self.run_command('GetPosition')
|
|
177
|
+
if status == HTTPStatus.OK:
|
|
178
|
+
return pos
|
|
179
|
+
else:
|
|
180
|
+
raise RuntimeError('GetPosition: received HTTP status %s' % status)
|
|
181
|
+
|
|
182
|
+
def SetPosition(self,pos):
|
|
183
|
+
status = self.run_command('SetPosition',pos)
|
|
184
|
+
if status != HTTPStatus.OK:
|
|
185
|
+
raise RuntimeError('SetPosition: received HTTP status %s' % status)
|
|
186
|
+
self.lastPosition = pos
|
|
187
|
+
self.OnChange()
|
|
188
|
+
|
|
189
|
+
def ProvideMetadata(self,mdh):
|
|
190
|
+
mdh.setEntry('NikonTi.LightPath', self.GetPort())
|
|
191
|
+
|
|
192
|
+
def OnChange(self):
|
|
193
|
+
for a in self.wantChangeNotification:
|
|
194
|
+
a()
|
|
195
|
+
|
|
196
|
+
def Poll_initialize(self):
|
|
197
|
+
if self.is_alive(): # make sure we are already running
|
|
198
|
+
self.lastPosition = self.GetPosition()
|
|
199
|
+
self.names = self.GetNames()
|
|
200
|
+
self.wantChangeNotification = []
|
|
201
|
+
else:
|
|
202
|
+
logging.warning('Thread not running - Poll initialisation failed')
|
|
203
|
+
|
|
204
|
+
def Poll(self):
|
|
205
|
+
pos = self.GetPosition()
|
|
206
|
+
if not self.lastPosition == pos:
|
|
207
|
+
self.lastPosition = pos
|
|
208
|
+
self.OnChange()
|
|
209
|
+
|
|
210
|
+
# test some aspects of the code here
|
|
211
|
+
def main():
|
|
212
|
+
import time
|
|
213
|
+
|
|
214
|
+
lpt = LPThread(name='LPThread')
|
|
215
|
+
lpt.Poll_initialize() # this one should give a warning, thread not yet started
|
|
216
|
+
lpt.start()
|
|
217
|
+
lpt.Poll_initialize() # this one is done at the proper time
|
|
218
|
+
|
|
219
|
+
status,port = lpt.run_command('GetPort')
|
|
220
|
+
if status == HTTPStatus.OK:
|
|
221
|
+
print('Port is %s' % port)
|
|
222
|
+
|
|
223
|
+
status = lpt.run_command('SetPort','L100')
|
|
224
|
+
time.sleep(2.0) # wait two seconds for the command to complete
|
|
225
|
+
|
|
226
|
+
status, port = lpt.run_command('GetPort')
|
|
227
|
+
if status == HTTPStatus.OK:
|
|
228
|
+
print('Port is %s' % port)
|
|
229
|
+
|
|
230
|
+
status, pos = lpt.run_command('GetPosition')
|
|
231
|
+
if status == HTTPStatus.OK:
|
|
232
|
+
print('Position is %d' % pos)
|
|
233
|
+
|
|
234
|
+
status, names = lpt.run_command('GetNames')
|
|
235
|
+
if status == HTTPStatus.OK:
|
|
236
|
+
print('Names:',names)
|
|
237
|
+
|
|
238
|
+
print('Testing high level interface: Port is %s' % lpt.GetPort())
|
|
239
|
+
|
|
240
|
+
status = lpt.run_command(None)
|
|
241
|
+
|
|
242
|
+
print(commandObject('GetIt',1,'L100'))
|
|
243
|
+
print(returnObject('Received',HTTPStatus.OK,1,'L100'))
|
|
244
|
+
|
|
245
|
+
if __name__ == '__main__':
|
|
246
|
+
logging.basicConfig(level=logging.DEBUG,
|
|
247
|
+
format='(%(threadName)-9s) %(message)s',)
|
|
248
|
+
main()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
from threading import Thread, Lock
|
|
2
|
+
from queue import Queue
|
|
3
|
+
import logging
|
|
4
|
+
from PYMEcs.Acquire.Hardware.NikonTiSim import LightPath
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
|
|
7
|
+
# derived class of NikonTi.LightPath that provides "safe" methods that catch certain exceptions
|
|
8
|
+
# and return a status if the method invocation was successful
|
|
9
|
+
#
|
|
10
|
+
# in actual hardware version we would check for a com_error only, all other exceptions will not be
|
|
11
|
+
# caught, i.e.
|
|
12
|
+
#
|
|
13
|
+
# from pywintypes import com_error
|
|
14
|
+
# ...
|
|
15
|
+
# except com_error:
|
|
16
|
+
# ...
|
|
17
|
+
|
|
18
|
+
# note that we use HTTP status codes as we use a REST server anyway and
|
|
19
|
+
# thus having status values we can directly pass back via HTTP responses are useful
|
|
20
|
+
class LPSafe(LightPath):
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__()
|
|
23
|
+
|
|
24
|
+
def GetNamesS(self):
|
|
25
|
+
try:
|
|
26
|
+
names = self.names
|
|
27
|
+
status = HTTPStatus.OK
|
|
28
|
+
except:
|
|
29
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
30
|
+
names = None
|
|
31
|
+
return status, names
|
|
32
|
+
|
|
33
|
+
def GetPortS(self):
|
|
34
|
+
try:
|
|
35
|
+
port = self.GetPort()
|
|
36
|
+
status = HTTPStatus.OK
|
|
37
|
+
except:
|
|
38
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
39
|
+
port = None
|
|
40
|
+
return status, port
|
|
41
|
+
|
|
42
|
+
# we check the input argument range and return a suitable status
|
|
43
|
+
# if an invalid argument was provided
|
|
44
|
+
def SetPortS(self,port):
|
|
45
|
+
if port not in self.names:
|
|
46
|
+
return HTTPStatus.BAD_REQUEST
|
|
47
|
+
try:
|
|
48
|
+
self.SetPort(port)
|
|
49
|
+
except:
|
|
50
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
51
|
+
else:
|
|
52
|
+
status = HTTPStatus.OK
|
|
53
|
+
return status
|
|
54
|
+
|
|
55
|
+
def GetPositionS(self):
|
|
56
|
+
try:
|
|
57
|
+
pos = self.GetPosition()
|
|
58
|
+
status = HTTPStatus.OK
|
|
59
|
+
except:
|
|
60
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
61
|
+
pos = None
|
|
62
|
+
return status, pos
|
|
63
|
+
|
|
64
|
+
def SetPositionS(self,pos):
|
|
65
|
+
if pos not in range(len(self.names)):
|
|
66
|
+
return HTTPStatus.BAD_REQUEST
|
|
67
|
+
try:
|
|
68
|
+
self.SetPosition(pos)
|
|
69
|
+
except:
|
|
70
|
+
status = HTTPStatus.SERVICE_UNAVAILABLE
|
|
71
|
+
else:
|
|
72
|
+
status = HTTPStatus.OK
|
|
73
|
+
|
|
74
|
+
return status
|
|
75
|
+
|
|
76
|
+
# objects that are used to pass info to and from the
|
|
77
|
+
# threaded version of the LightPath object
|
|
78
|
+
class commandObject(object):
|
|
79
|
+
def __init__(self,cmd,*args):
|
|
80
|
+
self.cmd = cmd
|
|
81
|
+
self.args = args
|
|
82
|
+
|
|
83
|
+
def __repr__(self):
|
|
84
|
+
return 'CMDMSG : %s!' % (self.cmd) + \
|
|
85
|
+
'[' + ','.join(str(x) for x in self.args) + ']'
|
|
86
|
+
|
|
87
|
+
class returnObject(object):
|
|
88
|
+
def __init__(self,cmd,status,*args):
|
|
89
|
+
self.cmd = cmd
|
|
90
|
+
self.status = status
|
|
91
|
+
self.return_args = args
|
|
92
|
+
|
|
93
|
+
def __repr__(self):
|
|
94
|
+
return 'RETMSG : %s!%s!' % (self.cmd,self.status) + \
|
|
95
|
+
'[' + ','.join(str(x) for x in self.return_args) + ']'
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class LPThread(Thread):
|
|
99
|
+
def __init__(self, *args, **kwargs):
|
|
100
|
+
super().__init__(*args, **kwargs)
|
|
101
|
+
self.command_queue = Queue()
|
|
102
|
+
self.results_queue = Queue()
|
|
103
|
+
self.lp = LPSafe()
|
|
104
|
+
|
|
105
|
+
self.daemon = True
|
|
106
|
+
|
|
107
|
+
def run(self):
|
|
108
|
+
while True:
|
|
109
|
+
cmd = self.command_queue.get()
|
|
110
|
+
logging.debug('received command %s' % cmd)
|
|
111
|
+
result = self._parse_command_and_execute(cmd)
|
|
112
|
+
self.results_queue.put(result)
|
|
113
|
+
|
|
114
|
+
def _parse_command_and_execute(self,cmd):
|
|
115
|
+
if (not cmd.cmd) or (cmd.cmd is None):
|
|
116
|
+
return returnObject(None, HTTPStatus.NO_CONTENT, 'no command received')
|
|
117
|
+
logging.warn('no command received')
|
|
118
|
+
elif cmd.cmd == 'GetPort':
|
|
119
|
+
status, port = self.lp.GetPortS()
|
|
120
|
+
return returnObject(cmd.cmd, status, port)
|
|
121
|
+
elif cmd.cmd == 'SetPort':
|
|
122
|
+
status = self.lp.SetPortS(cmd.args[0])
|
|
123
|
+
return returnObject(cmd.cmd, status)
|
|
124
|
+
elif cmd.cmd == 'GetPosition':
|
|
125
|
+
status, pos = self.lp.GetPositionS()
|
|
126
|
+
return returnObject(cmd.cmd, status,pos)
|
|
127
|
+
elif cmd.cmd == 'SetPosition':
|
|
128
|
+
status = self.lp.SetPositionS(cmd.args[0])
|
|
129
|
+
return returnObject(cmd.cmd, status)
|
|
130
|
+
elif cmd.cmd == 'GetNames':
|
|
131
|
+
status, names = self.lp.GetNamesS()
|
|
132
|
+
return returnObject(cmd.cmd, status, names)
|
|
133
|
+
else:
|
|
134
|
+
return returnObject(cmd.cmd, HTTPStatus.NOT_IMPLEMENTED)
|
|
135
|
+
logging.warn('received unknown command %s' % cmd)
|
|
136
|
+
|
|
137
|
+
# primary external facing method to execute a command in the NikonTi LP thread
|
|
138
|
+
# and return in a form suitable for the excecute command (e.g. getter, setter, array getter)
|
|
139
|
+
def run_command(self,cmd,*args):
|
|
140
|
+
self.command_queue.put(commandObject(cmd,*args))
|
|
141
|
+
logging.debug('running command %s' % cmd)
|
|
142
|
+
ret = self.results_queue.get()
|
|
143
|
+
# setter command
|
|
144
|
+
if len(ret.return_args) == 0:
|
|
145
|
+
return ret.status
|
|
146
|
+
# getter command
|
|
147
|
+
elif len(ret.return_args) == 1:
|
|
148
|
+
return ret.status, ret.return_args[0]
|
|
149
|
+
# getter command that expects an array back
|
|
150
|
+
else:
|
|
151
|
+
return ret.status, ret.return_args
|
|
152
|
+
|
|
153
|
+
# below we replicate the normal LightPath interface and allow using the threaded
|
|
154
|
+
# version as a stand-in for the "plain" LightPath
|
|
155
|
+
def GetPort(self):
|
|
156
|
+
status, port = self.run_command('GetPort')
|
|
157
|
+
if status == HTTPStatus.OK:
|
|
158
|
+
return port
|
|
159
|
+
else:
|
|
160
|
+
raise RuntimeError('GetPort: received HTTP status %s' % status)
|
|
161
|
+
|
|
162
|
+
def SetPort(self,port):
|
|
163
|
+
status = self.run_command('SetPort',port)
|
|
164
|
+
if status != HTTPStatus.OK:
|
|
165
|
+
raise RuntimeError('SetPort: received HTTP status %s' % status)
|
|
166
|
+
self.lastPosition = self.GetPosition()
|
|
167
|
+
self.OnChange()
|
|
168
|
+
|
|
169
|
+
def GetNames(self):
|
|
170
|
+
status, names = self.run_command('GetNames')
|
|
171
|
+
if status == HTTPStatus.OK:
|
|
172
|
+
return names
|
|
173
|
+
else:
|
|
174
|
+
raise RuntimeError('GetPort: received HTTP status %s' % status)
|
|
175
|
+
|
|
176
|
+
def GetPosition(self):
|
|
177
|
+
status, pos = self.run_command('GetPosition')
|
|
178
|
+
if status == HTTPStatus.OK:
|
|
179
|
+
return pos
|
|
180
|
+
else:
|
|
181
|
+
raise RuntimeError('GetPosition: received HTTP status %s' % status)
|
|
182
|
+
|
|
183
|
+
def SetPosition(self,pos):
|
|
184
|
+
status = self.run_command('SetPosition',pos)
|
|
185
|
+
if status != HTTPStatus.OK:
|
|
186
|
+
raise RuntimeError('SetPosition: received HTTP status %s' % status)
|
|
187
|
+
self.lastPosition = pos
|
|
188
|
+
self.OnChange()
|
|
189
|
+
|
|
190
|
+
def ProvideMetadata(self,mdh):
|
|
191
|
+
mdh.setEntry('NikonTi.LightPath', self.GetPort())
|
|
192
|
+
|
|
193
|
+
def OnChange(self):
|
|
194
|
+
for a in self.wantChangeNotification:
|
|
195
|
+
a()
|
|
196
|
+
|
|
197
|
+
def Poll_initialize(self):
|
|
198
|
+
if self.is_alive(): # make sure we are already running
|
|
199
|
+
self.lastPosition = self.GetPosition()
|
|
200
|
+
self.names = self.GetNames()
|
|
201
|
+
self.wantChangeNotification = []
|
|
202
|
+
else:
|
|
203
|
+
logging.warning('Thread not running - Poll initialisation failed')
|
|
204
|
+
|
|
205
|
+
def Poll(self):
|
|
206
|
+
pos = self.GetPosition()
|
|
207
|
+
if not self.lastPosition == pos:
|
|
208
|
+
self.lastPosition = pos
|
|
209
|
+
self.OnChange()
|
|
210
|
+
|
|
211
|
+
# test some aspects of the code here
|
|
212
|
+
def main():
|
|
213
|
+
lpt = LPThread(name='LPThread')
|
|
214
|
+
lpt.Poll_initialize() # this one should give a warning, thread not yet started
|
|
215
|
+
lpt.start()
|
|
216
|
+
lpt.Poll_initialize() # this one is done at the proper time
|
|
217
|
+
|
|
218
|
+
status,port = lpt.run_command('GetPort')
|
|
219
|
+
if status == HTTPStatus.OK:
|
|
220
|
+
print('Port is %s' % port)
|
|
221
|
+
|
|
222
|
+
status = lpt.run_command('SetPort','L100')
|
|
223
|
+
|
|
224
|
+
status, port = lpt.run_command('GetPort')
|
|
225
|
+
if status == HTTPStatus.OK:
|
|
226
|
+
print('Port is %s' % port)
|
|
227
|
+
|
|
228
|
+
status, pos = lpt.run_command('GetPosition')
|
|
229
|
+
if status == HTTPStatus.OK:
|
|
230
|
+
print('Position is %d' % pos)
|
|
231
|
+
|
|
232
|
+
status, names = lpt.run_command('GetNames')
|
|
233
|
+
if status == HTTPStatus.OK:
|
|
234
|
+
print('Names:',names)
|
|
235
|
+
|
|
236
|
+
print('Testing high level interface: Port is %s' % lpt.GetPort())
|
|
237
|
+
|
|
238
|
+
status = lpt.run_command(None)
|
|
239
|
+
|
|
240
|
+
print(commandObject('GetIt',1,'L100'))
|
|
241
|
+
print(returnObject('Received',HTTPStatus.OK,1,'L100'))
|
|
242
|
+
|
|
243
|
+
if __name__ == '__main__':
|
|
244
|
+
logging.basicConfig(level=logging.DEBUG,
|
|
245
|
+
format='(%(threadName)-9s) %(message)s',)
|
|
246
|
+
main()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from flask import Flask, request, jsonify, make_response
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
|
|
4
|
+
def create_app(mode='simulated'):
|
|
5
|
+
# create and configure the app
|
|
6
|
+
app = Flask(__name__)
|
|
7
|
+
|
|
8
|
+
if mode == 'simulated':
|
|
9
|
+
from PYMEcs.Acquire.Hardware import NikonTiSim
|
|
10
|
+
lp = NikonTiSim.LightPath()
|
|
11
|
+
else:
|
|
12
|
+
from PYME.Acquire.Hardware import NikonTi
|
|
13
|
+
lp = NikonTi.LightPath()
|
|
14
|
+
# lp.SetPosition(2)
|
|
15
|
+
|
|
16
|
+
@app.get("/names")
|
|
17
|
+
def get_names():
|
|
18
|
+
return jsonify(lp.names)
|
|
19
|
+
|
|
20
|
+
@app.get("/port")
|
|
21
|
+
def get_port():
|
|
22
|
+
response = make_response(lp.GetPort(), HTTPStatus.OK)
|
|
23
|
+
response.mimetype = "text/plain"
|
|
24
|
+
return response
|
|
25
|
+
|
|
26
|
+
@app.put("/port")
|
|
27
|
+
def set_port():
|
|
28
|
+
lp.SetPort(request.get_data().decode("utf-8"))
|
|
29
|
+
return lp.GetPort(), HTTPStatus.OK
|
|
30
|
+
|
|
31
|
+
@app.get("/position")
|
|
32
|
+
def get_position():
|
|
33
|
+
response = make_response(str(lp.GetPosition()), HTTPStatus.OK)
|
|
34
|
+
response.mimetype = "text/plain"
|
|
35
|
+
return response
|
|
36
|
+
|
|
37
|
+
@app.put("/position")
|
|
38
|
+
def set_position():
|
|
39
|
+
lp.SetPosition(int(request.get_data().decode("utf-8")))
|
|
40
|
+
return str(lp.GetPosition()), HTTPStatus.OK
|
|
41
|
+
|
|
42
|
+
return app
|
|
43
|
+
|
|
44
|
+
if __name__ == '__main__':
|
|
45
|
+
app.run(threaded=False, processes=1)
|