def move_forward(force, sm_distance, smoothie: adapters.SmoothieAdapter):
    """Move forward for a specified distance with specified force"""

    res = smoothie.custom_move_for(force, B=sm_distance)
    if res != smoothie.RESPONSE_OK:
        log_msg = "Couldn't move forward, smoothie error occurred: " + str(res)
        print(log_msg)
        exit(1)
Exemple #2
0
def move_forward(smoothie: adapters.SmoothieAdapter):
    log_msg = "Moving forward for 30 cm"
    print(log_msg)
    logging.info(log_msg)

    # move forward for 30 cm
    res = smoothie.custom_move_for(config.B_F_MAX, B=5.43)
    smoothie.wait_for_all_actions_done()
    if res != smoothie.RESPONSE_OK:
        log_msg = "Couldn't move forward (for 30 cm), smoothie error occurred: " + str(res)
        print(log_msg)
        logging.critical(log_msg)
Exemple #3
0
def do_extractions(smoothie: adapters.SmoothieAdapter):
    for y in [100, 150]:
        for x in range(10, 400, 40):
            log_msg = "Moving to a plant coordinates at X=" + str(x) + " Y=" + str(y)
            print(log_msg)
            logging.debug(log_msg)

            # move to a plant
            res = smoothie.custom_move_to(config.XY_F_MAX, X=x, Y=y)
            smoothie.wait_for_all_actions_done()
            if res != smoothie.RESPONSE_OK:
                log_msg = "Couldn't move cork over plant, smoothie error occurred: " + str(res)
                print(log_msg)
                logging.critical(log_msg)
                # exit(1)

            # extraction, cork down
            log_msg = "Extracting plant (cork down)"
            print(log_msg)
            logging.info(log_msg)

            res = smoothie.custom_move_for(config.Z_F_MAX, Z=-30)
            smoothie.wait_for_all_actions_done()
            if res != smoothie.RESPONSE_OK:
                log_msg = "Couldn't move the extractor down, smoothie error occurred:" + str(res)
                print(log_msg)
                logging.critical(log_msg)
                # exit(1)

            # extraction, cork up
            log_msg = "Extracting plant (cork up)"
            print(log_msg)
            logging.info(log_msg)

            res = smoothie.ext_cork_up()
            smoothie.wait_for_all_actions_done()
            if res != smoothie.RESPONSE_OK:
                log_msg = "Couldn't move the extractor up, smoothie error occurred:" + str(res)
                print(log_msg)
                logging.critical(log_msg)
