BioImager  3.6.0
A .NET microscopy imaging library. Supports various microscopes by using imported libraries & GUI automation. Supported libraries include PriorĀ® & ZeissĀ® & all devices supported by Micromanager 2.0 and python-microscope.
Loading...
Searching...
No Matches
microscope.device_server Namespace Reference

Classes

class  DeviceServer
 
class  DeviceServerOptions
 
class  Filter
 

Functions

def device (typing.Callable cls, str host, int port, typing.Mapping[str, typing.Any] conf={}, typing.Optional[str] uid=None)
 
def serve_devices (devices, DeviceServerOptions options, exit_event=None)
 
def validate_devices (configfile)
 
int main (typing.Sequence[str] argv)
 
None __main__ ()
 

Variables

 multiprocessing = multiprocessing.get_context("spawn")
 
 SERIALIZER
 
 REQUIRE_EXPOSE
 

Function Documentation

◆ __main__()

None microscope.device_server.__main__ ( )

Definition at line 624 of file device_server.py.

624def __main__() -> None:
625 # Kept for backwards compatibility. It keeps the setuptools
626 # scripts from older editable mode installations. Will be safe to
627 # remove soon.
628 _setuptools_entry_point()
629
630

◆ device()

def microscope.device_server.device ( typing.Callable  cls,
str  host,
int  port,
typing.Mapping[str, typing.Any]   conf = {},
typing.Optional[str]   uid = None 
)
Define devices and where to serve them.

A device definition for use in deviceserver config files.

Args:
    cls: :class:`Device` class of device to serve or function that
        returns a map of `Device` instances to wanted Pyro ID.
        The device class will be constructed, or the function will
        be called, with the arguments in ``conf``.
    host: hostname or ip address serving the devices.
    port: port number used to serve the devices.
    conf: keyword arguments for ``cls``.  The device or function
        are effectively constructed or called with `cls(**conf)`.
    uid: used to identify "floating" devices (see documentation
        for :class:`FloatingDeviceMixin`).  This must be specified
        if ``cls`` is a floating device.

Example

.. code-block:: python

    def construct_devices() -> typing.Dict[str, Device]:
        camera = Camera(some, arguments)
        # ... any other configuration that might be wanted
        return {'RedCamera': camera}

    DEVICES = [
        # passing a function that returns devices
        device(construct_devices, '127.0.0.1', 8000),
        # passing a Device class
        device(Camera, '127.0.0.1', 8001,
               conf={'kwarg1': some, 'kwarg2': arguments})
    ]

Definition at line 81 of file device_server.py.

87):
88 """Define devices and where to serve them.
89
90 A device definition for use in deviceserver config files.
91
92 Args:
93 cls: :class:`Device` class of device to serve or function that
94 returns a map of `Device` instances to wanted Pyro ID.
95 The device class will be constructed, or the function will
96 be called, with the arguments in ``conf``.
97 host: hostname or ip address serving the devices.
98 port: port number used to serve the devices.
99 conf: keyword arguments for ``cls``. The device or function
100 are effectively constructed or called with `cls(**conf)`.
101 uid: used to identify "floating" devices (see documentation
102 for :class:`FloatingDeviceMixin`). This must be specified
103 if ``cls`` is a floating device.
104
105 Example
106
107 .. code-block:: python
108
109 def construct_devices() -> typing.Dict[str, Device]:
110 camera = Camera(some, arguments)
111 # ... any other configuration that might be wanted
112 return {'RedCamera': camera}
113
114 DEVICES = [
115 # passing a function that returns devices
116 device(construct_devices, '127.0.0.1', 8000),
117 # passing a Device class
118 device(Camera, '127.0.0.1', 8001,
119 conf={'kwarg1': some, 'kwarg2': arguments})
120 ]
121
122 """
123 if not callable(cls):
124 raise TypeError("cls must be a callable")
125 elif isinstance(cls, type):
126 if issubclass(cls, FloatingDeviceMixin) and uid is None:
127 raise TypeError("uid must be specified for floating devices")
128 elif not issubclass(cls, FloatingDeviceMixin) and uid is not None:
129 raise TypeError("uid must not be given for non floating devices")
130 return dict(cls=cls, host=host, port=int(port), uid=uid, conf=conf)
131
132

◆ main()

int microscope.device_server.main ( typing.Sequence[str]  argv)

Definition at line 594 of file device_server.py.

594def main(argv: typing.Sequence[str]) -> int:
595 options = _parse_cmd_line_args(argv[1:])
596
597 root_logger = logging.getLogger()
598 root_logger.setLevel(options.logging_level)
599
600 stderr_handler = StreamHandler(sys.stderr)
601 stderr_handler.setFormatter(_create_log_formatter("device-server"))
602 root_logger.addHandler(stderr_handler)
603
604 root_logger.addFilter(Filter())
605
606 devices = validate_devices(options.config_fpath)
607
608 serve_devices(devices, options)
609
610 return 0
611
612

◆ serve_devices()

def microscope.device_server.serve_devices (   devices,
DeviceServerOptions  options,
  exit_event = None 
)

Definition at line 393 of file device_server.py.

