Exemple #1
0
def build_dla(wrapper):
    return call_ipt(
        ipt_id="IptLinearTransformation",
        source=wrapper,
        return_type="result",
        method="alpha_beta_target",
        target_brightness=200,
    ).current_image
Exemple #2
0
    def extract_image_data(
        self,
        mask: Any,
        source_image: Union[None, str, Any] = None,
        pseudo_color_channel: str = "v",
        pseudo_color_map: int = 2,
        boundary_position: int = -1,
        pseudo_background_type="bw",
    ):
        try:
            dictionary = call_ipt(
                ipt_id="IptAnalyseObservation",
                source=self,
                return_type="data",
                split_plant_name=0 if "zipfel" in self.experiment else 1,
                new_column_names="plant,plant_id,exit_order",
            )
            if isinstance(dictionary, dict):
                self.csv_data_holder.data_list.update(dictionary)
            else:
                self.error_holder.add_error("Failed to add extracted data")

            dictionary = call_ipt(
                ipt_id="IptAnalyzeBound",
                source=self,
                return_type="data",
                line_position=2090,
            )
            if isinstance(dictionary, dict):
                self.csv_data_holder.data_list.update(dictionary)
            else:
                self.error_holder.add_error("Failed to add extracted data")

            dictionary = call_ipt(ipt_id="IptAnalyzeChlorophyll",
                                  source=self,
                                  return_type="data")
            if isinstance(dictionary, dict):
                self.csv_data_holder.data_list.update(dictionary)
            else:
                self.error_holder.add_error("Failed to add extracted data")

            dictionary = call_ipt(ipt_id="IptAnalyzeColor",
                                  source=self,
                                  return_type="data")
            if isinstance(dictionary, dict):
                self.csv_data_holder.data_list.update(dictionary)
            else:
                self.error_holder.add_error("Failed to add extracted data")

            dictionary = call_ipt(ipt_id="IptAnalyzeObject",
                                  source=self,
                                  return_type="data")
            if isinstance(dictionary, dict):
                self.csv_data_holder.data_list.update(dictionary)
            else:
                self.error_holder.add_error("Failed to add extracted data")

            self.csv_data_holder.data_list = {
                k: v
                for k, v in self.csv_data_holder.data_list.items()
                if v is not None
            }
        except Exception as e:
            print(f"Failed to extract data because {repr(e)}")
            return False
        else:
            return True
Exemple #3
0
    def process_wrapper(self, **kwargs):
        """
        Visualization helper:
        'With the current image and a mask build a visualization for selected features
        Real time: True

        Keyword Arguments (in parentheses, argument name):
            * Activate tool (enabled): Toggle whether or not tool is active
            * Save generated image (save_image):
            * Name in csv (img_name):
            * Image output format (output_format):
            * Output naming convention (output_name):
            * Prefix or suffix (prefix_suffix):
            * Pseudo color channel (channel):
            * Select pseudo color map (color_map):
            * Foreground (foreground):
            * Background color (fore_color):
            * Background (background):
            * Background color (bcg_color):
            * Background intensity (bck_grd_luma):
            * Normalize source image (normalize):
            * Name of ROI to be used (roi_names): Operation will only be applied inside of ROI
            * ROI selection mode (roi_selection_mode):
            * Contour thickness (contour_thickness):
            * Add numeric value for contour (cnt_num):
            * Hull thickness (hull_thickness):
            * Bounding rectangle thickness (bounding_rec_thickness):
            * Straight bounding rectangle thickness (straight_bounding_rec_thickness):
            * Enclosing circle thickness (enclosing_circle_thickness):
            * Centroid width (centroid_width):
            * Centroid line width (centroid_line_width):
            * Add numeric value for centroid x value (cx_num):
            * Add numeric value for centroid y value (cy_num):
            * Height thickness (height_thickness):
            * Width thickness (width_thickness):
        """

        wrapper = self.init_wrapper(**kwargs)
        if wrapper is None:
            return False

        res = False
        try:
            if self.get_value_of("enabled") == 1:
                if self.get_value_of("normalize") == 1:
                    img = call_ipt(ipt_id="IptNormalize", source=wrapper)
                else:
                    img = wrapper.current_image

                self.result = wrapper.draw_image(
                    src_image=img,
                    src_mask=self.get_mask(),
                    **self.params_to_dict(
                        include_input=True,
                        include_output=False,
                        include_neutral=False,
                    ),
                )

                if self.get_value_of("save_image") != 0:
                    dst_path = self.build_path()
                    self.add_value(
                        key=self.get_value_of("img_name"),
                        value=dst_path,
                        force_add=True,
                    )
                    force_directories(self.output_path)
                    cv2.imwrite(filename=dst_path, img=self.result)
                wrapper.store_image(self.result, "visualization")
                self.demo_image = self.result
                res = True
            else:
                wrapper.store_image(wrapper.current_image, "current_image")
                res = True
        except Exception as e:
            res = False
            logger.error(f"Data viz FAILED, exception: {repr(e)}")
        else:
            pass
        finally:
            return res
