Example #1
0
def move_straightened_masks(source, dest):
    """
    move the straightened masks to their final destination
    """

    for file in os.listdir(source):
        # only straightened masks
        if "Curvature" in file and file.endswith(config["file_format"]):
            corrected_file = file.replace("_px", "px").replace("_ppm", "ppm")
            # extract info from file
            attributes = get_attributes_from_filename(corrected_file)
            year = attributes["UID"].split("-")[-1]
            # Year_Location > Genotype > straightened masks
            location = attributes.get("Location", "missing_location")
            year_location = "_".join([year, location])
            # compose target
            source = attributes.get("Genotype", None)
            if source is None:
                source = attributes.get("Source", "missing_genotype")
            result_target = os.path.join(dest, year_location, source)
            # create target if it does not exist yet
            os.makedirs(result_target, exist_ok=True)

            # move mask there
            source_filepath = os.path.join(source, file)
            dest_filepath = os.path.join(result_target, corrected_file)
            os.rename(source_filepath, dest_filepath)
def assemble_instance(file):
    instance = get_attributes_from_filename(file)

    scale = instance.get("Scale", None)

    if scale is None:
        click.secho(file, fg="red")
        click.secho("No 'Scale' attribute found!", fg="red")
        return

    image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)

    width_px = get_max_width(image.copy())
    width_mm = convert_length_to_mm(scale, width_px)

    decile_widths = get_decile_widths(image.copy(), scale)

    length_px = get_length(image.copy())
    length_mm = convert_length_to_mm(scale, length_px)

    biomass_px = get_biomass(image.copy())
    biomass_mm2 = convert_surface_to_mm2(scale, biomass_px)

    instance["biomass"] = biomass_mm2
    instance["max_width"] = width_mm
    instance["width_0"] = decile_widths[0]
    instance["width_10"] = decile_widths[1]
    instance["width_20"] = decile_widths[2]
    instance["width_30"] = decile_widths[3]
    instance["width_40"] = decile_widths[4]
    instance["width_50"] = decile_widths[5]
    instance["width_60"] = decile_widths[6]
    instance["width_70"] = decile_widths[7]
    instance["width_80"] = decile_widths[8]
    instance["width_90"] = decile_widths[9]
    instance["width_100"] = decile_widths[10]
    instance["length"] = length_mm
    instance["length_width_ratio"] = get_length_width_ratio(image.copy())

    shoulder_top_px, shoulder_bottom_px = get_shouldering(image.copy())
    shoulder_top_mm2 = convert_surface_to_mm2(scale, shoulder_top_px)
    shoulder_bottom_mm2 = convert_surface_to_mm2(scale, shoulder_bottom_px)

    instance["shoulder_top"] = shoulder_top_mm2
    instance["shoulder_bottom"] = shoulder_bottom_mm2

    tip_angle_top, tip_angle_bottom = get_tip_angles(image.copy())
    instance["tip_angle_top"] = tip_angle_top
    instance["tip_angle_bottom"] = tip_angle_bottom

    above_tipangle_top_px, above_tipangle_bottom_px = get_biomass_above_tip_angle(
        image.copy())
    above_tipangle_top_mm2 = convert_surface_to_mm2(scale,
                                                    above_tipangle_top_px)
    above_tipangle_bottom_mm2 = convert_surface_to_mm2(
        scale, above_tipangle_bottom_px)
    instance["above_tipangle_top"] = above_tipangle_top_mm2
    instance["above_tipangle_bottom"] = above_tipangle_bottom_mm2

    return instance
