|
|
|
@ -5,8 +5,6 @@
|
|
|
|
|
|
|
|
|
|
# Import basic libraries
|
|
|
|
|
import argparse as ap
|
|
|
|
|
import sys
|
|
|
|
|
import json
|
|
|
|
|
from os.path import exists
|
|
|
|
|
import hashlib
|
|
|
|
|
import math
|
|
|
|
@ -22,6 +20,7 @@ import trimesh.transformations as tmtra
|
|
|
|
|
# Import custom image filter library
|
|
|
|
|
import filters as flt
|
|
|
|
|
import config_parser as cp
|
|
|
|
|
import log
|
|
|
|
|
|
|
|
|
|
class app:
|
|
|
|
|
'''Main class for the application.
|
|
|
|
@ -41,28 +40,30 @@ class app:
|
|
|
|
|
cp.parse_conf(self.preset_name, self.filters, self.params, self.config_file)
|
|
|
|
|
|
|
|
|
|
elif self.args.filters:
|
|
|
|
|
print("No config file given, using command line arguments")
|
|
|
|
|
i = 0
|
|
|
|
|
filter_index = 0
|
|
|
|
|
log.print_message(
|
|
|
|
|
"No config file given, using command line arguments")
|
|
|
|
|
|
|
|
|
|
# Otherwise expect filters from command line
|
|
|
|
|
for filter_part in self.args.filters:
|
|
|
|
|
# If no '=' char in filter, it is a new filter name
|
|
|
|
|
if filter_part.find('=') == -1:
|
|
|
|
|
self.filters.append(filter_part)
|
|
|
|
|
i += 1
|
|
|
|
|
self.params[i] = {} # create empty dict for params
|
|
|
|
|
filter_index += 1
|
|
|
|
|
# create empty dict for params
|
|
|
|
|
self.params[filter_index] = {}
|
|
|
|
|
# Otherwise it's a parameter for current filter
|
|
|
|
|
else:
|
|
|
|
|
key, value = filter_part.split('=')
|
|
|
|
|
self.params[i][key] = value
|
|
|
|
|
self.params[filter_index][key] = value
|
|
|
|
|
|
|
|
|
|
cp.parse_params(self.params[i])
|
|
|
|
|
cp.parse_params(self.params[filter_index])
|
|
|
|
|
# If database flag is set, save filters to database as a new preset
|
|
|
|
|
if self.args.database:
|
|
|
|
|
cp.save_preset(self.filters, self.params, self.args.database[0])
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
print("No filters given, saving original image")
|
|
|
|
|
log.print_message("No filters given, saving original image")
|
|
|
|
|
|
|
|
|
|
# Set input and output file paths, dpi and mirror flag for easier readability
|
|
|
|
|
self.input_file = self.args.input_file
|
|
|
|
@ -72,9 +73,8 @@ class app:
|
|
|
|
|
|
|
|
|
|
if exists(self.input_file):
|
|
|
|
|
self.run_filtering()
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self.error_exit("Input file " + self.input_file +
|
|
|
|
|
log.error_exit("Input file " + self.input_file +
|
|
|
|
|
" does not exist")
|
|
|
|
|
|
|
|
|
|
if self.args.stl:
|
|
|
|
@ -128,12 +128,12 @@ class app:
|
|
|
|
|
if len(self.args.stl) < 3:
|
|
|
|
|
self.height_line = 2
|
|
|
|
|
self.height_base = 10
|
|
|
|
|
print(
|
|
|
|
|
log.print_message(
|
|
|
|
|
"Warning: Too few arguments, using default values (10mm base, 2mm lines)")
|
|
|
|
|
else:
|
|
|
|
|
self.height_line = float(self.args.stl[1])
|
|
|
|
|
self.height_base = float(self.args.stl[2])
|
|
|
|
|
print("Base height:", self.height_base,
|
|
|
|
|
log.print_message("Base height:", self.height_base,
|
|
|
|
|
"mm, lines depth/height:", self.height_line, "mm")
|
|
|
|
|
|
|
|
|
|
elif self.args.stl[0] == 'c':
|
|
|
|
@ -145,40 +145,31 @@ class app:
|
|
|
|
|
self.height_base = 10
|
|
|
|
|
self.curv_rate_x = 2
|
|
|
|
|
self.curv_rate_y = 6
|
|
|
|
|
print(
|
|
|
|
|
"Warning: Too few arguments, using default values (2mm lines, curvature 0.5 on x, 0.5 on y)")
|
|
|
|
|
log.print_message("Warning: Too few arguments, using default values (2mm lines, curvature 0.5 on x, 0.5 on y)")
|
|
|
|
|
else:
|
|
|
|
|
self.height_line = float(self.args.stl[1])
|
|
|
|
|
self.height_base = float(self.args.stl[2])
|
|
|
|
|
self.curv_rate_x = float(self.args.stl[3])
|
|
|
|
|
self.curv_rate_y = float(self.args.stl[4])
|
|
|
|
|
print("Line height:", self.height_line, "mm, base height: ", self.height_base,
|
|
|
|
|
"mm, x axis curvature: ", self.curv_rate_x, ", y axis curvature:", self.curv_rate_y)
|
|
|
|
|
log.print_message("Line height:", self.height_line, "mm, base height:", self.height_base,
|
|
|
|
|
"mm, x axis curvature:", self.curv_rate_x, ", y axis curvature:", self.curv_rate_y)
|
|
|
|
|
elif self.args.stl[0] == 'm':
|
|
|
|
|
self.mode = "mapped"
|
|
|
|
|
|
|
|
|
|
# TODO: add default values for mapped mode, add finger model?
|
|
|
|
|
if len(self.args.stl) < 2:
|
|
|
|
|
print(
|
|
|
|
|
log.print_message(
|
|
|
|
|
"Warning: Too few arguments, using default values")
|
|
|
|
|
else:
|
|
|
|
|
self.height_line = float(self.args.stl[1])
|
|
|
|
|
self.finger_model = self.args.stl[2]
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self.error_exit("Unrecognized generation mode")
|
|
|
|
|
log.error_exit("Unrecognized generation mode")
|
|
|
|
|
|
|
|
|
|
print("Stl generation in ", self.mode)
|
|
|
|
|
log.print_message("Stl generation in", self.mode, "mode")
|
|
|
|
|
self.run_stl()
|
|
|
|
|
|
|
|
|
|
def error_exit(self, message):
|
|
|
|
|
'''Print given error message and exit the application.
|
|
|
|
|
:param message: error message to be printed
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
print("ERROR:", message, file=sys.stderr)
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
# ------------------------- FILTERING -------------------------#
|
|
|
|
|
|
|
|
|
|
def run_filtering(self):
|
|
|
|
@ -216,7 +207,7 @@ class app:
|
|
|
|
|
'''Mirror image using opencv, should be used if we want a positive model.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
print("Mirroring image", file=sys.stderr)
|
|
|
|
|
log.print_message("Mirroring image")
|
|
|
|
|
self.img = cv.flip(self.img, 1) # 1 for vertical mirror
|
|
|
|
|
|
|
|
|
|
def apply_filters(self):
|
|
|
|
@ -228,7 +219,12 @@ class app:
|
|
|
|
|
for i, filter_name in enumerate(self.filters):
|
|
|
|
|
# Get filter class from filter.py, use the apply method
|
|
|
|
|
filter = getattr(flt, filter_name)
|
|
|
|
|
print("Applying filter:", filter_name, file=sys.stderr)
|
|
|
|
|
log.print_message("Applying filter:", filter_name)
|
|
|
|
|
for param in self.params[i+1]:
|
|
|
|
|
if self.params[i+1][param] is not None:
|
|
|
|
|
log.print_message("\twith parameter", param,
|
|
|
|
|
"=", str(self.params[i+1][param]))
|
|
|
|
|
|
|
|
|
|
filter.apply(self, self.params[i+1])
|
|
|
|
|
else:
|
|
|
|
|
pass
|
|
|
|
@ -237,7 +233,7 @@ class app:
|
|
|
|
|
'''Save processed image to the output file.
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
print("Saving image to", self.output_file, file=sys.stderr)
|
|
|
|
|
log.print_message("Saving image to", self.output_file)
|
|
|
|
|
|
|
|
|
|
# Colormap must be set to grayscale to avoid color mismatch.
|
|
|
|
|
ax.imshow(self.img, cmap="gray")
|
|
|
|
@ -253,7 +249,7 @@ class app:
|
|
|
|
|
# create ID for the model from all its parameters
|
|
|
|
|
self.get_ID()
|
|
|
|
|
|
|
|
|
|
print("Creating mesh", file=sys.stderr)
|
|
|
|
|
log.print_message("Creating mesh")
|
|
|
|
|
|
|
|
|
|
# Create a mesh using one of two modes
|
|
|
|
|
if self.mode == "planar":
|
|
|
|
@ -267,11 +263,11 @@ class app:
|
|
|
|
|
self.make_stl_map()
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self.error_exit("Mode not supported")
|
|
|
|
|
log.error_exit("Incorrect stl generation mode")
|
|
|
|
|
|
|
|
|
|
plt.show()
|
|
|
|
|
self.save_stl()
|
|
|
|
|
print(f"Saving model to ", self.stl_filename, file=sys.stderr)
|
|
|
|
|
log.print_message("Saving model to", self.stl_filename)
|
|
|
|
|
|
|
|
|
|
def prepare_heightmap(self):
|
|
|
|
|
'''Scale image values to get values from 0 to 255.
|
|
|
|
@ -281,27 +277,27 @@ class app:
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
if self.img.dtype != np.uint8:
|
|
|
|
|
print("Converting to uint8", file=sys.stderr)
|
|
|
|
|
log.print_message("Converting to uint8")
|
|
|
|
|
self.img = self.img / np.max(self.img) * 255
|
|
|
|
|
self.img = self.img.astype(np.uint8)
|
|
|
|
|
|
|
|
|
|
if self.mode == "planar":
|
|
|
|
|
if self.height_base <= 0:
|
|
|
|
|
self.error_exit("Depth of plate height must be positive")
|
|
|
|
|
log.error_exit("Depth of plate height must be positive")
|
|
|
|
|
|
|
|
|
|
if self.height_line + self.height_base <= 0:
|
|
|
|
|
self.error_exit("Line depth must be less than plate thickness")
|
|
|
|
|
log.error_exit("Line depth must be less than plate thickness")
|
|
|
|
|
|
|
|
|
|
if self.mode == "curved":
|
|
|
|
|
# Don't need to check curvature, check only heights
|
|
|
|
|
if self.height_base <= 0 or self.height_line <= 0:
|
|
|
|
|
self.error_exit("Base and line height must both be positive")
|
|
|
|
|
log.error_exit("Base and line height must both be positive")
|
|
|
|
|
|
|
|
|
|
if self.mode == "mapped":
|
|
|
|
|
if self.height_line <= 0:
|
|
|
|
|
self.error_exit("Line height must be positive")
|
|
|
|
|
log.error_exit("Line height must be positive")
|
|
|
|
|
if not exists(self.finger_model):
|
|
|
|
|
self.error_exit("Finger model file does not exist")
|
|
|
|
|
log.error_exit("Finger model file does not exist")
|
|
|
|
|
self.height_base = 0
|
|
|
|
|
|
|
|
|
|
# TODO: curved height base could be done here?
|
|
|
|
@ -324,10 +320,10 @@ class app:
|
|
|
|
|
# Truncate if necessary
|
|
|
|
|
if (len(self.param_string) >= 80):
|
|
|
|
|
self.param_string = self.param_string[:80]
|
|
|
|
|
print("Warning: Parameter string too long, truncating", file=sys.stderr)
|
|
|
|
|
log.print_message("Warning: Parameter string too long, truncating")
|
|
|
|
|
|
|
|
|
|
# Overwrite stl header (which is only 80 bytes)
|
|
|
|
|
print("Writing info to stl header", file=sys.stderr)
|
|
|
|
|
log.print_message("Writing info to stl header")
|
|
|
|
|
with open(self.stl_filename, "r+") as f:
|
|
|
|
|
f.write(self.param_string)
|
|
|
|
|
|
|
|
|
@ -681,8 +677,7 @@ class app:
|
|
|
|
|
|
|
|
|
|
# TODO: maybe use trimesh.update_vertices
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print("Mapping to finger")
|
|
|
|
|
log.print_message("Mapping to finger")
|
|
|
|
|
# TODO: try to merge meshes? or stl files?
|
|
|
|
|
# trimesh library?
|
|
|
|
|
finger = trimesh.load(self.finger_model)
|
|
|
|
@ -715,7 +710,6 @@ class app:
|
|
|
|
|
|
|
|
|
|
self.stl_model = trimesh.util.concatenate([finger, fingerprint])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_stl(self):
|
|
|
|
|
'''Save final mesh to stl file.
|
|
|
|
|
'''
|
|
|
|
|