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))
+