def assemble_instance(file):
    instance = get_attributes_from_filename(file)

    if "kernel" in file:
        image_type = "kernel"
    elif "in-shell" in file:
        image_type = "in-shell"
    instance["type"] = image_type

    image = cv2.imread(file, cv2.IMREAD_GRAYSCALE)

    white_pixels = cv2.countNonZero(image)
    instance["white_pixels"] = white_pixels

    scale = instance.get("Scale", None)
    if scale:
        instance["area"] = round(white_pixels / float(scale) ** 2, 3)
    else:
        instance["area"] = "no scale"

    contour = get_carrot_contour(image)

    # https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_features/py_contour_features.html
    # cnts = cv2.findContours(image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # cnts = cnts[0] if imutils.is_cv2() else cnts[1]
    # print(type(cnts))
    # c = max(cnts, key=cv2.contourArea)
    # color_image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
    # cv2.drawContours(color_image, [contour], -1, (0, 0, 255), 3)

    x, y, w, h = cv2.boundingRect(contour)
    # color_image = cv2.rectangle(color_image, (x, y), (x + w, y + h), (0, 255, 0), 2)
    instance["width"] = w
    instance["height"] = h

    # https://stackoverflow.com/questions/31281235/anomaly-with-ellipse-fitting-when-using-cv2-ellipse-with-different-parameters
    # https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_drawing_functions/py_drawing_functions.html#drawing-ellipse
    ellipse = cv2.fitEllipse(contour)
    ellipse_major_axis, ellipse_minor_axis = ellipse[1]
    ellipse_angle = ellipse[2]
    instance["ellipse_major_axis"] = round(ellipse_major_axis, 2)
    instance["ellipse_minor_axis"] = round(ellipse_minor_axis, 2)
    instance["ellipse_angle"] = round(ellipse_angle, 2)
    # cv2.ellipse(color_image, ellipse, (0, 255, 0), 2)

    # print(ellipse[1], ellipse[2])
    rect = cv2.minAreaRect(contour)
    _, width_height, _ = rect
    min_area_width, min_area_height = width_height
    instance["min_area_width"] = round(min_area_width, 2)
    instance["min_area_height"] = round(min_area_height, 2)
    # print(min_area_width, min_area_height)
    # box = cv2.boxPoints(rect)
    # box = np.int0(box)
    # cv2.drawContours(color_image, [box], 0, (255, 0, 0), 2)
    # print(len(cnts))
    # cv2.imwrite("/Users/creimers/Downloads/glibba.png", color_image)
    return instance
Example #4
0
def copy_results(source,
                 dest,
                 dest_dir_key="Genotype",
                 dest_sub_key=None,
                 flat_files=False):
    """
    move the straightened masks to their final destination

    Args:
        source (str): absolute path to the source directory
        dest (str): absolute path to the dest directory
        dest_dir_key (str): key to be used to name the directory
        dest_sub_key (str): key to be used to name the subdirectory
    """
    for file in os.listdir(source):
        if "Curvature" in file and file.endswith(config["file_format"]):
            corrected_file = file.replace("_px", "px").replace("_ppm", "ppm")
            if flat_files:
                source_filepath = os.path.join(source, file)
                dest_filepath = os.path.join(dest, file)
                os.makedirs(dest, exist_ok=True)
                os.rename(source_filepath, dest_filepath)
                continue
            # extract info from file
            attributes = get_attributes_from_filename(corrected_file)
            year = attributes["UID"].split("-")[-1]
            # Year_Location > Genotype > straightened masks
            location = attributes.get("Location", "missing_location")
            year_location = "_".join([year, location])

            # compose target
            # default
            if not dest:
                # move to sibling
                result_target = source.split(BINARY_MASKS_DIR)[0]

            else:
                result_target = os.path.join(dest, year_location)

                dirname = attributes.get(dest_dir_key, None)
                if dirname:
                    result_target = os.path.join(dest, year_location, dirname)

                    if dest_sub_key:
                        subdirname = attributes.get(dest_sub_key, None)
                        if subdirname:
                            result_target = os.path.join(
                                dest, year_location, dirname, subdirname)

            # create target if it does not exist yet
            result_target = os.path.join(result_target, STRAIGHTENED_MASKS_DIR)
            os.makedirs(result_target, exist_ok=True)

            # move mask there
            source_filepath = os.path.join(source, file)
            dest_filepath = os.path.join(result_target, corrected_file)
            os.rename(source_filepath, dest_filepath)