Exemple #4
0
def extract_all_plants(smoothie: adapters.SmoothieAdapter,
                       camera: adapters.CameraAdapterIMX219_170,
                       precise_det: detection.YoloOpenCVDetection,
                       working_zone_polygon: Polygon, frame, plant_boxes: list,
                       undistorted_zone_radius, working_zone_points_cv,
                       img_output_dir):
    """Extract all plants found in current position"""

    img_y_c, img_x_c = int(frame.shape[0] / 2), int(frame.shape[1] / 2)

    # loop over all detected plants
    for box in plant_boxes:
        # go to the extraction position Y min
        smoothie.custom_move_to(config.XY_F_MAX,
                                X=config.X_MAX / 2 /
                                config.XY_COEFFICIENT_TO_MM,
                                Y=config.Y_MIN)
        smoothie.wait_for_all_actions_done()

        box_x, box_y = box.get_center_points()

        # if plant is in working zone (can be reached by cork)
        if is_point_in_poly(box_x, box_y, working_zone_polygon):
            # extraction loop
            while True:
                box_x, box_y = box.get_center_points()

                # if plant inside undistorted zone
                if is_point_in_circle(box_x, box_y, img_x_c, img_y_c,
                                      config.UNDISTORTED_ZONE_RADIUS):
                    print("Plant is in undistorted zone")
                    # calculate values to move camera over a plant
                    sm_x = px_to_smoothie_value(box_x, img_x_c,
                                                config.ONE_MM_IN_PX)
                    sm_y = -px_to_smoothie_value(box_y, img_y_c,
                                                 config.ONE_MM_IN_PX)
                    # swap camera and cork for extraction immediately
                    sm_x += config.CORK_TO_CAMERA_DISTANCE_X
                    sm_y += config.CORK_TO_CAMERA_DISTANCE_Y

                    # move cork over a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX,
                                                   X=sm_x,
                                                   Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move cork over plant, smoothie error occurred:",
                            res)
                        break

                    # extraction, cork down
                    res = smoothie.custom_move_for(
                        F=1700, Z=config.EXTRACTION_Z
                    )  # TODO: calculation -Z depending on box size
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move the extractor down, smoothie error occurred:",
                            res)
                        break

                    # extraction, cork up
                    res = smoothie.ext_cork_up()
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        msg = "Couldn't move the extractor up, smoothie error occurred: " + res + \
                              "emergency exit as I don't want break corkscrew."
                        print(msg)
                        exit(1)

                    # Daisy additional corners extraction
                    if box.get_name(
                    ) == "Daisy":  # TODO: need to create flexible extraction method choosing (maybe dict of functions)
                        box_x_half, box_y_half = box.get_sizes()
                        box_x_half, box_y_half = int(box_x_half / 2 / config.ONE_MM_IN_PX), \
                                                 int(box_y_half / 2 / config.ONE_MM_IN_PX)

                        for x_shift, y_shift in [[-box_x_half, box_y_half],
                                                 [0, -box_y_half * 2],
                                                 [box_x_half * 2, 0],
                                                 [0, box_y_half * 2]]:
                            # move to the corner
                            response = smoothie.custom_move_for(
                                config.XY_F_MAX, X=x_shift, Y=y_shift)
                            smoothie.wait_for_all_actions_done()
                            if response != smoothie.RESPONSE_OK:
                                msg = "Aborting movement to the corner (couldn't reach): " + response
                                print(msg)
                                break

                            # extraction, cork down
                            res = smoothie.custom_move_for(
                                F=1700, Z=config.EXTRACTION_Z
                            )  # TODO: calculation -Z depending on box size
                            smoothie.wait_for_all_actions_done()
                            if res != smoothie.RESPONSE_OK:
                                msg = "Couldn't move the extractor down, smoothie error occurred: " + res
                                print(msg)
                                break

                            # extraction, cork up
                            res = smoothie.ext_cork_up()
                            smoothie.wait_for_all_actions_done()
                            if res != smoothie.RESPONSE_OK:
                                msg = "Couldn't move the extractor up, smoothie error occurred: " + res + \
                                      "emergency exit as I don't want break corkscrew."
                                print(msg)
                                exit(1)
                    break

                # if outside undistorted zone but in working zone
                else:
                    # calculate values for move camera closer to a plant
                    control_point = get_closest_control_point(
                        box_x, box_y, config.IMAGE_CONTROL_POINTS_MAP)
                    sm_x, sm_y = control_point[2], control_point[3]

                    # move camera closer to a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX,
                                                   X=sm_x,
                                                   Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move to plant, smoothie error occurred:",
                            res)
                        break

                    # make new photo and re-detect plants
                    frame = camera.get_image()
                    temp_plant_boxes = precise_det.detect(frame)

                    # check case if no plants detected
                    if len(temp_plant_boxes) == 0:
                        print(
                            "No plants detected (plant was in working zone before), trying to move on next item"
                        )
                        break

                    # debug image saving
                    if config.SAVE_DEBUG_IMAGES:
                        frame = draw_zone_circle(frame, img_x_c, img_y_c,
                                                 undistorted_zone_radius)
                        frame = draw_zone_poly(frame, working_zone_points_cv)
                        frame = detection.draw_boxes(frame, temp_plant_boxes)
                        save_image(img_output_dir, frame, None,
                                   "(extraction specify)")

                    # get closest box (update current box from main list coordinates after moving closer)
                    box = min_plant_box_dist(temp_plant_boxes, img_x_c,
                                             img_y_c)
        # if not in working zone
        else:
            print("Skipped", str(box), "(not in working area)")

    # set camera back to the Y min
    smoothie.custom_move_to(config.XY_F_MAX,
                            X=config.X_MAX / 2 / config.XY_COEFFICIENT_TO_MM,
                            Y=config.Y_MIN)
    smoothie.wait_for_all_actions_done()