Exemple #4
0
    def process_wrapper(self, **kwargs):
        """
        IPT Demo:
        IPT Demo (Image Processing Tool Demo)
        A simple showcase of some of the available widgets
        Best starting point if you want to build your own widgets
        Real time : True

        Keyword Arguments (in parentheses, argument name):
            * Output image mode (output_mode):
            * A sample checkbox (checkbox): This is a hint
            * Sample combobox (combobox): This is a sample combobox
            * A slider (slider): This is a hint for a slider
            * A spin box (spin_box): This is a hint for a slider
            * A text input (text_input): This is a hint for the text input
            * This is a button, click to see what happens (button_sample): This is a hint for a sample button
            * Channel (channel):
            * Select pseudo color map (color_map):
            * Select edge detection operator (operator):
            * Canny's sigma (canny_sigma): Sigma.
            * Canny's first Threshold (canny_first): First threshold for the hysteresis procedure.
            * Canny's second Threshold (canny_second): Second threshold for the hysteresis procedure.
            * Kernel size (kernel_size):
            * Threshold (threshold): Threshold for kernel based operators
            * Apply threshold (apply_threshold):
        --------------
            * output  (text_output): A sample text output widget
            * output  (table_output): ('key', 'value')
            * output  (adv_txt): Advanced widgets:
            * output  (cmp_txt): Composit widgets:
        """

        wrapper = self.init_wrapper(**kwargs)
        if wrapper is None:
            return False

        res = False
        try:
            # Update table output
            p = self.find_by_name("table_output")
            p.update_output(output_value=self.params_to_dict())

            # Update multiline output
            p_out = self.find_by_name("text_output")
            p_in = self.find_by_name("text_input")
            self.update_count += 1
            if p_in is not None and p_out is not None:
                p_out.update_output(
                    label_text=f"Text updated {self.update_count} times",
                    output_value=p_in.value,
                )

            # Accessing a widget value, there's always a default value available
            output_mode = self.get_value_of("output_mode")
            if output_mode == "raw":
                img = wrapper.current_image
            elif output_mode == "false_color":
                img = wrapper.draw_image(
                    src_image=wrapper.current_image,
                    channel=self.get_value_of("channel"),
                    color_map=self.get_value_of("color_map"),
                    foreground="false_colour",
                )
            elif output_mode == "full_widget":
                # As all the params needed for  the tool are already in this one
                # we pass everything and the next tool will keep and reject what it wants.
                # The ipt_id param is the name of the class of the target tool
                img = call_ipt(ipt_id="IptEdgeDetector",
                               source=wrapper,
                               **self.params_to_dict())
            else:  # This how we handle errors
                # If the error is added to the wrapper, it will be displayed in the main log
                logger.error("Unknown output mode")
                # We can also create an empty image that will generate another error when storing it
                img = None

            wrapper.store_image(
                img,
                self.name,
                text_overlay=self.input_params_as_str(
                    exclude_defaults=False,
                    excluded_params=("progress_callback", )).replace(
                        ", ", "\n"),
            )
            self.result = img
            res = True
        except Exception as e:
            logger.error(f'Failed to process {self. name}: "{repr(e)}"')
            res = False
        else:
            pass
        finally:
            return res
