Reworked logging, split it to separate file, refactored and cleaned main and filters files code, fixed test.sh overwriting originals

master
Rostislav Lán 2 years ago
parent 71d23f9cdb
commit 13fc747d63

@ -64,7 +64,7 @@ Once all the requirements are installed, the program is ready to use. There are
1. manually list filter names and parameters from command line
```sh
python3 src/main.py res/examples/Palec_P4.tif res/examples/Palec_P4_from_shell.png 600 total_variation weight=0.15 median ksize=5
python3 src/main.py res/examples/Palec_P4.tif res/examples/Palec_P4_from_cline.png 600 total_variation weight=0.15 median ksize=5
```
2. from preset saved in a json config file, that can be used to tune and modify existing presetrs, or create new ones
@ -75,6 +75,8 @@ Once all the requirements are installed, the program is ready to use. There are
# Configuration and presets
There is an option to input the filter series as a preset from json configuration file.
This preset is automatically stored inside a json file, which serves as a database for storing filters.
This prevents losing filter preset information when modifying filter which was used to generate 3D models.
<table style="width:100%;">
<thead>
@ -127,7 +129,17 @@ There is an option to input the filter series as a preset from json configuratio
</tbody>
</table>
There is also an option to save current command line setting as a preset using -d switch:
* General command for saving filter preset
```sh
python3 src/main.py input_file output_file dpi -d new_preset_name filters
```
* Working example
```sh
python3 src/main.py res/examples/Palec_P4.tif res/examples/Palec_P4_from_cline.png 600 -d preset_gaussian gaussian sigma=1
```
All the filters used and their parameters are described below.

