class SubSocket(object):
    """
    Subscriber Socket class.

    This class implements an IPC channel subcriber socker and sets up a callback function
    for receiving data from that socket that counts how many images it receives during its lifetime.
    """
    def __init__(self, parent, endpoint):
        """
        Initialise IPC channel as a subscriber, and register the callback.

        :param parent: the class that created this object, a LiveViewer, given so that this object
        can reference the method in the parent
        :param endpoint: the URI address of the socket to subscribe to
        """
        self.parent = parent
        self.endpoint = endpoint
        self.frame_count = 0
        self.channel = IpcTornadoChannel(IpcTornadoChannel.CHANNEL_TYPE_SUB,
                                         endpoint=endpoint)
        self.channel.subscribe()
        self.channel.connect()
        # register the get_image method to be called when the ZMQ socket receives a message
        self.channel.register_callback(self.callback)

    def callback(self, msg):
        """
        Handle incoming data on the socket.

        This callback method is called whenever data arrives on the IPC channel socket.
        Increments the counter, then passes the message on to the image renderer of the parent.
        :param msg: the multipart message from the IPC channel
        """
        self.frame_count += 1
        self.parent.create_image_from_socket(msg)

    def cleanup(self):
        """Cleanup channel when the server is closed. Closes the IPC channel socket correctly."""
        self.channel.close()
Example #2
0
class LiveViewProxyNode(object):
    """
    Live View Proxy. Connect to a ZMQ socket from an Odin Data Live View Plugin, saving any frames
    that arrive in a queue and passing them on to the central proxy controller when needed.
    """
    def __init__(self, name, endpoint, drop_warn_cutoff, callback):
        """
        Initialise a Node for the Adapter. The node should subscribe to
        a ZMQ socket and pass any frames received at that socket to the main
        adapter.
        """
        logging.debug("Live View Proxy Node Initialising: %s", endpoint)
        self.name = name
        self.endpoint = endpoint
        self.callback = callback
        self.dropped_frame_count = 0
        self.received_frame_count = 0
        self.last_frame = 0
        self.current_acq = 0
        self.drop_warn_cutoff = drop_warn_cutoff
        self.drop_unwarn_cutoff = drop_warn_cutoff * 0.75
        self.has_warned = False
        # subscribe to the given socket address.
        self.channel = IpcTornadoChannel(IpcTornadoChannel.CHANNEL_TYPE_SUB,
                                         endpoint=endpoint)
        self.channel.subscribe()
        self.channel.connect()
        # callback is called whenever data 'arrives' at the socket. This is driven by the IOLoop
        self.channel.register_callback(self.local_callback)

        self.param_tree = ParameterTree({
            'endpoint': (lambda: self.endpoint, None),
            'dropped_frames': (lambda: self.dropped_frame_count, None),
            'received_frames': (lambda: self.received_frame_count, None),
            'last_frame': (lambda: self.last_frame, None),
            'current_acquisition': (lambda: self.current_acq, None),
        })

        logging.debug("Proxy Connected to Socket: %s", endpoint)

    def cleanup(self):
        """
        Close the Subscriber socket for this node.
        """
        self.channel.close()

    def local_callback(self, msg):
        """
        Create an instance of the Frame class using the data from the socket.
        Then, pass the frame on to the adapter to add to the Priority Queue.
        """
        frame = Frame(msg)
        self.received_frame_count += 1
        if frame.num < self.last_frame:
            # Frames are assumed to be in order for each socket. If a frame suddenly has
            # a lower frame number, it can be assumed that a new acquisition has begun.
            logging.debug(
                "Frame number has reset, new acquisition started on Node %s",
                self.name)
            self.current_acq += 1
            self.has_warned = False

        frame.set_acq(self.current_acq)
        self.last_frame = frame.num
        self.callback(frame, self)

    def dropped_frame(self):
        """
        Increase the count of dropped frames due to frame age
        """
        self.dropped_frame_count += 1
        current_dropped_percent = float(self.dropped_frame_count) / float(
            self.received_frame_count)
        if current_dropped_percent > self.drop_warn_cutoff and not self.has_warned:
            # If the number of dropped frames reaches a certain percentage threshold of the total
            # received, warn the user, as it likely means one node is running slow and is unlikely
            # to display any frames.
            logging.warning("Node %s has dropped %d%% of frames", self.name,
                            current_dropped_percent * 100)
            self.has_warned = True
        elif current_dropped_percent < self.drop_unwarn_cutoff:
            self.has_warned = False

    def set_reset(self):
        """
        Reset frame counts for dropped and received frames for starting
        a new aqquisition
        """
        self.dropped_frame_count = 0
        self.received_frame_count = 0
        self.last_frame = 0
        self.current_acq = 0
        self.has_warned = False