From d8d572c0ee1033a11091fa5d79b2a9b101d54efe Mon Sep 17 00:00:00 2001 From: Matic Tonin Date: Tue, 10 Dec 2024 23:43:46 +0100 Subject: [PATCH] Diagnostic button_save_img --- crates/re_viewer/src/depthai/api.rs | 14 +++ crates/re_viewer/src/depthai/depthai.rs | 8 ++ crates/re_viewer/src/depthai/ws.rs | 9 +- .../re_viewer/src/ui/device_settings_panel.rs | 118 ++++++++++-------- .../depthai_viewer/_backend/config_api.py | 3 + rerun_py/depthai_viewer/_backend/device.py | 16 +++ rerun_py/depthai_viewer/_backend/main.py | 15 +++ rerun_py/depthai_viewer/_backend/messages.py | 1 + .../depthai_viewer/_backend/packet_handler.py | 49 +++++++- 9 files changed, 176 insertions(+), 57 deletions(-) diff --git a/crates/re_viewer/src/depthai/api.rs b/crates/re_viewer/src/depthai/api.rs index 3341d07bccda..75efdedc9219 100644 --- a/crates/re_viewer/src/depthai/api.rs +++ b/crates/re_viewer/src/depthai/api.rs @@ -127,6 +127,20 @@ impl BackendCommChannel { .unwrap(), ); } + + pub fn Camera_Diagnostics(&mut self, number: u32) { + self.ws.send( + serde_json::to_string( + &(WsMessage { + kind: WsMessageType::Camera_Diagnostics, + data: WsMessageData::Camera_Diagnostics(number), + ..Default::default() + }), + ) + .unwrap(), + ); + } + pub fn receive(&mut self) -> Option { self.ws.receive() } diff --git a/crates/re_viewer/src/depthai/depthai.rs b/crates/re_viewer/src/depthai/depthai.rs index 5db154be7072..27f154a455f4 100644 --- a/crates/re_viewer/src/depthai/depthai.rs +++ b/crates/re_viewer/src/depthai/depthai.rs @@ -1018,6 +1018,9 @@ impl State { WsMessageData::Reset_factory(number)=> { re_log::debug!("Reset to factory calibration recieved from backend.") } + WsMessageData::Camera_Diagnostics(number)=> { + re_log::debug!("Running device diagnostics.") + } } } @@ -1136,6 +1139,11 @@ impl State { self.backend_comms.flash_factorycalibration(recalib); } + pub fn Camera_Diagnostics(&mut self, recalib: u32) { + println!("Running device diagnostics"); + self.backend_comms.Camera_Diagnostics(recalib); + } + pub fn reset(&mut self) { *self = Self::default(); } diff --git a/crates/re_viewer/src/depthai/ws.rs b/crates/re_viewer/src/depthai/ws.rs index e84ea5fede5f..4daeddb6b794 100644 --- a/crates/re_viewer/src/depthai/ws.rs +++ b/crates/re_viewer/src/depthai/ws.rs @@ -93,7 +93,8 @@ pub enum WsMessageData { Recalibrate(u32), Calib_check(u32), Flash_calib(u32), - Reset_factory(u32) + Reset_factory(u32), + Camera_Diagnostics(u32) } #[derive(Deserialize, Serialize, fmt::Debug)] @@ -111,7 +112,8 @@ pub enum WsMessageType { Recalibrate, Calib_check, Flash_calib, - Reset_factory + Reset_factory, + Camera_Diagnostics } impl Default for WsMessageType { @@ -185,6 +187,9 @@ impl<'de> Deserialize<'de> for BackWsMessage { WsMessageType::Reset_factory => { WsMessageData::Reset_factory(serde_json::from_value(message.data).unwrap()) } + WsMessageType::Camera_Diagnostics => { + WsMessageData::Camera_Diagnostics(serde_json::from_value(message.data).unwrap()) + } }; Ok(Self { diff --git a/crates/re_viewer/src/ui/device_settings_panel.rs b/crates/re_viewer/src/ui/device_settings_panel.rs index 4e4399d2bbb9..17f541dd3d19 100644 --- a/crates/re_viewer/src/ui/device_settings_panel.rs +++ b/crates/re_viewer/src/ui/device_settings_panel.rs @@ -414,63 +414,75 @@ impl DeviceSettingsPanel { ..Default::default() }) .show(ui, |ui| { - ui.vertical(|ui| { - ui.scope(|ui| { - let mut style = ui.style_mut().clone(); - let color = style.visuals.selection.bg_fill; - style.visuals.widgets.hovered.bg_fill = color; - style.visuals.widgets.inactive.bg_fill = color; - style.visuals.widgets.inactive.fg_stroke.color = - egui::Color32::WHITE; - style.visuals.widgets.hovered.fg_stroke.color = - egui::Color32::WHITE; - style.spacing.button_padding = - egui::Vec2::new(24.0, 4.0); - ui.set_style(style); + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.collapsing( + egui::RichText::new("Recalibrate_device").color(text_color), + |ui| { + ui.vertical(|ui| { + ui.scope(|ui| { + let mut style = ui.style_mut().clone(); + let color = style.visuals.selection.bg_fill; + style.visuals.widgets.hovered.bg_fill = color; + style.visuals.widgets.inactive.bg_fill = color; + style.visuals.widgets.inactive.fg_stroke.color = egui::Color32::WHITE; + style.visuals.widgets.hovered.fg_stroke.color = egui::Color32::WHITE; + style.spacing.button_padding = egui::Vec2::new(24.0, 4.0); + ui.set_style(style); - if ui - .add_sized( - [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], - egui::Button::new("Recalibration"), - ) - .clicked() - { - ctx.depthai_state.start_recalibration(1); - } + if ui + .add_sized( + [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], + egui::Button::new("Recalibration"), + ) + .clicked() + { + ctx.depthai_state.start_recalibration(1); + } - if ui - .add_sized( - [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], - egui::Button::new("Calibration Check"), - ) - .clicked() - { - ctx.depthai_state.calibration_check(1); - } + if ui + .add_sized( + [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], + egui::Button::new("Calibration Check"), + ) + .clicked() + { + ctx.depthai_state.calibration_check(1); + } - if ui - .add_sized( - [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], - egui::Button::new("Flash Calibration"), - ) - .clicked() - { - ctx.depthai_state.flash_calibration(1); - } + if ui + .add_sized( + [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], + egui::Button::new("Flash Calibration"), + ) + .clicked() + { + ctx.depthai_state.flash_calibration(1); + } - if ui - .add_sized( - [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], - egui::Button::new("Restore Factory Calibration"), - ) - .clicked() - { - ctx.depthai_state.flash_factorycalibration(1); - } - }); - }); - ui.horizontal(|ui| { - ui.vertical(|ui| { + if ui + .add_sized( + [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], + egui::Button::new("Restore Factory Calibration"), + ) + .clicked() + { + ctx.depthai_state.flash_factorycalibration(1); + } + + if ui + .add_sized( + [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], + egui::Button::new("Save Device Diagnostics"), + ) + .clicked() + { + ctx.depthai_state.Camera_Diagnostics(1); + } + }); + }); + }, + ); Self::camera_config_ui(ctx, ui, &mut device_config); ui.collapsing( egui::RichText::new("AI settings").color(text_color), diff --git a/rerun_py/depthai_viewer/_backend/config_api.py b/rerun_py/depthai_viewer/_backend/config_api.py index e9283dde7b9e..087b8f5b567b 100644 --- a/rerun_py/depthai_viewer/_backend/config_api.py +++ b/rerun_py/depthai_viewer/_backend/config_api.py @@ -50,6 +50,7 @@ class Action(Enum): CALIB_CHECK = auto() FLASH_CALIB = auto() FLASH_FACTORY_CALIB = auto() + CAMERA_DIAGNOSTICS = auto() def dispatch_action(action: Action, **kwargs) -> Message: # type: ignore[no-untyped-def] @@ -172,6 +173,8 @@ async def ws_api(websocket: WebSocketServerProtocol) -> None: message = dispatch_action(Action.FLASH_CALIB) elif message_type == MessageType.FLASH_FACTORY_CALIB: message = dispatch_action(Action.FLASH_FACTORY_CALIB) + elif message_type == MessageType.CAMERA_DIAGNOSTICS: + message = dispatch_action(Action.CAMERA_DIAGNOSTICS) else: print("Unknown message type: ", message_type) continue diff --git a/rerun_py/depthai_viewer/_backend/device.py b/rerun_py/depthai_viewer/_backend/device.py index 879882cae4c6..4b4eaa016807 100644 --- a/rerun_py/depthai_viewer/_backend/device.py +++ b/rerun_py/depthai_viewer/_backend/device.py @@ -679,7 +679,22 @@ def update(self) -> None: self._component_outputs[list(self._component_outputs.keys())[0]]["component"], packets ) else: + camera_board_sockets = [name for name, packet in packets.items() if "CameraBoardSocket" in name] for name, packet in packets.items(): + if "CameraBoardSocket" in name and self._packet_handler.save_diagnostics != "": + camera_board_sockets.remove(name) + self._packet_handler._save_packet(name, packet.msg.getCvFrame()) + if camera_board_sockets == []: + import shutil + shutil.make_archive(self._packet_handler.save_diagnostics, 'zip', self._packet_handler.save_diagnostics) + print("\n") + print("----------------------------------------") + print(f"Diagnostics saved to {self._packet_handler.save_diagnostics}.zip.") + print(f"Please send them to Luxonis via mail.") + print("----------------------------------------") + print("\n") + self._packet_handler.save_diagnostics = "" + camera_board_sockets = [name for name, packet in packets.items() if "CameraBoardSocket" in name] self._packet_handler.log_packet(self._component_outputs[name]["component"], packet) if self._packet_handler.stereo and self._packet_handler.new_calib is not None: self._oak.device.setCalibration(self._packet_handler.new_calib) @@ -696,6 +711,7 @@ def update(self) -> None: self._oak.device.flashCalibration(self._packet_handler._dynamic_recalibration.calib_edit.factoryCalib) self._packet_handler.resetFactoryCalibration = False self._packet_handler._display_flashing = "Factory" + except QueueEmpty: pass diff --git a/rerun_py/depthai_viewer/_backend/main.py b/rerun_py/depthai_viewer/_backend/main.py index cbaf7714b9ba..718b8336d21e 100644 --- a/rerun_py/depthai_viewer/_backend/main.py +++ b/rerun_py/depthai_viewer/_backend/main.py @@ -177,6 +177,21 @@ def handle_action(self, action: Action, **kwargs) -> Message: # type: ignore[no elif action == Action.FLASH_FACTORY_CALIB: if self._device._packet_handler.stereo and not self._device._packet_handler.display_bar: self._device._packet_handler.resetFactoryCalibration = True + + elif action == Action.CAMERA_DIAGNOSTICS: + self._device._packet_handler.diagnostics_display = True + import tempfile + import os + import time + base_tmp_dir = tempfile.gettempdir() + new_dirname = os.path.join(base_tmp_dir, self._device._oak.device.getMxId()) + if not os.path.exists(new_dirname): + os.makedirs(new_dirname) # Create the directory if it doesn't exist + self._device._packet_handler.save_diagnostics = str(new_dirname) + self._device._oak.device.readCalibration().eepromToJsonFile(os.path.join(str(new_dirname), "calib_user.json")) + self._device._oak.device.readFactoryCalibration().eepromToJsonFile(os.path.join(str(new_dirname), "calib_factory.json")) + self._device._packet_handler._calib_time = time.time() + print(f"Saving to {new_dirname}") return ErrorMessage(f"Action: {action} not implemented") def run(self) -> None: diff --git a/rerun_py/depthai_viewer/_backend/messages.py b/rerun_py/depthai_viewer/_backend/messages.py index 24e73ff39029..71240ffc860e 100644 --- a/rerun_py/depthai_viewer/_backend/messages.py +++ b/rerun_py/depthai_viewer/_backend/messages.py @@ -22,6 +22,7 @@ class MessageType: CALIB_CHECK = "Calib_check" FLASH_CALIB = "Flash_calib" FLASH_FACTORY_CALIB = "Reset_factory" + CAMERA_DIAGNOSTICS = "Camera_Diagnostics" class ErrorAction(Enum): diff --git a/rerun_py/depthai_viewer/_backend/packet_handler.py b/rerun_py/depthai_viewer/_backend/packet_handler.py index af79425ed696..13d329cd6f46 100644 --- a/rerun_py/depthai_viewer/_backend/packet_handler.py +++ b/rerun_py/depthai_viewer/_backend/packet_handler.py @@ -31,6 +31,7 @@ from depthai_viewer.components.rect2d import RectFormat from depthai_viewer._backend.obscured_utilities.utilities.calibration_handler import Recalibration from depthai_viewer._backend.obscured_utilities.utilities.display_handler import Display +import os class PacketHandlerContext(BaseModel): # type: ignore[misc] class Config: @@ -94,6 +95,8 @@ def __init__(self, store: Store, calibration_handler: dai.CalibrationHandler, fa self._ahrs.Q = np.array([1, 0, 0, 0], dtype=np.float64) self._calibration_handler = CachedCalibrationHandler(calibration_handler) self.stereo = stereo + self.save_diagnostics = "" + self.diagnostics_display = False if self.stereo: self._dynamic_recalibration = Recalibration(calibration_handler, factoryCalibration_handler) self._dynamic_recalibration.min_pts_for_calib = 4000 @@ -275,6 +278,10 @@ def _log_img_frame( self._dynamic_recalibration.resolution = img_frame.shape[1::-1] elif (is_left_socket or is_right_socket) and self._display_flashing != "": self._display.draw_center_center_box(img_frame,f"Flashing {self._display_flashing} calibration ...") + + elif (is_left_socket or is_right_socket) and self.diagnostics_display: + self._display.draw_center_center_box(img_frame,f"Saving to:") + self.draw_center_center_box(img_frame, self.file_path) viewer.log_image(entity_path, img_frame) if is_left_socket: self._display.create_window((img_frame.shape[1], img_frame.shape[0])) @@ -328,6 +335,8 @@ def _log_img_frame( self.display_bar = False self._dynamic_recalibration.reset_aggregation() self._calib_time = None + self.diagnostics_display = False + if not self._dynamic_recalibration.result_queue.empty(): self.new_calib, out_before, out_after, fillrate_before, fillrate_after = self._dynamic_recalibration.result_queue.get() @@ -547,8 +556,44 @@ def _start_optimization(self) -> None: print("Starting feature collection and optimization...") self._dynamic_recalibration.start_optimization(8) - - + def draw_center_center_box(self, image, text, box_color=(0, 0, 0, 128), font_scale=0.3, font_color=(255, 255, 255)): + img_height, img_width = image.shape[:2] + + # Calculate text size + text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, 1)[0] + text_width, text_height = text_size + + # Define box dimensions + box_padding = 10 + box_width = text_width + box_padding * 2 + box_height = text_height + box_padding * 2 + box_x = (img_width - box_width) // 2 + box_y = (img_height - box_height) // 2 +50 + + # Draw semi-transparent background box + overlay = image.copy() + box_start = (box_x, box_y) + box_end = (box_x + box_width, box_y + box_height) + alpha = box_color[3] / 255.0 + cv2.rectangle(overlay, box_start, box_end, box_color[:3], -1) + cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0, image) + + # Draw text at the center of the box + text_x = box_x + (box_width - text_width) // 2 + text_y = box_y + (box_height + text_height) // 2 + cv2.putText(image, text, (text_x, text_y), cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_color, 1) + + return image + + def _save_packet(self, name, frame): + base_path = self.save_diagnostics + self.file_path = os.path.join(base_path, f"{name}.png") + + counter = 1 + while os.path.exists(self.file_path): + self.file_path = os.path.join(base_path, f"{name}_{counter}.png") + counter += 1 + cv2.imwrite(self.file_path, frame) def cam_kind_from_frame_type(dtype: dai.RawImgFrame.Type) -> str: """Returns camera kind string for given dai.RawImgFrame.Type."""