diff --git a/algorithm/SalamanderImage.py b/algorithm/SalamanderImage.py index f2f9c00c61e502417f9ef47a906ab8e13a001465..517a434b713880f620c4cec238e31d26d368e8de 100644 --- a/algorithm/SalamanderImage.py +++ b/algorithm/SalamanderImage.py @@ -1,6 +1,26 @@ -class SalamanderImage(): - descriptors = [] - filename = '' +from cv2 import cv2 +import numpy as np +import algorithm.dsift as dsift - def __init__(self, filename): - self.filename + +class SalamanderImage: + descriptors = [] + filename = '' + + def __init__(self, filename): + self.filename = filename + self.descriptors = self.calculate_descriptors() + + def calculate_descriptors(self): + """ + Calculates the descriptors of the member image + + Returns: The calculated descriptors + + """ + + image = cv2.imdecode(np.fromfile(self.filename, dtype=np.uint8), cv2.IMREAD_GRAYSCALE) + + if image is None: + raise FileNotFoundError("Cannot find image file " + self.filename) + return dsift.compute_descriptors(image) diff --git a/algorithm/brute_force_matching.py b/algorithm/brute_force_matching.py index c69d20af84a98caf9d5af57f013ec199858bc70c..2c383b9654a8fab57648c5a15fa09db6e45aa410 100644 --- a/algorithm/brute_force_matching.py +++ b/algorithm/brute_force_matching.py @@ -1,13 +1,24 @@ import os from cv2 import cv2 -from algorithm.imageprocessing import create_salamander_image import glob - -min_good_match = 15 -match_dist = 0.75 +from algorithm.SalamanderImage import SalamanderImage def match_single_image(input_salamander, match_salamander): + """ + Compares two SalamanderImages and determines if they are similar enough to be a match + + Args: + input_salamander: SalamanderImage of input salamander + match_salamander: SalamanderImage of salamander from the database + + Returns: + Boolean value indicating if the comparison was a match and the number of hits gotten in the comparison + """ + + min_good_match = 15 + match_dist = 0.75 + match = cv2.BFMatcher().knnMatch(input_salamander.descriptors, match_salamander.descriptors, k=2) goodmatch = [] @@ -20,16 +31,28 @@ def match_single_image(input_salamander, match_salamander): def match_file_structure(input_image: str, match_directory: str): + """ + Loops through a given directory of salamanders represented by folders containing their images, and finds the best + match (if any) based on an input image + + Args: + input_image: SalamanderImage of input salamander + match_directory: Path of directory to looped through + + Returns: + The ID of the best matched salamander or None if no match is found + """ + best_match = -1 match_count = 0 # check if input path is valid: if os.path.isfile(input_image): - input_salamander = create_salamander_image(input_image) + input_salamander = SalamanderImage(input_image) for folder in os.listdir(match_directory): name_list = glob.glob(os.path.join(match_directory, folder, "*_str.*")) for filename in name_list: - res, num_matches = match_single_image(input_salamander, create_salamander_image(filename)) + res, num_matches = match_single_image(input_salamander, SalamanderImage(filename)) if res and num_matches > match_count: match_count = num_matches best_match = int(folder) diff --git a/algorithm/dsift.py b/algorithm/dsift.py index 19ad68f07fdc2b421a6e6338943a9faa8d2a3658..dd782630657209eed335aa590b53f9c7b0d3c73a 100644 --- a/algorithm/dsift.py +++ b/algorithm/dsift.py @@ -7,6 +7,15 @@ sift = cv2.SIFT_create() def compute_descriptors(image): - dense = sift.compute(image,kp) + """ + Computes the descriptors of an incoming image + Args: + image: Image from openCV + + Returns: + Descriptors of image + """ + + dense = sift.compute(image, kp) des = dense[1] return des diff --git a/algorithm/imageprocessing.py b/algorithm/imageprocessing.py deleted file mode 100644 index fdc01ae30d09fd3921aaef1366d51795a33ed600..0000000000000000000000000000000000000000 --- a/algorithm/imageprocessing.py +++ /dev/null @@ -1,50 +0,0 @@ -from cv2 import cv2 -import numpy as np - -from algorithm.SalamanderImage import SalamanderImage - -import algorithm.dsift as dsift -import algorithm.segmentation as segmentation - - -def create_salamander_image(filename: str): - salamander_image = SalamanderImage(filename) - - salamander_image.filename = filename - salamander_image.descriptors = get_descriptors(filename) - - return salamander_image - - -def get_descriptors(filename: str): - image = get_straightened_image(filename) - return calculate_descriptors(image) - - -# Reads, straightens, resizes the image and returns it -def get_straightened_image(filename: str): - straightened_filename = filename # [0:-4] + '_str.jpg' - image = get_image(straightened_filename) - - return image - - -# Should return a binary image (numpy ndarray) with 1 for "Part of salamander" -# and 0 for "Not part of the salamander". -def get_segmented_image(filename: str): - image = get_image(filename) - return segmentation.get_salamander_mask(image) - - -def get_image(filename): - img = cv2.imdecode(np.fromfile(filename, dtype=np.uint8), cv2.IMREAD_GRAYSCALE) - - if img is None: - raise FileNotFoundError("Cannot find image file " + filename) - - return img - - -# Calculates descriptors from preprocessed image -def calculate_descriptors(image): - return dsift.compute_descriptors(image) diff --git a/algorithm/predict_salamander_abdomen.py b/algorithm/predict_salamander_abdomen.py index e54cd8cc92182a39330f1df245ca06f02f439e69..a4b8937e5b1c807ecfd739dd06d278463c8f6283 100644 --- a/algorithm/predict_salamander_abdomen.py +++ b/algorithm/predict_salamander_abdomen.py @@ -21,7 +21,6 @@ def run_prediction_dlc(config: str, image: np.ndarray, shuffle: int = 1, if gputouse is not None: # gpu selection os.environ['CUDA_VISIBLE_DEVICES'] = str(gputouse) - # tf.compat.v1.reset_default_graph() reset_default_graph() # record cwd to return to this directory in the end: @@ -30,7 +29,6 @@ def run_prediction_dlc(config: str, image: np.ndarray, shuffle: int = 1, model_folder = os.path.join(cfg["project_path"], str(auxiliaryfunctions.GetModelFolder(train_fraction, shuffle, cfg))) path_test_config = Path(model_folder) / 'test' / 'pose_cfg.yaml' - # print(path_test_config) try: dlc_cfg = load_config(str(path_test_config)) except FileNotFoundError: @@ -58,24 +56,17 @@ def run_prediction_dlc(config: str, image: np.ndarray, shuffle: int = 1, increasing_indices = np.argsort([int(m.split('-')[1]) for m in snapshots]) snapshots = snapshots[increasing_indices] - # print("Using %s" % snapshots[snapshot_index], "for model", model_folder) - ################################################## # Load and setup CNN part detector ################################################## # Check if data already was generated: dlc_cfg['init_weights'] = os.path.join(model_folder, 'train', snapshots[snapshot_index]) - trainingsiterations = (dlc_cfg['init_weights'].split(os.sep)[-1]).split('-')[-1] # Update batchsize (based on parameters in config.yaml) dlc_cfg['batch_size'] = 1 - # Name for scorer: - # DLCscorer, DLCscorerlegacy = auxiliaryfunctions.GetScorerName(cfg,shuffle,trainFraction,trainingsiterations=trainingsiterations) - # return tensorflow session, input and outbut (whatever those are): sess, inputs, outputs = predict.setup_pose_prediction(dlc_cfg) - # sess, inputs, outputs = predict.setup_pose_prediction(dlc_cfg) # update number of outputs and adjust pandas indices dlc_cfg['num_outputs'] = cfg.get('num_outputs', 1) @@ -86,26 +77,12 @@ def run_prediction_dlc(config: str, image: np.ndarray, shuffle: int = 1, ################################################## # Loading the images ################################################## - # Predicting data: - # print("session: ", sess) - # PredictedData,nframes,nx,ny=get_poses_deeplabcut_model(dlc_cfg, sess, inputs, outputs,image) ny, nx, nc = np.shape(image) nframes = 1 - # print("Frame dimensions: ", nx,ny) PredictedData = np.zeros((nframes, dlc_cfg['num_outputs'] * 3 * len(dlc_cfg['all_joints_names']))) - # change from int: frame = img_as_ubyte(image) pose = predict.getpose(frame, dlc_cfg, sess, inputs, outputs) PredictedData[0, :] = pose.flatten() - - stop = time.time() - # print("PredictedData done:\n") - - # closing the session: - # device = cuda.get_current_device() - # device.reset() - - # tf.Graph.reset_default_graph() return PredictedData[0], nframes, nx, ny diff --git a/algorithm/segmentation.py b/algorithm/segmentation.py deleted file mode 100644 index 3e530cfa6ac1a40542cbd98841932487fcefd097..0000000000000000000000000000000000000000 --- a/algorithm/segmentation.py +++ /dev/null @@ -1,35 +0,0 @@ -from cv2 import cv2 -import numpy as np - - -def get_salamander_mask(image): - ret, threshold = cv2.threshold(image, np.mean(image) - 40, 255, 0) - - i, contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - - largest_area, largest_contour_index = find_biggest_contour(contours, image.size) - - cv2.drawContours(image, contours[largest_contour_index], -1, (0, 0, 255), 3) - - mask = np.zeros(image.shape) - - cv2.fillPoly(mask, pts=[contours[largest_contour_index]], color=(255, 255, 255)) - - return mask - - -def find_biggest_contour(contours, imsize): - largest_area = 0 - largest_contour_index = -1 - - i = 0 - total_contours = len(contours) - while i < total_contours: - area = cv2.contourArea(contours[i]) - if largest_area < area < imsize * 0.9: - largest_area = area - largest_contour_index = i - - i += 1 - - return largest_area, largest_contour_index diff --git a/algorithm/straighten_with_dlc.py b/algorithm/straighten_with_dlc.py index cdf9861e81e22f186e4bc3f8290a70a8b18e4147..1b8692ee22340f503366a618a5870ee79024e0b6 100644 --- a/algorithm/straighten_with_dlc.py +++ b/algorithm/straighten_with_dlc.py @@ -169,11 +169,14 @@ def straighten(image): def generate_map_from_bellycurve(curve, width=STRAIGTHENED_IMAGE_WIDTH): """ Generates a map that can be passed to cv2.remap to extract the belly pattern - """ - # salamander_length = np.linalg.norm(curve[len(curve) - 1] - curve[0]) + Args: + curve: Interpolated curve along the spine + width: Width of the abdominal pattern + + Returns: Map generated from the curve + """ - # salamander_width = salamander_length * STRAIGHTENED_IMAGE_ASPECT_RATIO salamander_width = width + SHOULDER_ESTIMATION_BUFFER gradient = np.gradient(curve, axis=0) @@ -200,10 +203,16 @@ def get_smooth_curve(points, num_points): Takes in an array of 2-D points and returns a new set of points with numpoints elements where the missing points are interpolated using cubic interpolation + + Args: + points: + num_points: + + Returns: Interpolated points + """ # Calculate distance between all the points distance = np.sqrt(np.sum(np.diff(points, axis=0) ** 2, axis=1)) - # print("distance, ", distance.shape) # Accumulate distance to use as parameter accumulated_distance = np.cumsum(distance) @@ -223,7 +232,14 @@ def get_smooth_curve(points, num_points): def halfway_between(point1, point2): """ - halfway_between() calculates a point between two points: + halfway_between() calculates a point between two points + + Args: + point1: + point2: + + Returns: A point between the two provided points + """ vec = [(point2[0] - point1[0]) / 2, (point2[1] - point1[1]) / 2] return [point1[0] + vec[0], point1[1] + vec[1]] diff --git a/api/endpoints/editsalamander.py b/api/endpoints/editsalamander.py index 310e7e8baf14e431966e3802a7b37f76afa2841c..7c292f59218aaf26d7ed8e5ac3e6763446ca644c 100644 --- a/api/endpoints/editsalamander.py +++ b/api/endpoints/editsalamander.py @@ -10,7 +10,8 @@ import glob from shutil import move from path_constants import _ACCESS_DATABASE from image_encoder.image_encoder import * -import cv2 +from cv2 import cv2 +import numpy as np """ Endpoint for editing salamanders. @@ -43,7 +44,10 @@ class EditSalamander(Resource): basename = os.path.basename(path) if basename.__contains__(str(image_id)): - image = cv2.imread(path) + image = cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_UNCHANGED) + if image is None: + raise FileNotFoundError("Cannot find image file " + path) + if not basename.__contains__("str"): # Scale only original to set size height, width, _ = image.shape diff --git a/api/endpoints/findsalamanderinfo.py b/api/endpoints/findsalamanderinfo.py index e18ae1410a145718340024fa9c9ccf4a08adbb84..d8e2af7e9de2c15d183782356c8020304a7787f1 100644 --- a/api/endpoints/findsalamanderinfo.py +++ b/api/endpoints/findsalamanderinfo.py @@ -2,7 +2,7 @@ from flask import request, jsonify from flask_restful import Resource from flask_jwt_extended import get_jwt_identity, jwt_required from algorithm.straighten_with_dlc import straighten -import cv2 +from cv2 import cv2 import os from api import limiter from image_encoder.image_encoder import * diff --git a/api/endpoints/salamander.py b/api/endpoints/salamander.py index 5240d29ca7e7918518a21a7b08d08cfd4d4a49c5..abb739733cea808506aacb0ddf92efb8db49c4f5 100644 --- a/api/endpoints/salamander.py +++ b/api/endpoints/salamander.py @@ -5,7 +5,8 @@ from api import db, limiter from api.models.dbmodels import Location, User, Salamander import os import glob -import cv2 +import numpy as np +from cv2 import cv2 from image_encoder.image_encoder import * from path_constants import _ACCESS_DATABASE @@ -33,8 +34,10 @@ class SalamanderEndpoint(Resource): list_of_paths = glob.glob(os.path.join(path_to_salamander_images, '*.*')) for path in list_of_paths: if not path.__contains__("_str"): + image = cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_UNCHANGED) - image = cv2.imread(path) + if image is None: + raise FileNotFoundError("Cannot find image file " + path) # scaling to set size height, width, _ = image.shape diff --git a/run.py b/run.py index 4a8885568d3f3175ce002776a21e6719f345f628..6e51c1ac74ee3c4a4a97943856f71de3c7874cc9 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,4 @@ from api import app if __name__ == "__main__": - app.run(debug=True, host='0.0.0.0') \ No newline at end of file + app.run(debug=True, host='0.0.0.0')