Example #5
0
def copy_results(source, dest, dest_dir_key="Genotype", dest_sub_key=None):
    """
    move the straightened masks to their final destination

    Args:
        source (str): absolute path to the source directory
        dest (str): absolute path to the dest directory
        dest_dir_key (str): key to be used to name the directory
        dest_sub_key (str): key to be used to name the subdirectory
    """

    for file in os.listdir(source):
        corrected_file = file.replace("_px", "px").replace("_ppm", "ppm")
        # extract info from file
        attributes = get_attributes_from_filename(corrected_file)
        year = attributes["UID"].split("-")[-1]
        # Year_Location > Genotype > straightened masks
        location = attributes.get("Location", "missing_location")
        year_location = "_".join([year, location])

        # compose target
        # default
        result_target = os.path.join(dest, year_location)

        dirname = attributes.get(dest_dir_key, None)
        if dirname:
            result_target = os.path.join(dest, year_location, dirname)

            if dest_sub_key:
                subdirname = attributes.get(dest_sub_key, None)
                if subdirname:
                    result_target = os.path.join(
                        dest, year_location, dirname, subdirname
                    )

        # create target if it does not exist yet
        result_target = os.path.join(result_target, BINARY_MASKS_DIR)
        os.makedirs(result_target, exist_ok=True)

        # move mask there
        source_filepath = os.path.join(source, file)
        dest_filepath = os.path.join(result_target, corrected_file)
        shutil.copyfile(source_filepath, dest_filepath)
def generate_avg_filename(masks: typing.List) -> str:
    """
    generate the filename for the average masks
    """
    mask_count = 0
    scale_sum = 0
    genotypes = []
    for mask in masks:
        attrs = get_attributes_from_filename(mask)
        genotypes.append(attrs["Genotype"])
        scale = attrs.get("Scale", None)
        if scale:
            scale_sum += int(scale)
            mask_count += 1
    avg_scale = int(scale_sum / mask_count)

    filename = "{Scale_%s}" % avg_scale

    if len(list(set(genotypes))) == 1:
        avg_genotype = genotypes[0]
        filename += "{Genotype_%s}" % avg_genotype
    return f"{filename}.png"
Example #7
0
def get_histogram_data(src, diff_thresh=25):
    diff_too_big_count = 0
    total_count = 0
    regr = load("tip-mask-model.joblib")
    pairs = get_mask_pairs(src)
    data = []
    for pair in pairs:
        total_count += 1
        raw = pair["with-tips"]
        training = pair["without-tips"]

        raw_mask = cv2.imread(raw, cv2.IMREAD_GRAYSCALE)
        training_mask = cv2.imread(training, cv2.IMREAD_GRAYSCALE)

        attributes = get_attributes_from_filename(raw)
        scale = attributes.get("Scale", None)
        mm_per_px = pixel_to_mm(scale)

        tip_index = tip_mask_ml(raw_mask, regr, mm_per_px)

        detipped_length = get_length(training_mask)

        length_diff = round(tip_index[0]) - detipped_length

        if abs(length_diff) > diff_thresh:
            diff_too_big_count += 1
            raw_length = get_length(raw_mask)
            print(">>>>>>>>>>>>>>>>")
            print(f"tip mask diff > {diff_thresh} px", raw)
            print("with-tip -> without-tip diff: ",
                  raw_length - detipped_length)
        data.append(length_diff)
    print(
        f"diff > {diff_thresh}px in {diff_too_big_count} out of {total_count} cases."
    )
    return data
