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

Send signal when recording images with Camera Block #107

Merged
merged 3 commits into from
Feb 27, 2024
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
3 changes: 2 additions & 1 deletion docs/source/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ Hardware control
Acquires images from a :ref:`Camera` object, and then displays and/or records
the acquired images. It is the base class for other Blocks that can also
perform image processing, in addition to the recording and display. This
Block usually doesn't have input nor output Links.
Block usually doesn't have input nor output Links, but can in some specific
situations.

The examples folder on GitHub contains `several examples of the Camera Block
<https://github.com/LaboratoireMecaniqueLille/crappy/tree/master/examples/
Expand Down
73 changes: 73 additions & 0 deletions examples/blocks/camera/camera_record_send_msg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# coding: utf-8

"""
This example demonstrates the use of the Camera Block, in the case when
recording the acquired images and sending a message to downstream Blocks each
time a new image is saved. It does not require any hardware to run, but
necessitates the opencv-python, scikit-image and Pillow modules to be
installed.

It acquires images on a fake camera, and records part of them at the given
location. Before the test starts, it also lets the user adjust some settings on
the camera in a configuration window. Because images are recorded, no image
processing is performed, and an output Link is defined, a message is sent to
downstream Blocks each time a new image is saved. This message is caught by a
Dashboard Block, that displays the last timestamp when an image was saved. In
addition, a StopButton Block allows stopping the script properly without using
CTRL+C by clicking on a button.

After starting this script, you can play with the parameters in the
configuration window. Once you're done, close the configuration window. Images
will start being recorded at the given location, and the timestamp of the last
saved image should be displayed by the Dashboard. Stop the test after a few
seconds by clicking on the stop button that appears, and check the destination
folder to see the recorded images. You can also hit CTRL+C, but it is not a
clean way to stop Crappy.
"""

import crappy

if __name__ == '__main__':

# The Block in charge of acquiring the images and recording them
# It also displays a configuration windows before the test starts, in which
# the user can tune a few parameters of the Camera
# Here, a fake camera is used so that no hardware is required
# Because save_images is True, no image processing is performed, and an
# output Link is defined, the timestamp and metadata are sent at each new
# saved image over the labels 't(s)' and 'meta'
cam = crappy.blocks.Camera(
'FakeCamera', # Using the FakeCamera camera so that no hardware is
# required
config=True, # Before the test starts, displays a configuration window
# for configuring the camera
display_images=False, # Here, we don't want the images displayed
save_images=True, # The acquired images should be recorded during this
# test
img_extension='tiff', # The images should be saved as .tiff files
save_folder='demo_record_images_send', # The images will be saved in
# this folder, whose path can be relative or absolute
save_period=10, # Only one out of 10 images will be saved, to avoid
# bothering you with tons of images
save_backend=None, # The first available backend will be used
freq=40, # Lowering the default frequency because it's just a demo

# Sticking to default for the other arguments
)

# This Block allows the user to properly exit the script
stop = crappy.blocks.StopButton(
# No specific argument to give for this Block
)

# This Block displays the time value of the moments when an image is saved by
# the Camera Block
# It is here to demonstrate that the information is properly sent to
# downstream Blocks
dash = crappy.blocks.Dashboard(('t(s)',))

# Linking the Blocks together so that the correct information is sent
crappy.link(cam, dash)

# Mandatory line for starting the test, this call is blocking
crappy.start()
29 changes: 20 additions & 9 deletions src/crappy/blocks/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@ class Camera(Block):
once.

It takes no input :class:`~crappy.links.Link` in a majority of situations,
and never has output Links. Most of the time, this Block is used for
recording to the desired location the images it acquires. Optionally, the
images can also be displayed in a dedicated window. Both of these features
are however optional, and it is possible to acquire images and not do
anything with them. Several options are available for tuning the record and
the display.
and usually doesn't have output Links neither. The only situations when it
can accept input Links is when an ``image_generator`` is defined, or when
defining a ``software_trig_label``. If ``save_images`` is set to :obj:`True`,
and if an output Link is present, a message is sent to downstream Blocks at
each saved image, containing the timestamp and the metadata of the image.
They are respectively carried by the `'t(s)'` and `'meta'` labels. This is
useful for performing an action conditionally at each new saved image.

Most of the time, this Block is used for recording to the desired location
the images it acquires. Optionally, the images can also be displayed in a
dedicated window. Both of these features are however optional, and it is
possible to acquire images and not do anything with them. Several options are
available for tuning the record and the display.

Before a test starts, this Block can also display a
:class:`~crappy.tool.camera_config.CameraConfig` window in which the user can
Expand Down Expand Up @@ -349,13 +356,17 @@ def prepare(self) -> None:
self._disp_lock = RLock()
self._proc_lock = RLock()

# instantiating the ImageSaver CameraProcess
# Instantiating the ImageSaver CameraProcess
if self._save_images:
self.log(logging.INFO, "Instantiating the saver process")
# The ImageSaver sends a message on each saved image only if no
# processing is performed and if there are output Links
send_msg = self.process_proc is None and self.outputs
self._save_proc = ImageSaver(img_extension=self._img_extension,
save_folder=self._save_folder,
save_period=self._save_period,
save_backend=self._save_backend)
save_backend=self._save_backend,
send_msg=send_msg)

# instantiating the Displayer CameraProcess
if self._display_images:
Expand Down Expand Up @@ -462,7 +473,7 @@ def get_image(self_) -> (float, np.ndarray):
shape=self._img_shape,
dtype=self._img_dtype,
to_draw_conn=None,
outputs=list(),
outputs=self.outputs,
labels=list(),
log_queue=self._log_queue,
log_level=self._log_level,
Expand Down
14 changes: 13 additions & 1 deletion src/crappy/blocks/camera_processes/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def __init__(self,
img_extension: str = "tiff",
save_folder: Optional[Union[str, Path]] = None,
save_period: int = 1,
save_backend: Optional[str] = None) -> None:
save_backend: Optional[str] = None,
send_msg: bool = False) -> None:
"""Sets the arguments and initializes the parent class.

Args:
Expand All @@ -68,6 +69,12 @@ def __init__(self,
Fork), :mod:`cv2` (OpenCV), and :mod:`numpy`. Depending on the machine,
some may be faster or slower. The ``img_extension`` is ignored for the
backend ``'npy'``, that saves the images as raw numpy arrays.
send_msg: In case no processing is performed, and if output Links are
present, this argument is set to :obj:`True`. In that case, a message
containing the timestamp and the metadata of the image is sent to
downstream Blocks each time an image is saved.

.. versionadded:: 2.0.5
"""

super().__init__()
Expand Down Expand Up @@ -99,6 +106,7 @@ def __init__(self,
self._save_folder = Path(save_folder)

self._save_period = int(save_period)
self._send_msg: bool = send_msg

self._csv_created = False
self._csv_path = None
Expand Down Expand Up @@ -230,3 +238,7 @@ def loop(self) -> None:

elif self._save_backend == 'npy':
np.save(path, self.img)

# Sending the results to the downstream Blocks
if self._send_msg:
self.send({'t(s)': self.metadata['t(s)'], 'meta': self.metadata})
Loading