def main():
    # Get the file
    # ____________
    # Set working folder
    old_wd = os.getcwd()
    abspath = os.path.abspath(__file__)
    fld_name = os.path.dirname(abspath)
    os.chdir(fld_name)

    # Construct the argument parser and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True, help="Path to the image")
    ap.add_argument("-d", "--destination", required=False, help="Destination folder")
    ap.add_argument("-p", "--print_images", required=False, help="Print images, y or n")
    ap.add_argument("-m", "--print_mosaic", required=False, help="Print mosaic, y or n")
    args = vars(ap.parse_args())
    file_name = args["image"]
    print_images = args.get("print_images", "n") == "y"
    print_mosaic = args.get("print_mosaic", "n") == "y"
    dst_folder = args.get("destination", "")

    # Restore working folder
    os.chdir(old_wd)

    # Build wrapper
    # _____________
    wrapper = BaseImageProcessor(file_name)
    wrapper.lock = True
    wrapper.store_image(wrapper.current_image, "true_source_image")
    if print_images or print_mosaic:
        wrapper.store_images = True
    if print_images:
        wrapper.write_images = "plot"
    if print_mosaic:
        wrapper.write_mosaic = "plot"

    # Fix exposure
    # ____________________
    wrapper.current_image = call_ipt(
        ipt_id="IptLinearTransformation",
        source=wrapper,
        return_type="result",
        method="gamma_target",
        target_brightness=145,
        text_overlay=1,
    )

    # Store image name for analysis
    wrapper.store_image(wrapper.current_image, "exposure_fixed")
    analysis_image = "exposure_fixed"

    if print_mosaic:
        wrapper.store_image(wrapper.current_image, "fixed_source")
    # Build static ROIs
    # _________________
    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="check_exp_pot_top",
        roi_type="other",
        tool_target="IptExposureChecker",
        left=394,
        width=1317,
        top=1990,
        height=70,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="check_exp_pot_bottom",
        roi_type="other",
        tool_target="IptExposureChecker",
        left=394,
        width=1317,
        top=2049,
        height=320,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="check_exp_top",
        roi_type="other",
        tool_target="IptExposureChecker",
        left=1583,
        width=461,
        height=90,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="open_pot_top",
        roi_type="open",
        left=500,
        width=1100,
        top=1940,
        height=200,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="erode_pot_bottom",
        roi_type="erode",
        left=500,
        width=1100,
        top=2140,
        height=240,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="keep_roi",
        left=200,
        width=1600,
        height=2350,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="erode_top_right",
        roi_type="erode",
        left=1606,
        width=279,
        top=2,
        height=376,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="open_left_leg",
        roi_type="open",
        left=527,
        width=140,
        top=2050,
        height=381,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="open_right_leg",
        roi_type="open",
        left=1390,
        width=140,
        top=2050,
        height=381,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="split_threshold_inside_bottom",
        tool_target="IptSplittedRangeThreshold",
        left=489,
        width=1071,
        top=2019,
        height=427,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="split_threshold_inside_top_right",
        tool_target="IptSplittedRangeThreshold",
        left=1593,
        width=453,
        height=370,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="split_threshold_inside_right",
        tool_target="IptSplittedRangeThreshold",
        left=1920,
        width=125,
        top=351,
        height=2088,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    # Pre process image (make segmentation easier)
    # ____________________________________________
    wrapper.current_image = call_ipt(
        ipt_id="IptPartialPosterizer",
        source=wrapper,
        return_type="result",
        blue_color="black",
        post_blue_value=41,
    )

    wrapper.current_image = call_ipt(
        ipt_id="IptExposureChecker",
        source=wrapper,
        return_type="result",
        overexposed_limit=200,
        over_color="black",
        underexposed_limit=40,
        under_color="black",
        show_grey_zones=1,
        grey_zone_limit=2,
        grey_zone_color="black",
        roi_names="check_exp_pot_top,check_exp_top",
    )

    wrapper.current_image = call_ipt(
        ipt_id="IptExposureChecker",
        source=wrapper,
        return_type="result",
        overexposed_limit=180,
        over_color="black",
        underexposed_limit=20,
        under_color="black",
        show_grey_zones=1,
        grey_zone_limit=6,
        grey_zone_color="black",
        roi_names="check_exp_pot_bottom",
    )

    if print_mosaic:
        wrapper.store_image(wrapper.current_image, "pre_processed_image")
    # Build coarse masks
    # __________________
    mask_list = []
    current_mask_ = call_ipt(
        ipt_id="IptThreshold",
        source=wrapper,
        return_type="result",
        channel="l",
        min_t=15,
    )
    mask_list.append(current_mask_)

    current_mask_ = call_ipt(
        ipt_id="IptSplittedRangeThreshold",
        source=wrapper,
        return_type="result",
        channel="b",
        roi_names="split_threshold_inside_bottom,split_threshold_inside_top_right,split_threshold_inside_right",
        min_inside_t=135,
        min_outside_t=120,
        kernel_size=4,
        build_mosaic=1,
    )
    mask_list.append(current_mask_)

    # Merge masks
    func = getattr(wrapper, "multi_and", None)
    if func:
        wrapper.mask = func([mask for mask in mask_list if mask is not None])
        wrapper.store_image(wrapper.mask, f"mask_multi_and")
        if print_mosaic:
            wrapper.store_image(wrapper.mask, "coarse_mask")
    else:
        wrapper.error_holder.add_error("Unable to merge coarse masks")
        return

    # ROIs to be applied after mask merging
    # _____________________________________
    handled_rois = ["keep", "delete", "erode", "dilate", "open", "close"]
    rois_list = [
        roi
        for roi in wrapper.rois_list
        if roi.tag in handled_rois and not (roi.target and roi.target != "none")
    ]
    wrapper.mask = wrapper.apply_roi_list(
        img=wrapper.mask, rois=rois_list, print_dbg=True
    )
    if print_mosaic:
        wrapper.store_image(wrapper.mask, "mask_after_roi")

    # Clean merged mask
    # _________________
    wrapper.mask = call_ipt(
        ipt_id="IptKeepLinkedContours",
        source=wrapper,
        return_type="result",
        tolerance_distance=50,
        tolerance_area=500,
        root_position="MIDDLE_CENTER",
    )
    if wrapper.mask is None:
        return

    if print_mosaic:
        wrapper.store_image(wrapper.mask, "clean_mask")

    # Check that the mask is where it belongs
    # _______________________________________
    mask = None
    if print_images:
        res = True
        enforcers_list = wrapper.get_rois({"enforce"})
        for i, enforcer in enumerate(enforcers_list):
            mask = wrapper.mask.copy()
            mask = wrapper.keep_roi(mask, enforcer)
            partial_ok = np.count_nonzero(mask) > 0
            res = partial_ok and res
            if partial_ok:
                roi_img = np.dstack((np.zeros_like(mask), mask, np.zeros_like(mask)))
            else:
                roi_img = np.dstack((np.zeros_like(mask), np.zeros_like(mask), mask))
            background_img = cv2.bitwise_and(
                wrapper.mask, wrapper.mask, mask=255 - mask
            )
            img = cv2.bitwise_or(
                roi_img, np.dstack((background_img, background_img, background_img))
            )
            enforcer.draw_to(img, line_width=4)
            wrapper.store_image(img, f"enforcer_{i}_{enforcer.name}")
        if not res:
            return
    else:
        enforcers_list = wrapper.get_rois({"enforce"})
        for i, enforcer in enumerate(enforcers_list):
            mask = wrapper.mask.copy()
            mask = wrapper.keep_roi(mask, enforcer)
            if np.count_nonzero(mask) == 0:
                return

    # Print selection as color on bw background
    # ____________________________________________
    id_objects, obj_hierarchy = ipc.get_contours_and_hierarchy(
        mask=mask, retrieve_mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE
    )
    wrapper.object_composition(wrapper.current_image, id_objects, obj_hierarchy)

    # Build mosaic
    # ____________
    if print_mosaic:
        wrapper.store_mosaic = "result"
        wrapper.mosaic_data = np.array(
            [
                ["fixed_source", "pre_processed_image", "coarse_mask"],
                [
                    "mask_after_roi",
                    "clean_mask",
                    wrapper.draw_image(
                        src_image=wrapper.current_image,
                        src_mask=wrapper.mask,
                        background="bw",
                        foreground="source",
                        bck_grd_luma=120,
                        contour_thickness=6,
                        hull_thickness=6,
                        width_thickness=6,
                        height_thickness=6,
                        centroid_width=20,
                        centroid_line_width=8,
                    ),
                ],
            ]
        )
        wrapper.print_mosaic(padding=4)

    print("Done.")
