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
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)
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"
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)
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)
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, )