def extract_all_plants(smoothie: adapters.SmoothieAdapter, camera: adapters.CameraAdapterIMX219_170,
                       detector: detection.YoloOpenCVDetection, working_zone_polygon: Polygon, image, plant_boxes: list):
    """Extract all plants found in current position"""

    img_y_c, img_x_c = int(image.shape[0] / 2), int(image.shape[1] / 2)

    # loop over all detected plants
    for box in plant_boxes:
        # go to the view position
        smoothie.custom_move_to(config.XY_F_MAX, X=config.X_MAX / 2, Y=config.Y_MIN)
        smoothie.wait_for_all_actions_done()

        box_x, box_y = box.get_center_points()

        # if plant is in working zone (can be reached by cork)
        if is_point_in_poly(box_x, box_y, working_zone_polygon):
            # extraction loop
            while True:
                box_x, box_y = box.get_center_points()

                # if plant inside undistorted zone
                if is_point_in_circle(box_x, box_y, img_x_c, img_y_c, UNDISTORTED_ZONE_RADIUS):
                    print("Plant is in undistorted zone")
                    # calculate values to move camera over a plant
                    sm_x = px_to_smoothie_value(box_x, img_x_c, config.ONE_MM_IN_PX)
                    sm_y = -px_to_smoothie_value(box_y, img_y_c, config.ONE_MM_IN_PX)
                    # swap camera and cork for extraction immediately
                    sm_y += CORK_CAMERA_DISTANCE

                    # move cork over a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX, X=sm_x, Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print("Couldn't move cork over plant, smoothie error occurred:", res)
                        break

                    # extraction, cork down
                    res = smoothie.custom_move_for(F=1700, Z=-35)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print("Couldn't move the extractor down, smoothie error occurred:", res)
                        break

                    # extraction, cork up
                    res = smoothie.ext_cork_up()
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print("Couldn't move the extractor up, smoothie error occurred:", res)
                        exit(1)
                    break

                # if outside undistorted zone but in working zone
                else:
                    # calculate values for move camera closer to a plant
                    control_point = get_closest_control_point(box_x, box_y, IMAGE_CONTROL_POINTS_MAP)
                    sm_x = control_point[2]
                    sm_y = control_point[3]

                    # move camera closer to a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX, X=sm_x, Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print("Couldn't move to plant, smoothie error occurred:", res)
                        break

                    # make new photo and re-detect plants
                    image = camera.get_image()
                    temp_plant_boxes = detector.detect(image)

                    # check case if no plants detected
                    if len(temp_plant_boxes) == 0:
                        print("No plants detected (plant was in working zone before), trying to move on next item")
                        break

                    # get closest box (update current box from main list coordinates after moving closer)
                    box = min_plant_box_dist(temp_plant_boxes, img_x_c, img_y_c)

        # if not in working zone
        else:
            print("Skipped", str(box), "(not in working area)")