def tip_mask(src, model, visualize=False):
    """
    mask the tips of the straightened carrots

    Args:
        src (str) - absolute path to the binary mask
        visualize (bool) - only visualize the masking
    """

    # if not dest:
    dest = src.split(STRAIGHTENED_MASKS_DIR)[0]
    dest = os.path.join(dest, DETIPPED_MASKS_DIR)
    if os.path.exists(dest):
        shutil.rmtree(dest)

    if not os.path.exists(dest):
        os.makedirs(dest)

    for file in os.listdir(src):
        print(file)
        src_filepath = os.path.join(src, file)
        dest_filepath = os.path.join(dest, file)

        mask = cv2.imread(src_filepath, cv2.IMREAD_GRAYSCALE)

        attributes = get_attributes_from_filename(src_filepath)
        scale = attributes.get("Scale", None)
        mm_per_px = pixel_to_mm(scale)

        if mask is None:
            msg = "File %s is empty!" % src_filepath
            click.secho(msg, fg="red")
            continue

        # get index from ml model
        try:
            tip_index = tip_mask_ml(mask, model, mm_per_px)
        except Exception as e:
            click.secho(file, fg="red")
            print(e)
            tip_index = [0]
        tip_index = int(tip_index[0])
        # print(tip_index)
        # print(mask.shape[1])
        tip_index = mask.shape[1] - tip_index

        # get index based on threshold
        # tip_index = find_tip_pseudo_dynamic(mask, pure=True)
        # tip_index_advanced = find_tip_pseudo_dynamic(mask, pure=False)

        # if tip_index_advanced > 0:
        #     crop_index = tip_index_advanced
        # else:
        #     crop_index = tip_index
        crop_index = tip_index

        if visualize:
            # paint only
            tip = mark_start_of_tail(mask.copy(), tip_index, [0, 0, 255])
            # tip = mark_start_of_tail(tip, tip_index_advanced, [0, 255, 0])
            # print(dest)
            write_file(tip, dest, file)
            continue

        else:
            # crop + buffer + wirte
            mask = mask[:, crop_index:]

            black_col = np.zeros((mask.shape[0], 10), dtype=np.uint8)
            mask = np.hstack([black_col, mask])

            # another round of contour reduction to remove dangling white pixels
            mask = reduce_to_contour(mask, minimize=False)

            cv2.imwrite(dest_filepath, mask)

        old_tip_index = get_index_of_tip(mask.T)
        tip_length = crop_index - old_tip_index
        if tip_length < 0:
            tip_length = 0
        tip_biomass = get_biomass(mask[:, old_tip_index:crop_index])
        new_filepath = append_or_change_filename(dest_filepath, "TipLength",
                                                 None, tip_length)
        append_or_change_filename(new_filepath, "TipBiomass", None,
                                  tip_biomass)
Example #9
0
def run(src):
    start = timeit.default_timer()
    pairs = get_mask_pairs(src)

    # pixel müssen vergleichbar sein.
    data = []
    for pair in pairs:
        raw = pair["with-tips"]
        training = pair["without-tips"]

        # print(raw)
        attributes = get_attributes_from_filename(raw)
        scale = attributes.get("Scale", None)
        mm_per_px = pixel_to_mm(scale)
        # print(mm_per_px)

        raw_mask = cv2.imread(raw, cv2.IMREAD_GRAYSCALE)
        training_mask = cv2.imread(training, cv2.IMREAD_GRAYSCALE)

        # reverse, so the thick end is at 0
        width_array = get_width_array_mm(raw_mask, mm_per_px)[::-1]
        # print(width_array)
        normalized_width_array = normalize_width_array(width_array)

        # length of raw carrot
        # raw_length = get_length(raw_mask)
        # print("length", raw_length)

        # length of detipped carrot
        detipped_length = get_length(training_mask)
        # print("detipped length", detipped_length)

        # difference in length
        # length_diff = raw_length - detipped_length

        # white_index_raw = get_index_first_white_pixel(raw_mask)

        # tip_index = white_index_raw + length_diff

        # because the widths are reversed
        tip_index = detipped_length

        data.append(
            {"tip_index": tip_index, "normalized_widths": normalized_width_array}
        )

    # resampling sagt Gilles...
    equalized_data = equalize_lengths(data)

    X = [d["normalized_widths"] for d in equalized_data]
    y = [d["tip_index"] for d in equalized_data]

    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

    # linreg = LinearRegression().fit(X_train, y_train)

    # print('R-squared score (training): {:.3f}'
    #  .format(linreg.score(X_train, y_train)))
    # print('R-squared score (test): {:.3f}'
    #     .format(linreg.score(X_test, y_test)))

    regr = RandomForestRegressor(max_depth=5, random_state=0, n_estimators=10)
    regr.fit(X_train, y_train)
    print("score", regr.score(X_test, y_test))

    # regr.predict([[feature1, feature2]])

    dump(regr, "tip-mask-model.joblib")
    print("model dumped")
    stop = timeit.default_timer()
    print(f"training: {stop - start}")
