Ohara Lab

gRPC Frame: StreamCall and Image Transfer

gRPC development guide / gRPC Quick Start / Event subscriptions

StreamCall and Call

The gRPC Frame exposes ordinary DeviceClass methods through Describe / Call on ctrl_obj. Continuous results use the separate StreamCall endpoint on stream_obj. Streaming is optional: pass stream_obj only when required. A public streaming method returns an iterator. Each yielded value is serialized by adapter.pack_result() and reconstructed on the client by adapter.unpack_result().

DeviceClass ─ ctrl_obj ─ Describe / Call ─ client methods
          └─ stream_obj ─ StreamCall ─ client.stream_call(...)

Streaming methods are not automatically bound by Describe. Call stream_call(method_name, *args, **kwargs) explicitly.

MiniPIX server example

The MiniPIX implementation registers an independent streaming adapter. _MiniPixStream is defined in the MiniPIX package, not in the Frame.

from grpc_frame.grpc_server import create_grpc_server

ctrl = MiniPixCtrl(logger=logger, log_level=log_level)
ctrl.connect()
stream_obj = _MiniPixStream(ctrl)
server = create_grpc_server(
    ctrl_obj=ctrl,
    stream_obj=stream_obj,
    logger=logger,
    log_level=log_level,
    max_workers=10,
)

_MiniPixStream.acquire_frames(count, sec) and acquire_integral(count, sec) yield from a common _acquire() implementation. Its measurement callback places frames in a queue.Queue; a worker thread performs acquisition while the generator yields queued frames. Queue messages also signal completion and errors. This keeps gRPC streaming outside the DeviceClass.

Receiving streamed frames

from grpc_frame.grpc_client import GrpcClient

client = GrpcClient(server_ip="127.0.0.1", server_port=50051)
try:
    for frame in client.stream_call("acquire_frames", 3, 0.1):
        print(type(frame))
finally:
    client.close()

Adjust the address and measurement parameters to the device server. stream_call() returns a generator. The client implementation cancels the underlying RPC when the iterator is closed; the server checks context.is_active() and closes the source iterator when streaming ends.

Image files and client-side overrides

MiniPIX also demonstrates a different path, ordinary Call returning acquisition data that is saved locally by specialized clients. AsyncPixetGrpcClient stores dynamically bound remote methods as _grpc_acquire_frames and _grpc_acquire_integral, then replaces the public methods with local-save wrappers. SyncPixetGrpcClient follows the same pattern.

# Extract from AsyncPixetGrpcClient
result = await self._grpc_acquire_frames(
    count, sec, save_format=save_format, save_path=save_path,
)
return self._finish_acquisition(result, name, save_local, return_data)

This is not a StreamCall example: the remote acquisition method uses ordinary RPC. TIFF output preserves numerical arrays; PNG output converts data to display-oriented 16-bit grayscale. Server and client grpc_options can be used to configure gRPC message-size limits for large images. Consider frame sizes, consumer throughput, queue growth, and cleanup on disconnection.

Source implementations

Further gRPC implementation examples