diff --git a/src/vstarstack/library/objects/features.py b/src/vstarstack/library/objects/features.py index a5366dac..ebde642b 100644 --- a/src/vstarstack/library/objects/features.py +++ b/src/vstarstack/library/objects/features.py @@ -16,6 +16,8 @@ import numpy as np import imutils +import imutils.contours + from skimage import measure import matplotlib.pyplot as plt @@ -33,9 +35,11 @@ def get_subimage(image, num_splits): subimage = image[base_y:next_y, base_x:next_x] yield subimage, base_x, base_y -def _find_keypoints_orb(image, base_x, base_y, detector): +def _find_keypoints_orb(image : np.ndarray, base_x : int, base_y : int, detector): """Find keypoints with ORB detector""" cpts = [] + if image.dtype != np.uint8: + image = (image * 255 / np.amax(image)).astype("uint8") points = detector.detect(image, mask=None) for point in points: pdesc = { @@ -46,9 +50,9 @@ def _find_keypoints_orb(image, base_x, base_y, detector): cpts.append(pdesc) return cpts -def find_keypoints_orb(image, num_splits): +def find_keypoints_orb(image, num_splits, param): points = [] - orb = cv2.ORB_create() + orb = cv2.ORB_create(patchSize=param["patchSize"]) for subimage, bx, by in get_subimage(image, num_splits): points += _find_keypoints_orb(subimage, bx, by, orb) return points @@ -108,7 +112,8 @@ def find_keypoints_brightness(image, num_splits, params): return points def describe_keypoints(image : np.ndarray, - keypoints : list) -> list: + keypoints : list, + param : dict) -> list: """ Build keypoints and calculate their descriptors @@ -120,7 +125,7 @@ def describe_keypoints(image : np.ndarray, Return: list of keypoint and list of descriptors """ - orb = cv2.ORB_create() + orb = cv2.ORB_create(patchSize=param["patchSize"]) kps = [cv2.KeyPoint(point["x"], point["y"], point["size"]) for point in keypoints] image = np.clip(image / np.amax(image), 0, 1)*255 image = image.astype('uint8') diff --git a/src/vstarstack/tool/objects/config.py b/src/vstarstack/tool/objects/config.py index ba5105d3..0ac7e221 100644 --- a/src/vstarstack/tool/objects/config.py +++ b/src/vstarstack/tool/objects/config.py @@ -30,10 +30,10 @@ "max_diameter": (int, 40), }), "features" : ("module", { - "detector" : (str, "brightness"), + "path" : (str, "features/"), "num_splits" : (int, 4), - "max_feature_delta" : (int, 10), - "features_percent" : (int, 20), + "max_feature_delta" : (int, 20), + "features_percent" : (int, 100), "bright_spots": { "blurSize" : (int, 21), "k_thr" : (float, 1.15), @@ -41,7 +41,9 @@ "minPixel" : (int, 5), "maxPixel" : (int, 20), }, - "orb" : {}, + "orb" : { + "patchSize" : (int, 31), + }, }), } diff --git a/src/vstarstack/tool/objects/features.py b/src/vstarstack/tool/objects/features.py deleted file mode 100644 index 46ce4ca3..00000000 --- a/src/vstarstack/tool/objects/features.py +++ /dev/null @@ -1,103 +0,0 @@ -# -# Copyright (c) 2023 Vladislav Tsendrovskii -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, version 3 of the License. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -import os -import json -import numpy as np - -import vstarstack.tool.common -import vstarstack.tool.cfg -import vstarstack.library.data -from vstarstack.library.objects.features import find_keypoints_orb -from vstarstack.library.objects.features import find_keypoints_brightness -from vstarstack.library.objects.features import describe_keypoints -from vstarstack.library.objects.features import build_clusters - - -def find_keypoints(files, num_splits, detector_type, param): - points = {} - descs = {} - fnames = {} - - for fname in files: - name = os.path.splitext(os.path.basename(fname))[0] - #print(name) - fnames[name] = fname - dataframe = vstarstack.library.data.DataFrame.load(fname) - for channel in dataframe.get_channels(): - image, opts = dataframe.get_channel(channel) - if not opts["brightness"]: - continue - if channel not in points: - points[channel] = {} - descs[channel] = {} - - if detector_type == "orb": - keypoints = find_keypoints_orb(image, num_splits) - elif detector_type == "brightness": - keypoints = find_keypoints_brightness(image, num_splits, param) - else: - raise Exception(f"Invalid detector {detector_type}") - - ds = describe_keypoints(image, keypoints) - points[channel][name] = keypoints - descs[channel][name] = ds - - return points, descs, fnames - -def run(project: vstarstack.tool.cfg.Project, argv: list[str]): - if len(argv) >= 2: - inputs = argv[0] - clusters_fname = argv[1] - else: - inputs = project.config.paths.npy_fixed - clusters_fname = project.config.cluster.path - - detector_type = project.config.objects.features.detector - num_splits = project.config.objects.features.num_splits - max_feature_delta = project.config.objects.features.max_feature_delta - features_percent = project.config.objects.features.features_percent / 100 - - if detector_type == "orb": - param = None - print("Using ORB detector") - elif detector_type == "brightness": - param = { - "blur_size" : project.config.objects.features.bright_spots.blurSize, - "k_thr" : project.config.objects.features.bright_spots.k_thr, - "min_value" : project.config.objects.features.bright_spots.minValue, - "min_pixel" : project.config.objects.features.bright_spots.minPixel, - "max_pixel" : project.config.objects.features.bright_spots.maxPixel, - } - print("Using brightness detector") - - files = vstarstack.tool.common.listfiles(inputs, ".zip") - files = [filename for _, filename in files] - points, descs, _ = find_keypoints(files, num_splits, detector_type, param) - print("Found keypoints") - - total_clusters = [] - for channel in points: - print(f"\tBuild clusters for {channel} channel") - crd_clusters = build_clusters(points[channel], - descs[channel], - max_feature_delta, - features_percent) - - total_clusters += crd_clusters - - print("Builded clusters") - vstarstack.tool.common.check_dir_exists(clusters_fname) - with open(clusters_fname, "w") as f: - json.dump(total_clusters, f, indent=4, ensure_ascii=False) diff --git a/src/vstarstack/tool/objects/find_features.py b/src/vstarstack/tool/objects/find_features.py new file mode 100644 index 00000000..faeb783a --- /dev/null +++ b/src/vstarstack/tool/objects/find_features.py @@ -0,0 +1,128 @@ +# +# Copyright (c) 2023-2024 Vladislav Tsendrovskii +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import os +import json + +import vstarstack.tool.common +import vstarstack.tool.cfg +import vstarstack.library.data +import vstarstack.library.common +from vstarstack.library.objects.features import find_keypoints_orb +from vstarstack.library.objects.features import find_keypoints_brightness +from vstarstack.library.objects.features import describe_keypoints + +def build_keypoints_structure(keypoints, ds, fname, name): + record = { + "fname" : fname, + "name" : name, + "points" : [], + } + + for keypoint, desc in zip(keypoints, ds): + for di in desc: + if di != 0: + break + else: + continue + record["points"].append({ + "keypoint" : keypoint, + "descriptor" : [int(item) for item in list(desc)], + }) + + return record + +def proj_find_keypoints_orb(files, num_splits, param): + points = {} + + for fname in files: + name = os.path.splitext(os.path.basename(fname))[0] + dataframe = vstarstack.library.data.DataFrame.load(fname) + gray, _ = vstarstack.library.common.df_to_light(dataframe) + + keypoints = find_keypoints_orb(gray, num_splits, param) + ds = describe_keypoints(gray, keypoints, param) + points[name] = build_keypoints_structure(keypoints, ds, fname, name) + + return points + +def proj_find_keypoints_brightness(files, num_splits, param, orb_param): + points = {} + + for fname in files: + name = os.path.splitext(os.path.basename(fname))[0] + dataframe = vstarstack.library.data.DataFrame.load(fname) + gray, _ = vstarstack.library.common.df_to_light(dataframe) + + keypoints = find_keypoints_brightness(gray, num_splits, param) + ds = describe_keypoints(gray, keypoints, orb_param) + points[name] = build_keypoints_structure(keypoints, ds, fname, name) + + return points + +def save_features(points, features_path): + for name in points: + record = points[name] + with open(os.path.join(features_path, f"{name}_keypoints.json"), "w") as f: + json.dump(record, f, indent=4, ensure_ascii=False) + +def find_points_orb(project: vstarstack.tool.cfg.Project, argv: list[str]): + if len(argv) >= 2: + inputs = argv[0] + features = argv[1] + else: + inputs = project.config.paths.npy_fixed + features = project.config.objects.features.path + + num_splits = project.config.objects.features.num_splits + param = { + "patchSize" : project.config.objects.features.orb.patchSize + } + + files = vstarstack.tool.common.listfiles(inputs, ".zip") + files = [filename for _, filename in files] + points = proj_find_keypoints_orb(files, num_splits, param) + save_features(points, features) + +def find_points_brightness(project: vstarstack.tool.cfg.Project, argv: list[str]): + if len(argv) >= 2: + inputs = argv[0] + features = argv[1] + else: + inputs = project.config.paths.npy_fixed + features = project.config.objects.features.path + + num_splits = project.config.objects.features.num_splits + + orb_param = { + "patchSize" : project.config.objects.features.orb.patchSize + } + + param = { + "blur_size" : project.config.objects.features.bright_spots.blurSize, + "k_thr" : project.config.objects.features.bright_spots.k_thr, + "min_value" : project.config.objects.features.bright_spots.minValue, + "min_pixel" : project.config.objects.features.bright_spots.minPixel, + "max_pixel" : project.config.objects.features.bright_spots.maxPixel, + } + + files = vstarstack.tool.common.listfiles(inputs, ".zip") + files = [filename for _, filename in files] + points = proj_find_keypoints_brightness(files, num_splits, param, orb_param) + save_features(points, features) + +commands = { + "brightness": (find_points_brightness, "find keypoints with brightness detector", "npys/ features/"), + "orb": (find_points_orb, "find keypoints with ORB detector", "npys/ features/") +} diff --git a/src/vstarstack/tool/objects/match_features.py b/src/vstarstack/tool/objects/match_features.py new file mode 100644 index 00000000..c4a7154d --- /dev/null +++ b/src/vstarstack/tool/objects/match_features.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2023-2024 Vladislav Tsendrovskii +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import math +import json +import numpy as np + +import vstarstack.tool.common +import vstarstack.tool.cfg +import vstarstack.library.data +from vstarstack.library.objects.features import build_clusters + +def load_keypoints(files): + points = {} + for file in files: + with open(file) as f: + record = json.load(f) + name = record["name"] + points[name] = record + return points + +def run(project: vstarstack.tool.cfg.Project, argv: list[str]): + if len(argv) >= 2: + points_path = argv[0] + clusters_fname = argv[1] + else: + points_path = project.config.paths.npy_fixed + clusters_fname = project.config.cluster.path + + max_feature_delta = project.config.objects.features.max_feature_delta + features_percent = project.config.objects.features.features_percent / 100.0 + + files = vstarstack.tool.common.listfiles(points_path, ".json") + files = [filename for _, filename in files] + keypoints = load_keypoints(files) + print("Found keypoints") + + points = {} + descs = {} + + for name in keypoints: + points[name] = [item["keypoint"] for item in keypoints[name]["points"]] + descs[name] = np.array([np.array(item["descriptor"], dtype=np.uint8) for item in keypoints[name]["points"]]) + + clusters = build_clusters(points, descs, + max_feature_delta, + features_percent) + + print("Builded clusters") + vstarstack.tool.common.check_dir_exists(clusters_fname) + with open(clusters_fname, "w") as f: + json.dump(clusters, f, indent=4, ensure_ascii=False) diff --git a/src/vstarstack/tool/objects/objects.py b/src/vstarstack/tool/objects/objects.py index 623ae1c0..71f8aba6 100644 --- a/src/vstarstack/tool/objects/objects.py +++ b/src/vstarstack/tool/objects/objects.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Vladislav Tsendrovskii +# Copyright (c) 2022-2024 Vladislav Tsendrovskii # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +17,8 @@ import vstarstack.tool.objects.cut import vstarstack.tool.objects.config -import vstarstack.tool.objects.features +import vstarstack.tool.objects.find_features +import vstarstack.tool.objects.match_features def _enable_objects(project : vstarstack.tool.cfg.Project, _argv: list[str]): project.config.enable_module("objects") @@ -26,6 +27,7 @@ def _enable_objects(project : vstarstack.tool.cfg.Project, _argv: list[str]): commands = { "config": (_enable_objects, "configure compact_objects pipeline"), "detect": ("vstarstack.tool.objects.detect", "detect compact objects"), - "features": (vstarstack.tool.objects.features.run, "detect and match image features"), + "find-features": (vstarstack.tool.objects.find_features.commands, "detect image features"), + "match-features": (vstarstack.tool.objects.match_features.run, "match image features"), "cut": (vstarstack.tool.objects.cut.run, "cut compact objects"), }