Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support empty AvailableData structs #138

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "acquire-imaging"
authors = ["Nathan Clack <[email protected]>"]
version = "0.3.0"
version = "0.3.0-rc1"
edition = "2021"

[lib]
Expand Down
2 changes: 1 addition & 1 deletion drivers.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"acquire-driver-common": "0.1.6",
"acquire-driver-zarr": "0.1.7",
"acquire-driver-zarr": "0.1.8",
"acquire-driver-egrabber": "0.1.5",
"acquire-driver-hdcam": "0.1.7",
"acquire-driver-spinnaker": "0.1.1"
Expand Down
2 changes: 1 addition & 1 deletion python/acquire/acquire.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from numpy.typing import NDArray

@final
class AvailableDataContext:
def __enter__(self) -> Optional[AvailableData]: ...
def __enter__(self) -> AvailableData: ...
def __exit__(
self, exc_type: Any, exc_value: Any, traceback: Any
) -> None: ...
Expand Down
65 changes: 27 additions & 38 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ impl Runtime {
Ok(AvailableDataContext {
inner: self.inner.clone(),
stream_id,
available_data: None,
available_data: Python::with_gil(|py| Py::new(py, AvailableData { inner: None }))?,
})
}
}
Expand Down Expand Up @@ -273,7 +273,7 @@ impl Drop for RawAvailableData {

#[pyclass]
pub(crate) struct AvailableData {
inner: Option<Arc<RawAvailableData>>,
inner: Option<RawAvailableData>,
}

#[pymethods]
Expand All @@ -290,7 +290,6 @@ impl AvailableData {
VideoFrameIterator {
inner: if let Some(frames) = &self.inner {
Some(VideoFrameIteratorInner {
store: frames.clone(),
cur: Mutex::new(frames.beg),
end: frames.end,
})
Expand All @@ -315,12 +314,12 @@ impl AvailableData {
pub(crate) struct AvailableDataContext {
inner: Arc<RawRuntime>,
stream_id: u32,
available_data: Option<Py<AvailableData>>,
available_data: Py<AvailableData>,
}

#[pymethods]
impl AvailableDataContext {
fn __enter__(&mut self) -> PyResult<Option<Py<AvailableData>>> {
fn __enter__(&mut self) -> PyResult<Py<AvailableData>> {
let AvailableDataContext {
inner,
stream_id,
Expand All @@ -329,45 +328,39 @@ impl AvailableDataContext {
let stream_id = *stream_id;
let (beg, end) = inner.map_read(stream_id)?;
let nbytes = unsafe { byte_offset_from(beg, end) };
if nbytes > 0 {
log::trace!(
"[stream {}] ACQUIRED {:p}-{:p}:{} bytes",
stream_id,
beg,
end,
nbytes

log::trace!(
"[stream {}] ACQUIRED {:p}-{:p}:{} bytes",
stream_id,
beg,
end,
nbytes
);
*available_data = Python::with_gil(|py| {
Py::new(
py,
AvailableData {
inner: Some(RawAvailableData {
runtime: self.inner.clone(),
beg: NonNull::new(beg).ok_or(anyhow!("Expected non-null buffer"))?,
end: NonNull::new(end).ok_or(anyhow!("Expected non-null buffer"))?,
stream_id,
consumed_bytes: None,
}),
},
)
};
if nbytes > 0 {
*available_data = Some(Python::with_gil(|py| {
Py::new(
py,
AvailableData {
inner: Some(Arc::new(RawAvailableData {
runtime: self.inner.clone(),
beg: NonNull::new(beg).ok_or(anyhow!("Expected non-null buffer"))?,
end: NonNull::new(end).ok_or(anyhow!("Expected non-null buffer"))?,
stream_id,
consumed_bytes: None,
})),
},
)
})?);
}
})?;
return Ok(self.available_data.clone());
}

fn __exit__(&mut self, _exc_type: &PyAny, _exc_value: &PyAny, _traceback: &PyAny) {
Python::with_gil(|py| {
if let Some(a) = &self.available_data {
a.as_ref(py).borrow_mut().invalidate()
};
(&self.available_data).as_ref(py).borrow_mut().invalidate();
});
}
}

struct VideoFrameIteratorInner {
store: Arc<RawAvailableData>,
cur: Mutex<NonNull<capi::VideoFrame>>,
end: NonNull<capi::VideoFrame>,
}
Expand All @@ -380,10 +373,7 @@ impl Iterator for VideoFrameIteratorInner {
fn next(&mut self) -> Option<Self::Item> {
let mut cur = self.cur.lock();
if *cur < self.end {
let out = VideoFrame {
_store: self.store.clone(),
cur: *cur,
};
let out = VideoFrame { cur: *cur };

let c = cur.as_ptr();
let o = unsafe { (c as *const u8).offset((*c).bytes_of_frame as _) }
Expand Down Expand Up @@ -503,7 +493,6 @@ impl IntoDimension for capi::ImageShape {

#[pyclass]
pub(crate) struct VideoFrame {
_store: Arc<RawAvailableData>,
cur: NonNull<capi::VideoFrame>,
}

Expand Down
76 changes: 34 additions & 42 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,8 @@ def test_repeat_acq(runtime: Runtime):
runtime.start()
while True:
with runtime.get_available_data(0) as a:
if a:
logging.info(f"Got {a.get_frame_count()}")
break
logging.info(f"Got {a.get_frame_count()}")
break
if a:
assert a.get_frame_count() == 0
assert next(a.frames()) is None
Expand All @@ -107,12 +106,11 @@ def test_repeat_acq(runtime: Runtime):
runtime.start()
while True:
with runtime.get_available_data(0) as a:
if a:
logging.info(f"Got {a.get_frame_count()}")
break
if a:
assert a.get_frame_count() == 0
assert next(a.frames()) is None
logging.info(f"Got {a.get_frame_count()}")
break

assert a.get_frame_count() == 0
assert next(a.frames()) is None
runtime.stop()
# TODO: (nclack) assert 1 more acquired frame. stop cancels and waits.

Expand All @@ -131,7 +129,7 @@ def test_repeat_with_no_stop(runtime: Runtime):
# wait for 1 frame
while True:
with runtime.get_available_data(0) as a:
if a:
if a.get_frame_count() > 0:
logging.info(f"Got {a.get_frame_count()} frame")
break
# acq is still on going here
Expand Down Expand Up @@ -181,17 +179,16 @@ def took_too_long():
while nframes < p.video[0].max_frame_count and not took_too_long():
clock = time.time()
with runtime.get_available_data(0) as a:
if a:
packet = a.get_frame_count()
for f in a.frames():
logging.info(
f"{f.data().shape} {f.data()[0][0][0][0]} "
+ f"{f.metadata()}"
)
nframes += packet
packet = a.get_frame_count()
for f in a.frames():
logging.info(
f"frame count: {nframes} - frames in packet: {packet}"
f"{f.data().shape} {f.data()[0][0][0][0]} "
+ f"{f.metadata()}"
)
nframes += packet
logging.info(
f"frame count: {nframes} - frames in packet: {packet}"
)

elapsed = time.time() - clock
sleep(max(0, 0.1 - elapsed))
Expand Down Expand Up @@ -231,8 +228,7 @@ def test_change_filename(runtime: Runtime):
runtime.start()
while nframes < p.video[0].max_frame_count:
with runtime.get_available_data(0) as packet:
if packet:
nframes += packet.get_frame_count()
nframes += packet.get_frame_count()
logging.info("Stopping")
runtime.stop()

Expand All @@ -257,8 +253,7 @@ def test_write_external_metadata_to_tiff(
runtime.start()
while nframes < p.video[0].max_frame_count:
with runtime.get_available_data(0) as packet:
if packet:
nframes += packet.get_frame_count()
nframes += packet.get_frame_count()
runtime.stop()

# Check that the written tif has the expected structure
Expand Down Expand Up @@ -321,20 +316,17 @@ def is_not_done() -> bool:
while is_not_done():
if nframes[stream_id] < p.video[stream_id].max_frame_count:
with runtime.get_available_data(stream_id) as packet:
if packet:
n = packet.get_frame_count()
for i, frame in enumerate(packet.frames()):
expected_frame_id = nframes[stream_id] + i
assert (
frame.metadata().frame_id == expected_frame_id
), (
"frame id's didn't match "
+ f"({frame.metadata().frame_id}"
+ f"!={expected_frame_id})"
+ f" [stream {stream_id} nframes {nframes}]"
)
nframes[stream_id] += n
logging.debug(f"NFRAMES {nframes}")
n = packet.get_frame_count()
for i, frame in enumerate(packet.frames()):
expected_frame_id = nframes[stream_id] + i
assert frame.metadata().frame_id == expected_frame_id, (
"frame id's didn't match "
+ f"({frame.metadata().frame_id}"
+ f"!={expected_frame_id})"
+ f" [stream {stream_id} nframes {nframes}]"
)
nframes[stream_id] += n
logging.debug(f"NFRAMES {nframes}")

stream_id = (stream_id + 1) % 2
logging.info("Stopping")
Expand Down Expand Up @@ -362,9 +354,9 @@ def test_abort(runtime: Runtime):

while True:
with runtime.get_available_data(0) as packet:
if packet:
nframes += packet.get_frame_count()
else:
frame_count = packet.get_frame_count()
nframes += frame_count
if frame_count == 0:
break

logging.debug(
Expand All @@ -383,7 +375,7 @@ def wait_for_data(
elapsed = timedelta()
while elapsed < timeout:
with runtime.get_available_data(stream_id) as packet:
if packet:
if packet.get_frame_count() > 0:
frames = list(packet.frames())
return (len(frames), frames[0].metadata().frame_id)
sleep(sleep_duration.total_seconds())
Expand Down Expand Up @@ -419,7 +411,7 @@ def test_execute_trigger(runtime: Runtime):

# No triggers yet, so no data.
with runtime.get_available_data(0) as packet:
assert packet is None
assert packet.get_frame_count() == 0

# Snap a few individual frames
for i in range(p.video[0].max_frame_count):
Expand Down
3 changes: 1 addition & 2 deletions tests/test_zarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ def test_write_external_metadata_to_zarr(
runtime.start()
while nframes < p.video[0].max_frame_count:
with runtime.get_available_data(0) as packet:
if packet:
nframes += packet.get_frame_count()
nframes += packet.get_frame_count()
runtime.stop()

assert p.video[0].storage.settings.filename
Expand Down
Loading