pyREyes 3.4.0__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.
- pyREyes/REyes.py +958 -0
- pyREyes/append_targets_nav.py +343 -0
- pyREyes/config/filtering_options.json +19 -0
- pyREyes/config/microscope_configs.json +92 -0
- pyREyes/create_final_targets_nav.py +476 -0
- pyREyes/dif_map.py +1219 -0
- pyREyes/docs/README_FULL.md +522 -0
- pyREyes/docs/modules/diffraction-map.md +90 -0
- pyREyes/docs/modules/eucentricity.md +64 -0
- pyREyes/docs/modules/final-targets.md +92 -0
- pyREyes/docs/modules/grid-squares.md +130 -0
- pyREyes/docs/modules/manual-squares.md +61 -0
- pyREyes/docs/modules/map-plotter.md +95 -0
- pyREyes/docs/modules/monitor.md +113 -0
- pyREyes/docs/modules/navigation.md +88 -0
- pyREyes/docs/modules/targets.md +96 -0
- pyREyes/eucentricity.py +392 -0
- pyREyes/grid_squares.py +530 -0
- pyREyes/lib/REyes_errors.py +18 -0
- pyREyes/lib/REyes_logging.py +44 -0
- pyREyes/lib/REyes_microscope_configurations.py +36 -0
- pyREyes/lib/REyes_utils.py +35 -0
- pyREyes/lib/__init__.py +0 -0
- pyREyes/lib/diffraction/DiffractionDataProcessor.py +210 -0
- pyREyes/lib/grid_processing/CentroidProcessor.py +692 -0
- pyREyes/lib/grid_processing/GridProcessor.py +50 -0
- pyREyes/lib/grid_processing/filtering_options.py +30 -0
- pyREyes/lib/image_processing/CartImage.py +660 -0
- pyREyes/lib/image_processing/CartImage_utils.py +51 -0
- pyREyes/lib/image_processing/PlottingManager.py +210 -0
- pyREyes/lib/image_processing/__init__.py +0 -0
- pyREyes/lib/image_processing/image_processor.py +531 -0
- pyREyes/lib/ui/REyes_ui.py +16 -0
- pyREyes/lib/ui/__init__.py +0 -0
- pyREyes/manual_squares.py +148 -0
- pyREyes/mnt_maps_targets.py +650 -0
- pyREyes/write_sorted_targets_nav.py +158 -0
- pyreyes-3.4.0.dist-info/METADATA +117 -0
- pyreyes-3.4.0.dist-info/RECORD +42 -0
- pyreyes-3.4.0.dist-info/WHEEL +4 -0
- pyreyes-3.4.0.dist-info/entry_points.txt +10 -0
- pyreyes-3.4.0.dist-info/licenses/LICENSE +15 -0
pyREyes/REyes.py
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REyes Monitor
|
|
3
|
+
Monitoring tool for REyes processing scripts to automate the workflow.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
import logging
|
|
9
|
+
from subprocess import run, PIPE, Popen
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
from enum import Enum, auto
|
|
13
|
+
import argparse
|
|
14
|
+
import shutil
|
|
15
|
+
import threading
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from pyREyes.lib.ui.REyes_ui import print_banner
|
|
19
|
+
from pyREyes.lib.REyes_microscope_configurations import MicroscopeConfig, load_microscope_configs
|
|
20
|
+
|
|
21
|
+
MICROSCOPE_CONFIGS = load_microscope_configs()
|
|
22
|
+
|
|
23
|
+
class ProcessingState(Enum):
|
|
24
|
+
WAITING_FOR_MONTAGE = auto()
|
|
25
|
+
WAITING_FOR_GRID_SQUARES = auto()
|
|
26
|
+
WAITING_FOR_MANUAL_SQUARES = auto()
|
|
27
|
+
WAITING_FOR_DIFFRACTION_MAP = auto()
|
|
28
|
+
GENERATING_TARGETS = auto()
|
|
29
|
+
GENERATING_FINAL_MAP = auto()
|
|
30
|
+
WAITING_FOR_MOVIES = auto()
|
|
31
|
+
RUNNING_AUTOSOLVE = auto()
|
|
32
|
+
COMPLETED = auto()
|
|
33
|
+
|
|
34
|
+
class CustomHelpFormatter(argparse.HelpFormatter):
|
|
35
|
+
def _split_lines(self, text, width):
|
|
36
|
+
if text.startswith('States:'):
|
|
37
|
+
return text.splitlines()
|
|
38
|
+
return super()._split_lines(text, width)
|
|
39
|
+
|
|
40
|
+
__version__ = '3.4.0'
|
|
41
|
+
__min_required_version__ = '3.4.0'
|
|
42
|
+
|
|
43
|
+
class REyesProcessor:
|
|
44
|
+
def __init__(self,
|
|
45
|
+
working_directory='.',
|
|
46
|
+
microscope='Arctica-CETA',
|
|
47
|
+
filtering='default',
|
|
48
|
+
camera_length=None,
|
|
49
|
+
manual_squares=False,
|
|
50
|
+
start_state=None,
|
|
51
|
+
current_block=1,
|
|
52
|
+
top_target_per_category=2,
|
|
53
|
+
top_target_per_block=None,
|
|
54
|
+
stepscan_only=False,
|
|
55
|
+
# Autoprocess parameters
|
|
56
|
+
autoprocess=False,
|
|
57
|
+
microscope_config=None,
|
|
58
|
+
rotation_axis=None,
|
|
59
|
+
frame_size=None,
|
|
60
|
+
signal_pixel=None,
|
|
61
|
+
min_pixel=None,
|
|
62
|
+
background_pixel=None,
|
|
63
|
+
pixel_size=None,
|
|
64
|
+
wavelength=None,
|
|
65
|
+
beam_center_x=None,
|
|
66
|
+
beam_center_y=None,
|
|
67
|
+
file_extension=None,
|
|
68
|
+
detector_distance=None,
|
|
69
|
+
exposure=None,
|
|
70
|
+
rotation=None,
|
|
71
|
+
# Autosolve parameters
|
|
72
|
+
autosolve=False,
|
|
73
|
+
shelx=None,
|
|
74
|
+
ntryt=None,
|
|
75
|
+
ntryf=None):
|
|
76
|
+
|
|
77
|
+
# Setup logging
|
|
78
|
+
log_file_path = os.path.join('REyes_logs', 'REyes_monitor.log')
|
|
79
|
+
|
|
80
|
+
# Ensure the log directory exists
|
|
81
|
+
os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
|
|
82
|
+
|
|
83
|
+
formatter = logging.Formatter('%(asctime)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
84
|
+
|
|
85
|
+
file_handler = logging.FileHandler(log_file_path)
|
|
86
|
+
file_handler.setFormatter(formatter)
|
|
87
|
+
|
|
88
|
+
class FlushingStreamHandler(logging.StreamHandler):
|
|
89
|
+
def emit(self, record):
|
|
90
|
+
super().emit(record)
|
|
91
|
+
self.flush()
|
|
92
|
+
|
|
93
|
+
console_handler = FlushingStreamHandler(sys.stdout)
|
|
94
|
+
console_handler.setFormatter(formatter)
|
|
95
|
+
|
|
96
|
+
logging.basicConfig(
|
|
97
|
+
level=logging.INFO, # or INFO
|
|
98
|
+
handlers=[file_handler, console_handler]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
self.logger = logging.getLogger(__name__)
|
|
102
|
+
self.working_directory = os.path.abspath(working_directory)
|
|
103
|
+
self.processed_logs = set()
|
|
104
|
+
self.state = ProcessingState[start_state] if start_state else ProcessingState.WAITING_FOR_MONTAGE
|
|
105
|
+
|
|
106
|
+
self.nav_pattern = re.compile(r'(.+)_(\d+)LM_(\d+)x(\d+)_nav\.nav$')
|
|
107
|
+
|
|
108
|
+
self.current_block = current_block
|
|
109
|
+
self.microscope = microscope
|
|
110
|
+
self.filtering = filtering
|
|
111
|
+
self.manual_squares = manual_squares
|
|
112
|
+
self.camera_length = camera_length
|
|
113
|
+
self.top_target_per_category = top_target_per_category
|
|
114
|
+
self.top_target_per_block = top_target_per_block
|
|
115
|
+
|
|
116
|
+
self.stepscan_only = stepscan_only
|
|
117
|
+
|
|
118
|
+
self.autoprocess = autoprocess
|
|
119
|
+
# Save autoprocess arguments individually
|
|
120
|
+
if autoprocess:
|
|
121
|
+
self.microscope_config = microscope_config
|
|
122
|
+
self.rotation_axis = rotation_axis
|
|
123
|
+
self.frame_size = frame_size
|
|
124
|
+
self.signal_pixel = signal_pixel
|
|
125
|
+
self.min_pixel = min_pixel
|
|
126
|
+
self.background_pixel = background_pixel
|
|
127
|
+
self.pixel_size = pixel_size
|
|
128
|
+
self.wavelength = wavelength
|
|
129
|
+
self.beam_center_x = beam_center_x
|
|
130
|
+
self.beam_center_y = beam_center_y
|
|
131
|
+
self.file_extension = file_extension
|
|
132
|
+
self.detector_distance = detector_distance
|
|
133
|
+
self.exposure = exposure
|
|
134
|
+
self.rotation = rotation
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
self.autosolve = autosolve
|
|
138
|
+
|
|
139
|
+
if autosolve:
|
|
140
|
+
self.shelx = shelx
|
|
141
|
+
self.ntryt = ntryt
|
|
142
|
+
self.ntryf = ntryf
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
self.processed_movies = set()
|
|
146
|
+
self.movies_count = 0
|
|
147
|
+
self.movies_directory = os.path.join(self.working_directory, 'movies')
|
|
148
|
+
|
|
149
|
+
self.logger.info(f"Initializing with state: {self.state.name}")
|
|
150
|
+
self.logger.info(f"Current block: {self.current_block}")
|
|
151
|
+
|
|
152
|
+
def run_module(self, module_name, args=None):
|
|
153
|
+
"""Run a package module as a subprocess command with arguments"""
|
|
154
|
+
try:
|
|
155
|
+
self.logger.info(f"Running module: {module_name}")
|
|
156
|
+
|
|
157
|
+
# Map module names to their corresponding command line names
|
|
158
|
+
module_commands = {
|
|
159
|
+
'grid_squares': 'grid-squares-0',
|
|
160
|
+
'manual_squares': 'manual-squares-0-1',
|
|
161
|
+
'eucentricity': 'eucentricity-1',
|
|
162
|
+
'dif_map': 'dif-map-2',
|
|
163
|
+
'write_targets': 'write-targets-3',
|
|
164
|
+
'append_targets': 'append-targets-3-1',
|
|
165
|
+
'create_final_targets': 'create-final-targets-4',
|
|
166
|
+
'mnt_maps_targets': 'mnt-maps-targets-5'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if module_name not in module_commands:
|
|
170
|
+
self.logger.error(f"Module {module_name} not found")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
# Construct the command with the entry point name
|
|
174
|
+
cmd = [module_commands[module_name]]
|
|
175
|
+
|
|
176
|
+
# Add arguments if provided
|
|
177
|
+
if args:
|
|
178
|
+
for key, value in args.items():
|
|
179
|
+
if isinstance(value, bool):
|
|
180
|
+
if value: # Only add flag if True
|
|
181
|
+
cmd.append(f"--{key.replace('_', '-')}")
|
|
182
|
+
else:
|
|
183
|
+
cmd.append(f"--{key.replace('_', '-')}")
|
|
184
|
+
cmd.append(str(value))
|
|
185
|
+
|
|
186
|
+
self.logger.info(f"Running command: {' '.join(cmd)}")
|
|
187
|
+
|
|
188
|
+
# Run the command and capture output
|
|
189
|
+
result = run(cmd, stdout=PIPE, stderr=PIPE, text=True, cwd=self.working_directory)
|
|
190
|
+
|
|
191
|
+
# Log the complete output to the main log file
|
|
192
|
+
if result.stdout:
|
|
193
|
+
self.logger.info(f"Process output:\n{result.stdout}")
|
|
194
|
+
|
|
195
|
+
# Log stderr as well, but as a warning or error
|
|
196
|
+
if result.stderr:
|
|
197
|
+
self.logger.warning(f"Process stderr:\n{result.stderr}")
|
|
198
|
+
|
|
199
|
+
if result.returncode == 0:
|
|
200
|
+
self.logger.info(f"Successfully executed {module_name}")
|
|
201
|
+
return True
|
|
202
|
+
else:
|
|
203
|
+
self.logger.error(f"{module_name} failed with return code {result.returncode}")
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
self.logger.error(f"Error running module {module_name}: {str(e)}")
|
|
208
|
+
import traceback
|
|
209
|
+
self.logger.error(traceback.format_exc())
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
def find_nav_files(self):
|
|
213
|
+
"""Find .nav files and extract their information"""
|
|
214
|
+
nav_files = [f for f in os.listdir(self.working_directory) if f.endswith('_nav.nav')]
|
|
215
|
+
nav_info = []
|
|
216
|
+
|
|
217
|
+
for nav_file in nav_files:
|
|
218
|
+
match = self.nav_pattern.match(nav_file)
|
|
219
|
+
if match:
|
|
220
|
+
prefix, lm_number, x_dim, y_dim = match.groups()
|
|
221
|
+
nav_info.append({
|
|
222
|
+
'nav_file': nav_file,
|
|
223
|
+
'prefix': prefix,
|
|
224
|
+
'lm_number': lm_number,
|
|
225
|
+
'dimensions': f"{x_dim}x{y_dim}"
|
|
226
|
+
})
|
|
227
|
+
self.logger.debug(f"Found .nav file: {nav_file} with prefix {prefix}, "
|
|
228
|
+
f"LM number {lm_number}, dimensions {x_dim}x{y_dim}")
|
|
229
|
+
|
|
230
|
+
return nav_info
|
|
231
|
+
|
|
232
|
+
def find_montage_logs(self):
|
|
233
|
+
"""Find montage log files based on .nav file information"""
|
|
234
|
+
nav_info_list = self.find_nav_files()
|
|
235
|
+
montage_logs = []
|
|
236
|
+
|
|
237
|
+
for nav_info in nav_info_list:
|
|
238
|
+
# Look for corresponding log file with the same prefix and dimensions
|
|
239
|
+
expected_log = f"{nav_info['prefix']}_{nav_info['lm_number']}LM_{nav_info['dimensions']}.log"
|
|
240
|
+
if (os.path.exists(os.path.join(self.working_directory, expected_log)) and
|
|
241
|
+
expected_log not in self.processed_logs):
|
|
242
|
+
montage_logs.append(expected_log)
|
|
243
|
+
self.logger.debug(f"Found matching montage log file: {expected_log}")
|
|
244
|
+
|
|
245
|
+
return montage_logs
|
|
246
|
+
|
|
247
|
+
def find_grid_squares_logs(self):
|
|
248
|
+
"""Find grid squares log files"""
|
|
249
|
+
return [f for f in os.listdir(self.working_directory)
|
|
250
|
+
if f.endswith('_grid_squares.log') and
|
|
251
|
+
f not in self.processed_logs]
|
|
252
|
+
|
|
253
|
+
def find_diffraction_map_logs(self, nav_info):
|
|
254
|
+
"""Find diffraction map log files for current block"""
|
|
255
|
+
block_log = f"{nav_info['prefix']}_{self.current_block}.log"
|
|
256
|
+
log_path = os.path.join(self.working_directory, block_log)
|
|
257
|
+
|
|
258
|
+
if os.path.exists(log_path) and block_log not in self.processed_logs:
|
|
259
|
+
return [block_log]
|
|
260
|
+
return []
|
|
261
|
+
|
|
262
|
+
def find_movie_logs(self):
|
|
263
|
+
"""Find movie acquisition log file"""
|
|
264
|
+
movie_logs = [f for f in os.listdir(self.working_directory)
|
|
265
|
+
if f.endswith('_targets.log') and
|
|
266
|
+
f not in self.processed_logs]
|
|
267
|
+
#self.logger.info(f"Found movie log files: {movie_logs}")
|
|
268
|
+
return movie_logs
|
|
269
|
+
|
|
270
|
+
def find_new_movies(self, log_file):
|
|
271
|
+
"""Find newly acquired movies from the targets log file"""
|
|
272
|
+
try:
|
|
273
|
+
new_movies = []
|
|
274
|
+
current_folder = None
|
|
275
|
+
current_movie = None
|
|
276
|
+
|
|
277
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
278
|
+
for line in f:
|
|
279
|
+
line = line.strip()
|
|
280
|
+
|
|
281
|
+
# Extract movie folder information
|
|
282
|
+
if 'Movie folder:' in line:
|
|
283
|
+
current_folder = line.split('Movie folder:')[-1].strip()
|
|
284
|
+
|
|
285
|
+
# Extract acquired movie name
|
|
286
|
+
elif 'Acquired movie:' in line:
|
|
287
|
+
current_movie = line.split('Acquired movie:')[-1].strip()
|
|
288
|
+
|
|
289
|
+
# If we have both folder and movie name, create movie info
|
|
290
|
+
if current_folder and current_movie:
|
|
291
|
+
movie_info = {
|
|
292
|
+
'folder': current_folder,
|
|
293
|
+
'acquired_name': current_movie,
|
|
294
|
+
'suffix': '_integrated_movie.mrc' # Generic filename suffix
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Create a unique identifier for tracking processed movies
|
|
298
|
+
movie_id = f"{current_folder}/{current_movie}"
|
|
299
|
+
|
|
300
|
+
if movie_id not in self.processed_movies:
|
|
301
|
+
new_movies.append(movie_info)
|
|
302
|
+
|
|
303
|
+
# Reset for next movie
|
|
304
|
+
current_folder = None
|
|
305
|
+
current_movie = None
|
|
306
|
+
|
|
307
|
+
return new_movies
|
|
308
|
+
|
|
309
|
+
except Exception as e:
|
|
310
|
+
self.logger.error(f"Error reading movie log file {log_file}: {str(e)}")
|
|
311
|
+
return []
|
|
312
|
+
|
|
313
|
+
def check_montage_completion(self, log_file):
|
|
314
|
+
"""Check if montage is complete"""
|
|
315
|
+
try:
|
|
316
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
317
|
+
content = f.read()
|
|
318
|
+
# Extract the LM number from the filename
|
|
319
|
+
match = re.search(r'_(\d+)LM_', log_file)
|
|
320
|
+
if match:
|
|
321
|
+
expected_number = match.group(1)
|
|
322
|
+
completion_pattern = f'completed {expected_number}x lm montage data collection'
|
|
323
|
+
if completion_pattern.lower() in content.lower():
|
|
324
|
+
self.logger.info(f"Montage completion detected in {log_file}")
|
|
325
|
+
return True
|
|
326
|
+
except Exception as e:
|
|
327
|
+
self.logger.error(f"Error reading montage log file {log_file}: {str(e)}")
|
|
328
|
+
return False
|
|
329
|
+
|
|
330
|
+
def check_nav_file_ready(self, nav_file):
|
|
331
|
+
"""Check if nav file has more than one item"""
|
|
332
|
+
try:
|
|
333
|
+
with open(nav_file, 'r') as f:
|
|
334
|
+
content = f.read()
|
|
335
|
+
items = re.findall(r'\[Item\s*=\s*(\d+)\]', content)
|
|
336
|
+
item_count = len([item for item in items if item != '1'])
|
|
337
|
+
self.logger.debug(f"Found {item_count} additional items in nav file")
|
|
338
|
+
return item_count > 0
|
|
339
|
+
except Exception as e:
|
|
340
|
+
self.logger.error(f"Error checking nav file {nav_file}: {str(e)}")
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
def handle_manual_squares_state(self):
|
|
344
|
+
"""Handle the manual squares state processing"""
|
|
345
|
+
start_time = time.time()
|
|
346
|
+
timeout = 3600
|
|
347
|
+
|
|
348
|
+
# Add clear initial message
|
|
349
|
+
self.logger.info("Waiting for manual grid squares to be added...")
|
|
350
|
+
self.logger.info("Please add grid squares through SerialEM")
|
|
351
|
+
self.logger.info("Once all grid squares are added, initiate the next script")
|
|
352
|
+
|
|
353
|
+
while time.time() - start_time < timeout:
|
|
354
|
+
nav_info_list = self.find_nav_files()
|
|
355
|
+
|
|
356
|
+
for nav_info in nav_info_list:
|
|
357
|
+
nav_file = os.path.join(self.working_directory, nav_info['nav_file'])
|
|
358
|
+
if self.check_nav_file_ready(nav_file):
|
|
359
|
+
self.logger.info("Additional grid squares detected in nav file")
|
|
360
|
+
if self.run_module('manual_squares', {}):
|
|
361
|
+
# Wait for log file to confirm completion
|
|
362
|
+
log_file = os.path.join(self.working_directory, 'REyes_logs', 'manual_squares.log')
|
|
363
|
+
if os.path.exists(log_file) and self.check_manual_squares_completion(log_file):
|
|
364
|
+
self.state = ProcessingState.WAITING_FOR_GRID_SQUARES
|
|
365
|
+
self.logger.info("Manual squares processing completed")
|
|
366
|
+
self.logger.info("Moving to eucentricity adjustment state")
|
|
367
|
+
return True
|
|
368
|
+
time.sleep(5)
|
|
369
|
+
self.logger.warning("Manual squares processing timed out")
|
|
370
|
+
return False
|
|
371
|
+
|
|
372
|
+
def check_manual_squares_completion(self, log_file):
|
|
373
|
+
"""Check if manual squares processing is complete"""
|
|
374
|
+
try:
|
|
375
|
+
with open(log_file, 'r', encoding='utf-8') as f:
|
|
376
|
+
content = f.read()
|
|
377
|
+
if 'processing completed successfully!'.lower() in content.lower():
|
|
378
|
+
self.logger.info("Manual squares completion detected")
|
|
379
|
+
return True
|
|
380
|
+
except Exception as e:
|
|
381
|
+
self.logger.error(f"Error reading manual squares log file: {str(e)}")
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
def check_grid_squares_completion(self, log_file):
|
|
385
|
+
"""Check if grid squares processing is complete"""
|
|
386
|
+
try:
|
|
387
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
388
|
+
content = f.read()
|
|
389
|
+
if 'completed fine eucentricity for all grid squares'.lower() in content.lower():
|
|
390
|
+
self.logger.info(f"Grid squares completion detected in {log_file}")
|
|
391
|
+
return True
|
|
392
|
+
except Exception as e:
|
|
393
|
+
self.logger.error(f"Error reading grid squares log file {log_file}: {str(e)}")
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
def check_diffraction_map_completion(self, log_file):
|
|
397
|
+
"""Check if diffraction map processing is complete for current block"""
|
|
398
|
+
try:
|
|
399
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
400
|
+
content = f.read()
|
|
401
|
+
if 'completed stepscan for current grid square'.lower() in content.lower():
|
|
402
|
+
self.logger.info(f"Diffraction map block {self.current_block} completion detected in {log_file}")
|
|
403
|
+
return True
|
|
404
|
+
except Exception as e:
|
|
405
|
+
self.logger.error(f"Error reading diffraction map log file {log_file}: {str(e)}")
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
def check_all_maps_completion(self, log_file):
|
|
409
|
+
"""Check if all diffraction map blocks are complete"""
|
|
410
|
+
try:
|
|
411
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
412
|
+
content = f.read()
|
|
413
|
+
if 'completed stepscan for all grid squares'.lower() in content.lower():
|
|
414
|
+
self.logger.info("Diffraction mapping completed")
|
|
415
|
+
return True
|
|
416
|
+
except Exception as e:
|
|
417
|
+
self.logger.error(f"Error reading diffraction map log file {log_file}: {str(e)}")
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
def check_targets_completion(self, log_file):
|
|
421
|
+
"""Check diffraction movies complete"""
|
|
422
|
+
try:
|
|
423
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
424
|
+
content = f.read()
|
|
425
|
+
if 'completed movie acquisition'.lower() in content.lower():
|
|
426
|
+
self.logger.info("Diffraction movie completion detected")
|
|
427
|
+
return True
|
|
428
|
+
except Exception as e:
|
|
429
|
+
self.logger.error(f"Error reading diffraction map log file {log_file}: {str(e)}")
|
|
430
|
+
return False
|
|
431
|
+
|
|
432
|
+
def check_all_targets_completion(self, log_file):
|
|
433
|
+
"""Check all diffraction movies complete"""
|
|
434
|
+
try:
|
|
435
|
+
with open(os.path.join(self.working_directory, log_file), 'r', encoding='utf-8') as f:
|
|
436
|
+
content = f.read()
|
|
437
|
+
if 'completed acquiring tilt movies for all crystals!'.lower() in content.lower():
|
|
438
|
+
self.logger.info("All diffraction movies completion detected")
|
|
439
|
+
return True
|
|
440
|
+
except Exception as e:
|
|
441
|
+
self.logger.error(f"Error reading diffraction map log file {log_file}: {str(e)}")
|
|
442
|
+
return False
|
|
443
|
+
|
|
444
|
+
def process_movie(self, movie_info):
|
|
445
|
+
def find_movie_file(root_dir, target_name, suffix):
|
|
446
|
+
for dirpath, _, filenames in os.walk(root_dir):
|
|
447
|
+
for f in filenames:
|
|
448
|
+
if f == target_name or f.endswith(suffix):
|
|
449
|
+
return os.path.join(dirpath, f)
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
"""Process a single movie using autoprocess.py"""
|
|
453
|
+
try:
|
|
454
|
+
# Construct the full path to the movie directory
|
|
455
|
+
movie_dir = os.path.join(self.movies_directory, movie_info['folder'])
|
|
456
|
+
|
|
457
|
+
# Recursively search for the movie file
|
|
458
|
+
movie_path = find_movie_file(movie_dir, movie_info['acquired_name'], movie_info['suffix'])
|
|
459
|
+
if not movie_path:
|
|
460
|
+
self.logger.error(f"No integrated movie file found in directory or subdirectories: {movie_dir}")
|
|
461
|
+
return False
|
|
462
|
+
|
|
463
|
+
target_path = os.path.join(movie_dir, movie_info['acquired_name'])
|
|
464
|
+
|
|
465
|
+
# Rename if needed
|
|
466
|
+
if movie_path != target_path:
|
|
467
|
+
try:
|
|
468
|
+
self.logger.info(f"Renaming {movie_path} → {target_path}")
|
|
469
|
+
os.rename(movie_path, target_path)
|
|
470
|
+
self.logger.info(f"Successfully renamed file to {movie_info['acquired_name']}")
|
|
471
|
+
except Exception as e:
|
|
472
|
+
self.logger.error(f"Failed to rename movie file: {str(e)}")
|
|
473
|
+
return False
|
|
474
|
+
|
|
475
|
+
# Create unique movie identifier for tracking
|
|
476
|
+
movie_id = f"{movie_info['folder']}/{movie_info['acquired_name']}"
|
|
477
|
+
self.processed_movies.add(movie_id)
|
|
478
|
+
self.movies_count += 1
|
|
479
|
+
|
|
480
|
+
if self.autoprocess:
|
|
481
|
+
self.logger.info(f"Running autoprocess on movie {movie_info['acquired_name']}")
|
|
482
|
+
|
|
483
|
+
cmd = ["autoprocess"]
|
|
484
|
+
|
|
485
|
+
# Add autoprocess arguments
|
|
486
|
+
def add_arg(flag, value):
|
|
487
|
+
if value is not None:
|
|
488
|
+
cmd.append(f"--{flag}")
|
|
489
|
+
if not isinstance(value, bool):
|
|
490
|
+
cmd.append(str(value))
|
|
491
|
+
|
|
492
|
+
add_arg("microscope-config", self.microscope_config)
|
|
493
|
+
add_arg("rotation-axis", self.rotation_axis)
|
|
494
|
+
add_arg("frame-size", self.frame_size)
|
|
495
|
+
add_arg("signal-pixel", self.signal_pixel)
|
|
496
|
+
add_arg("min-pixel", self.min_pixel)
|
|
497
|
+
add_arg("background-pixel", self.background_pixel)
|
|
498
|
+
add_arg("pixel-size", self.pixel_size)
|
|
499
|
+
add_arg("wavelength", self.wavelength)
|
|
500
|
+
add_arg("beam-center-x", self.beam_center_x)
|
|
501
|
+
add_arg("beam-center-y", self.beam_center_y)
|
|
502
|
+
add_arg("file-extension", self.file_extension)
|
|
503
|
+
add_arg("detector-distance", self.detector_distance)
|
|
504
|
+
add_arg("exposure", self.exposure)
|
|
505
|
+
add_arg("rotation", self.rotation)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
self.logger.info(f"Running autoprocess command: {' '.join(cmd)}")
|
|
509
|
+
|
|
510
|
+
# Run the command and capture output
|
|
511
|
+
try:
|
|
512
|
+
result = run(cmd, stdout=PIPE, stderr=PIPE, text=True, cwd=movie_dir)
|
|
513
|
+
|
|
514
|
+
# Print stdout directly without logger formatting
|
|
515
|
+
if result.stdout:
|
|
516
|
+
self.logger.info(f"Autoprocess stdout:\n{result.stdout}")
|
|
517
|
+
|
|
518
|
+
# Print stderr directly without logger formatting
|
|
519
|
+
if result.stderr:
|
|
520
|
+
self.logger.warning(f"Autoprocess stderr:\n{result.stderr}")
|
|
521
|
+
|
|
522
|
+
if result.returncode == 0:
|
|
523
|
+
self.logger.info(f"Successfully processed movie {movie_info['acquired_name']}")
|
|
524
|
+
return True
|
|
525
|
+
else:
|
|
526
|
+
self.logger.error(f"Autoprocess failed with return code {result.returncode}")
|
|
527
|
+
return False
|
|
528
|
+
except Exception as e:
|
|
529
|
+
self.logger.error(f"Error running autoprocess: {str(e)}")
|
|
530
|
+
return False
|
|
531
|
+
else:
|
|
532
|
+
self.logger.info(f"Skipping autoprocess for movie {movie_info['acquired_name']} (autoprocess disabled)")
|
|
533
|
+
|
|
534
|
+
self.logger.info(f"Successfully handled movie {self.movies_count}: {movie_info['acquired_name']}")
|
|
535
|
+
return True
|
|
536
|
+
|
|
537
|
+
except Exception as e:
|
|
538
|
+
self.logger.error(f"Error processing movie {movie_info['acquired_name']}: {str(e)}")
|
|
539
|
+
import traceback
|
|
540
|
+
self.logger.error(traceback.format_exc())
|
|
541
|
+
return False
|
|
542
|
+
|
|
543
|
+
def run_autosolve(self):
|
|
544
|
+
def stream_reader(pipe, log_func):
|
|
545
|
+
for line in iter(pipe.readline, ''):
|
|
546
|
+
log_func(line.strip())
|
|
547
|
+
|
|
548
|
+
"""Run the AutoSolve pipeline after REyes is completed."""
|
|
549
|
+
try:
|
|
550
|
+
autosolve_cmd = [
|
|
551
|
+
"autosolve",
|
|
552
|
+
"--shelx", self.shelx,
|
|
553
|
+
"--ntryt", str(self.ntryt),
|
|
554
|
+
"--ntryf", str(self.ntryf)
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
self.logger.info(f"Running AutoSolve command: {' '.join(autosolve_cmd)}")
|
|
558
|
+
|
|
559
|
+
process = Popen(autosolve_cmd, cwd=self.working_directory, stdout=PIPE, stderr=PIPE, text=True)
|
|
560
|
+
|
|
561
|
+
# Start threads to read stdout and stderr concurrently
|
|
562
|
+
stdout_thread = threading.Thread(target=stream_reader, args=(process.stdout, self.logger.info))
|
|
563
|
+
stderr_thread = threading.Thread(target=stream_reader, args=(process.stderr, self.logger.warning))
|
|
564
|
+
stdout_thread.start()
|
|
565
|
+
stderr_thread.start()
|
|
566
|
+
|
|
567
|
+
# Wait for process and threads to complete
|
|
568
|
+
returncode = process.wait()
|
|
569
|
+
stdout_thread.join()
|
|
570
|
+
stderr_thread.join()
|
|
571
|
+
|
|
572
|
+
if returncode == 0:
|
|
573
|
+
self.logger.info("AutoSolve completed successfully!")
|
|
574
|
+
return True
|
|
575
|
+
else:
|
|
576
|
+
# Fixed: changed result.returncode to returncode
|
|
577
|
+
self.logger.error(f"AutoSolve failed with return code {returncode}")
|
|
578
|
+
return False
|
|
579
|
+
|
|
580
|
+
except Exception as e:
|
|
581
|
+
self.logger.error(f"Error running AutoSolve: {str(e)}")
|
|
582
|
+
import traceback
|
|
583
|
+
self.logger.error(traceback.format_exc())
|
|
584
|
+
|
|
585
|
+
def monitor(self):
|
|
586
|
+
"""Main monitoring loop with state machine"""
|
|
587
|
+
self.logger.info(f"Starting monitoring in working directory: {self.working_directory}")
|
|
588
|
+
|
|
589
|
+
try:
|
|
590
|
+
while True:
|
|
591
|
+
if self.state == ProcessingState.WAITING_FOR_MONTAGE:
|
|
592
|
+
self.logger.debug("Waiting for montage completion...")
|
|
593
|
+
montage_logs = self.find_montage_logs()
|
|
594
|
+
|
|
595
|
+
for log_file in montage_logs:
|
|
596
|
+
if self.check_montage_completion(log_file):
|
|
597
|
+
self.processed_logs.add(log_file)
|
|
598
|
+
if self.run_module('grid_squares', {
|
|
599
|
+
'microscope': self.microscope,
|
|
600
|
+
'filtering': self.filtering
|
|
601
|
+
}):
|
|
602
|
+
if self.manual_squares:
|
|
603
|
+
self.state = ProcessingState.WAITING_FOR_MANUAL_SQUARES
|
|
604
|
+
self.logger.info("Moving to manual squares monitoring state")
|
|
605
|
+
else:
|
|
606
|
+
self.state = ProcessingState.WAITING_FOR_GRID_SQUARES
|
|
607
|
+
self.logger.info("Moving to grid squares monitoring state")
|
|
608
|
+
|
|
609
|
+
elif self.state == ProcessingState.WAITING_FOR_MANUAL_SQUARES:
|
|
610
|
+
if self.handle_manual_squares_state():
|
|
611
|
+
continue
|
|
612
|
+
time.sleep(5) # Wait before checking again
|
|
613
|
+
|
|
614
|
+
elif self.state == ProcessingState.WAITING_FOR_GRID_SQUARES:
|
|
615
|
+
self.logger.debug("Waiting for eucentricity adjustment...")
|
|
616
|
+
grid_logs = self.find_grid_squares_logs()
|
|
617
|
+
|
|
618
|
+
for log_file in grid_logs:
|
|
619
|
+
if self.check_grid_squares_completion(log_file):
|
|
620
|
+
self.processed_logs.add(log_file)
|
|
621
|
+
if self.run_module('eucentricity', {}):
|
|
622
|
+
self.state = ProcessingState.WAITING_FOR_DIFFRACTION_MAP
|
|
623
|
+
self.logger.info("Moving to diffraction map monitoring state")
|
|
624
|
+
|
|
625
|
+
elif self.state == ProcessingState.WAITING_FOR_DIFFRACTION_MAP:
|
|
626
|
+
self.logger.debug(f"Waiting for diffraction map block {self.current_block} completion...")
|
|
627
|
+
nav_info_list = self.find_nav_files()
|
|
628
|
+
|
|
629
|
+
for nav_info in nav_info_list:
|
|
630
|
+
diffraction_logs = self.find_diffraction_map_logs(nav_info)
|
|
631
|
+
|
|
632
|
+
for log_file in diffraction_logs:
|
|
633
|
+
# First check if current block is complete and process it
|
|
634
|
+
if self.check_diffraction_map_completion(log_file):
|
|
635
|
+
self.processed_logs.add(log_file)
|
|
636
|
+
folder_name = log_file.replace(".log", "")
|
|
637
|
+
|
|
638
|
+
success = self.run_module('dif_map', {
|
|
639
|
+
'microscope': self.microscope,
|
|
640
|
+
'folder': folder_name,
|
|
641
|
+
'skip_processed': True,
|
|
642
|
+
'proc_blocks': 1
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
if success:
|
|
646
|
+
if self.check_all_maps_completion(log_file):
|
|
647
|
+
self.state = ProcessingState.GENERATING_TARGETS
|
|
648
|
+
self.logger.info("All diffraction map blocks completed, moving to target generation")
|
|
649
|
+
else:
|
|
650
|
+
self.current_block += 1
|
|
651
|
+
self.logger.info(f"Moving to next block: {self.current_block}")
|
|
652
|
+
else:
|
|
653
|
+
self.logger.warning(f"Failed to process {folder_name}, skipping to next block")
|
|
654
|
+
self.current_block += 1
|
|
655
|
+
|
|
656
|
+
elif self.state == ProcessingState.GENERATING_TARGETS:
|
|
657
|
+
self.logger.debug("Generating target lists...")
|
|
658
|
+
write_targets_args = {
|
|
659
|
+
'microscope': self.microscope
|
|
660
|
+
}
|
|
661
|
+
if self.camera_length is not None:
|
|
662
|
+
write_targets_args['camera_length'] = self.camera_length
|
|
663
|
+
|
|
664
|
+
if self.run_module('write_targets', write_targets_args):
|
|
665
|
+
self.logger.info("All targets identified")
|
|
666
|
+
self.logger.debug("Generating spatialy aware targets navigator...")
|
|
667
|
+
create_targets_args = {
|
|
668
|
+
'top-target-per-category': self.top_target_per_category
|
|
669
|
+
}
|
|
670
|
+
if self.top_target_per_block is not None:
|
|
671
|
+
create_targets_args['top-target-per-block'] = self.top_target_per_block
|
|
672
|
+
|
|
673
|
+
if self.run_module('create_final_targets', create_targets_args):
|
|
674
|
+
self.state = ProcessingState.GENERATING_FINAL_MAP
|
|
675
|
+
self.logger.info("Moving to final map generation state")
|
|
676
|
+
|
|
677
|
+
elif self.state == ProcessingState.GENERATING_FINAL_MAP:
|
|
678
|
+
self.logger.debug("Generating final diffraction map...")
|
|
679
|
+
if self.run_module('mnt_maps_targets', {
|
|
680
|
+
'microscope': self.microscope
|
|
681
|
+
}):
|
|
682
|
+
if self.stepscan_only:
|
|
683
|
+
self.state = ProcessingState.COMPLETED
|
|
684
|
+
self.logger.info("REyes processing completed! (Step scan only)")
|
|
685
|
+
return
|
|
686
|
+
else:
|
|
687
|
+
self.state = ProcessingState.WAITING_FOR_MOVIES
|
|
688
|
+
self.logger.info("REyes processing completed!")
|
|
689
|
+
self.logger.debug("Monitoring for new movies...")
|
|
690
|
+
|
|
691
|
+
elif self.state == ProcessingState.WAITING_FOR_MOVIES:
|
|
692
|
+
self.logger.debug("Waiting for the next tilt dataset...")
|
|
693
|
+
movie_logs = self.find_movie_logs()
|
|
694
|
+
|
|
695
|
+
for log_file in movie_logs:
|
|
696
|
+
# Find any new movies
|
|
697
|
+
new_movies = self.find_new_movies(log_file)
|
|
698
|
+
|
|
699
|
+
# Process each new movie
|
|
700
|
+
for movie_info in new_movies:
|
|
701
|
+
if self.process_movie(movie_info):
|
|
702
|
+
self.logger.info(f"Successfully processed movie {self.movies_count}")
|
|
703
|
+
else:
|
|
704
|
+
self.logger.error(f"Failed to process movie: {movie_info['acquired_name']}")
|
|
705
|
+
|
|
706
|
+
# Check if all movies are completed
|
|
707
|
+
if self.check_all_targets_completion(log_file):
|
|
708
|
+
if self.autosolve:
|
|
709
|
+
self.state = ProcessingState.RUNNING_AUTOSOLVE
|
|
710
|
+
self.logger.info(f"All movies processed ({self.movies_count} total). Starting AutoSolve.")
|
|
711
|
+
else:
|
|
712
|
+
self.state = ProcessingState.COMPLETED
|
|
713
|
+
self.logger.info(f"All movies processed ({self.movies_count} total). Sample to Indexing completed.")
|
|
714
|
+
return
|
|
715
|
+
|
|
716
|
+
elif self.state == ProcessingState.RUNNING_AUTOSOLVE:
|
|
717
|
+
self.logger.debug("Running AutoSolve...")
|
|
718
|
+
if self.run_autosolve():
|
|
719
|
+
self.state = ProcessingState.COMPLETED
|
|
720
|
+
self.logger.info("AutoSolve completed. Marking processing as COMPLETED.")
|
|
721
|
+
else:
|
|
722
|
+
self.logger.error("AutoSolve failed. Still marking processing as COMPLETED.")
|
|
723
|
+
self.state = ProcessingState.COMPLETED
|
|
724
|
+
return
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
time.sleep(5)
|
|
728
|
+
|
|
729
|
+
except KeyboardInterrupt:
|
|
730
|
+
self.logger.info("Shutting down monitor...")
|
|
731
|
+
except Exception as e:
|
|
732
|
+
self.logger.error(f"Unexpected error in monitor loop: {str(e)}")
|
|
733
|
+
import traceback
|
|
734
|
+
self.logger.error(traceback.format_exc())
|
|
735
|
+
raise
|
|
736
|
+
|
|
737
|
+
def main():
|
|
738
|
+
"""
|
|
739
|
+
Args:
|
|
740
|
+
args (argparse.Namespace): Must contain:
|
|
741
|
+
- working_directory (str): Path to working directory
|
|
742
|
+
- microscope (str): Microscope type
|
|
743
|
+
- filtering (str): Filtering type
|
|
744
|
+
Returns:
|
|
745
|
+
int: 0 on success, non-zero on failure
|
|
746
|
+
"""
|
|
747
|
+
print_banner()
|
|
748
|
+
parser = argparse.ArgumentParser(
|
|
749
|
+
description='REyes Processing Monitor Settings',
|
|
750
|
+
formatter_class=CustomHelpFormatter
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
state_descriptions = {
|
|
754
|
+
'WAITING_FOR_MONTAGE': 'Wait for montage image acquisition',
|
|
755
|
+
'WAITING_FOR_GRID_SQUARES': 'Wait for grid squares processing and eucentricity',
|
|
756
|
+
'WAITING_FOR_MANUAL_SQUARES': 'Wait for manual grid squares selection',
|
|
757
|
+
'WAITING_FOR_DIFFRACTION_MAP': 'Wait for diffraction mapping',
|
|
758
|
+
'GENERATING_TARGETS': 'Generate target lists',
|
|
759
|
+
'GENERATING_FINAL_MAP': 'Generate final diffraction atlas',
|
|
760
|
+
'WAITING_FOR_MOVIES': 'Wait for movie acquisition',
|
|
761
|
+
'RUNNING_AUTOSOLVE': 'Run AutoSolve for structure solution',
|
|
762
|
+
'COMPLETED': 'Processing completed'
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
states_help_text = 'States:\n' + '\n'.join(f' {state}: {desc}'
|
|
766
|
+
for state, desc in state_descriptions.items())
|
|
767
|
+
|
|
768
|
+
# Create argument groups
|
|
769
|
+
reyes_group = parser.add_argument_group('REyes arguments', 'Arguments for REyes processing')
|
|
770
|
+
autoprocess_group = parser.add_argument_group('Autoprocess arguments', 'Arguments passed to autoprocess.py')
|
|
771
|
+
autosolve_group = parser.add_argument_group('Autosolve arguments', 'Arguments passed to autosolve.py')
|
|
772
|
+
|
|
773
|
+
# Microscope-specific arguments
|
|
774
|
+
parser.add_argument('--microscope',
|
|
775
|
+
type=str,
|
|
776
|
+
choices=list(MICROSCOPE_CONFIGS.keys()),
|
|
777
|
+
default="Arctica-CETA",
|
|
778
|
+
help='Microscope configuration to use')
|
|
779
|
+
|
|
780
|
+
# REyes-specific arguments
|
|
781
|
+
reyes_group.add_argument('--filtering',
|
|
782
|
+
choices=['default', '1', '4', '9', '16', '25', '36', '49', '64', '81', '100', '121', '144', '169', '196', '96', 'None'],
|
|
783
|
+
default='default',
|
|
784
|
+
help='Filtering type to use (default: default)')
|
|
785
|
+
reyes_group.add_argument('--manual-squares',
|
|
786
|
+
action='store_true',
|
|
787
|
+
help='Enable manual squares processing step')
|
|
788
|
+
|
|
789
|
+
reyes_group.add_argument('--camera-length',
|
|
790
|
+
type=float,
|
|
791
|
+
default=None,
|
|
792
|
+
help='Optional override for camera length in mm (default: based on microscope config)')
|
|
793
|
+
|
|
794
|
+
reyes_group.add_argument('--top-target-per-category',
|
|
795
|
+
type=int,
|
|
796
|
+
default=2,
|
|
797
|
+
help='Number of top targets to select per category (used by create-final-targets)')
|
|
798
|
+
|
|
799
|
+
reyes_group.add_argument('--top-target-per-block',
|
|
800
|
+
type=int,
|
|
801
|
+
default=None,
|
|
802
|
+
help='Number of top targets to select per block (used by create-final-targets)')
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
# Add state selection argument
|
|
806
|
+
reyes_group.add_argument('--starting-state',
|
|
807
|
+
choices=[state.name for state in ProcessingState],
|
|
808
|
+
help=states_help_text)
|
|
809
|
+
|
|
810
|
+
reyes_group.add_argument('--current-block',
|
|
811
|
+
type=int,
|
|
812
|
+
default=1,
|
|
813
|
+
help='Current block number (for diffraction map state)')
|
|
814
|
+
|
|
815
|
+
reyes_group.add_argument('--stepscan-only',
|
|
816
|
+
action='store_true',
|
|
817
|
+
help='Do not collect tilt series movies for targets')
|
|
818
|
+
|
|
819
|
+
reyes_group.add_argument('--autoprocess',
|
|
820
|
+
action='store_true',
|
|
821
|
+
help='Run AutoProcess after movie acquisition')
|
|
822
|
+
|
|
823
|
+
# Autoprocess arguments (parsed individually like autosolve)
|
|
824
|
+
autoprocess_group.add_argument('--microscope-config',
|
|
825
|
+
type=str,
|
|
826
|
+
required='--autoprocess' in sys.argv,
|
|
827
|
+
help='Microscope config name (e.g., Arctica-CETA-mrc-SM)')
|
|
828
|
+
|
|
829
|
+
autoprocess_group.add_argument('--rotation-axis',
|
|
830
|
+
type=str,
|
|
831
|
+
help='Rotation axis (e.g., "-1 0 0")')
|
|
832
|
+
|
|
833
|
+
autoprocess_group.add_argument('--frame-size',
|
|
834
|
+
type=int,
|
|
835
|
+
help='Frame size of the movie (e.g., 2048)')
|
|
836
|
+
|
|
837
|
+
autoprocess_group.add_argument('--signal-pixel',
|
|
838
|
+
type=int,
|
|
839
|
+
help='Signal pixel radius')
|
|
840
|
+
|
|
841
|
+
autoprocess_group.add_argument('--min-pixel',
|
|
842
|
+
type=int,
|
|
843
|
+
help='Minimum pixel radius')
|
|
844
|
+
|
|
845
|
+
autoprocess_group.add_argument('--background-pixel',
|
|
846
|
+
type=int,
|
|
847
|
+
help='Background pixel radius')
|
|
848
|
+
|
|
849
|
+
autoprocess_group.add_argument('--pixel-size',
|
|
850
|
+
type=float,
|
|
851
|
+
help='Pixel size in microns (e.g., 0.028)')
|
|
852
|
+
|
|
853
|
+
autoprocess_group.add_argument('--wavelength',
|
|
854
|
+
type=float,
|
|
855
|
+
help='Beam wavelength (e.g., 0.0251)')
|
|
856
|
+
|
|
857
|
+
autoprocess_group.add_argument('--beam-center-x',
|
|
858
|
+
type=int,
|
|
859
|
+
help='Beam center X coordinate')
|
|
860
|
+
|
|
861
|
+
autoprocess_group.add_argument('--beam-center-y',
|
|
862
|
+
type=int,
|
|
863
|
+
help='Beam center Y coordinate')
|
|
864
|
+
|
|
865
|
+
autoprocess_group.add_argument('--file-extension',
|
|
866
|
+
type=str,
|
|
867
|
+
help='File extension for movies (e.g., ".mrc" or ".ser")')
|
|
868
|
+
|
|
869
|
+
autoprocess_group.add_argument('--detector-distance',
|
|
870
|
+
type=float,
|
|
871
|
+
help='Detector distance in mm')
|
|
872
|
+
|
|
873
|
+
autoprocess_group.add_argument('--exposure',
|
|
874
|
+
type=float,
|
|
875
|
+
help='Exposure time')
|
|
876
|
+
|
|
877
|
+
autoprocess_group.add_argument('--rotation',
|
|
878
|
+
type=float,
|
|
879
|
+
help='Rotation value')
|
|
880
|
+
|
|
881
|
+
autosolve_group.add_argument('--autosolve',
|
|
882
|
+
action='store_true',
|
|
883
|
+
help='Run AutoSolve after AutoProcess')
|
|
884
|
+
|
|
885
|
+
autosolve_group.add_argument('--shelx',
|
|
886
|
+
choices=['t', 'd', 'td'],
|
|
887
|
+
default='td',
|
|
888
|
+
help='Which SHELX programs to run (t: SHELXT, d: SHELXD, td: both)')
|
|
889
|
+
|
|
890
|
+
autosolve_group.add_argument('--ntryt',
|
|
891
|
+
type=int,
|
|
892
|
+
default=1000,
|
|
893
|
+
help='Number of trials for initial SHELXD screening (default: 1000)')
|
|
894
|
+
|
|
895
|
+
autosolve_group.add_argument('--ntryf',
|
|
896
|
+
type=int,
|
|
897
|
+
default=10000,
|
|
898
|
+
help='Number of trials for extended SHELXD runs (default: 10000)')
|
|
899
|
+
|
|
900
|
+
args = parser.parse_args()
|
|
901
|
+
|
|
902
|
+
# Validate autoprocess dependencies
|
|
903
|
+
if args.autoprocess and not args.microscope_config:
|
|
904
|
+
parser.error("--microscope-config is required when using --autoprocess")
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
working_dir = os.path.abspath('.')
|
|
909
|
+
|
|
910
|
+
processor_kwargs = dict(
|
|
911
|
+
working_directory=working_dir,
|
|
912
|
+
microscope=args.microscope,
|
|
913
|
+
filtering=args.filtering,
|
|
914
|
+
camera_length=args.camera_length,
|
|
915
|
+
manual_squares=args.manual_squares,
|
|
916
|
+
start_state=args.starting_state,
|
|
917
|
+
current_block=args.current_block,
|
|
918
|
+
autoprocess=args.autoprocess,
|
|
919
|
+
top_target_per_category=args.top_target_per_category,
|
|
920
|
+
top_target_per_block=args.top_target_per_block,
|
|
921
|
+
stepscan_only=args.stepscan_only
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
# Only include AutoProcess args if the flag is set
|
|
925
|
+
if args.autoprocess:
|
|
926
|
+
processor_kwargs.update({
|
|
927
|
+
'microscope_config': args.microscope_config,
|
|
928
|
+
'rotation_axis': args.rotation_axis,
|
|
929
|
+
'frame_size': args.frame_size,
|
|
930
|
+
'signal_pixel': args.signal_pixel,
|
|
931
|
+
'min_pixel': args.min_pixel,
|
|
932
|
+
'background_pixel': args.background_pixel,
|
|
933
|
+
'pixel_size': args.pixel_size,
|
|
934
|
+
'wavelength': args.wavelength,
|
|
935
|
+
'beam_center_x': args.beam_center_x,
|
|
936
|
+
'beam_center_y': args.beam_center_y,
|
|
937
|
+
'file_extension': args.file_extension,
|
|
938
|
+
'detector_distance': args.detector_distance,
|
|
939
|
+
'exposure': args.exposure,
|
|
940
|
+
'rotation': args.rotation,
|
|
941
|
+
})
|
|
942
|
+
|
|
943
|
+
# Only include AutoSolve args if the flag is set
|
|
944
|
+
if args.autosolve:
|
|
945
|
+
processor_kwargs.update({
|
|
946
|
+
'autosolve': True,
|
|
947
|
+
'shelx': args.shelx,
|
|
948
|
+
'ntryt': args.ntryt,
|
|
949
|
+
'ntryf': args.ntryf,
|
|
950
|
+
})
|
|
951
|
+
|
|
952
|
+
processor = REyesProcessor(**processor_kwargs)
|
|
953
|
+
|
|
954
|
+
processor.monitor()
|
|
955
|
+
return 0
|
|
956
|
+
|
|
957
|
+
if __name__ == "__main__":
|
|
958
|
+
main()
|