def gather_data(smoothie: adapters.SmoothieAdapter,
                camera: adapters.CameraAdapterIMX219_170,
                detector: detection.YoloOpenCVDetection, counter,
                session_label, working_zone_polygon):
    """Gathers photos from robot's on-board camera (no position changes for robot's body).
    Saves single image if no plants were detected on view scan to separate directory.
    Saves images with few positions for each detected plant if any plants detected, and saves all that images to the
    separate directory."""

    # go to the view position (y min, x max / 2)
    smoothie.custom_move_to(config.XY_F_MAX,
                            X=config.X_MAX / 2 / config.XY_COEFFICIENT_TO_MM,
                            Y=config.Y_MIN)
    smoothie.wait_for_all_actions_done()

    image = camera.get_image()
    img_y_c, img_x_c = int(image.shape[0] / 2), int(image.shape[1] / 2)
    plant_boxes = detector.detect(image)

    # save image once if no plants detected, get more each plant positions images otherwise
    if len(plant_boxes) == 0:
        save_image(WITHOUT_PLANTS_DIR, image, counter, session_label)
        counter += 1
    # if any plants detected on the image
    else:
        save_image(WITH_PLANTS_DIR, image, counter, session_label)
        counter += 1

        # loop over all detected plants
        for box in plant_boxes:
            # go to the view position (y min, x max / 2)
            smoothie.custom_move_to(config.XY_F_MAX,
                                    X=config.X_MAX / 2 /
                                    config.XY_COEFFICIENT_TO_MM,
                                    Y=config.Y_MIN)
            smoothie.wait_for_all_actions_done()
            box_x, box_y = box.get_center_points()

            # if plant is in working zone and can be reached by cork
            if is_point_in_poly(box_x, box_y, working_zone_polygon):
                for tuning_counter in range(
                        config.EXTRACTION_TUNING_MAX_COUNT):
                    box_x, box_y = box.get_center_points()

                    # if inside undistorted zone
                    if is_point_in_circle(box_x, box_y, img_x_c, img_y_c,
                                          config.UNDISTORTED_ZONE_RADIUS):
                        # get image right over plant
                        image = camera.get_image()
                        save_image(WITH_PLANTS_DIR, image, counter,
                                   session_label)
                        counter += 1

                        sm_x = px_to_smoothie_value(box_x, img_x_c,
                                                    config.ONE_MM_IN_PX)
                        sm_y = -px_to_smoothie_value(box_y, img_y_c,
                                                     config.ONE_MM_IN_PX)

                        # try to get images over a plant and for 8 sides (left right top bot and diagonals)
                        # for x_shift, y_shift in [[sm_x, sm_y],[-20,0],[0,20],[20,0],[20,0],[0,-20],[0,-20],[-20,0],[-20,0]]:  # do 8 additional photos around plant
                        for x_shift, y_shift in [[
                                sm_x, sm_y
                        ]]:  # only one photo right over plant
                            response = smoothie.custom_move_for(
                                config.XY_F_MAX, X=x_shift, Y=y_shift)
                            smoothie.wait_for_all_actions_done()
                            # skip this photo if couldn't change camera position
                            # skipping will affect that plant's other photos positions
                            if response != smoothie.RESPONSE_OK:
                                continue
                            image = camera.get_image()
                            save_image(WITH_PLANTS_DIR, image, counter,
                                       session_label)
                            counter += 1
                        break

                    # if outside undistorted zone but in working zone
                    else:
                        control_point = get_closest_control_point(
                            box_x, box_y, config.IMAGE_CONTROL_POINTS_MAP)
                        sm_x = control_point[2]
                        sm_y = control_point[3]

                        # move camera closer to a plant
                        response = smoothie.custom_move_for(config.XY_F_MAX,
                                                            X=sm_x,
                                                            Y=sm_y)
                        smoothie.wait_for_all_actions_done()
                        if response != smoothie.RESPONSE_OK:
                            if tuning_counter == 0:
                                print(
                                    "Something gone wrong with control point #"
                                    + str(control_point[4]),
                                    "and smoothie error occurred when I tried to go closer to a plant:",
                                    response)
                            break

                        # make a new photo and re-detect plants
                        temp_plant_boxes = detector.detect(camera.get_image())
                        # check if no plants detected
                        if len(temp_plant_boxes) == 0:
                            break
                        # get closest box (exactly update current box from main list coordinates after moving closer)
                        box = min_plant_box_dist(temp_plant_boxes, img_x_c,
                                                 img_y_c)
    return counter