@ -6,7 +6,7 @@
from os.path import exists
import json
import hashlib
import log
def save_preset(filters, params, preset_name):
'''Save filter preset to database.
@ -46,7 +46,7 @@ def store_to_db(preset, preset_name):
# If database doesn't exist, create it
if not exists("db.json"):
print("Storing preset to database")
log.print_message("Storing preset to database")
with open("db.json", 'w') as db:
json.dump(preset, db)
else:
@ -61,9 +61,9 @@ def store_to_db(preset, preset_name):
# If preset already exists, skip it
if preset_name in db_presets:
print("Preset already exists in database, skipping")
log.print_message("Preset already exists in database, skipping")
else:
print("Storing preset to database")
log.print_message("Storing preset to database")
db_presets.update(preset)
# Finally write the updated entries to db file
@ -91,10 +91,10 @@ def parse_conf(preset_name, filters, params, config_file):
if attribute != "name":
params[i][attribute] = value
parse_params(params[i])
print("Loaded preset: " + preset_name +
" from file: " + config_file)
log.print_message("Loaded preset:", preset_name,
"from file:", config_file)
else:
print("Preset not found")
log.print_message("Preset not found")
def parse_params(params):

@ -41,7 +41,6 @@ class gaussian(filter):
# Standard deviation for Gaussian kernel
sigma = float(params["sigma"]) if params["sigma"] else 1
print("with params: sigma: " + str(sigma))
self.img = skiflt.gaussian(
self.img, sigma=sigma, preserve_range=True)
@ -59,7 +58,6 @@ class median(filter):
# Used kernel is disk of size ksize
ksize = int(params["ksize"]) if params["ksize"] else 3
print("with params: ksize: " + str(ksize))
self.img = skiflt.median(self.img, footprint=skimorph.disk(ksize))
@ -84,8 +82,6 @@ class bilateral(filter):
# A larger value results in averaging of pixels with larger spatial differences
sigmaSpace = int(params["sigmaSpace"]) if params["sigmaSpace"] else 75
print("with params: diameter: " + str(diameter) + " sigmaColor: " +
str(sigmaColor) + " sigmaSpace: " + str(sigmaSpace))
self.img = np.uint8(self.img)
self.img = cv.bilateralFilter(
self.img, diameter, sigmaColor, sigmaSpace)
@ -115,8 +111,6 @@ class bilateral_scikit(filter):
sigmaSpace = float(params["sigmaSpace"]
) if params["sigmaSpace"] else 9.0
print("with params: sigma_color: " + str(sigmaColor) +
" sigma_spatial: " + str(sigmaSpace))
self.img = skirest.denoise_bilateral(
self.img, sigma_color=sigmaColor, sigma_spatial=sigmaSpace)
self.img = np.uint8(self.img * 255.0) # converting back to uint8
@ -144,8 +138,6 @@ class nlmeans(filter):
# Cut-off distance, higher means more smoothed image
h = float(params["h"])*sigma if params["h"] else 0.1*sigma
print("with params: patch_size: " + str(patch_size) + " patch_distance: " +
str(patch_distance) + " h: " + str(round(h, 4)))
self.img = skirest.denoise_nl_means(
self.img, patch_size=patch_size, fast_mode=True, patch_distance=patch_distance, h=h)
self.img = np.uint8(self.img * 255.0) # converting back to uint8
@ -166,7 +158,6 @@ class total_variation(filter):
# Denoising weight. Larger values result in more denoising.
weight = float(params["weight"]) if params["weight"] else 0.1
print("with params: weight: " + str(weight))
self.img = skirest.denoise_tv_chambolle(
self.img, weight=weight)
self.img = np.uint8(self.img * 255.0) # converting back to uint8
@ -184,7 +175,6 @@ class block_match(filter):
def apply(self, params):
sigma = float(params["sigma"]) if params["sigma"] else 20
print("with params: sigma: " + str(sigma))
self.img = bm3d.bm3d(self.img, sigma_psd=sigma,
stage_arg=bm3d.BM3DStages.ALL_STAGES)
@ -207,8 +197,6 @@ class unsharp_mask_scikit(filter):
# strength of the unsharp mask
amount = float(params["amount"]) if params["amount"] else 1.0
print("with params: radius: " +
str(radius) + " amount: " + str(amount))
self.img = skiflt.unsharp_mask(self.img, radius=radius,
amount=amount, channel_axis=None)
self.img = np.uint8(self.img * 255.0) # converting back to uint
@ -315,8 +303,6 @@ class binarize(filter):
maxval = int(params["maxval"]) if params["maxval"] else 255
type = int(params["type"]) if params["type"] else 0
print("with params: threshold: " + str(threshold) +
" maxval: " + str(maxval) + " type: " + str(type))
self.img = cv.threshold(self.img, threshold, maxval, type)[1]
@ -338,8 +324,6 @@ class add_margin(filter):
margin = int(params["margin"]) if params["margin"] else 10
color = int(params["color"]) if params["color"] else 255
print("with params: margin: " + str(margin) + " color: " + str(color))
self.fig.set_size_inches(
((self.width + 2 * margin) / self.dpi, (self.height + 2 * margin) / self.dpi))
self.img = cv.copyMakeBorder(
@ -365,7 +349,6 @@ class convolve(filter):
kernel = np.array(params["kernel"]) if params["kernel"] else np.ones(
(3, 3), np.float32) / 9
print("with params: kernel: \n" + str(kernel))
self.img = cv.filter2D(self.img, -1, kernel)
@ -380,7 +363,6 @@ class blur(filter):
def apply(self, params):
ksize = int(params["ksize"]) if params["ksize"] else 3
print("with params: ksize: " + str(ksize))
self.img = cv.blur(self.img, ksize=(ksize, ksize))
@ -395,8 +377,6 @@ class denoise(filter):
sWS = int(params["searchWindowSize"]
) if params["searchWindowSize"] else 21
# print("with params: h: " + str(h) +
# " tWS: " + str(tWS) + " sWS: " + str(sWS))
self.img = np.uint8(self.img)
self.img = cv.fastNlMeansDenoising(
self.img, h, tWS, sWS)
@ -413,7 +393,6 @@ class sharpen(filter):
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array(
[[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
# print("with params: kernel: \n" + str(kernel))
self.img = cv.filter2D(self.img, ddepth=-1, kernel=kernel)
@ -434,8 +413,6 @@ class unsharp_mask(filter):
blurred = cv.medianBlur(self.img, ksize)
lap = cv.Laplacian(blurred, cv.CV_32F)
# print("with params: strength: " +
# str(strength) + " ksize: " + str(ksize))
self.img = blurred - strength*lap
@ -458,8 +435,6 @@ class unsharp_mask_pil(filter):
# Threshold controls the minimum brightness change that will be sharpened
threshold = int(params["threshold"]) if params["threshold"] else 3
# print("with params: radius: " +
# str(radius) + " percent: " + str(percent) + " threshold: " + str(threshold))
self.img = np.uint8(self.img)
tmp = Image.fromarray(self.img)
tmp = tmp.filter(ImageFilter.UnsharpMask(radius, percent, threshold))
@ -481,7 +456,6 @@ class erode(filter):
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
cv.MORPH_ELLIPSE, (3, 3))
print("with params: kernel: \n" + str(kernel))
self.img = cv.morphologyEx(
np.uint8(self.img), op=cv.MORPH_ERODE, kernel=kernel)
@ -501,7 +475,6 @@ class dilate(filter):
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
cv.MORPH_ELLIPSE, (3, 3))
print("with params: kernel: \n" + str(kernel))
self.img = cv.morphologyEx(
np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel)

@ -0,0 +1,22 @@
"""! @file log.py
@brief File with printing functions
@author xlanro00
"""
import sys
def print_message(*args, **kwargs):
'''Print given message to stderr.
:param message: message to be printed
'''
print("APP:", *args, file=sys.stderr, **kwargs)
def error_exit(error_message):
'''Print given error message and exit the application.
:param message: error message to be printed
'''
print("ERROR:" + error_message, file=sys.stderr)
exit(1)

@ -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.
'''

