Skip to content

Commit 7e44edf

Browse files
Add cleanup support (#186)
* add wait_to_continue feature * test multi-step nodes * update feature container * add cleanup support * add cleanup tests * add cleanup setting * Update aslm_model.py * execute cleanup routine when threads stop
1 parent efaf6e8 commit 7e44edf

File tree

5 files changed

+331
-78
lines changed

5 files changed

+331
-78
lines changed

src/aslm/model/aslm_model.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,8 +579,10 @@ def end_acquisition(self):
579579
"""
580580
self.current_channel = 0
581581
if hasattr(self, 'signal_container'):
582+
self.signal_container.cleanup()
582583
delattr(self, 'signal_container')
583584
if hasattr(self, 'data_container'):
585+
self.data_container.cleanup()
584586
delattr(self, 'data_container')
585587
if self.camera.is_acquiring:
586588
self.camera.close_image_series()
@@ -634,6 +636,9 @@ def run_data_process(self, num_of_frames=0, data_func=None):
634636
data_func(frame_ids)
635637

636638
if hasattr(self, 'data_container'):
639+
if self.data_container.is_closed:
640+
self.stop_acquisition = True
641+
break
637642
self.data_container.run(frame_ids)
638643

639644
# show image
@@ -912,6 +917,9 @@ def run_single_channel_acquisition_with_features(self, target_channel=1):
912917
self.run_single_channel_acquisition(self.target_channel)
913918
if not hasattr(self, 'signal_container'):
914919
return
920+
if self.signal_container.is_closed:
921+
self.stop_acquisition = True
922+
return
915923

916924
def change_resolution(self, resolution_value):
917925
r"""Switch resolution mode of the microscope.

src/aslm/model/model_features/aslm_common_features.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class Snap:
6565
def __init__(self, model):
6666
self.model = model
6767

68-
self.config_table={'data': {'main': self.data_func}}
68+
self.config_table = {'data': {'main': self.data_func}}
6969

7070
def data_func(self, frame_ids):
7171
print('the camera is:', self.model.camera.serial_number, frame_ids, self.model.frame_id)
@@ -75,6 +75,25 @@ def generate_meta_data(self, *args):
7575
# print('This frame: snap one frame', self.model.frame_id)
7676
return True
7777

78+
class WaitToContinue:
79+
def __init__(self, model):
80+
self.model = model
81+
self.can_continue = False
82+
self.target_frame_id = -1
83+
84+
self.config_table = {'signal': {'main': self.signal_func},
85+
'data': {'pre-main': self.data_func}}
86+
87+
def signal_func(self):
88+
self.can_continue = True
89+
self.target_frame_id = self.model.frame_id
90+
print('--wait to continue:', self.target_frame_id)
91+
return True
92+
93+
def data_func(self, frame_ids):
94+
print('??continue??', self.target_frame_id, frame_ids)
95+
return self.can_continue and (self.target_frame_id in frame_ids)
96+
7897

7998
class ZStackAcquisition:
8099
def __init__(self, model):

src/aslm/model/model_features/aslm_feature_container.py

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,10 @@ def set_property(self, **kwargs):
4747
setattr(self, key, kwargs[key])
4848

4949
class SignalNode(TreeNode):
50-
def __init__(self, feature_name, func_dict, *, node_type='one-step', device_related=False, need_response=False):
50+
def __init__(self, feature_name, func_dict, *, node_type='one-step', device_related=False, need_response=False, **kwargs):
5151
super().__init__(feature_name, func_dict, node_type=node_type, device_related=device_related, need_response=need_response)
52-
self.has_response_func = func_dict.get('main-response') != None
5352
self.wait_response = False
5453

55-
# if node type is multi-step, the node should have one response function
56-
if self.node_type == 'multi-step' and self.has_response_func == False and self.device_related == False:
57-
self.node_funcs['main-response'] = dummy_True
58-
self.has_response_func = True
59-
6054
def run(self, *args, wait_response=False):
6155
# initialize the node when first time entering it
6256
if not self.is_initialized:
@@ -66,15 +60,15 @@ def run(self, *args, wait_response=False):
6660
if not wait_response:
6761
# print(self.node_name, 'running function:', self.node_funcs['main'])
6862
result = self.node_funcs['main'](*args)
69-
if self.has_response_func:
63+
if self.need_response:
7064
self.wait_response = True
7165
return result, False
7266

7367
elif self.wait_response:
7468
# print(self.node_name, 'running response function:', self.node_funcs['main-response'])
7569
result = self.node_funcs['main-response'](*args)
7670
self.wait_response = False
77-
elif self.device_related or self.has_response_func:
71+
elif self.device_related or self.need_response:
7872
return None, False
7973
else:
8074
# run(wait_response=True)
@@ -87,10 +81,14 @@ def run(self, *args, wait_response=False):
8781
return result, True
8882

8983
class DataNode(TreeNode):
90-
def __init__(self, feature_name, func_dict, *, node_type='one-step', device_related=False, need_response=False):
84+
def __init__(self, feature_name, func_dict, *, node_type='one-step', device_related=False, need_response=False, **kwargs):
9185
super().__init__(feature_name, func_dict, node_type=node_type, device_related=device_related, need_response=need_response)
86+
self.is_marked = False
9287

9388
def run(self, *args):
89+
if self.is_marked:
90+
return None, True
91+
9492
# initialize the node when first time entering it
9593
if not self.is_initialized:
9694
self.node_funcs['init']()
@@ -111,19 +109,29 @@ def run(self, *args):
111109

112110

113111
class Container:
114-
def __init__(self, root=None):
112+
def __init__(self, root=None, cleanup_list=[]):
115113
self.root = root # root node of the tree
116114
self.curr_node = None # current running node
117115
self.end_flag = False # stop running flag
116+
self.cleanup_list = cleanup_list # a list of nodes containing 'cleanup' functions.
117+
self.is_closed = False
118118

119119
def reset(self):
120120
self.curr_node = None
121121
self.end_flag = False
122122

123+
def cleanup(self):
124+
for node in self.cleanup_list:
125+
try:
126+
node.node_funcs['cleanup']()
127+
except:
128+
pass
129+
self.is_closed = True
130+
123131

124132
class SignalContainer(Container):
125-
def __init__(self, root=None, number_of_execution=1):
126-
super().__init__(root)
133+
def __init__(self, root=None, cleanup_list=[], number_of_execution=1):
134+
super().__init__(root, cleanup_list)
127135
self.number_of_execution = number_of_execution
128136
self.remaining_number_of_execution = number_of_execution
129137

@@ -139,7 +147,12 @@ def run(self, *args, wait_response=False):
139147
self.curr_node = self.root
140148
while self.curr_node:
141149
print('running signal node:', self.curr_node.node_name)
142-
result, is_end = self.curr_node.run(*args, wait_response=wait_response)
150+
try:
151+
result, is_end = self.curr_node.run(*args, wait_response=wait_response)
152+
except:
153+
self.end_flag = True
154+
self.cleanup()
155+
return
143156
if not is_end:
144157
return
145158
if self.curr_node.sibling:
@@ -155,14 +168,12 @@ def run(self, *args, wait_response=False):
155168
return
156169

157170
if self.curr_node.device_related:
158-
return
159-
160-
171+
return
161172

162173

163174
class DataContainer(Container):
164-
def __init__(self, root=None):
165-
super().__init__(root)
175+
def __init__(self, root=None, cleanup_list=[]):
176+
super().__init__(root, cleanup_list)
166177
self.returned_a_response = False
167178

168179
def run(self, *args):
@@ -172,7 +183,24 @@ def run(self, *args):
172183
self.curr_node = self.root
173184
self.returned_a_response = False
174185
while self.curr_node:
175-
result, is_end = self.curr_node.run(*args)
186+
try:
187+
result, is_end = self.curr_node.run(*args)
188+
except:
189+
if self.curr_node.need_response == False and self.curr_node.node_type == 'one-step':
190+
try:
191+
self.curr_node.node_funcs.get('cleanup', dummy_func)()
192+
except:
193+
print(f'The node({self.curr_node.node_name}) is not closed correctly! Please check the cleanup function')
194+
pass
195+
self.curr_node.is_marked = True
196+
result, is_end = False, True
197+
else:
198+
# terminate the container.
199+
# the signal container may stuck there waiting a response,
200+
# the cleanup function of that node should give it a fake response to make it stop
201+
self.end_flag = True
202+
self.cleanup()
203+
return
176204
# print('Data running node:', self.curr_node.node_name, 'get result:', result)
177205
if not is_end:
178206
return
@@ -197,11 +225,7 @@ def get_registered_funcs(feature_module, func_type='signal'):
197225
if 'init' not in func_dict:
198226
func_dict['init'] = dummy_func
199227
if 'main' not in func_dict:
200-
# TODO: keep this now, later might change it after figuring out the meta data thing
201-
if hasattr(feature_module, 'generate_meta_data') and func_type == 'signal':
202-
func_dict['main'] = feature_module.generate_meta_data
203-
else:
204-
func_dict['main'] = dummy_True
228+
func_dict['main'] = dummy_True
205229
if 'end' not in func_dict:
206230
func_dict['end'] = dummy_True
207231
if func_type == 'data' and 'pre-main' not in func_dict:
@@ -210,6 +234,7 @@ def get_registered_funcs(feature_module, func_type='signal'):
210234

211235
def load_features(model, feature_list):
212236
""" turn list to child-sibling tree"""
237+
signal_cleanup_list, data_cleanup_list = [], []
213238
signal_root, data_root = TreeNode('none', None), TreeNode('none', None)
214239
pre_signal = signal_root
215240
pre_data = data_root
@@ -219,18 +244,26 @@ def load_features(model, feature_list):
219244
if 'args' in temp[i]:
220245
args = temp[i]['args']
221246
feature = temp[i]['name'](model, *args)
222-
signal_node = SignalNode(temp[i]['name'].__name__, get_registered_funcs(feature, 'signal'))
223-
data_node = DataNode(temp[i]['name'].__name__, get_registered_funcs(feature, 'data'))
247+
248+
node_config = feature.config_table.get('node', {})
224249
# if signal function has a waiting func, then the nodes are 'need_response' nodes
225250
if 'main-response' in feature.config_table.get('signal', {}):
226-
signal_node.need_response = True
227-
data_node.need_response = True
228-
if 'node' in feature.config_table:
229-
signal_node.set_property(**feature.config_table['node'])
230-
data_node.set_property(**feature.config_table['node'])
251+
node_config['need_response'] = True
231252
if 'node' in temp[i]:
232-
signal_node.set_property(**temp[i]['node'])
233-
data_node.set_property(**temp[i]['node'])
253+
for k, v in temp[i]['node'].items():
254+
node_config[k] = v
255+
# 'multi-step' must set to be 'device_related'
256+
if node_config.get('node_type', '') == 'multi-step':
257+
node_config['device_related'] = True
258+
259+
signal_node = SignalNode(temp[i]['name'].__name__, get_registered_funcs(feature, 'signal'), **node_config)
260+
data_node = DataNode(temp[i]['name'].__name__, get_registered_funcs(feature, 'data'), **node_config)
261+
262+
if 'cleanup' in feature.config_table.get('signal', {}):
263+
signal_cleanup_list.append(signal_node)
264+
if 'cleanup' in feature.config_table.get('data', {}):
265+
data_cleanup_list.append(data_node)
266+
234267
if i == 0:
235268
pre_signal.child = signal_node
236269
pre_data.child = data_node
@@ -240,7 +273,7 @@ def load_features(model, feature_list):
240273
pre_signal = signal_node
241274
pre_data = data_node
242275

243-
return SignalContainer(signal_root.child), DataContainer(data_root.child)
276+
return SignalContainer(signal_root.child, signal_cleanup_list), DataContainer(data_root.child, data_cleanup_list)
244277

245278

246279
def dummy_True(*args):

src/aslm/model/model_features/aslm_image_writer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def __init__(self, model, sub_dir=''):
5959
self.data_buffer = self.model.data_buffer
6060
self.current_time_point = 0
6161
self.config_table = {'signal': {},
62-
'data': {'main': self.save_image}}
62+
'data': {'main': self.save_image,
63+
'cleanup': self.close}}
6364

6465
# create the save directory if it doesn't already exist
6566
self.save_directory = os.path.join(self.model.experiment.Saving['save_directory'], self.sub_dir)

0 commit comments

Comments
 (0)