Exemple #7
0
def extract_all_plants(smoothie: adapters.SmoothieAdapter, camera: adapters.CameraAdapterIMX219_170,
                       detector: detection.YoloOpenCVDetection, working_zone_polygon: Polygon, frame,
                       plant_boxes: list, undistorted_zone_radius, working_zone_points_cv, img_output_dir,
                       logger_full: utility.Logger, data_collector: datacollection.DataCollector):
    """Extract all plants found in current position"""

    msg = "Extracting " + str(len(plant_boxes)) + " plants"
    logger_full.write(msg + "\n")

    # loop over all detected plants
    for box in plant_boxes:
        # go to the extraction position Y min
        smoothie.custom_move_to(config.XY_F_MAX, X=config.X_MAX / 2 / config.XY_COEFFICIENT_TO_MM, Y=config.Y_MIN)
        smoothie.wait_for_all_actions_done()

        box_x, box_y = box.get_center_points()

        # if plant is in working zone (can be reached by cork)
        if is_point_in_poly(box_x, box_y, working_zone_polygon):
            # extraction loop
            for _ in range(config.EXTRACTION_TUNING_MAX_COUNT):
                box_x, box_y = box.get_center_points()

                # if plant inside undistorted zone
                if is_point_in_circle(box_x, box_y, config.SCENE_CENTER_X, config.SCENE_CENTER_Y, undistorted_zone_radius):
                    msg = "Plant " + str(box) + " is in undistorted zone"
                    logger_full.write(msg + "\n")

                    # TODO: use plant box from precise NN for movement calculations

                    # calculate values to move camera over a plant
                    sm_x = px_to_smoothie_value(box_x, config.SCENE_CENTER_X, config.ONE_MM_IN_PX)
                    sm_y = -px_to_smoothie_value(box_y, config.SCENE_CENTER_Y, config.ONE_MM_IN_PX)
                    # swap camera and cork for extraction immediately
                    sm_x += config.CORK_TO_CAMERA_DISTANCE_X
                    sm_y += config.CORK_TO_CAMERA_DISTANCE_Y

                    # move cork over a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX, X=sm_x, Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        msg = "Couldn't move cork over plant, smoothie error occurred:\n" + res
                        logger_full.write(msg + "\n")
                        break

                    # debug image saving
                    frame = camera.get_image()
                    debug_save_image(img_output_dir, "(before first cork down)", frame, [],
                                     undistorted_zone_radius, working_zone_points_cv)

                    # extraction
                    if hasattr(extraction.ExtractionMethods, box.get_name()):
                        # TODO: it's temporary log (1)
                        msg = "Trying extractions: 5"  # only Daisy implemented, it has 5 drops
                        logger_full.write(msg + "\n")

                        res, cork_is_stuck = getattr(extraction.ExtractionMethods, box.get_name())(smoothie, box)
                    else:
                        # TODO: it's temporary log (2)
                        # 5 drops is default, also 1 center drop is possible
                        drops = 5 if config.EXTRACTION_DEFAULT_METHOD == "five_drops_near_center" else 1
                        msg = "Trying extractions: " + str(drops)
                        logger_full.write(msg + "\n")

                        res, cork_is_stuck = getattr(extraction.ExtractionMethods, config.EXTRACTION_DEFAULT_METHOD)(smoothie, box)

                    if res != smoothie.RESPONSE_OK:
                        logger_full.write(res + "\n")
                        if cork_is_stuck:  # danger flag is True if smoothie couldn't pick up cork
                            msg = "Cork is stuck! Emergency stopping."
                            logger_full.write(msg + "\n")
                            exit(1)
                    else:
                        data_collector.add_extractions_data(box.get_name(), 1)
                    break

                # if outside undistorted zone but in working zone
                else:
                    msg = "Plant is in working zone, trying to get closer"
                    logger_full.write(msg + "\n")

                    # calculate values for move camera closer to a plant
                    control_point = get_closest_control_point(box_x, box_y, config.IMAGE_CONTROL_POINTS_MAP)

                    # fixing cork tube view obscuring
                    if config.AVOID_CORK_VIEW_OBSCURING:
                        # compute target point x
                        C_H = box_x - control_point[0]  # may be negative
                        H_x = control_point[0] + C_H
                        target_x = H_x

                        # compute target point y
                        T1_y = control_point[1] - config.UNDISTORTED_ZONE_RADIUS
                        T1_P = box_y - T1_y  # always positive
                        target_y = control_point[1] + T1_P - config.DISTANCE_FROM_UNDIST_BORDER

                        # transfer that to millimeters
                        sm_x = px_to_smoothie_value(target_x, control_point[0], config.ONE_MM_IN_PX)
                        sm_y = -px_to_smoothie_value(target_y, control_point[1], config.ONE_MM_IN_PX)

                        # move camera closer to a plant (and trying to avoid obscuring)
                        res = smoothie.custom_move_for(config.XY_F_MAX, X=sm_x, Y=sm_y)
                        smoothie.wait_for_all_actions_done()
                        if res != smoothie.RESPONSE_OK:
                            msg = "Couldn't apply cork obscuring, smoothie's response:\n" + res + "\n" + \
                                "(box_x: " + str(box_x) + " box_y: " + str(box_y) + " target_x: " + str(target_x) + \
                                " target_y: " + str(target_y) + " cp_x: " + str(control_point[0]) + " cp_y: " + \
                                str(control_point[1]) + ")"
                            logger_full.write(msg + "\n")

                            sm_x, sm_y = control_point[2], control_point[3]

                            # move camera closer to a plant
                            res = smoothie.custom_move_for(config.XY_F_MAX, X=sm_x, Y=sm_y)
                            smoothie.wait_for_all_actions_done()
                            if res != smoothie.RESPONSE_OK:
                                msg = "Couldn't move camera closer to plant, smoothie error occurred:\n" + res
                                logger_full.write(msg + "\n")
                                break
                    else:
                        sm_x, sm_y = control_point[2], control_point[3]

                        # move camera closer to a plant
                        res = smoothie.custom_move_for(config.XY_F_MAX, X=sm_x, Y=sm_y)
                        smoothie.wait_for_all_actions_done()
                        if res != smoothie.RESPONSE_OK:
                            msg = "Couldn't move camera closer to plant, smoothie error occurred:\n" + res
                            logger_full.write(msg + "\n")
                            break

                    # make new photo and re-detect plants
                    frame = camera.get_image()
                    temp_plant_boxes = detector.detect(frame)

                    # debug image saving
                    debug_save_image(img_output_dir, "(extraction specify)", frame, temp_plant_boxes,
                                     undistorted_zone_radius, working_zone_points_cv)

                    # check case if no plants detected
                    if len(temp_plant_boxes) == 0:
                        msg = "No plants detected (plant was in working zone before), trying to move on next item"
                        logger_full.write(msg + "\n")
                        break

                    # get closest box (update current box from main list coordinates after moving closer)
                    box = min_plant_box_dist(temp_plant_boxes, config.SCENE_CENTER_X, config.SCENE_CENTER_Y)
            else:
                msg = "Too much extraction attempts, trying to extract next plant if there is."
                logger_full.write(msg)
        # if not in working zone
        else:
            msg = "Skipped " + str(box) + " (not in working area)"
            logger_full.write(msg + "\n")

    # set camera back to the Y min
    smoothie.custom_move_to(config.XY_F_MAX, X=config.X_MAX / 2 / config.XY_COEFFICIENT_TO_MM, Y=config.Y_MIN)
    smoothie.wait_for_all_actions_done()