@ -9,15 +9,15 @@ source .venv/bin/activate
#----------------------------Configuration------------------------------#
# place all image files to one folder
input_path=res/test/test-skript
input_path=res/test/Jenetrics/leva
# !!!!!!!!!!!!!!!!!!
# this is very important, run this on directories containing files with the same dpi
dpi=500
dpi=600
# !!!!!!!!!!!!!!!!!!
# recommend png, it's supported by opencv
format=jpg
format=png
# name of configuration file
config_file=conf/conf.json
@ -28,16 +28,18 @@ presets=("ridge")
# generate stl files and set generation mode {"planar", "curved", "mapped"}
generate_stl=true
generate_stl_mode="planar"
generate_stl_mode="curved"
# in 1/10 of milimeters
height_line=2
height_base=3
curv_x=1.5
curv_y=4
height_line=2.5
height_base=6
curv_x=1.3
curv_y=3
#----------------------------Application---------------------------#
# TODO: check if file will overwrite the original, print warning if that's the case
# function to apply filter to all files in directory
apply_filter() {
for in in ${file_arr[@]}
@ -46,14 +48,19 @@ apply_filter() {
[[ -f "$in" ]] || continue
# skip stl files and files with preset name in them
[[ "$in" == *_* ]] && continue
[[ "$in" == *"ridge"* ]] && continue
[[ "$in" == *".stl" ]] && continue
((i++))
echo -e "|\n|----------------------- File no. $i: $in ------------------------------|\n"
echo -e "\n|----------------------- File no. $i: $in ------------------------------|\n"
if $generate_stl; then
out="${in%%${match1}*}${match1}$format"
if [[ "$in" == "$out" ]]; then
echo "SCRIPT: Changing filename to avoid overwrite"
out="${in%%${match1}*}_mod${match1}$format"
fi
case "$generate_stl_mode" in
"planar")
python3 $exec_path $in $out $dpi -c $config_file $1 --stl p $height_line $height_base || break
@ -65,7 +72,7 @@ apply_filter() {
python3 $exec_path $in $out $dpi -c $config_file $1 --stl m $height_line $height_base $fp_file|| break
;;
*)
echo "Invalid stl generation mode"
echo "SCRIPT: Invalid stl generation mode"
exit 1
;;
esac
@ -93,11 +100,11 @@ done
# apply all given filter presets to all the discovered files
for preset in ${presets[@]}
do
echo -e "\n|----------------------- Filter "$preset" ------------------------------|\n|"
echo -e "\n|----------------------- Filter "$preset" ------------------------------|\n"
j=0
apply_filter $preset $j
done
echo -e "\n|--------------------------- Done ----------------------------------|\n"
echo "Skipped $j files"
echo "Generated $i files"
echo "SCRIPT: Skipped $j files"
echo "SCRIPT: Generated $i files"

Loading…
Cancel
Save