Exemple #6
0
def main():
    # Get the file
    # ____________
    # Set working folder
    old_wd = os.getcwd()
    abspath = os.path.abspath(__file__)
    fld_name = os.path.dirname(abspath)
    os.chdir(fld_name)

    # Construct the argument parser and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", required=True, help="Path to the image")
    ap.add_argument("-d",
                    "--destination",
                    required=False,
                    help="Destination folder")
    ap.add_argument("-p",
                    "--print_images",
                    required=False,
                    help="Print images, y or n")
    ap.add_argument("-m",
                    "--print_mosaic",
                    required=False,
                    help="Print mosaic, y or n")
    args = vars(ap.parse_args())
    file_name = args["image"]
    print_images = args.get("print_images", "n") == "y"
    print_mosaic = args.get("print_mosaic", "n") == "y"
    dst_folder = args.get("destination", "")

    # Restore working folder
    os.chdir(old_wd)

    # Build wrapper
    # _____________
    wrapper = BaseImageProcessor(file_name)
    wrapper.lock = True
    wrapper.store_image(wrapper.current_image, "true_source_image")
    if print_images or print_mosaic:
        wrapper.store_images = True
    if print_images:
        wrapper.write_images = "plot"
    if print_mosaic:
        wrapper.write_mosaic = "plot"

    # Fix exposure
    # ____________________
    wrapper.current_image = call_ipt(
        ipt_id="IptLinearTransformation",
        source=wrapper,
        return_type="result",
        method="alpha_beta_target",
        target_brightness=150,
    )

    # Store image name for analysis
    wrapper.store_image(wrapper.current_image, "exposure_fixed")
    analysis_image = "exposure_fixed"

    if print_mosaic:
        wrapper.store_image(wrapper.current_image, "fixed_source")
    # Build static ROIs
    # _________________
    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="keep_roi",
        left=100,
        width=1885,
        top=300,
        height=1740,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="check_roi",
        roi_type="enforce",
        left=840,
        width=400,
        top=1640,
        height=400,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_type="other",
        left=100,
        width=1885,
        top=300,
        height=1740,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="open_pot_top",
        roi_type="open",
        left=710,
        width=660,
        top=1990,
        height=50,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    roi = call_ipt_func(
        ipt_id="IptRoiManager",
        source=wrapper,
        function_name="generate_roi",
        roi_name="safe_roi",
        roi_type="safe",
        left=230,
        width=1550,
        top=350,
        height=1580,
    )
    if roi is not None:
        wrapper.add_roi(new_roi=roi)

    # Pre process image (make segmentation easier)
    # ____________________________________________
    wrapper.current_image = call_ipt(
        ipt_id="IptExposureChecker",
        source=wrapper,
        return_type="result",
        overexposed_limit=175,
        over_color="blue_cabin",
        underexposed_limit=35,
        under_color="blue_cabin",
    )

    wrapper.current_image = call_ipt(
        ipt_id="IptPartialPosterizer",
        source=wrapper,
        return_type="result",
        blue_color="blue_cabin",
        post_blue_value=45,
    )

    if print_mosaic:
        wrapper.store_image(wrapper.current_image, "pre_processed_image")
    # Build coarse masks
    # __________________
    mask_list = []
    current_mask_ = call_ipt(ipt_id="IptThreshold",
                             source=wrapper,
                             return_type="result",
                             max_t=100)
    mask_list.append(current_mask_)

    current_mask_ = call_ipt(
        ipt_id="IptThreshold",
        source=wrapper,
        return_type="result",
        channel="b",
        min_t=125,
    )
    mask_list.append(current_mask_)

    # Merge masks
    func = getattr(wrapper, "multi_and", None)
    if func:
        wrapper.mask = func([mask for mask in mask_list if mask is not None])
        wrapper.store_image(wrapper.mask, f"mask_multi_and")
        if print_mosaic:
            wrapper.store_image(wrapper.mask, "coarse_mask")
    else:
        logger.error("Unable to merge coarse masks")
        return

    # ROIs to be applied after mask merging
    # _____________________________________
    handled_rois = ["keep", "delete", "erode", "dilate", "open", "close"]
    rois_list = [
        roi for roi in wrapper.rois_list if roi.tag in handled_rois
        and not (roi.target and roi.target != "none")
    ]
    wrapper.mask = wrapper.apply_roi_list(img=wrapper.mask,
                                          rois=rois_list,
                                          print_dbg=True)
    if print_mosaic:
        wrapper.store_image(wrapper.mask, "mask_after_roi")

    # Clean merged mask
    # _________________
    wrapper.mask = call_ipt(
        ipt_id="IptKeepLinkedContours",
        source=wrapper,
        return_type="result",
        tolerance_distance=50,
        tolerance_area=100,
    )
    if wrapper.mask is None:
        return

    if print_mosaic:
        wrapper.store_image(wrapper.mask, "clean_mask")

    # Check that the mask is where it belongs
    # _______________________________________
    if print_images:
        res = True
        enforcers_list = wrapper.get_rois({"enforce"})
        for i, enforcer in enumerate(enforcers_list):
            mask = wrapper.mask.copy()
            mask = wrapper.keep_roi(mask, enforcer)
            partial_ok = np.count_nonzero(mask) > 0
            res = partial_ok and res
            if partial_ok:
                roi_img = np.dstack(
                    (np.zeros_like(mask), mask, np.zeros_like(mask)))
            else:
                roi_img = np.dstack(
                    (np.zeros_like(mask), np.zeros_like(mask), mask))
            background_img = cv2.bitwise_and(wrapper.mask,
                                             wrapper.mask,
                                             mask=255 - mask)
            img = cv2.bitwise_or(
                roi_img,
                np.dstack((background_img, background_img, background_img)))
            enforcer.draw_to(img, line_width=4)
            wrapper.store_image(img, f"enforcer_{i}_{enforcer.name}")
        if not res:
            return
    else:
        enforcers_list = wrapper.get_rois({"enforce"})
        for i, enforcer in enumerate(enforcers_list):
            mask = wrapper.mask.copy()
            mask = wrapper.keep_roi(mask, enforcer)
            if np.count_nonzero(mask) == 0:
                return

    # Extract features
    # ________________
    wrapper.current_image = wrapper.retrieve_stored_image("exposure_fixed")
    wrapper.csv_data_holder = AbstractCsvWriter()
    current_data = call_ipt(ipt_id="IptAnalyseObservation",
                            source=wrapper,
                            return_type="data")
    if isinstance(current_data, dict):
        wrapper.csv_data_holder.data_list.update(current_data)
    else:
        logger.error("Failed to add extracted data")

    current_data = call_ipt(ipt_id="IptAnalyzeChlorophyll",
                            source=wrapper,
                            return_type="data")
    if isinstance(current_data, dict):
        wrapper.csv_data_holder.data_list.update(current_data)
    else:
        logger.error("Failed to add extracted data")

    current_data = call_ipt(ipt_id="IptAnalyzeColor",
                            source=wrapper,
                            return_type="data")
    if isinstance(current_data, dict):
        wrapper.csv_data_holder.data_list.update(current_data)
    else:
        logger.error("Failed to add extracted data")

    current_data = call_ipt(ipt_id="IptAnalyzeObject",
                            source=wrapper,
                            return_type="data")
    if isinstance(current_data, dict):
        wrapper.csv_data_holder.data_list.update(current_data)
    else:
        logger.error("Failed to add extracted data")

    # Save CSV
    if dst_folder and (len(wrapper.csv_data_holder.data_list) > 0):
        with open(
                os.path.join(dst_folder, "",
                             wrapper.file_handler.file_name_no_ext + ".csv"),
                "w",
                newline="",
        ) as csv_file_:
            wr = csv.writer(csv_file_, quoting=csv.QUOTE_NONE)
            wr.writerow(wrapper.csv_data_holder.header_to_list())
            wr.writerow(wrapper.csv_data_holder.data_to_list())

    # Build mosaic
    # ____________
    if print_mosaic:
        wrapper.store_mosaic = "result"
        wrapper.mosaic_data = np.array([
            ["fixed_source", "pre_processed_image", "coarse_mask"],
            [
                "mask_after_roi",
                "clean_mask",
                wrapper.draw_image(
                    src_image=wrapper.current_image,
                    src_mask=wrapper.mask,
                    background="bw",
                    foreground="source",
                    bck_grd_luma=120,
                    contour_thickness=6,
                    hull_thickness=6,
                    width_thickness=6,
                    height_thickness=6,
                    centroid_width=20,
                    centroid_line_width=8,
                ),
            ],
        ])
        wrapper.print_mosaic(padding=4)

    print("Done.")