From cf2f15594dbfa0d42fe3a824fce12c3587515f10 Mon Sep 17 00:00:00 2001 From: Romain Bazile Date: Wed, 16 Dec 2020 13:11:35 +0100 Subject: [PATCH] python: better imaging management of the raspimjpeg process --- scripts/planktoscope/imager.py | 65 +++++++++++++++++------------ scripts/planktoscope/raspimjpeg.py | 3 +- scripts/planktoscope/stepper.py | 2 +- scripts/raspimjpeg/bin/raspimjpeg | Bin 84520 -> 84520 bytes scripts/raspimjpeg/raspimjpeg.conf | 8 ++-- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/scripts/planktoscope/imager.py b/scripts/planktoscope/imager.py index 6c144ba..85d53b0 100644 --- a/scripts/planktoscope/imager.py +++ b/scripts/planktoscope/imager.py @@ -12,10 +12,11 @@ import datetime import time # Libraries manipulate json format, execute bash commands -import json, shutil +import json # Library for path and filesystem manipulations import os +import shutil # Library for starting processes import multiprocessing @@ -166,6 +167,7 @@ class ImagerProcess(multiprocessing.Process): self.__pump_direction = "FORWARD" self.__img_goal = None self.imager_client = None + self.__error = 0 # Initialise the camera and the process # Also starts the streaming to the temporary file @@ -196,7 +198,7 @@ class ImagerProcess(multiprocessing.Process): self.__white_balance_gain = ( configuration.get("wb_red_gain", 2.00) * 100, configuration.get("wb_blue_gain", 1.40) * 100, - ) # Those values were tested on a HQ camera to give a whitish background + ) self.__base_path = "/home/pi/data/img" # Let's make sure the base path exists @@ -491,7 +493,9 @@ class ImagerProcess(multiprocessing.Process): self.__imager.change(planktoscope.imager_state_machine.Capture) self.imager_client.client.unsubscribe("status/pump") else: - logger.info(f"The pump is not done yet {payload}") + logger.info( + f"The pump is not done yet {self.imager_client.msg['payload']}" + ) else: logger.error( "There is an error, we received an unexpected pump message" @@ -641,39 +645,25 @@ class ImagerProcess(multiprocessing.Process): # Sleep a duration before to start acquisition time.sleep(self.__sleep_before) - # Capture an image with the proper filename + # Capture an image to the temporary file try: - self.__camera.capture(filename_path) + self.__camera.capture("", timeout=5) except TimeoutError as e: - logger.error("A timeout happened while waiting for a capture to happen") - # Publish the name of the image to via MQTT to Node-RED - self.imager_client.client.publish( - "status/imager", - f'{{"status":"Image {self.__img_done + 1}/{self.__img_goal} WAS NOT CAPTURED! STOPPING THE PROCESS!"}}', - ) - # Reset the counter to 0 - self.__img_done = 0 - self.__img_goal = 0 - self.__imager.change(planktoscope.imager_state_machine.Stop) - planktoscope.light.error() + self.__capture_error("timeout during capture") return + logger.debug(f"Copying the image from the temp file to {filename_path}") + shutil.copy("/dev/shm/mjpeg/image.jpg", filename_path) + logger.debug("Syncing the disk") + os.sync() + # Add the checksum of the captured image to the integrity file try: planktoscope.integrity.append_to_integrity_file(filename_path) except FileNotFoundError as e: - logger.error( - f"{filename_path} was not found, the camera did not work properly! Trying again" - ) - self.imager_client.client.publish( - "status/imager", - f'{{"status":"Image {self.__img_done + 1}/{self.__img_goal} was not found, retrying the capture now."}}', - ) - # Let's try again after a tiny delay! - time.sleep(1) + self.__capture_error(f"{filename_path} was not found") return - # Publish the name of the image to via MQTT to Node-RED self.imager_client.client.publish( "status/imager", f'{{"status":"Image {self.__img_done + 1}/{self.__img_goal} has been imaged to {filename}"}}', @@ -681,6 +671,7 @@ class ImagerProcess(multiprocessing.Process): # Increment the counter self.__img_done += 1 + self.__error = 0 # If counter reach the number of frame, break if self.__img_done >= self.__img_goal: @@ -690,7 +681,6 @@ class ImagerProcess(multiprocessing.Process): self.__imager.change(planktoscope.imager_state_machine.Stop) planktoscope.light.ready() - return else: # We have not reached the final stage, let's keep imaging self.imager_client.client.subscribe("status/pump") @@ -699,6 +689,27 @@ class ImagerProcess(multiprocessing.Process): self.__imager.change(planktoscope.imager_state_machine.Waiting) + def __capture_error(self, message=""): + logger.error(f"An error occurred during the capture: {message}") + planktoscope.light.error() + if self.__error: + logger.error("This is a repeating problem, stopping the capture now") + self.imager_client.client.publish( + "status/imager", + f'{{"status":"Image {self.__img_done + 1}/{self.__img_goal} WAS NOT CAPTURED! STOPPING THE PROCESS!"}}', + ) + self.__img_done = 0 + self.__img_goal = 0 + self.__error = 0 + self.__imager.change(planktoscope.imager_state_machine.Stop) + else: + self.__error += 1 + self.imager_client.client.publish( + "status/imager", + f'{{"status":"Image {self.__img_done + 1}/{self.__img_goal} was not captured due to this error:{message}! Retrying once!"}}', + ) + time.sleep(1) + @logger.catch def state_machine(self): if self.__imager.state.name == "imaging": diff --git a/scripts/planktoscope/raspimjpeg.py b/scripts/planktoscope/raspimjpeg.py index 0ac8586..f74be3c 100644 --- a/scripts/planktoscope/raspimjpeg.py +++ b/scripts/planktoscope/raspimjpeg.py @@ -458,9 +458,10 @@ class raspimjpeg(object): self.__send_command(f"im") else: self.__send_command(f"im {path}") + time.sleep(0.1) self.__wait_for_output("Capturing image", timeout / 2) - self.__wait_for_status("ready", timeout / 2) + self.__wait_for_output("Ready", timeout / 2) def stop(self): """Halt and release the camera. """ diff --git a/scripts/planktoscope/stepper.py b/scripts/planktoscope/stepper.py index e0d84ed..c4acad6 100644 --- a/scripts/planktoscope/stepper.py +++ b/scripts/planktoscope/stepper.py @@ -354,7 +354,7 @@ class StepperProcess(multiprocessing.Process): command = self.actuator_client.msg["topic"].split("/", 1)[1] logger.debug(command) self.actuator_client.read_message() - + if command == "pump": self.__message_pump(last_message) elif command == "focus": diff --git a/scripts/raspimjpeg/bin/raspimjpeg b/scripts/raspimjpeg/bin/raspimjpeg index 9c197a93c129d27029eccec79f80ac185689b5e8..665222ce792dc2e827e9dc91c4b090ddf6a35f2f 100755 GIT binary patch delta 16847 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 W*#1|8={6)zO?TOw_n9dBJN!S1c(JGe delta 16729 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

aDLR9@2Am>10T2RMJmq1*E6#(g~23 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