Exemple #8
0
def extract_all_plants(smoothie: adapters.SmoothieAdapter,
                       camera: adapters.CameraAdapterIMX219_170,
                       detector: detection.YoloOpenCVDetection,
                       working_zone_polygon: Polygon, image, plant_boxes: list,
                       counter):
    """Extract all plants found in current position"""

    img_y_c, img_x_c = int(image.shape[0] / 2), int(image.shape[1] / 2)

    # loop over all detected plants
    for box in plant_boxes:
        # go to the view position
        smoothie.custom_move_to(config.XY_F_MAX,
                                X=config.X_MAX / 2,
                                Y=config.Y_MIN)
        smoothie.wait_for_all_actions_done()

        box_x, box_y = box.get_center_points()

        # if plant is in working zone (can be reached by cork)
        if is_point_in_poly(box_x, box_y, working_zone_polygon):
            # extraction loop
            while True:
                box_x, box_y = box.get_center_points()

                # if plant inside undistorted zone
                if is_point_in_circle(box_x, box_y, img_x_c, img_y_c,
                                      UNDISTORTED_ZONE_RADIUS):
                    print("Plant is in undistorted zone")
                    # calculate values to move camera over a plant
                    sm_x = px_to_smoothie_value(box_x, img_x_c,
                                                config.ONE_MM_IN_PX)
                    sm_y = -px_to_smoothie_value(box_y, img_y_c,
                                                 config.ONE_MM_IN_PX)
                    # swap camera and cork for extraction immediately
                    sm_y += CORK_CAMERA_DISTANCE

                    # move cork over a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX,
                                                   X=sm_x,
                                                   Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move cork over plant, smoothie error occurred:",
                            res)
                        break

                    # extraction, cork down
                    res = smoothie.custom_move_for(F=1700, Z=-42)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move the extractor down, smoothie error occurred:",
                            res)
                        break

                    # extraction, cork up
                    res = smoothie.ext_cork_up()
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move the extractor up, smoothie error occurred:",
                            res)
                        exit(1)

                    # do photo after extraction (same counter for each robot's BODY position)
                    image = camera.get_image()
                    save_image(IMAGES_OUTPUT_DIR_HOLES, image, counter,
                               "After extraction")
                    # break

                    # Daisy additional corners extraction test
                    if box.get_name() == "Daisy":
                        box_x_half, box_y_half = box.get_sizes()
                        box_x_half, box_y_half = int(box_x_half / 2), int(
                            box_y_half / 2)

                        for x_shift, y_shift in [[-box_x_half, box_y_half],
                                                 [0, -box_y_half * 2],
                                                 [box_x_half * 2, 0],
                                                 [0, box_y_half * 2]]:
                            response = smoothie.custom_move_for(
                                config.XY_F_MAX, X=x_shift, Y=y_shift)
                            smoothie.wait_for_all_actions_done()
                            # skip this photo if couldn't change camera position
                            # skipping will affect that plant's other photos positions
                            if response != smoothie.RESPONSE_OK:
                                print("Aborting movement to the corner:",
                                      response)
                                break

                            # extraction, cork down
                            res = smoothie.custom_move_for(F=1700, Z=-42)
                            smoothie.wait_for_all_actions_done()
                            if res != smoothie.RESPONSE_OK:
                                print(
                                    "Couldn't move the extractor down, smoothie error occurred:",
                                    res)
                                break

                            # extraction, cork up
                            res = smoothie.ext_cork_up()
                            smoothie.wait_for_all_actions_done()
                            if res != smoothie.RESPONSE_OK:
                                print(
                                    "Couldn't move the extractor up, smoothie error occurred:",
                                    res)
                                exit(1)

                            # do photo after extraction (same counter for each robot's BODY position)
                            image = camera.get_image()
                            save_image(IMAGES_OUTPUT_DIR_HOLES, image, counter,
                                       "After extraction (Daisy)")
                    break

                # if outside undistorted zone but in working zone
                else:
                    # calculate values for move camera closer to a plant
                    control_point = get_closest_control_point(
                        box_x, box_y, IMAGE_CONTROL_POINTS_MAP)
                    sm_x = control_point[2]
                    sm_y = control_point[3]

                    # move camera closer to a plant
                    res = smoothie.custom_move_for(config.XY_F_MAX,
                                                   X=sm_x,
                                                   Y=sm_y)
                    smoothie.wait_for_all_actions_done()
                    if res != smoothie.RESPONSE_OK:
                        print(
                            "Couldn't move to plant, smoothie error occurred:",
                            res)
                        break

                    # make new photo and re-detect plants
                    image = camera.get_image()
                    temp_plant_boxes = detector.detect(image)

                    # check case if no plants detected
                    if len(temp_plant_boxes) == 0:
                        print(
                            "No plants detected (plant was in working zone before), trying to move on next item"
                        )
                        break

                    # get closest box (update current box from main list coordinates after moving closer)
                    box = min_plant_box_dist(temp_plant_boxes, img_x_c,
                                             img_y_c)

        # if not in working zone
        else:
            print("Skipped", str(box), "(not in working area)")