From 4a6bd9d988a9d8e811f5d23cdedc8dc88dcdd5c5 Mon Sep 17 00:00:00 2001 From: Pascal Schade <165774906+pascalzauberzeug@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:25:23 +0200 Subject: [PATCH] remove punch check (#160) --- .../automations/implements/punch_dialog.py | 137 ------------------ .../automations/implements/tornado.py | 2 +- .../implements/weeding_implement.py | 26 +--- .../automations/implements/weeding_screw.py | 2 +- 4 files changed, 3 insertions(+), 164 deletions(-) delete mode 100644 field_friend/automations/implements/punch_dialog.py diff --git a/field_friend/automations/implements/punch_dialog.py b/field_friend/automations/implements/punch_dialog.py deleted file mode 100644 index f96c95d0..00000000 --- a/field_friend/automations/implements/punch_dialog.py +++ /dev/null @@ -1,137 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -import rosys -from nicegui import ui -from rosys.automation import Automator, automation_controls -from rosys.driving import Odometer -from rosys.geometry import Point3d -from rosys.vision import Image - -from ..plant import Plant - -if TYPE_CHECKING: - from field_friend.system import System - - from ...automations import PlantLocator - - -class PunchDialog(ui.dialog): - def __init__(self, system: 'System', shrink_factor: int = 1, timeout: float = 20.0, ui_update_rate: float = 0.2) -> None: - super().__init__() - self.camera_provider = system.camera_provider - self.plant_locator: 'PlantLocator' = system.plant_locator - self.odometer: Odometer = system.odometer - self.automator: Automator = system.automator - self.shrink_factor: int = shrink_factor - self.timeout: float = timeout - self._duration_left: float = timeout - self.ui_update_rate: float = ui_update_rate - - self.camera: Optional[rosys.vision.CalibratableCamera] = None - self.static_image_view: Optional[ui.interactive_image] = None - self.live_image_view: Optional[ui.interactive_image] = None - self.target_plant: Optional[Plant] = None - self.timer = ui.timer(self.ui_update_rate, self.update_live_view) - self.setup_camera() - with self, ui.card().style('max-width: 1400px'): - with ui.row(wrap=False): - with ui.column().classes('w-1/2'): - ui.label('Last detection').classes('text-lg') - self.static_image_view = ui.interactive_image('') - with ui.column().classes('w-1/2'): - ui.label('Live').classes('text-lg') - self.live_image_view = ui.interactive_image('') - self.label = ui.label('Do you want to punch at the current position?').classes('text-lg') - with ui.row().classes('w-full'): - # TODO: doesn't load properly on first open - ui.circular_progress(min=0.0, max=self.timeout, - show_value=False, size='lg').bind_value_from(self, '_duration_left') - ui.button('Yes', on_click=lambda: self.submit('Yes')) - ui.button('No', on_click=lambda: self.submit('No')) - ui.space() - automation_controls(self.automator) - - def open(self) -> None: - self._duration_left = self.timeout - assert self.target_plant is not None - assert self.camera is not None - detection_image = self.camera.latest_detected_image if self.target_plant.detection_image is None else self.target_plant.detection_image - assert detection_image is not None - assert self.static_image_view is not None - self.update_content(self.static_image_view, detection_image, draw_target=True) - self.timer.activate() - super().open() - - def close(self) -> None: - self.timer.deactivate() - super().close() - - def setup_camera(self) -> None: - cameras = list(self.camera_provider.cameras.values()) - active_camera = next((camera for camera in cameras if camera.is_connected), None) - assert active_camera is not None - assert isinstance(active_camera, rosys.vision.CalibratableCamera) - self.camera = active_camera - - def update_content(self, image_view: ui.interactive_image, image: Image, draw_target: bool = False) -> None: - self._duration_left -= self.ui_update_rate - assert self.camera is not None - if self.shrink_factor > 1: - url = f'{self.camera.get_latest_image_url()}?shrink={self.shrink_factor}' - else: - url = self.camera.get_latest_image_url() - image_view.set_source(url) - if image and image.detections: - target_point = None - confidence = None - assert self.camera.calibration is not None - if self.target_plant and draw_target: - confidence = self.target_plant.confidence - point = self.target_plant.position - target_point = self.camera.calibration.project_to_image(Point3d(x=point.x, y=point.y, z=0)) - image_view.set_content(self.to_svg(image.detections, target_point, confidence)) - - def update_live_view(self) -> None: - assert self.live_image_view - if self.camera is None: - self.live_image_view.set_source('assets/field_friend.webp') - return - if self.camera.latest_detected_image is not None: - self.update_content(self.live_image_view, self.camera.latest_detected_image) - - def to_svg(self, detections: rosys.vision.Detections, target_point: Optional[rosys.geometry.Point], confidence: Optional[float], color: str = 'blue') -> str: - svg = '' - cross_size = 20 - for point in detections.points: - x = point.x / self.shrink_factor - y = point.y / self.shrink_factor - if point.category_name in self.plant_locator.crop_category_names: - if point.confidence > self.plant_locator.minimum_crop_confidence: - svg += f'''''' - svg += f'''Crop''' - else: - svg += f'''''' - svg += f'''{point.category_name}''' - elif point.category_name in self.plant_locator.weed_category_names: - if point.confidence > self.plant_locator.minimum_weed_confidence: - svg += f''' - - - Weed - ''' - else: - svg += f''' - - - Weed - ''' - if target_point and confidence: - x = target_point.x / self.shrink_factor - y = target_point.y / self.shrink_factor - svg += f'' - svg += f'{confidence}' - return svg diff --git a/field_friend/automations/implements/tornado.py b/field_friend/automations/implements/tornado.py index 82f6e501..bae09a0f 100644 --- a/field_friend/automations/implements/tornado.py +++ b/field_friend/automations/implements/tornado.py @@ -79,7 +79,7 @@ async def get_stretch(self, max_distance: float) -> float: return self.WORKING_DISTANCE if stretch < 0: stretch = 0 - if stretch < max_distance and await self.ask_for_punch(closest_crop_id): + if stretch < max_distance: self.next_punch_y_position = closest_crop_position.y return stretch return self.WORKING_DISTANCE diff --git a/field_friend/automations/implements/weeding_implement.py b/field_friend/automations/implements/weeding_implement.py index ad5f2018..4e53cd47 100644 --- a/field_friend/automations/implements/weeding_implement.py +++ b/field_friend/automations/implements/weeding_implement.py @@ -9,7 +9,6 @@ from ...hardware import ChainAxis from .implement import Implement -from .punch_dialog import PunchDialog if TYPE_CHECKING: from system import System @@ -34,7 +33,6 @@ def __init__(self, name: str, system: 'System', persistence_key: str = 'weeding self.puncher = system.puncher self.cultivated_crop: str | None = None self.crop_safety_distance: float = 0.01 - self.with_punch_check: bool = False # dual mechanism self.with_drilling: bool = False @@ -50,7 +48,6 @@ def __init__(self, name: str, system: 'System', persistence_key: str = 'weeding self.last_punches: deque[rosys.geometry.Point] = deque(maxlen=5) self.next_punch_y_position: float = 0 - self.punch_dialog: PunchDialog | None = None rosys.on_repeat(self._update_time_and_distance, 0.1) async def prepare(self) -> bool: @@ -169,8 +166,7 @@ def backup(self) -> dict: 'with_chopping': self.with_chopping, 'chop_if_no_crops': self.chop_if_no_crops, 'cultivated_crop': self.cultivated_crop, - 'crop_safety_distance': self.crop_safety_distance, - 'with_punch_check': self.with_punch_check, + 'crop_safety_distance': self.crop_safety_distance } def restore(self, data: dict[str, Any]) -> None: @@ -179,7 +175,6 @@ def restore(self, data: dict[str, Any]) -> None: self.chop_if_no_crops = data.get('chop_if_no_crops', self.chop_if_no_crops) self.cultivated_crop = data.get('cultivated_crop', self.cultivated_crop) self.crop_safety_distance = data.get('crop_safety_distance', self.crop_safety_distance) - self.with_punch_check = data.get('with_punch_check', self.with_punch_check) def clear(self) -> None: self.crops_to_handle = {} @@ -214,22 +209,3 @@ def settings_ui(self): .classes('w-24') \ .bind_value(self, 'crop_safety_distance') \ .tooltip('Set the crop safety distance for the weeding automation') - ui.checkbox('With punch check', value=True) \ - .bind_value(self, 'with_punch_check') \ - .tooltip('Set the weeding automation to check for punch') - self.punch_dialog = PunchDialog(self.system) - - async def ask_for_punch(self, plant_id: str | None = None) -> bool: - if not self.with_punch_check or plant_id is None or self.punch_dialog is None: - return True - self.punch_dialog.target_plant = self.system.plant_provider.get_plant_by_id(plant_id) - result: str | None = None - try: - result = await asyncio.wait_for(self.punch_dialog, timeout=self.punch_dialog.timeout) - if result == 'Yes': - self.log.info('punching was allowed') - return True - except asyncio.TimeoutError: - self.punch_dialog.close() - self.log.warning('punch was not allowed') - return False diff --git a/field_friend/automations/implements/weeding_screw.py b/field_friend/automations/implements/weeding_screw.py index 575c6587..7865a9e7 100644 --- a/field_friend/automations/implements/weeding_screw.py +++ b/field_friend/automations/implements/weeding_screw.py @@ -68,7 +68,7 @@ async def get_stretch(self, max_distance: float) -> float: stretch = 0 self.log.info(f'Targeting weed {next_weed_id} which is {stretch} away at world: ' f'{weed_world_position}, local: {next_weed_position}') - if stretch < max_distance and await self.ask_for_punch(next_weed_id): + if stretch < max_distance: self.next_punch_y_position = next_weed_position.y return stretch else: