Exemple #1
0
 def test_image_transformation(self):
     """Crop: Test that when an image is in an image goes out"""
     op = IptCrop()
     op.apply_test_values_overrides(use_cases=("Pre processing", ))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(res, "Failed to process Crop")
     self.assertIsInstance(op.result, np.ndarray, "Empty result for Crop")
     """Crop: "Test that when using the basic mask generated script this tool extracts features"""
     op = IptCrop()
     op.apply_test_values_overrides(use_cases=("Feature extraction", ))
     script = LoosePipeline.load(
         "./ipso_phen/ipapi/samples/pipelines/test_extractors.json")
     script.add_module(operator=op, target_group="grp_test_extractors")
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = script.execute(src_image=wrapper, silent_mode=True)
     self.assertIsInstance(
         op,
         IptBaseAnalyzer,
         "Crop must inherit from ipso_phen.ipapi.iptBaseAnalyzer",
     )
     self.assertTrue(res, "Failed to process Crop with test script")
     self.assertNotEqual(
         first=len(wrapper.csv_data_holder.data_list),
         second=0,
         msg="Crop returned no data",
     )
Exemple #2
0
 def test_visualization(self):
     """Print color spaces: Test that visualization tools add images to list"""
     op = IptPrintColorSpaces()
     op.apply_test_values_overrides(use_cases=("Visualization", ))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     wrapper.store_images = True
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(res, "Failed to process Simple white balance")
     self.assertGreater(len(wrapper.image_list), 0,
                        "Visualizations must add images to list")
Exemple #3
0
 def test_mask_transformation(self):
     """Keep countours near ROIs: Test that when using the basic mask generated script this tool produces a mask"""
     op = IptKeepCountoursNearRois()
     op.apply_test_values_overrides(use_cases=("Mask cleanup",))
     script = LoosePipeline.load(
         "./ipso_phen/ipapi/samples/pipelines/test_cleaners.json"
     )
     script.add_module(operator=op, target_group="grp_test_cleaners")
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = script.execute(src_image=wrapper, silent_mode=True)
     self.assertTrue(
         res, "Failed to process Keep countours near ROIs with test script"
     )
     self.assertIsInstance(
         wrapper.mask, np.ndarray, "Empty result for Range threshold"
     )
     self.assertEqual(len(wrapper.mask.shape), 2, "Masks can only have one channel")
     self.assertEqual(
         np.sum(wrapper.mask[wrapper.mask != 255]),
         0,
         "Masks values can only be 0 or 255",
     )
 def test_extractors_pipeline(self):
     """Loose pipeline: Test extractors's test pipeline"""
     pipeline = LoosePipeline.load(
         os.path.join(
             self.pipeline_dir_path,
             "test_extractors.json",
         ))
     wrapper = BaseImageProcessor(
         os.path.join(
             os.path.dirname(__file__),
             "..",
             "ipso_phen",
             "ipapi",
             "samples",
             "images",
             "arabido_small.jpg",
         ),
         database=None,
     )
     res = pipeline.execute(src_image=wrapper, silent_mode=True)
     self.assertTrue(
         res,
         "Failed to process Keep countours near ROIs with test pipeline")
     self.assertIsInstance(wrapper.mask, np.ndarray,
                           "Empty result for Range threshold")
     self.assertEqual(len(wrapper.mask.shape), 2,
                      "Masks can only have one channel")
     self.assertEqual(
         np.sum(wrapper.mask[wrapper.mask != 255]),
         0,
         "Masks values can only be 0 or 255",
     )
 def test_mask_transformation(self):
     """Clean horizontal noise (Hough method): Test that when using the basic mask generated script this tool produces a mask"""
     op = IptCleanHorizontalNoiseHough()
     op.apply_test_values_overrides(use_cases=("Mask cleanup", ))
     script = LoosePipeline.load(
         "./ipso_phen/ipapi/samples/pipelines/test_cleaners.json")
     script.add_module(operator=op, target_group="grp_test_cleaners")
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/18HP01U17-CAM11-20180712221558.bmp",
         database=None,
     )
     res = script.execute(src_image=wrapper, silent_mode=True)
     self.assertTrue(
         res,
         "Failed to process Clean horizontal noise (Hough method) with test script",
     )
     self.assertIsInstance(wrapper.mask, np.ndarray,
                           "Empty result for Range threshold")
     self.assertEqual(len(wrapper.mask.shape), 2,
                      "Masks can only have one channel")
     self.assertEqual(
         np.sum(wrapper.mask[wrapper.mask != 255]),
         0,
         "Masks values can only be 0 or 255",
     )
 def test_feature_out(self):
     """Split overlapped ellipses: "Test that when using the basic mask generated script this tool extracts features"""
     op = IptSplitOverlappedEllipses()
     op.apply_test_values_overrides(use_cases=("Feature extraction", ))
     script = LoosePipeline.load(
         "./ipso_phen/ipapi/samples/pipelines/test_extractors.json")
     script.add_module(operator=op, target_group="grp_test_extractors")
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = script.execute(src_image=wrapper, silent_mode=True)
     self.assertIsInstance(
         op,
         IptBaseAnalyzer,
         "Split overlapped ellipses must inherit from ipso_phen.ipapi.iptBaseAnalyzer",
     )
     self.assertTrue(
         res,
         "Failed to process Split overlapped ellipses with test script")
     self.assertNotEqual(
         first=len(wrapper.csv_data_holder.data_list),
         second=0,
         msg="Split overlapped ellipses returned no data",
     )
Exemple #7
0
def ipo_factory(
    file_path,
    options=None,
    force_abstract: bool = False,
    data_base=None,
    scale_factor=1,
):
    if force_abstract:
        return BaseImageProcessor(
            file_path,
            options,
            database=data_base,
            scale_factor=scale_factor,
        )
    else:
        # Build unique class list
        ipt_classes_list = get_module_classes(
            package=class_pipelines,
            class_inherits_from=BaseImageProcessor,
            remove_abstract=True,
        )

        # Create temporary image wrapper to detect experiment
        fh = file_handler_factory(file_path, data_base)

        # Select able class
        ipt_classes_list = list(set(ipt_classes_list))
        for cls in ipt_classes_list:
            if callable(getattr(cls, "can_process", None)) and cls.can_process(
                    dict(experiment=fh.experiment,
                         robot=fh.__class__.__name__)):
                return cls(
                    file_path,
                    options,
                    database=data_base,
                    scale_factor=scale_factor,
                )

        return BaseImageProcessor(
            file_path,
            options,
            database=data_base,
            scale_factor=scale_factor,
        )
 def test_image_transformation(self):
     """Slic: Test that when an image is in an image goes out"""
     op = IptSlic()
     op.apply_test_values_overrides(use_cases=("Pre processing",))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(res, "Failed to process Slic")
     self.assertIsInstance(op.result, np.ndarray, "Empty result for Slic")
 def test_bool_out(self):
     """Assert mask position: Test that tool returns a boolean"""
     op = IptAssertMaskPosition()
     op.apply_test_values_overrides(use_cases=("Assert...",))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(res, "Failed to process Assert mask position")
     self.assertIsInstance(
         op.result, bool, "Assert mask position must return a boolean"
     )
 def test_image_transformation(self):
     """Horizontal line remover: Test that when an image is in an image goes out"""
     op = IptHorizontalLineDetector()
     op.apply_test_values_overrides(use_cases=("Pre processing",))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/18HP01U17-CAM11-20180712221558.bmp",
         database=None,
     )
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(res, "Failed to process Horizontal line remover")
     self.assertIsInstance(
         op.result, np.ndarray, "Empty result for Horizontal line remover"
     )
Exemple #11
0
 def test_roi_out(self):
     """Circle ROI: Test that tool generates an ROI"""
     op = IptCircleRoi()
     op.apply_test_values_overrides(use_cases=("Create an ROI", ))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(hasattr(op, "generate_roi"),
                     "Class must have method generate_roi")
     self.assertTrue(res, "Failed to process Circle ROI")
     r = op.generate_roi()
     self.assertIsInstance(r, regions.AbstractRegion,
                           "ROI must be of type Region")
 def _fix_source_image(self, img):
     if self.is_msp:
         # Fix brightness for darker images
         tmp_wrapper = BaseImageProcessor(self.file_path)
         with IptLinearTransformation(
             wrapper=tmp_wrapper,
             method="gamma_target",
             apply_case="if_under",
             target_brightness=75,
             max_delta_for_brightness=20,
         ) as (res, ed):
             if res:
                 return ed.result
     else:
         return img
Exemple #13
0
 def test_mask_generation(self):
     """Otsu: Test that when an image is in a mask goes out"""
     op = IptOtsu()
     op.apply_test_values_overrides(use_cases=("Threshold", ))
     wrapper = BaseImageProcessor(
         "./ipso_phen/ipapi/samples/images/arabido_small.jpg",
         database=None,
     )
     res = op.process_wrapper(wrapper=wrapper)
     self.assertTrue(res, "Failed to process Otsu")
     self.assertIsInstance(op.result, np.ndarray, "Empty result for Otsu")
     self.assertEqual(len(op.result.shape), 2,
                      "Masks can only have one channel")
     self.assertEqual(
         np.sum(op.result[op.result != 255]),
         0,
         "Masks values can only be 0 or 255",
     )
Exemple #14
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.")
Exemple #15
0
def build_single_plant_video(args):
    plant, dst_folder_, db, dates_, experiment_, angles_ = args

    p_output = os.path.join(dst_folder_, f"{plant}.mp4")
    if os.path.isfile(p_output):
        return f"Plant {plant} already handled"

    ret = db.query(
        command="SELECT",
        columns="filepath",
        additional="ORDER BY date_time ASC",
        date=dict(operator="IN", values=dates_),
        experiment=experiment_,
        plant=plant,
        angle=angles_[0],
    )
    main_angle_image_list_ = [item[0] for item in ret]

    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    out = cv2.VideoWriter(p_output, fourcc, 24.0, (video_width, video_height))
    fnt = cv2.FONT_HERSHEY_DUPLEX

    for main_angle_image_ in main_angle_image_list_:
        main_angle_wrapper_side = BaseImageProcessor(
            main_angle_image_,
            db,
        )
        try:
            img_main_angle = build_image(main_angle_wrapper_side)
            if img_main_angle is None:
                continue
            main_angle_wrapper_side.store_image(image=img_main_angle,
                                                text=angles_[0],
                                                force_store=True)
        except Exception as e:
            print(
                f'Exception "{repr(e)}" while handling {str(main_angle_wrapper_side)}'
            )

        current_date_time = main_angle_wrapper_side.date_time

        for secondary_angle in angles_[1:]:
            secondary_angle_image_ = db.query_one(
                command="SELECT",
                columns="filepath",
                additional="ORDER BY date_time ASC",
                experiment=experiment_,
                plant=plant,
                angle=secondary_angle,
                date_time=dict(
                    operator="BETWEEN",
                    date_min=current_date_time - datetime.timedelta(hours=1),
                    date_max=current_date_time + datetime.timedelta(hours=1),
                ),
            )
            if secondary_angle_image_:
                secondary_angle_image_ = secondary_angle_image_[0]
            if secondary_angle_image_ and os.path.isfile(
                    secondary_angle_image_):
                secondary_angle_wrapper = BaseImageProcessor(
                    secondary_angle_image_)
                try:
                    secondary_angle_img = build_image(secondary_angle_wrapper)
                    main_angle_wrapper_side.store_image(
                        image=secondary_angle_img,
                        text=secondary_angle,
                        force_store=True)
                except Exception as e:
                    print(
                        f'Exception "{repr(e)}" while handling {str(secondary_angle_wrapper)}'
                    )

        mosaic = main_angle_wrapper_side.build_mosaic(
            (video_height, video_width, 3), angles_)
        cv2.putText(
            mosaic,
            current_date_time.strftime("%d/%m/%Y - %H:%M:%S"),
            (10, 1000),
            fnt,
            1,
            (255, 0, 255),
            2,
            cv2.LINE_AA,
        )

        # cv2.imwrite(
        #     os.path.join(os.path.dirname(p_output), main_angle_wrapper_side.luid + ".jpg"), mosaic
        # )

        def write_image_times(out_writer, img_, times=24):
            for _ in range(0, times):
                out_writer.write(img_)

        # Print source image
        write_image_times(out, mosaic)

    # Release everything if job is finished
    out.release()
    cv2.destroyAllWindows()

    return None
Exemple #16
0
def build_sbs_video():
    p_output = os.path.join(dst_folder, f'output_{"_".join(plants)}_.mp4')
    if os.path.isfile(p_output):
        return f"Plants {'_'.join(plants)} already handled"

    ret = current_database.query(
        command="SELECT",
        columns="filepath",
        additional="ORDER BY date_time ASC",
        date=dict(operator="IN", values=dates),
        experiment=experiment,
        plant=plants[0],
        angle=angle,
    )
    file_list_ = [item[0] for item in ret]

    fourcc = cv2.VideoWriter_fourcc(*"mp4v")
    out = cv2.VideoWriter(p_output, fourcc, 24.0, (video_width, video_height))
    fnt = cv2.FONT_HERSHEY_DUPLEX

    total = len(file_list_)
    current_progress = st.progress(0)
    time_counter = 1
    for i, source_vis_plant in enumerate(file_list_):
        if not (source_vis_plant and os.path.isfile(source_vis_plant)):
            continue
        # Handle first image
        main_wrapper = BaseImageProcessor(source_vis_plant)
        try:
            img_main = build_image(main_wrapper)
            main_wrapper.store_image(image=img_main,
                                     text=plants[0],
                                     force_store=True)
        except Exception as e:
            print(
                f'Exception "{repr(e)}" while handling {str(source_vis_plant)}'
            )

        current_date_time = main_wrapper.date_time

        # Handle the rest
        has_missing_ = False
        for counter, ancillary_plant in enumerate(plants[1:]):
            file_name_ = current_database.query_one(
                command="SELECT",
                columns="filepath",
                additional="ORDER BY date_time ASC",
                experiment=experiment,
                plant=ancillary_plant,
                angle=angle,
                date_time=dict(
                    operator="BETWEEN",
                    date_min=current_date_time - datetime.timedelta(hours=10),
                    date_max=current_date_time + datetime.timedelta(hours=10),
                ),
            )
            if file_name_:
                file_name_ = file_name_[0]
            if file_name_ and os.path.isfile(file_name_):
                try:
                    main_wrapper.store_image(
                        image=build_image(BaseImageProcessor(file_name_)),
                        text=plants[counter + 1],
                        force_store=True,
                    )
                except Exception as e:
                    print(
                        f'Exception "{repr(e)}" while handling {str(file_name_)}'
                    )
            else:
                has_missing_ = True

        mosaic = main_wrapper.build_mosaic((video_height, video_width, 3),
                                           plants)

        # cv2.imwrite(
        #     os.path.join(os.path.dirname(p_output), f"{main_wrapper.plant}_{time_counter}.jpg"),
        #     mosaic,
        # )
        time_counter += 1

        def write_image_times(out_writer, img_, times=12):
            for _ in range(0, times):
                out_writer.write(img_)

        # Print source image
        write_image_times(out, mosaic)

        current_progress.progress((i + 1) / total)

    # Release everything if job is finished
    out.release()
    cv2.destroyAllWindows()
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.")