1
- import logging
2
- import subprocess
3
- import platform
4
- import atexit
5
- import threading
6
- import types
7
- import os
8
-
1
+ from mmpycorex import create_core_instance , terminate_core_instances
2
+ from mmpycorex import Core
9
3
from pycromanager .acquisition .acq_eng_py .internal .engine import Engine
10
- from pymmcore import CMMCore
4
+ from pyjavaz import DEFAULT_BRIDGE_PORT
5
+ import atexit
11
6
import pymmcore
12
- from pyjavaz import DEFAULT_BRIDGE_PORT , server_terminated
13
-
14
- import re
15
-
16
- logger = logging .getLogger (__name__ )
17
-
18
- class TaggedImage :
19
-
20
- def __init__ (self , tags , pix ):
21
- self .tags = tags
22
- self .pix = pix
23
-
24
- def _camel_to_snake (name ):
25
- """
26
- Convert camelCase string to snake_case
27
- """
28
- s1 = re .sub ('(.)([A-Z][a-z]+)' , r'\1_\2' , name )
29
- return re .sub ('([a-z0-9])([A-Z])' , r'\1_\2' , s1 ).lower ()
30
-
31
- def _create_pymmcore_instance ():
32
- """
33
- Make a subclass of CMMCore with two differences:
34
-
35
- 1. All methods are converted to snake_case
36
- 2. add convenience methods to match the MMCoreJ API:
37
- """
38
-
39
- # Create a new dictionary for the class attributes
40
- new_attributes = {}
41
-
42
- # Iterate through the original attributes
43
- for attr_name , attr_value in vars (CMMCore ).items ():
44
- # If it's a dunder method, skip it (we don't want to override these)
45
- if attr_name .startswith ("__" ) and attr_name .endswith ("__" ):
46
- continue
47
- # If the attribute is callable (i.e., a method), convert its name to snake_case and add it
48
- if callable (attr_value ):
49
- new_attr_name = _camel_to_snake (attr_name )
50
- new_attributes [new_attr_name ] = attr_value
51
-
52
- # Create and return a new class that subclasses the original class and has the new attributes
53
- clz = type (CMMCore .__name__ + "SnakeCase" , (CMMCore ,), new_attributes )
54
-
55
- instance = clz ()
56
-
57
- def pop_next_tagged_image (self ):
58
- md = pymmcore .Metadata ()
59
- pix = self .pop_next_image_md (0 , 0 , md )
60
- tags = {key : md .GetSingleTag (key ).GetValue () for key in md .GetKeys ()}
61
- return TaggedImage (tags , pix )
62
-
63
- def get_tagged_image (core , cam_index , camera , height , width , binning = None , pixel_type = None , roi_x_start = None ,
64
- roi_y_start = None ):
65
- """
66
- Different signature than the Java version because of difference in metadata handling in the swig layers
67
- """
68
- pix = core .get_image ()
69
- md = pymmcore .Metadata ()
70
- # most of the same tags from pop_next_tagged_image, which may not be the same as the MMCoreJ version of this function
71
- tags = {'Camera' : camera , 'Height' : height , 'Width' : width , 'PixelType' : pixel_type ,
72
- 'CameraChannelIndex' : cam_index }
73
- # Could optionally add these for completeness but there might be a performance hit
74
- if binning is not None :
75
- tags ['Binning' ] = binning
76
- if roi_x_start is not None :
77
- tags ['ROI-X-start' ] = roi_x_start
78
- if roi_y_start is not None :
79
- tags ['ROI-Y-start' ] = roi_y_start
80
-
81
- return TaggedImage (tags , pix )
82
-
83
- instance .get_tagged_image = types .MethodType (get_tagged_image , instance )
84
- instance .pop_next_tagged_image = types .MethodType (pop_next_tagged_image , instance )
85
-
86
- # attach TaggedImage class
87
- instance .TaggedImage = TaggedImage
88
- return instance
89
-
90
-
91
- _JAVA_HEADLESS_SUBPROCESSES = []
92
- _PYMMCORES = []
93
-
94
- def stop_headless (debug = False ):
95
-
96
- for p in _JAVA_HEADLESS_SUBPROCESSES :
97
- port = p .port
98
- if debug :
99
- logger .debug ('Stopping headless process with pid {}' .format (p .pid ))
100
- p .terminate ()
101
- server_terminated (port )
102
- if debug :
103
- logger .debug ('Waiting for process with pid {} to terminate' .format (p .pid ))
104
- p .wait () # wait for process to terminate
105
- if debug :
106
- logger .debug ('Process with pid {} terminated' .format (p .pid ))
107
- _JAVA_HEADLESS_SUBPROCESSES .clear ()
108
- if debug :
109
- logger .debug ('Stopping {} pymmcore instances' .format (len (_PYMMCORES )))
110
- for c in _PYMMCORES :
111
- if debug :
112
- logger .debug ('Stopping pymmcore instance' )
113
- c .unloadAllDevices ()
114
- if debug :
115
- logger .debug ('Unloaded all devices' )
116
- Engine .get_instance ().shutdown ()
117
- if debug :
118
- logger .debug ('Engine shut down' )
119
- _PYMMCORES .clear ()
120
- if debug :
121
- logger .debug ('Headless stopped' )
7
+ import types
122
8
123
- # make sure any Java processes are cleaned up when Python exits
124
- atexit .register (stop_headless )
125
9
126
10
def start_headless (
127
11
mm_app_path : str , config_file : str = None , java_loc : str = None ,
128
12
python_backend = False , core_log_path : str = '' ,
129
13
buffer_size_mb : int = 1024 , max_memory_mb : int = 2000 ,
130
14
port : int = DEFAULT_BRIDGE_PORT , debug = False ):
131
15
"""
132
- Start a Java process that contains the neccessary libraries for pycro-manager to run,
133
- so that it can be run independently of the Micro-Manager GUI/application. This calls
134
- will create and initialize MMCore with the configuration file provided .
16
+ Start an instance of the Micro-Manager core and acquisition engine in headless mode. This can be
17
+ either a Python (i.e. pymmcore) or Java (i.e. MMCoreJ) backend. If a Python backend is used,
18
+ the core will be started in the same process .
135
19
136
20
On windows plaforms, the Java Runtime Environment will be grabbed automatically
137
21
as it is installed along with the Micro-Manager application.
@@ -161,68 +45,17 @@ def start_headless(
161
45
debug : bool
162
46
Print debug messages
163
47
"""
164
-
48
+ create_core_instance (
49
+ mm_app_path = mm_app_path , config_file = config_file , java_loc = java_loc ,
50
+ python_backend = python_backend , core_log_path = core_log_path ,
51
+ buffer_size_mb = buffer_size_mb , max_memory_mb = max_memory_mb ,
52
+ port = port , debug = debug )
165
53
if python_backend :
166
- mmc = _create_pymmcore_instance ()
167
- mmc .set_device_adapter_search_paths ([mm_app_path ])
168
- if config_file is not None and config_file != "" :
169
- mmc .load_system_configuration (config_file )
170
- mmc .set_circular_buffer_memory_footprint (buffer_size_mb )
171
- _PYMMCORES .append (mmc ) # Store so it doesn't get garbage collected
172
- Engine (mmc )
173
- else :
174
- classpath = mm_app_path + '/plugins/Micro-Manager/*'
175
- if java_loc is None :
176
- if platform .system () == "Windows" :
177
- # windows comes with its own JRE
178
- java_loc = mm_app_path + "/jre/bin/javaw.exe"
179
- else :
180
- java_loc = "java"
181
- if debug :
182
- logger .debug (f'Java location: { java_loc } ' )
183
- #print classpath
184
- logger .debug (f'Classpath: { classpath } ' )
185
- # print stuff in the classpath directory
186
- logger .debug ('Contents of classpath directory:' )
187
- for f in os .listdir (classpath .split ('*' )[0 ]):
188
- logger .debug (f )
189
-
190
- # This starts Java process and instantiates essential objects (core,
191
- # acquisition engine, ZMQServer)
192
- process = subprocess .Popen (
193
- [
194
- java_loc ,
195
- "-classpath" ,
196
- classpath ,
197
- "-Dsun.java2d.dpiaware=false" ,
198
- f"-Xmx{ max_memory_mb } m" ,
199
- # This is used by MM desktop app but breaks things on MacOS...Don't think its neccessary
200
- # "-XX:MaxDirectMemorySize=1000",
201
- "org.micromanager.remote.HeadlessLauncher" ,
202
- str (port ),
203
- config_file if config_file is not None else '' ,
204
- str (buffer_size_mb ),
205
- core_log_path ,
206
- ], cwd = mm_app_path , stdout = subprocess .PIPE
207
- )
208
- process .port = port
209
- _JAVA_HEADLESS_SUBPROCESSES .append (process )
210
-
211
- started = False
212
- output = True
213
- # Some drivers output various status messages which need to be skipped over to look for the STARTED token.
214
- while output and not started :
215
- output = process .stdout .readline ()
216
- started = "STARTED" in output .decode ('utf-8' )
217
- if not started :
218
- raise Exception ('Error starting headless mode' )
219
- if debug :
220
- logger .debug ('Headless mode started' )
221
- def loggerFunction ():
222
- while process in _JAVA_HEADLESS_SUBPROCESSES :
223
- line = process .stdout .readline ().decode ('utf-8' )
224
- if line .strip () != '' :
225
- logger .debug (line )
226
- threading .Thread (target = loggerFunction ).start ()
54
+ Engine (Core ())
227
55
56
+ def stop_headless (debug = False ):
57
+ terminate_core_instances (debug = debug )
58
+ Engine .get_instance ().shutdown ()
228
59
60
+ # make sure any Java processes are cleaned up when Python exits
61
+ atexit .register (stop_headless )
0 commit comments