Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions deapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ class Client:
def __init__(self):
self.commandVersion = commandVersion
self.read_only = False
#: How many acquisitions this client has started, and when the last one was sent
#: (time.monotonic). SimulatedGpuFrames times its frames from these: a short
#: simulated acquisition can be over before its consumer first asks.
self.acquisitions_started = 0
self.acquisition_started_at = 0.0

def set_log_level(self, level):
log = logging.getLogger("DECameraClientLib")
Expand Down Expand Up @@ -1333,6 +1338,7 @@ def start_acquisition(
log.debug(" Build Time: %.1f ms", lapsed)
step_time = self.GetTime()

started = time.monotonic()
response = self._sendCommand(command)
if logLevel == logging.DEBUG:
lapsed = (self.GetTime() - step_time) * 1000
Expand All @@ -1342,6 +1348,9 @@ def start_acquisition(
if response:
ret = response.acknowledge[0].error != True
self.refreshProperties = True
if ret:
self.acquisition_started_at = started
self.acquisitions_started += 1

if logLevel == logging.DEBUG:
lapsed = (self.GetTime() - step_time) * 1000
Expand Down Expand Up @@ -1982,6 +1991,52 @@ def get_movie_buffer_info(self, movieBufferInfo=None, timeoutMsec=5000):

return movieBufferInfo

def get_gpu_frames(self, peer_devices=()):
"""
Zero-copy CuPy access to the server's GPU movie buffers (Windows, same computer).

Call before start_acquisition. The returned GpuFrames holds both movie buffers as
CuPy arrays (``.buffers``) and the semaphores; iterating it yields each finished
buffer as ``batch.images`` (cupy float32, images x height x width, on GPU
``.device``) and ``batch.info`` (frame, scanX, scanY, valid per image). The buffer
is released back to the server when the loop advances; the server's processing
waits while it is held, so close it when done (or use it in a ``with`` block).
Requires CuPy. See deapi.gpu_frames.

``peer_devices``: other GPUs that will work on the frames. They read the buffers
directly where the driver allows peer access (``.peer_access``), and their current
streams are synchronized before each release either way.

Only a process on the server computer can map the buffers; from another computer
this raises with the server's reason. Against deapi's simulated server this
returns a SimulatedGpuFrames instead: the same interface, with NumPy batches of the
simulated dataset and no CuPy needed.
"""
from deapi.gpu_frames import open_gpu_frames

return open_gpu_frames(self._attach_gpu_frames(), self, peer_devices)

def _attach_gpu_frames(self, attach=True):
"""Ask DE-Server to hand its GPU movie buffer handles to this process (or detach).
Returns what it answered: [control block handle], or ["simulated"] from the fake
server."""
command = self._addSingleCommand(
self.GET_GPU_FRAMES, None, [str(os.getpid()), attach]
)
response = self._sendCommand(command)
if response is False:
raise RuntimeError("DE-Server did not answer GET_GPU_FRAMES")
acknowledge = response.acknowledge[0]
if acknowledge.error:
raise RuntimeError(
"DE-Server cannot share its GPU movie buffers with this process: "
+ (acknowledge.error_message or "no reason given")
)
values = list(self.__getParameters(acknowledge))
if attach and not values:
raise RuntimeError("DE-Server answered GET_GPU_FRAMES without a handle")
return values

def get_movie_buffer(
self, movieBuffer, movieBufferSize, numFrames, timeoutMsec=5000
):
Expand Down Expand Up @@ -3245,6 +3300,7 @@ def ParseChangedProperties(self, changedProperties, response):
GET_VIRTUAL_IMAGE_INFO = 41
GET_VIRTUAL_IMAGE = 42
GET_CLIENT_READ_ONLY = 43
GET_GPU_FRAMES = 44


MMF_DATA_HEADER_SIZE = 24
Expand Down
Loading
Loading