393def serve_devices(devices, options: DeviceServerOptions, exit_event=None):
394 # We make changes to `devices` (would be great if we didn't had
395 # to) so make a a copy of it because we don't want to make those
396 # changes on the caller. See original issue on #211 and PRs #212
397 # and #217 (most discussion happens on #212).
398 devices = copy.deepcopy(devices)
399
400 root_logger = logging.getLogger()
401
402 log_handler = RotatingFileHandler("__MAIN__.log")
403 log_handler.setFormatter(_create_log_formatter("device-server"))
404 root_logger.addHandler(log_handler)
405
406 # An event to trigger clean termination of subprocesses. This is the
407 # only way to ensure devices are shut down properly when processes
408 # exit, as __del__ is not necessarily called when the interpreter exits.
409 if exit_event is None:
410 exit_event = multiprocessing.Event()
411
412 servers = (
413 []
414 ) # DeviceServers instances that we need to wait for when exiting
415
416 # Child processes inherit signal handling from the parent so we
417 # need to make sure that only the parent process sets the exit
418 # event and waits for the DeviceServers to exit. See issue #9.
419 # This won't work behind a Windows service wrapper, so we deal with
420 # clean shutdown on win32 elsewhere.
421 parent = multiprocessing.current_process()
422
423 def term_func(sig, frame):
424 """Terminate subprocesses cleanly."""
425 if parent == multiprocessing.current_process():
426 _logger.debug("Shutting down all servers.")
427 exit_event.set()
428 # Join keep_alive_thread so that it can't modify the list
429 # of servers.
430 keep_alive_thread.join()
431 for this_server in servers:
432 this_server.join()
433 sys.exit()
434
435 if sys.platform != "win32":
436 signal.signal(signal.SIGTERM, term_func)
437 signal.signal(signal.SIGINT, term_func)
438
439 # Group devices by class.
440 by_class = {}
441 for dev in devices:
442 by_class[dev["cls"]] = by_class.get(dev["cls"], []) + [dev]
443
444 if not by_class:
445 _logger.warning("No valid devices specified. Maybe an empty list?")
446
447 for cls, devs in by_class.items():
448 # Floating devices are devices that can only be identified
449 # after having been initialized, so the constructor will
450 # return any device that it supports. To work around this we
451 # map all device uid to host/port first. After the
452 # DeviceServer constructs the device, it can check on the map
453 # where to serve it. For non floating devices that
454 # information is part of the device definition, no map is
455 # needed.
456 uid_to_host = {}
457 uid_to_port = {}
458 if isinstance(cls, type) and issubclass(cls, FloatingDeviceMixin):
459 # In addition to the maps of uid to host/port, floating
460 # devices SDKs need the number of devices to index them.
461 count = 0
462 for dev in devs:
463 uid = dev["uid"]
464 uid_to_host[uid] = dev["host"]
465 uid_to_port[uid] = dev["port"]
466
467 dev["conf"]["index"] = count
468 count += 1
469
470 for dev in devs:
471 servers.append(
472 DeviceServer(
473 dev,
474 options,
475 uid_to_host,
476 uid_to_port,
477 exit_event=exit_event,
478 )
479 )
480 servers[-1].start()
481
482 # Main thread must be idle to process signals correctly, so use another
483 # thread to check DeviceServers, restarting them where necessary. Define
484 # the thread target here so that it can access variables in __main__ scope.
485 def keep_alive():
486 """Keep DeviceServers alive."""
487 while not exit_event.is_set():
488 for s in servers:
489 if s.is_alive():
490 continue
491 else:
492 _logger.info(
493 "DeviceServer Failure. Process %s is dead with"
494 " exitcode %s. Restarting...",
495 s.pid,
496 s.exitcode,
497 )
498 servers.remove(s)
499 servers.append(s.clone())
500
501 try:
502 s.join(30)
503 except:
504 _logger.error("... could not join PID %s.", s.pid)
505 else:
506 old_pid = s.pid
507 del s
508 servers[-1].start()
509 _logger.info(
510 "... DeviceServer with PID %s restarted"
511 " as PID %s.",
512 old_pid,
513 servers[-1].pid,
514 )
515 if not servers:
516 # Log and exit if no servers running. May want to change this
517 # if we add some interface to interactively restart servers.
518 _logger.info("No servers running. Exiting.")
519 exit_event.set()
520 else:
521 try:
522 time.sleep(5)
523 except (KeyboardInterrupt, IOError):
524 pass
525
526 keep_alive_thread = Thread(target=keep_alive)
527 keep_alive_thread.start()
528
529 _logger.info("Device Server started. Press Ctrl+C to exit.")
530 while not exit_event.is_set():
531 try:
532 time.sleep(5)
533 except (KeyboardInterrupt, IOError):
534 _logger.debug("KeyboardInterrupt or IOError")
535 exit_event.set()
536
537 _logger.debug("Shutting down servers ...")
538 while servers:
539 for s in servers:
540 if not s.is_alive():
541 servers.remove(s)
542 del s
543 time.sleep(1)
544 _logger.info(" ... No more servers running.")
545 _logger.debug("Joining threads ...")
546 keep_alive_thread.join()
547 _logger.debug("... Threads joined. Exiting.")
548 return
549
550

◆ validate_devices()

def microscope.device_server.validate_devices (   configfile)

Definition at line 583 of file device_server.py.

583def validate_devices(configfile):
584 config = _load_source(configfile)
585 try:
586 devices = getattr(config, "DEVICES")
587 except AttributeError:
588 raise Exception("No 'DEVICES=...' in config file.")
589 if not isinstance(devices, Iterable):
590 raise Exception("Error in config: DEVICES should be an iterable.")
591 return devices
592
593

Variable Documentation

◆ multiprocessing

microscope.device_server.multiprocessing = multiprocessing.get_context("spawn")

Definition at line 66 of file device_server.py.

◆ REQUIRE_EXPOSE

microscope.device_server.REQUIRE_EXPOSE

Definition at line 78 of file device_server.py.

◆ SERIALIZER

microscope.device_server.SERIALIZER

Definition at line 71 of file device_server.py.