def process_image(
    image, dest: str, columns: int, rows: int, scan_type: str, qr_code_content: str
):
    qr_attributes = get_attributes_from_filename(qr_code_content)
    expected_nuts = columns * rows
    cropped_nuts = crop_hazelnuts(image, expected_nuts)

    masking_method = create_nut_mask_blue_hsv

    sorted_nuts = sort_nuts(cropped_nuts, rows, columns)
    cells_with_nuts = 0
    for i, cropped_nut_dict in enumerate(sorted_nuts):
        i = i + 1
        cropped_nut = cropped_nut_dict["mask"]
        width_px = cropped_nut_dict["width"]
        height_px = cropped_nut_dict["height"]
        scale = round(
            (width_px / config["square_width"] + height_px / config["square_height"])
            / 2,
            3,
        )

        location = qr_attributes["Location"]
        year = qr_attributes["Year"]
        row = qr_attributes["Row"]
        plant = qr_attributes["Plant"]

        dest_dir = os.path.join(
            dest, location, year, f"Row_{row}-Plant_{plant}", scan_type
        )

        mask = create_nut_mask(cropped_nut, masking_method)
        if mask is not None:
            cells_with_nuts += 1
            # write crop to disk
            os.makedirs(dest_dir, exist_ok=True)
            file_name = f"{qr_code_content}{{Scale_{scale}}}{{Nut_{i}}}.png"
            dest_path_crop = os.path.join(dest_dir, file_name)
            cv2.imwrite(dest_path_crop, cropped_nut)

            # write mask to disk
            dest_dir_mask = os.path.join(dest_dir, "binary-masks")
            os.makedirs(dest_dir_mask, exist_ok=True)
            dest_path_mask = os.path.join(dest_dir_mask, file_name)
            cv2.imwrite(dest_path_mask, mask)

            # write mask overlay to disk
            mask_overlay = create_nut_mask_overlay(cropped_nut, masking_method)
            if mask_overlay is not None:
                dest_dir_mask_overlay = os.path.join(dest_dir, "binary-masks-overlay")
                os.makedirs(dest_dir_mask_overlay, exist_ok=True)
                dest_path_mask_overlay = os.path.join(dest_dir_mask_overlay, file_name)
                cv2.imwrite(dest_path_mask_overlay, mask_overlay)
            try:
                ellipse_overlay = create_ellipse_overlay(cropped_nut, masking_method)
                if ellipse_overlay is not None:
                    dest_dir_ellipse_overlay = os.path.join(dest_dir, "ellipse-overlay")
                    os.makedirs(dest_dir_ellipse_overlay, exist_ok=True)
                    dest_path_ellipse_overlay = os.path.join(
                        dest_dir_ellipse_overlay, file_name
                    )
                    cv2.imwrite(dest_path_ellipse_overlay, ellipse_overlay)
            except Exception as e:
                print(e)

    print("ℹ️  total nuts found:", cells_with_nuts, "🌰 " * cells_with_nuts)
Example #11
0
def do_acquisition(image_path, dest, tmp, destdir, destsub, discard, columns,
                   rows, manual_mode):
    """
    Args:
        image_path - path to the image as string
        dest - destination directory as string
        destdir: (str) key to be used to name directory
        destsub: (str) key to be used to name sub directory
        discard: a directory to copy the photo to if not processed (str)
        columns: (int) number of columns in grid
        rows: (int) number of rows in grid
        manual_mode: (bool) enter content of missing qr code
    """

    temp_dest = tmp
    if not os.path.exists(temp_dest):
        os.makedirs(temp_dest)

    scan_type_map = {"s": "in-shell", "k": "kernel", "b": "blanched"}
    scan_type = click.prompt("in-shell [s], kernels [k] or blanched [b]?",
                             type=str,
                             default="s")

    qr_code_content, image = get_qr_code_content(image_path, manual_mode)
    if not qr_code_content:
        return
    qr_code_attributes = get_attributes_from_filename(qr_code_content)

    # do the checks here
    double_check_message = hazelnut_double_check(
        dest, scan_type_map[scan_type.lower()], qr_code_attributes)
    if double_check_message:
        proceed = click.prompt(f"{double_check_message} [N|y]", default="N")
        if proceed == "y":
            process_image(
                image,
                dest,
                columns,
                rows,
                scan_type_map[scan_type.lower()],
                qr_code_content,
            )
        else:
            do_acquisition(
                image_path,
                dest,
                tmp,
                destdir,
                destsub,
                discard,
                columns,
                rows,
                manual_mode,
            )
    else:
        process_image(
            image,
            dest,
            columns,
            rows,
            scan_type_map[scan_type.lower()],
            qr_code_content,
        )