diff --git a/classes/droplet/process_automated_droplet.py b/classes/droplet/process_automated_droplet.py new file mode 100644 index 0000000000000000000000000000000000000000..7807f3fbf6df58b7e6b2610488c4911831f1a091 --- /dev/null +++ b/classes/droplet/process_automated_droplet.py @@ -0,0 +1,745 @@ +""" +MRCNN Particle Detection +Process images with MRCNN model trained on the droplet class. + +The source code of "MRCNN Particle Detection" (https://git.rwth-aachen.de/avt-fvt/private/mrcnn-particle-detection) +is based on the source code of "Mask R-CNN" (https://github.com/matterport/Mask_RCNN). + +The source code of "Mask R-CNN" is licensed under the MIT License (MIT). +Copyright (c) 2017 Matterport, Inc. +Written by Waleed Abdulla + +All source code modifications to the source code of "Mask R-CNN" in "MRCNN Particle Detection" +are licensed under the Eclipse Public License v2.0 (EPL 2.0). +Copyright (c) 2022-2023 Fluid Process Engineering (AVT.FVT), RWTH Aachen University +Edited by Stepan Sibirtsev, Mathias Neufang & Jakob Seiler + +The coyprights and license terms are given in LICENSE. + +Ideas and a small code snippets were adapted from these sources: +https://github.com/mat02/Mask_RCNN +""" + +### --------------------------- ### +### Input processing parameters ### +### --------------------------- ### + +# Is the script executed on the cluster? +# E.g., RWTH High Performance Computing cluster? +# True=yes, False=no +cluster=False + +### Please specify only for non-cluster evaluations + +# File format of images +file_format="jpg" +# Input dataset folder located in path: "...\datasets\input\..." +dataset_path="test_input" +# Output images folder located in path: "...\datasets\output\..." +save_path="test_output" +# Name of the excel output file located in path: "...\datasets\output\..." +name_result_file="DSD" +# Folder of the MRCNN model located in: "...\models\..." +# Weights of the individual epochs=MRCNN models +weights_path="weights" +# MRCNN model name located in path: "...\models\<WeightsFolderName>\" +weights_name="weights_name" +# Generate detection masks? +# True=yes, False=no +masks=False +# Use GPU or CPU? +# True=GPU, False=CPU +device=False +# Save n-th result image +save_nth_image=1 +# Pixel size in [µm/px]. +# To read the pixel size value from Sopat log file enter pixelsize=0 +# (Sopat generates a JSON file with including information) +pixelsize=1 +# Do you want the image to be center cropped before detection? +# no=None, yes=(x, y) coordinates (e.g.: image_crop=(1000, 1500)) +image_crop=None + +### Specifications for the processing parameters + +# Number of images used to evaluate with the MRCNN model on each GPU. +# If only one GPU is used, this parameter is equivalent to batch size +# (see BATCH_SIZE parameter in config.py). +# A 12GB GPU can typically handle 2 images of 1024x1024px resolution. +# Adjust this parameter based on your GPU memory and image resolution. +images_gpu=1 +# Image resolution (see IMAGE_MAX_DIM parameter in config.py). +# Should be the same value as for training. +# 0=512, 1=1024, 2=2048 +image_max=1 +# Skip detections with confidence < specified confidence parameter value +confidence=0.7 + +### Specifications for filters + +# Detect and mark reflections in droplets? +# The detected reflections are excluded from the evaluation +# and do not appear in the excel output file. +# Marking color is blue. +# True=yes, False=no +detect_reflections=False +# Detect and mark oval droplets? +# The detected oval droplets are excluded from the evaluation +# and do not appear in the excel output file. +# Marking color is red. +# True=yes, False=no +detect_oval_droplets=False +# Minimum aspect ratio: filter for elliptical shapes +min_aspect_ratio=0.9 +# Detect and mark adhesive droplets? +# The detected adhesive droplets are excluded from the evaluation +# and do not appear in the excel output file. +# Marking color is orange. +# True=yes, False=no +detect_adhesive_droplets=False +# Save coordinates of detected adhesive droplets in excel output file? +# True=yes, False=no +save_coordinates=False +# Minimum velocity: threshold to filter adhesive droplets +# Minimum distance [% of droplet mean diameter] that a droplet has to travel between 2 frames +min_velocity=0.3 +# Minimum size difference: threshold to filter adhesive droplets +# [%] to be considered a different droplet +min_size_diff=0.3 +# Number of images that are being compared. +# This is necessary because adhesive droplets may not get detected every frame. +n_images_compared=3 +# Number of times a droplet has to be detected at a similar position to be defined as adhesive. +n_adhesive_high=3 +n_adhesive_low=2 +low_distance_threshold=0.05 +# Edge threshold: filter for image border intersecting droplets. +# Image border intersecting droplets are marked in color red. +edge_tolerance=0.01 + +# Use contrast adjustment? +# 0=no, 1=contrast limited adaptive histogram equalization, 2=contrast stretching +contrast=0 + +### ----------------------------------- ### +### Initialization ### +### ----------------------------------- ### + +from PIL import Image +import os +import json +import sys +import random +import math +import re +import time +import glob +import itertools +import numpy as np +import tensorflow as tf +import matplotlib +import matplotlib.image as mpimg +import matplotlib.pyplot as plt +import matplotlib.patches as patches +import cv2 +import pandas as pd +from numpy import asarray +from random import random +from skimage import exposure +from pathlib import Path +matplotlib.use("agg") + +start_time = time.time() + +tf.to_float = lambda x: tf.cast(x, tf.float32) +# Root directory of the project +if cluster is False: + ROOT_DIR = os.path.abspath("") + WEIGHTS_DIR = os.path.join(ROOT_DIR, "models", weights_path, weights_name + '.h5') + DATASET_DIR = os.path.join(ROOT_DIR, "datasets\\input", dataset_path) + SAVE_DIR = os.path.join(ROOT_DIR, "datasets\\output", save_path) + EXCEL_DIR = os.path.join(SAVE_DIR, name_result_file + '.xlsx') + IMAGE_MAX = image_max + MASKS = masks + DEVICE = device + IMAGES_GPU = images_gpu + SAVE_NTH_IMAGE = save_nth_image + DETECT_OVAL_DROPLETS = detect_oval_droplets + DETECT_REFLECTIONS = detect_reflections + MIN_ASPECT_RATIO = min_aspect_ratio + PIXELSIZE = pixelsize + DETECT_ADHESIVE_DROPLETS = detect_adhesive_droplets + SAVE_COORDINATES = save_coordinates + MIN_VELOCITY = min_velocity + MIN_SIZE_DIFF = min_size_diff + N_IMAGES_COMPARED = n_images_compared + N_ADHESIVE_HIGH = n_adhesive_high + N_ADHESIVE_LOW = n_adhesive_low + LOW_DISTANCE_THRESHOLD = low_distance_threshold + EDGE_TOLERANCE = edge_tolerance + IMAGE_CROP = image_crop + CONTRAST = contrast + CONFIDENCE = confidence + FILE_FORMAT = file_format +else: + import argparse + # Parse command line arguments + parser = argparse.ArgumentParser( + description='evaluation on cluster') + parser.add_argument('--dataset_path', required=True, + help='Dataset path to find in Mask_R_CNN\datasets\input') + parser.add_argument('--save_path', required=False, default='test_output', + help='Save path to find in Mask_R_CNN\datasets\output') + parser.add_argument('--name_result_file', required=False, default='DSD', + help='Name of the excel result file to find in Mask_R_CNN\datasets\output') + parser.add_argument('--weights_path', required=True, + help='Weights path to find in Mask_R_CNN\models') + parser.add_argument('--weights_name', required=True, + help='Choose Neuronal Network / Epoch to find in Mask_R_CNN\models') + parser.add_argument('--file_format', required=True, + help='') + parser.add_argument('--masks', required=False, type=str, + default="False", + help='Generate detection masks?') + parser.add_argument('--device', required=True, type=str, + help='is the evaluation done on CPU or GPU? 1=GPU, 0=CPU') + parser.add_argument('--detect_oval_droplets', required=False, type=str, + default="False", + help="") + parser.add_argument('--detect_reflections', required=False, type=str, + default="False", + help="") + parser.add_argument('--detect_adhesive_droplets', required=False, type=str, + default="False", + help="") + parser.add_argument('--save_coordinates', required=False, type=str, + default="False", + help="") + parser.add_argument('--images_gpu', required=False, type=int, + default=1, + help='Number of images to train with on each GPU') + parser.add_argument('--image_max', required=True, type=int, + help="max. image size") + parser.add_argument('--save_nth_image', required=False, type=int, + default=1, + help="") + parser.add_argument('--n_images_compared', required=False, type=int, + default=3, + help="") + parser.add_argument('--n_adhesive_high', required=False, type=int, + default=3, + help="") + parser.add_argument('--n_adhesive_low', required=False, type=int, + default=2, + help="") + parser.add_argument('--image_crop', required=False, type=int, + default=None, + help="") + parser.add_argument('--contrast', required=False, type=int, + default=0, + help="") + parser.add_argument('--min_aspect_ratio', required=False, type=float, + default=0.9, + help="") + parser.add_argument('--pixelsize', required=True, type=float, + help="") + parser.add_argument('--min_velocity', required=False, type=float, + default=0.3, + help="") + parser.add_argument('--min_size_diff', required=False, type=float, + default=0.3, + help="") + parser.add_argument('--low_distance_threshold', required=False, type=float, + default=0.05, + help="") + parser.add_argument('--edge_tolerance', required=False, type=float, + default=0.01, + help="") + parser.add_argument('--confidence', required=False, type=float, + default=0.5, + help="") + + args = parser.parse_args() + ROOT_DIR = os.path.join("/rwthfs/rz/cluster", os.path.abspath("../..")) + WEIGHTS_DIR = os.path.join(ROOT_DIR, "models", args.weights_path, args.weights_name + '.h5') + DATASET_DIR = os.path.join(ROOT_DIR, "datasets/input", args.dataset_path) + SAVE_DIR = os.path.join(ROOT_DIR, "datasets/output", args.save_path) + EXCEL_DIR = os.path.join(SAVE_DIR, args.name_result_file + '.xlsx') + FILE_FORMAT = args.file_format + if args.detect_oval_droplets == "True": + DETECT_OVAL_DROPLETS = True + elif args.detect_oval_droplets == "False": + DETECT_OVAL_DROPLETS = False + if args.detect_reflections == "True": + DETECT_REFLECTIONS = True + elif args.detect_reflections == "False": + DETECT_REFLECTIONS = False + if args.masks == "True": + MASKS = True + elif args.masks == "False": + MASKS = False + if args.device == "True": + DEVICE = True + elif args.device == "False": + DEVICE = False + if args.detect_adhesive_droplets == "True": + DETECT_ADHESIVE_DROPLETS = True + elif args.detect_adhesive_droplets == "False": + DETECT_ADHESIVE_DROPLETS = False + if args.save_coordinates == "True": + SAVE_COORDINATES = True + elif args.save_coordinates == "False": + SAVE_COORDINATES = False + + # + IMAGE_MAX = args.image_max + IMAGES_GPU = args.images_gpu + SAVE_NTH_IMAGE = args.save_nth_image + N_IMAGES_COMPARED = args.n_images_compared + N_ADHESIVE_HIGH = args.n_adhesive_high + N_ADHESIVE_LOW = args.n_adhesive_low + IMAGE_CROP = args.image_crop + CONTRAST = args.contrast + # + MIN_ASPECT_RATIO = args.min_aspect_ratio + PIXELSIZE = args.pixelsize + MIN_VELOCITY = args.min_velocity + MIN_SIZE_DIFF = args.min_size_diff + LOW_DISTANCE_THRESHOLD = args.low_distance_threshold + EDGE_TOLERANCE = args.edge_tolerance + CONFIDENCE = args.confidence + +# Directory to save logs and trained model +MODEL_DIR = os.path.join(ROOT_DIR, "models") +Path(SAVE_DIR).mkdir(parents=True, exist_ok=True) + +### mean pixel detectieren +images_mean_pixel = [] +images_path = glob.glob(os.path.join(DATASET_DIR + "/*." + FILE_FORMAT)) +for img_path in images_path: + img = cv2.imread(img_path) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + if CONTRAST == 1: + # adaptive Equalization + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + img = exposure.equalize_adapthist(img) + img = img.astype('float32') * 255 + images_mean_pixel.append(img) +color_sum=[0,0,0] +for img2 in images_mean_pixel: + pixels = asarray(img2) + pixels = pixels.astype('float32') + # calculate per-channel means and standard deviations + means = pixels.mean(axis=(0, 1), dtype='float64') + color_sum += means +mean_pixel = color_sum/len(images_mean_pixel) + +# read pixelsize from JSON-File (if input data is from a Sopat measurement) +if PIXELSIZE == 0: + sopat_find = [file for file in os.listdir(DATASET_DIR) if file.endswith('.json')] + sopat_name = (DATASET_DIR + '/' + sopat_find[0]) + sopat_name_new = (DATASET_DIR + '/Sopat_Log.json') + + with open(sopat_name, "r",encoding="utf-8") as sopat_content: + content_lines = sopat_content.readlines() + + current_line = 1 + with open(sopat_name_new, "w",encoding="utf-8") as sopat_content_new: + for line in content_lines: + if current_line == 30: + pass + else: + sopat_content_new.write(line) + current_line += 1 + sopat_data = json.load(open(sopat_name_new, "r", encoding="utf-8")) + PIXELSIZE = sopat_data["sopatCamControlAcquisitionLog"]["conversionMicronsPerPx"] + +# Import Mask RCNN +sys.path.append(ROOT_DIR) # To find local version of the library +from mrcnn import utils +from mrcnn import visualize +from mrcnn.visualize import display_images +import mrcnn.model as modellib +from mrcnn.model import log +from mrcnn.config import Config + +class DropletConfig(Config): + """Configuration for training on the toy dataset. + Derives from the base Config class and overrides some values. + """ + # Give the configuration a recognizable name + NAME = "droplet" + + # NUMBER OF GPUs to use. When using only a CPU, this needs to be set to 1. + GPU_COUNT = 1 + + # Backbone network architecture + # Supported values are: resnet50, resnet101. + # You can also provide a callable that should have the signature + # of model.resnet_graph. If you do so, you need to supply a callable + # to COMPUTE_BACKBONE_SHAPE as well + BACKBONE = "resnet50" + + # Generate detection masks + # False: Output only bounding boxes like in Faster-RCNN + # True: Generate masks as in Mask-RCNN + if MASKS is True: + GENERATE_MASKS = True + else: + GENERATE_MASKS = False + + # We use a GPU with 12GB memory, which can fit two images. + # Adjust down if you use a smaller GPU. + if DEVICE is True: + IMAGES_PER_GPU = IMAGES_GPU + else: + IMAGES_PER_GPU = 1 + + # Number of classes (including background) + NUM_CLASSES = 1 + 1 # Background + droplet + + # Skip detections with confidence < value + DETECTION_MIN_CONFIDENCE = CONFIDENCE + + # Input image resizing + if IMAGE_MAX == 0: + IMAGE_MAX_DIM = 512 + elif IMAGE_MAX == 1: + IMAGE_MAX_DIM = 1024 + elif IMAGE_MAX == 2: + IMAGE_MAX_DIM = 2048 + + IMAGE_MIN_DIM = IMAGE_MAX_DIM + + MEAN_PIXEL = mean_pixel + +### Configurations +config = DropletConfig() +config.display() + +### Notebook Preferences + +# Device to load the neural network on. +# Useful if you're training a model on the same +# machine, in which case use CPU and leave the +# GPU for training. +if DEVICE is True: + dev = "/gpu:0" # /cpu:0 or /gpu:0 +else: + dev = "/cpu:0" # /cpu:0 or /gpu:0 + +# Inspect the model in training or inference modes +# values: 'inference' or 'training' +# TODO: code for 'training' test mode not ready yet +TEST_MODE = "inference" + +def get_ax(rows=1, cols=1, size=8): + """Return a Matplotlib Axes array to be used in + all visualizations in the notebook. Provide a + central point to control graph sizes. + + Adjust the size attribute to control how big to render images + """ + _, ax = plt.subplots(rows, cols, figsize=(size*cols, size*rows)) + return ax + +### Load Model +# Create model in inference mode + +with tf.device(dev): + model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config) + +# Load weights + print("Loading weights ", WEIGHTS_DIR) + model.load_weights(WEIGHTS_DIR, by_name=True) + +### Run Detection +class Droplet: + """Class to structure Droplet information in memory and calculate values for comparison + """ + + def __init__(self, roi, mask, img): + self.roi = roi + if config.GENERATE_MASKS: + self.mask = mask + # calculate edge lengths and center of roi. roi[1]-roi[3]=box_width. roi[0]-roi[2]=image_height + self.range = (abs(roi[1] - roi[3]), abs(roi[0] - roi[2])) + self.center = (abs((roi[0]+roi[2])//2), abs((roi[1]+roi[3])//2)) + # self.mean_diameter = abs((self.range[0]+self.range[1]+2)/2) + self.mean_diameter = ((self.range[1]+1)*(self.range[0]+1)**2)**(1/3) + self.mean_diameter_mm = round(self.mean_diameter * PIXELSIZE / 1000, 3) + self.stuck = [] + self.check_roi() + self.img = img + + def check_roi(self): + """Run at Droplet creation to check for aspect ratio and whether it touches the edge + """ + global DETECT_OVAL_DROPLETS, EDGE_TOLERANCE, MIN_ASPECT_RATIO, image_height, image_width + if DETECT_OVAL_DROPLETS is True: + if( + # checks if bbox touches the edge + self.roi[0] <= image_height*EDGE_TOLERANCE or self.roi[1] <= image_width*EDGE_TOLERANCE or + self.roi[2] >= image_height*(1-EDGE_TOLERANCE) or self.roi[3] >= image_width*(1-EDGE_TOLERANCE) + ): + self.fault = 1 + else: + self.fault = 0 + else: + if( + # checks if bbox touches the edge + self.roi[0] <= image_height*EDGE_TOLERANCE or self.roi[1] <= image_width*EDGE_TOLERANCE or + self.roi[2] >= image_height*(1-EDGE_TOLERANCE) or self.roi[3] >= image_width*(1-EDGE_TOLERANCE) or + # checks if bbox is within allowed aspect ratio + self.range[0] / self.range[1] >= 1 / MIN_ASPECT_RATIO or + self.range[0] / self.range[1] <= MIN_ASPECT_RATIO + ): + self.fault = 1 + else: + self.fault = 0 + # Parameter um klebende Tropfen zu erkennen + def distance(self, center): + """Returns distance to given coordinates + """ + offset = np.array([center[0]-self.center[0], center[1]-self.center[1]]) + dist = np.linalg.norm(offset) + dist /= self.mean_diameter + return dist + + def size_difference(self, range): + """Returns size difference in percent compared to given ranges + """ + size_diff = abs( + 1-((self.range[0]*self.range[1]) / (range[0]*range[1]))) + return size_diff + +def visualize_result(memory,counter_1): + """collects all necessary parameters from the first entry in memory and then calls visualize.display_instances() + Also appends droplet diameter to mean_diameter_total if no fault was determined + """ + # Create Lists to pass onto display_instances() + global stuck_droplet_data + ax = get_ax(size=8) + rois, masks, colors, diameter_vis_list = [], [], [], [] + xl = False + if memory[0][0]: + print(f"Visualizing {memory[0][0][0].img}") + for droplet in memory[0][0]: + rois.append(droplet.roi) + diameter_vis_list.append(droplet.mean_diameter_mm) + if config.GENERATE_MASKS: + masks.append(droplet.mask) + if droplet.fault == 0: + mean_diameter_total.append(droplet.mean_diameter) + colors.append((0, 1, 0)) + elif droplet.fault == 1: + colors.append((1, 0, 0)) + elif droplet.fault == 4: + colors.append((0, 0, 1)) + elif DETECT_ADHESIVE_DROPLETS is False: + mean_diameter_total.append(droplet.mean_diameter) + colors.append((0, 1, 0)) + elif droplet.fault == 2: + if len(droplet.stuck) >= N_ADHESIVE_HIGH: + colors.append((1, 0.65, 0)) + if not xl: + stuck_droplet_data.append( + [droplet.img, droplet.mean_diameter_mm, "", droplet.stuck[0][0], droplet.stuck[0][1], + droplet.stuck[0][2], droplet.stuck[0][3], droplet.stuck[0][4]]) + xl = True + else: + stuck_droplet_data.append( + ["", droplet.mean_diameter_mm, "", droplet.stuck[0][0], droplet.stuck[0][1], + droplet.stuck[0][2], droplet.stuck[0][3], droplet.stuck[0][4]]) + + for data in droplet.stuck[1:]: + stuck_droplet_data.append( + ["", "", "", data[0], data[1], data[2], data[3], data[4]]) + else: + mean_diameter_total.append(droplet.mean_diameter) + colors.append((0, 1, 0)) + elif droplet.fault == 3: + if len(droplet.stuck) >= N_ADHESIVE_LOW: + colors.append((1, 0.65, 0)) + if not xl: + stuck_droplet_data.append( + [droplet.img, droplet.mean_diameter_mm, "<5%", droplet.stuck[0][0], droplet.stuck[0][1], + droplet.stuck[0][2], droplet.stuck[0][3], droplet.stuck[0][4]]) + xl = True + else: + stuck_droplet_data.append( + ["", droplet.mean_diameter_mm, "<5%", droplet.stuck[0][0], droplet.stuck[0][1], + droplet.stuck[0][2], droplet.stuck[0][3], droplet.stuck[0][4]]) + for data in droplet.stuck[1:]: + stuck_droplet_data.append( + ["", "", "<5%", data[0], data[1], data[2], data[3], data[4]]) + else: + mean_diameter_total.append(droplet.mean_diameter) + colors.append((0, 1, 0)) + + + # Convert Lists to numpy arrays and create placeholders for class_ids and scores + rois = np.array(rois) + masks = np.array(masks) + class_ids = np.array(range(len(colors))) + scores = np.array([1]*len(colors)) + img_name = "result_{}.jpg".format(os.path.splitext(memory[0][2])[0]) + if masks.any(): + masks = np.stack(masks, axis=-1) + if config.GENERATE_MASKS: + visualize.display_instances(memory[0][1], rois, masks, class_ids, scores, ax=ax, colors=colors, captions=diameter_vis_list, + title=None, save_dir=SAVE_DIR, img_name=img_name, save_img=True, number_saved_images=SAVE_NTH_IMAGE, counter_1=counter_1) + else: + visualize.display_instances(memory[0][1], rois, None, class_ids, scores, ax=ax, show_mask=False, colors=colors, captions=diameter_vis_list, + title=None, save_dir=SAVE_DIR, img_name=img_name, save_img=True, number_saved_images=SAVE_NTH_IMAGE, counter_1=counter_1) + + +def pre_processing(image, crop=None, size_y=1024, size_x=1024, contrast=0): + """Crops image and optional contrast adjustments + """ + if crop: + size_x = crop[0] + size_y = crop[1] + image_height, image_width, _ = image.shape + # center crop image to given size + crop_y = (image_height-size_y)//2 + crop_x = (image_width-size_x)//2 + image = image[crop_y:crop_y+size_y, crop_x:crop_x+size_x] + #im = Image.fromarray(image.astype(np.uint8)) + #im.save(os.path.join(SAVE_DIR, 'test.bmp')) + # original = image.copy() + + if CONTRAST != 0: + image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + if CONTRAST == 1: + # adaptive Equalization + image = exposure.equalize_adapthist(image) + image = image.astype('float32') * 255 + elif CONTRAST == 2: + # contrast stretching + p2, p98 = np.percentile(image, (2, 98)) + image = exposure.rescale_intensity(image, in_range=(p2, p98)) + + return image + +# Tropfendetektion & Erkennung von klebenden Tropfen +memory = [] +mean_diameter_total = [] +measurements = [] +stuck_droplet_data = [] + +counter_1 = SAVE_NTH_IMAGE +filenames = os.listdir(DATASET_DIR) +filenames.sort() +for filename in filenames: + if not filename.endswith('.json'): + image = cv2.imread(os.path.join(DATASET_DIR, filename)) + image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + # pre process image + if IMAGE_CROP or CONTRAST != 0: + image = pre_processing(image, crop=IMAGE_CROP, contrast=CONTRAST) + image_height, image_width, _ = image.shape + image_length_max = max([image_width, image_height]) + results = model.detect([image], filename=filename, verbose=1) + r = results[0] + # Creates a List of Droplet Objects with detected information + # Droplet object checks if droplet is too close to edge and whether aspec ratio is off on initialization + droplets = [] + if config.GENERATE_MASKS: + for i, roi in enumerate(r['rois']): + droplets.append(Droplet(roi, r['masks'][:, :, i], filename)) + else: + for roi in r['rois']: + droplets.append(Droplet(roi, None, filename)) + print(f"Images in memory: {len(memory)}") + + if DETECT_REFLECTIONS == True: + for a, b in itertools.combinations(droplets, 2): + if( + a.center[0] > b.center[0]*0.95 and a.center[0] < b.center[0]*1.05 and + a.center[1] > b.center[1]*0.95 and a.center[1] < b.center[1]*1.05 + ): + if a.mean_diameter_mm < b.mean_diameter_mm: + a.fault = 4 + else: + b.fault = 4 + # If memory is not empty iterate through droplets in current picture + if memory: + for current in droplets: + # If droplet has no faults so far compare to all droplets in memory + if current.fault == 0: + shortest_distance = 2048 + closest_size_diff = 0 + # iterate memory from the back (most recent picture first) + # t is a variable for time between reference image and current memory entry + for t, img in enumerate(reversed(memory), 1): + for droplet in img[0]: + measured_distance = current.distance(droplet.center) + measured_size_diff = current.size_difference(droplet.range) + measurements.append([measured_distance, measured_distance/t, measured_size_diff]) + # Checks whether measured distance and size diff is within defined threshholds + if(measured_distance < MIN_VELOCITY * t and + measured_size_diff < MIN_SIZE_DIFF + ): + shortest_distance = measured_distance / t + closest_size_diff = measured_size_diff + current.stuck.append(((-1*t), droplet.mean_diameter_mm, measured_distance, measured_distance/t, measured_size_diff)) + droplet.stuck.append((t, current.mean_diameter_mm, measured_distance, measured_distance/t, measured_size_diff)) + if measured_distance < LOW_DISTANCE_THRESHOLD: + current.fault = 3 + else: + current.fault = 2 + if droplet.fault == 2 or droplet.fault == 0: + droplet.fault = current.fault + break + # Keeps track of shortest distances to help adjust threshholds + elif measured_distance < shortest_distance: + shortest_distance = measured_distance / t + closest_size_diff = measured_size_diff + if current.fault == 0: + print("Droplet is valid:") + print(f"\tshortest distance to any box:\t{round(shortest_distance,2)}") + print(f"\tsize difference to closest box:\t{round(closest_size_diff*100,2)}%") + else: + print("Droplet is stuck to Lens:") + print(f"\tdistance too close to box:\t{round(shortest_distance,2)}") + print(f"\tsize difference to that box:\t{round(closest_size_diff*100,2)}%") + else: + print( + "Droplet is either to close to the edge or aspect ratio is off") + # Depending on the number of images campared, visualizes the one furthest back and removes it from the list + if(len(memory) == N_IMAGES_COMPARED): + visualize_result(memory,counter_1) + memory.pop(0) + # Append List of current droplets and image information to the memory + memory.append((droplets, image, filename)) + print("\n") + if counter_1 == SAVE_NTH_IMAGE: + counter_1 = 0 + counter_1 = counter_1 + 1 + +# Visualizes the remaining pictures +while memory: + visualize_result(memory,counter_1) + memory.pop(0) + if counter_1 == SAVE_NTH_IMAGE: + counter_1 = 0 + counter_1 = counter_1 + 1 +### Translate Mean Diameter In Actual Droplet Sizes (mm) + +# recalculate mean diameter +mean_diameter_total_resize = [(i * PIXELSIZE / 1000) + for i in mean_diameter_total] + +### Convert Mean Diameter To Excel +df = pd.DataFrame(mean_diameter_total_resize).to_excel( + EXCEL_DIR, header=False, index=False) +### Save measured data for threshold tuning +if SAVE_COORDINATES is True: + pd.DataFrame(measurements).to_excel( + os.path.join(SAVE_DIR, "xyz_measurements.xlsx"), header=False, index=False) + pd.DataFrame(stuck_droplet_data).to_excel( + os.path.join(SAVE_DIR, "stuck_droplet_data.xlsx"), header=False, index=False) + +print("--- %s seconds ---" % (time.time() - start_time)) +