Reworked logging, split it to separate file, refactored and cleaned main and filters files code, fixed test.sh overwriting originals
This commit is contained in:
14
README.md
14
README.md
@ -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
|
1. manually list filter names and parameters from command line
|
||||||
```sh
|
```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
|
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
|
# Configuration and presets
|
||||||
|
|
||||||
There is an option to input the filter series as a preset from json configuration file.
|
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%;">
|
<table style="width:100%;">
|
||||||
<thead>
|
<thead>
|
||||||
@ -127,7 +129,17 @@ There is an option to input the filter series as a preset from json configuratio
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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.
|
All the filters used and their parameters are described below.
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
from os.path import exists
|
from os.path import exists
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import log
|
||||||
|
|
||||||
def save_preset(filters, params, preset_name):
|
def save_preset(filters, params, preset_name):
|
||||||
'''Save filter preset to database.
|
'''Save filter preset to database.
|
||||||
@ -46,7 +46,7 @@ def store_to_db(preset, preset_name):
|
|||||||
|
|
||||||
# If database doesn't exist, create it
|
# If database doesn't exist, create it
|
||||||
if not exists("db.json"):
|
if not exists("db.json"):
|
||||||
print("Storing preset to database")
|
log.print_message("Storing preset to database")
|
||||||
with open("db.json", 'w') as db:
|
with open("db.json", 'w') as db:
|
||||||
json.dump(preset, db)
|
json.dump(preset, db)
|
||||||
else:
|
else:
|
||||||
@ -61,9 +61,9 @@ def store_to_db(preset, preset_name):
|
|||||||
|
|
||||||
# If preset already exists, skip it
|
# If preset already exists, skip it
|
||||||
if preset_name in db_presets:
|
if preset_name in db_presets:
|
||||||
print("Preset already exists in database, skipping")
|
log.print_message("Preset already exists in database, skipping")
|
||||||
else:
|
else:
|
||||||
print("Storing preset to database")
|
log.print_message("Storing preset to database")
|
||||||
db_presets.update(preset)
|
db_presets.update(preset)
|
||||||
|
|
||||||
# Finally write the updated entries to db file
|
# Finally write the updated entries to db file
|
||||||
@ -91,10 +91,10 @@ def parse_conf(preset_name, filters, params, config_file):
|
|||||||
if attribute != "name":
|
if attribute != "name":
|
||||||
params[i][attribute] = value
|
params[i][attribute] = value
|
||||||
parse_params(params[i])
|
parse_params(params[i])
|
||||||
print("Loaded preset: " + preset_name +
|
log.print_message("Loaded preset:", preset_name,
|
||||||
" from file: " + config_file)
|
"from file:", config_file)
|
||||||
else:
|
else:
|
||||||
print("Preset not found")
|
log.print_message("Preset not found")
|
||||||
|
|
||||||
|
|
||||||
def parse_params(params):
|
def parse_params(params):
|
||||||
|
@ -41,7 +41,6 @@ class gaussian(filter):
|
|||||||
# Standard deviation for Gaussian kernel
|
# Standard deviation for Gaussian kernel
|
||||||
sigma = float(params["sigma"]) if params["sigma"] else 1
|
sigma = float(params["sigma"]) if params["sigma"] else 1
|
||||||
|
|
||||||
print("with params: sigma: " + str(sigma))
|
|
||||||
self.img = skiflt.gaussian(
|
self.img = skiflt.gaussian(
|
||||||
self.img, sigma=sigma, preserve_range=True)
|
self.img, sigma=sigma, preserve_range=True)
|
||||||
|
|
||||||
@ -59,7 +58,6 @@ class median(filter):
|
|||||||
# Used kernel is disk of size ksize
|
# Used kernel is disk of size ksize
|
||||||
ksize = int(params["ksize"]) if params["ksize"] else 3
|
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))
|
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
|
# A larger value results in averaging of pixels with larger spatial differences
|
||||||
sigmaSpace = int(params["sigmaSpace"]) if params["sigmaSpace"] else 75
|
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 = np.uint8(self.img)
|
||||||
self.img = cv.bilateralFilter(
|
self.img = cv.bilateralFilter(
|
||||||
self.img, diameter, sigmaColor, sigmaSpace)
|
self.img, diameter, sigmaColor, sigmaSpace)
|
||||||
@ -115,8 +111,6 @@ class bilateral_scikit(filter):
|
|||||||
sigmaSpace = float(params["sigmaSpace"]
|
sigmaSpace = float(params["sigmaSpace"]
|
||||||
) if params["sigmaSpace"] else 9.0
|
) if params["sigmaSpace"] else 9.0
|
||||||
|
|
||||||
print("with params: sigma_color: " + str(sigmaColor) +
|
|
||||||
" sigma_spatial: " + str(sigmaSpace))
|
|
||||||
self.img = skirest.denoise_bilateral(
|
self.img = skirest.denoise_bilateral(
|
||||||
self.img, sigma_color=sigmaColor, sigma_spatial=sigmaSpace)
|
self.img, sigma_color=sigmaColor, sigma_spatial=sigmaSpace)
|
||||||
self.img = np.uint8(self.img * 255.0) # converting back to uint8
|
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
|
# Cut-off distance, higher means more smoothed image
|
||||||
h = float(params["h"])*sigma if params["h"] else 0.1*sigma
|
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 = skirest.denoise_nl_means(
|
||||||
self.img, patch_size=patch_size, fast_mode=True, patch_distance=patch_distance, h=h)
|
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
|
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.
|
# Denoising weight. Larger values result in more denoising.
|
||||||
weight = float(params["weight"]) if params["weight"] else 0.1
|
weight = float(params["weight"]) if params["weight"] else 0.1
|
||||||
|
|
||||||
print("with params: weight: " + str(weight))
|
|
||||||
self.img = skirest.denoise_tv_chambolle(
|
self.img = skirest.denoise_tv_chambolle(
|
||||||
self.img, weight=weight)
|
self.img, weight=weight)
|
||||||
self.img = np.uint8(self.img * 255.0) # converting back to uint8
|
self.img = np.uint8(self.img * 255.0) # converting back to uint8
|
||||||
@ -184,7 +175,6 @@ class block_match(filter):
|
|||||||
def apply(self, params):
|
def apply(self, params):
|
||||||
sigma = float(params["sigma"]) if params["sigma"] else 20
|
sigma = float(params["sigma"]) if params["sigma"] else 20
|
||||||
|
|
||||||
print("with params: sigma: " + str(sigma))
|
|
||||||
self.img = bm3d.bm3d(self.img, sigma_psd=sigma,
|
self.img = bm3d.bm3d(self.img, sigma_psd=sigma,
|
||||||
stage_arg=bm3d.BM3DStages.ALL_STAGES)
|
stage_arg=bm3d.BM3DStages.ALL_STAGES)
|
||||||
|
|
||||||
@ -207,8 +197,6 @@ class unsharp_mask_scikit(filter):
|
|||||||
# strength of the unsharp mask
|
# strength of the unsharp mask
|
||||||
amount = float(params["amount"]) if params["amount"] else 1.0
|
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,
|
self.img = skiflt.unsharp_mask(self.img, radius=radius,
|
||||||
amount=amount, channel_axis=None)
|
amount=amount, channel_axis=None)
|
||||||
self.img = np.uint8(self.img * 255.0) # converting back to uint
|
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
|
maxval = int(params["maxval"]) if params["maxval"] else 255
|
||||||
type = int(params["type"]) if params["type"] else 0
|
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]
|
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
|
margin = int(params["margin"]) if params["margin"] else 10
|
||||||
color = int(params["color"]) if params["color"] else 255
|
color = int(params["color"]) if params["color"] else 255
|
||||||
|
|
||||||
print("with params: margin: " + str(margin) + " color: " + str(color))
|
|
||||||
|
|
||||||
self.fig.set_size_inches(
|
self.fig.set_size_inches(
|
||||||
((self.width + 2 * margin) / self.dpi, (self.height + 2 * margin) / self.dpi))
|
((self.width + 2 * margin) / self.dpi, (self.height + 2 * margin) / self.dpi))
|
||||||
self.img = cv.copyMakeBorder(
|
self.img = cv.copyMakeBorder(
|
||||||
@ -365,7 +349,6 @@ class convolve(filter):
|
|||||||
kernel = np.array(params["kernel"]) if params["kernel"] else np.ones(
|
kernel = np.array(params["kernel"]) if params["kernel"] else np.ones(
|
||||||
(3, 3), np.float32) / 9
|
(3, 3), np.float32) / 9
|
||||||
|
|
||||||
print("with params: kernel: \n" + str(kernel))
|
|
||||||
self.img = cv.filter2D(self.img, -1, kernel)
|
self.img = cv.filter2D(self.img, -1, kernel)
|
||||||
|
|
||||||
|
|
||||||
@ -380,7 +363,6 @@ class blur(filter):
|
|||||||
def apply(self, params):
|
def apply(self, params):
|
||||||
ksize = int(params["ksize"]) if params["ksize"] else 3
|
ksize = int(params["ksize"]) if params["ksize"] else 3
|
||||||
|
|
||||||
print("with params: ksize: " + str(ksize))
|
|
||||||
self.img = cv.blur(self.img, ksize=(ksize, ksize))
|
self.img = cv.blur(self.img, ksize=(ksize, ksize))
|
||||||
|
|
||||||
|
|
||||||
@ -395,8 +377,6 @@ class denoise(filter):
|
|||||||
sWS = int(params["searchWindowSize"]
|
sWS = int(params["searchWindowSize"]
|
||||||
) if params["searchWindowSize"] else 21
|
) 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 = np.uint8(self.img)
|
||||||
self.img = cv.fastNlMeansDenoising(
|
self.img = cv.fastNlMeansDenoising(
|
||||||
self.img, h, tWS, sWS)
|
self.img, h, tWS, sWS)
|
||||||
@ -413,7 +393,6 @@ class sharpen(filter):
|
|||||||
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array(
|
kernel = np.matrix(params["kernel"]) if params["kernel"] else np.array(
|
||||||
[[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
|
[[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)
|
self.img = cv.filter2D(self.img, ddepth=-1, kernel=kernel)
|
||||||
|
|
||||||
|
|
||||||
@ -434,8 +413,6 @@ class unsharp_mask(filter):
|
|||||||
blurred = cv.medianBlur(self.img, ksize)
|
blurred = cv.medianBlur(self.img, ksize)
|
||||||
lap = cv.Laplacian(blurred, cv.CV_32F)
|
lap = cv.Laplacian(blurred, cv.CV_32F)
|
||||||
|
|
||||||
# print("with params: strength: " +
|
|
||||||
# str(strength) + " ksize: " + str(ksize))
|
|
||||||
self.img = blurred - strength*lap
|
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 controls the minimum brightness change that will be sharpened
|
||||||
threshold = int(params["threshold"]) if params["threshold"] else 3
|
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)
|
self.img = np.uint8(self.img)
|
||||||
tmp = Image.fromarray(self.img)
|
tmp = Image.fromarray(self.img)
|
||||||
tmp = tmp.filter(ImageFilter.UnsharpMask(radius, percent, threshold))
|
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(
|
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
|
||||||
cv.MORPH_ELLIPSE, (3, 3))
|
cv.MORPH_ELLIPSE, (3, 3))
|
||||||
|
|
||||||
print("with params: kernel: \n" + str(kernel))
|
|
||||||
self.img = cv.morphologyEx(
|
self.img = cv.morphologyEx(
|
||||||
np.uint8(self.img), op=cv.MORPH_ERODE, kernel=kernel)
|
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(
|
kernel = np.matrix(params["kernel"]) if params["kernel"] else cv.getStructuringElement(
|
||||||
cv.MORPH_ELLIPSE, (3, 3))
|
cv.MORPH_ELLIPSE, (3, 3))
|
||||||
|
|
||||||
print("with params: kernel: \n" + str(kernel))
|
|
||||||
self.img = cv.morphologyEx(
|
self.img = cv.morphologyEx(
|
||||||
np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel)
|
np.uint8(self.img), op=cv.MORPH_DILATE, kernel=kernel)
|
||||||
|
|
||||||
|
22
src/log.py
Normal file
22
src/log.py
Normal file
@ -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)
|
84
src/main.py
84
src/main.py
@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
# Import basic libraries
|
# Import basic libraries
|
||||||
import argparse as ap
|
import argparse as ap
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
from os.path import exists
|
from os.path import exists
|
||||||
import hashlib
|
import hashlib
|
||||||
import math
|
import math
|
||||||
@ -22,6 +20,7 @@ import trimesh.transformations as tmtra
|
|||||||
# Import custom image filter library
|
# Import custom image filter library
|
||||||
import filters as flt
|
import filters as flt
|
||||||
import config_parser as cp
|
import config_parser as cp
|
||||||
|
import log
|
||||||
|
|
||||||
class app:
|
class app:
|
||||||
'''Main class for the application.
|
'''Main class for the application.
|
||||||
@ -41,28 +40,30 @@ class app:
|
|||||||
cp.parse_conf(self.preset_name, self.filters, self.params, self.config_file)
|
cp.parse_conf(self.preset_name, self.filters, self.params, self.config_file)
|
||||||
|
|
||||||
elif self.args.filters:
|
elif self.args.filters:
|
||||||
print("No config file given, using command line arguments")
|
filter_index = 0
|
||||||
i = 0
|
log.print_message(
|
||||||
|
"No config file given, using command line arguments")
|
||||||
|
|
||||||
# Otherwise expect filters from command line
|
# Otherwise expect filters from command line
|
||||||
for filter_part in self.args.filters:
|
for filter_part in self.args.filters:
|
||||||
# If no '=' char in filter, it is a new filter name
|
# If no '=' char in filter, it is a new filter name
|
||||||
if filter_part.find('=') == -1:
|
if filter_part.find('=') == -1:
|
||||||
self.filters.append(filter_part)
|
self.filters.append(filter_part)
|
||||||
i += 1
|
filter_index += 1
|
||||||
self.params[i] = {} # create empty dict for params
|
# create empty dict for params
|
||||||
|
self.params[filter_index] = {}
|
||||||
# Otherwise it's a parameter for current filter
|
# Otherwise it's a parameter for current filter
|
||||||
else:
|
else:
|
||||||
key, value = filter_part.split('=')
|
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 database flag is set, save filters to database as a new preset
|
||||||
if self.args.database:
|
if self.args.database:
|
||||||
cp.save_preset(self.filters, self.params, self.args.database[0])
|
cp.save_preset(self.filters, self.params, self.args.database[0])
|
||||||
|
|
||||||
else:
|
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
|
# Set input and output file paths, dpi and mirror flag for easier readability
|
||||||
self.input_file = self.args.input_file
|
self.input_file = self.args.input_file
|
||||||
@ -72,9 +73,8 @@ class app:
|
|||||||
|
|
||||||
if exists(self.input_file):
|
if exists(self.input_file):
|
||||||
self.run_filtering()
|
self.run_filtering()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.error_exit("Input file " + self.input_file +
|
log.error_exit("Input file " + self.input_file +
|
||||||
" does not exist")
|
" does not exist")
|
||||||
|
|
||||||
if self.args.stl:
|
if self.args.stl:
|
||||||
@ -128,12 +128,12 @@ class app:
|
|||||||
if len(self.args.stl) < 3:
|
if len(self.args.stl) < 3:
|
||||||
self.height_line = 2
|
self.height_line = 2
|
||||||
self.height_base = 10
|
self.height_base = 10
|
||||||
print(
|
log.print_message(
|
||||||
"Warning: Too few arguments, using default values (10mm base, 2mm lines)")
|
"Warning: Too few arguments, using default values (10mm base, 2mm lines)")
|
||||||
else:
|
else:
|
||||||
self.height_line = float(self.args.stl[1])
|
self.height_line = float(self.args.stl[1])
|
||||||
self.height_base = float(self.args.stl[2])
|
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")
|
"mm, lines depth/height:", self.height_line, "mm")
|
||||||
|
|
||||||
elif self.args.stl[0] == 'c':
|
elif self.args.stl[0] == 'c':
|
||||||
@ -145,40 +145,31 @@ class app:
|
|||||||
self.height_base = 10
|
self.height_base = 10
|
||||||
self.curv_rate_x = 2
|
self.curv_rate_x = 2
|
||||||
self.curv_rate_y = 6
|
self.curv_rate_y = 6
|
||||||
print(
|
log.print_message("Warning: Too few arguments, using default values (2mm lines, curvature 0.5 on x, 0.5 on y)")
|
||||||
"Warning: Too few arguments, using default values (2mm lines, curvature 0.5 on x, 0.5 on y)")
|
|
||||||
else:
|
else:
|
||||||
self.height_line = float(self.args.stl[1])
|
self.height_line = float(self.args.stl[1])
|
||||||
self.height_base = float(self.args.stl[2])
|
self.height_base = float(self.args.stl[2])
|
||||||
self.curv_rate_x = float(self.args.stl[3])
|
self.curv_rate_x = float(self.args.stl[3])
|
||||||
self.curv_rate_y = float(self.args.stl[4])
|
self.curv_rate_y = float(self.args.stl[4])
|
||||||
print("Line height:", self.height_line, "mm, base height: ", self.height_base,
|
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)
|
"mm, x axis curvature:", self.curv_rate_x, ", y axis curvature:", self.curv_rate_y)
|
||||||
elif self.args.stl[0] == 'm':
|
elif self.args.stl[0] == 'm':
|
||||||
self.mode = "mapped"
|
self.mode = "mapped"
|
||||||
|
|
||||||
# TODO: add default values for mapped mode, add finger model?
|
# TODO: add default values for mapped mode, add finger model?
|
||||||
if len(self.args.stl) < 2:
|
if len(self.args.stl) < 2:
|
||||||
print(
|
log.print_message(
|
||||||
"Warning: Too few arguments, using default values")
|
"Warning: Too few arguments, using default values")
|
||||||
else:
|
else:
|
||||||
self.height_line = float(self.args.stl[1])
|
self.height_line = float(self.args.stl[1])
|
||||||
self.finger_model = self.args.stl[2]
|
self.finger_model = self.args.stl[2]
|
||||||
|
|
||||||
else:
|
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()
|
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 -------------------------#
|
# ------------------------- FILTERING -------------------------#
|
||||||
|
|
||||||
def run_filtering(self):
|
def run_filtering(self):
|
||||||
@ -216,7 +207,7 @@ class app:
|
|||||||
'''Mirror image using opencv, should be used if we want a positive model.
|
'''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
|
self.img = cv.flip(self.img, 1) # 1 for vertical mirror
|
||||||
|
|
||||||
def apply_filters(self):
|
def apply_filters(self):
|
||||||
@ -228,7 +219,12 @@ class app:
|
|||||||
for i, filter_name in enumerate(self.filters):
|
for i, filter_name in enumerate(self.filters):
|
||||||
# Get filter class from filter.py, use the apply method
|
# Get filter class from filter.py, use the apply method
|
||||||
filter = getattr(flt, filter_name)
|
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])
|
filter.apply(self, self.params[i+1])
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
@ -237,7 +233,7 @@ class app:
|
|||||||
'''Save processed image to the output file.
|
'''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.
|
# Colormap must be set to grayscale to avoid color mismatch.
|
||||||
ax.imshow(self.img, cmap="gray")
|
ax.imshow(self.img, cmap="gray")
|
||||||
@ -253,7 +249,7 @@ class app:
|
|||||||
# create ID for the model from all its parameters
|
# create ID for the model from all its parameters
|
||||||
self.get_ID()
|
self.get_ID()
|
||||||
|
|
||||||
print("Creating mesh", file=sys.stderr)
|
log.print_message("Creating mesh")
|
||||||
|
|
||||||
# Create a mesh using one of two modes
|
# Create a mesh using one of two modes
|
||||||
if self.mode == "planar":
|
if self.mode == "planar":
|
||||||
@ -267,11 +263,11 @@ class app:
|
|||||||
self.make_stl_map()
|
self.make_stl_map()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.error_exit("Mode not supported")
|
log.error_exit("Incorrect stl generation mode")
|
||||||
|
|
||||||
plt.show()
|
plt.show()
|
||||||
self.save_stl()
|
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):
|
def prepare_heightmap(self):
|
||||||
'''Scale image values to get values from 0 to 255.
|
'''Scale image values to get values from 0 to 255.
|
||||||
@ -281,27 +277,27 @@ class app:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
if self.img.dtype != np.uint8:
|
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 / np.max(self.img) * 255
|
||||||
self.img = self.img.astype(np.uint8)
|
self.img = self.img.astype(np.uint8)
|
||||||
|
|
||||||
if self.mode == "planar":
|
if self.mode == "planar":
|
||||||
if self.height_base <= 0:
|
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:
|
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":
|
if self.mode == "curved":
|
||||||
# Don't need to check curvature, check only heights
|
# Don't need to check curvature, check only heights
|
||||||
if self.height_base <= 0 or self.height_line <= 0:
|
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.mode == "mapped":
|
||||||
if self.height_line <= 0:
|
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):
|
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
|
self.height_base = 0
|
||||||
|
|
||||||
# TODO: curved height base could be done here?
|
# TODO: curved height base could be done here?
|
||||||
@ -324,10 +320,10 @@ class app:
|
|||||||
# Truncate if necessary
|
# Truncate if necessary
|
||||||
if (len(self.param_string) >= 80):
|
if (len(self.param_string) >= 80):
|
||||||
self.param_string = 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)
|
# 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:
|
with open(self.stl_filename, "r+") as f:
|
||||||
f.write(self.param_string)
|
f.write(self.param_string)
|
||||||
|
|
||||||
@ -681,8 +677,7 @@ class app:
|
|||||||
|
|
||||||
# TODO: maybe use trimesh.update_vertices
|
# TODO: maybe use trimesh.update_vertices
|
||||||
|
|
||||||
|
log.print_message("Mapping to finger")
|
||||||
print("Mapping to finger")
|
|
||||||
# TODO: try to merge meshes? or stl files?
|
# TODO: try to merge meshes? or stl files?
|
||||||
# trimesh library?
|
# trimesh library?
|
||||||
finger = trimesh.load(self.finger_model)
|
finger = trimesh.load(self.finger_model)
|
||||||
@ -715,7 +710,6 @@ class app:
|
|||||||
|
|
||||||
self.stl_model = trimesh.util.concatenate([finger, fingerprint])
|
self.stl_model = trimesh.util.concatenate([finger, fingerprint])
|
||||||
|
|
||||||
|
|
||||||
def save_stl(self):
|
def save_stl(self):
|
||||||
'''Save final mesh to stl file.
|
'''Save final mesh to stl file.
|
||||||
'''
|
'''
|
||||||
|
37
src/test.sh
37
src/test.sh
@ -9,15 +9,15 @@ source .venv/bin/activate
|
|||||||
#----------------------------Configuration------------------------------#
|
#----------------------------Configuration------------------------------#
|
||||||
|
|
||||||
# place all image files to one folder
|
# 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
|
# 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
|
# recommend png, it's supported by opencv
|
||||||
format=jpg
|
format=png
|
||||||
|
|
||||||
# name of configuration file
|
# name of configuration file
|
||||||
config_file=conf/conf.json
|
config_file=conf/conf.json
|
||||||
@ -28,16 +28,18 @@ presets=("ridge")
|
|||||||
|
|
||||||
# generate stl files and set generation mode {"planar", "curved", "mapped"}
|
# generate stl files and set generation mode {"planar", "curved", "mapped"}
|
||||||
generate_stl=true
|
generate_stl=true
|
||||||
generate_stl_mode="planar"
|
generate_stl_mode="curved"
|
||||||
|
|
||||||
# in 1/10 of milimeters
|
# in 1/10 of milimeters
|
||||||
height_line=2
|
height_line=2.5
|
||||||
height_base=3
|
height_base=6
|
||||||
curv_x=1.5
|
curv_x=1.3
|
||||||
curv_y=4
|
curv_y=3
|
||||||
|
|
||||||
#----------------------------Application---------------------------#
|
#----------------------------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
|
# function to apply filter to all files in directory
|
||||||
apply_filter() {
|
apply_filter() {
|
||||||
for in in ${file_arr[@]}
|
for in in ${file_arr[@]}
|
||||||
@ -46,14 +48,19 @@ apply_filter() {
|
|||||||
[[ -f "$in" ]] || continue
|
[[ -f "$in" ]] || continue
|
||||||
|
|
||||||
# skip stl files and files with preset name in them
|
# skip stl files and files with preset name in them
|
||||||
[[ "$in" == *_* ]] && continue
|
[[ "$in" == *"ridge"* ]] && continue
|
||||||
[[ "$in" == *".stl" ]] && continue
|
[[ "$in" == *".stl" ]] && continue
|
||||||
|
|
||||||
((i++))
|
((i++))
|
||||||
echo -e "|\n|----------------------- File no. $i: $in ------------------------------|\n"
|
echo -e "\n|----------------------- File no. $i: $in ------------------------------|\n"
|
||||||
|
|
||||||
if $generate_stl; then
|
if $generate_stl; then
|
||||||
|
|
||||||
out="${in%%${match1}*}${match1}$format"
|
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
|
case "$generate_stl_mode" in
|
||||||
"planar")
|
"planar")
|
||||||
python3 $exec_path $in $out $dpi -c $config_file $1 --stl p $height_line $height_base || break
|
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
|
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
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -93,11 +100,11 @@ done
|
|||||||
# apply all given filter presets to all the discovered files
|
# apply all given filter presets to all the discovered files
|
||||||
for preset in ${presets[@]}
|
for preset in ${presets[@]}
|
||||||
do
|
do
|
||||||
echo -e "\n|----------------------- Filter "$preset" ------------------------------|\n|"
|
echo -e "\n|----------------------- Filter "$preset" ------------------------------|\n"
|
||||||
j=0
|
j=0
|
||||||
apply_filter $preset $j
|
apply_filter $preset $j
|
||||||
done
|
done
|
||||||
|
|
||||||
echo -e "\n|--------------------------- Done ----------------------------------|\n"
|
echo -e "\n|--------------------------- Done ----------------------------------|\n"
|
||||||
echo "Skipped $j files"
|
echo "SCRIPT: Skipped $j files"
|
||||||
echo "Generated $i files"
|
echo "SCRIPT: Generated $i files"
|
||||||
|
Reference in New Issue
Block a user