diff --git a/src/vstarstack/library/movement/basic_movement.py b/src/vstarstack/library/movement/basic_movement.py index ce9d422..820d9f5 100644 --- a/src/vstarstack/library/movement/basic_movement.py +++ b/src/vstarstack/library/movement/basic_movement.py @@ -13,6 +13,7 @@ # along with this program. If not, see . # +from typing import List import numpy as np from abc import ABC, abstractmethod @@ -60,3 +61,13 @@ def inverse(self): @abstractmethod def __mul__(self, other): """Multiply movements""" + + @staticmethod + @abstractmethod + def average(transformations, percent=100): + """Average of multiple movements""" + + @staticmethod + @abstractmethod + def interpolate(transformations : list, coefficients : List[float]): + """Interpolate of multiple movements""" diff --git a/src/vstarstack/library/movement/flat.py b/src/vstarstack/library/movement/flat.py index 87d69dd..6d99a2f 100644 --- a/src/vstarstack/library/movement/flat.py +++ b/src/vstarstack/library/movement/flat.py @@ -108,6 +108,24 @@ def average(transformations : list): transformation = Movement(angle, dy, dx) return transformation + @staticmethod + def interpolate(transformations : list, coefficients : List[float]): + """Interpolate of multiple movements""" + s = sum(coefficients) + coefficients = [item / s for item in coefficients] + angles = [] + dxs = [] + dys = [] + for i, transformation in enumerate(transformations): + angles.append(transformation.a * coefficients[i]) + dxs.append(transformation.dx * coefficients[i]) + dys.append(transformation.dy * coefficients[i]) + angle = np.sum(angles) + dy = np.sum(dys) + dx = np.sum(dxs) + transformation = Movement(angle, dy, dx) + return transformation + def __mul__(self, other): """Multiply movements""" angle1 = self.a diff --git a/src/vstarstack/library/movement/sphere.py b/src/vstarstack/library/movement/sphere.py index 6144dfd..f14e207 100644 --- a/src/vstarstack/library/movement/sphere.py +++ b/src/vstarstack/library/movement/sphere.py @@ -16,7 +16,7 @@ import logging import math import json -from typing import Any +from typing import Any, List import numpy as np from scipy.spatial.transform import Rotation @@ -174,6 +174,17 @@ def build(point1_from, point2_from, point1_to, point2_to, debug=False): return Movement(rot) + @staticmethod + def build_by_single(point_from, point_to): + """Build movement by single pair of stars""" + v_from = p2vec(point_from) + v_to = p2vec(point_to) + angle = vecangle(v_from, v_to) + axis = vecmul(v_from, v_to) + axis = axis / np.linalg.norm(axis) + rot = Rotation.from_rotvec(angle * axis) + return Movement(rot) + @staticmethod def average(transformations, percent=100): """Average of multiple movements""" @@ -206,6 +217,20 @@ def average(transformations, percent=100): transformation = Movement(rot) return transformation + @staticmethod + def interpolate(transformations : list, coefficients : List[float]): + """Interpolate of multiple movements""" + s = sum(coefficients) + coefficients = [item / s for item in coefficients] + axises = np.zeros((len(transformations), 3)) + for i, transformation in enumerate(transformations): + rotvec = transformation.rot.as_rotvec() + axises[i, 0:3] = rotvec * coefficients[i] + rotvec = np.sum(axises, axis=0) + rot = Rotation.from_rotvec(rotvec) + transformation = Movement(rot) + return transformation + def __mul__(self, other): """Multiply movements""" rot1 = self.rot diff --git a/src/vstarstack/tool/moving_object_shift.py b/src/vstarstack/tool/moving_object_shift.py new file mode 100644 index 0000000..409de2e --- /dev/null +++ b/src/vstarstack/tool/moving_object_shift.py @@ -0,0 +1,89 @@ +# +# Copyright (c) 2025 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 json +import datetime +import logging + +import vstarstack.tool.cfg +import vstarstack.tool.common +import vstarstack.library.data +import vstarstack.library.projection.tools +import vstarstack.library.movement.sphere + +logger = logging.getLogger(__name__) + +def linear(project : vstarstack.tool.cfg.Project, argv: list[str]): + # path with image files + path = argv[0] + + # object position on first file - with lowest UTC + x1 = int(argv[1]) + y1 = int(argv[2]) + + # object position of last file - with highest UTC + x2 = int(argv[3]) + y2 = int(argv[4]) + + # output shift file + shift_file = argv[5] + + # we align all files so object position is the same on all images + # we use UTC parameter for determine interpolation position + utcs = {} + projections = {} + lowest_utc = None + highest_utc = None + lowest_name = None + highest_name = None + imgs = vstarstack.tool.common.listfiles(path, ".zip") + for name, fname in imgs: + logging.info(f"Processing {name}") + df = vstarstack.library.data.DataFrame.load(fname) + utc = df.get_parameter("UTC") + if utc is None: + logging.warning(f"File {name} doesn't have UTC") + continue + try: + logging.info(f"UTC {utc}") + utc = datetime.datetime.fromisoformat(utc) + except: + logging.error(f"File {name} has incorrect UTC {utc}") + continue + utcs[name] = utc + projections[name] = vstarstack.library.projection.tools.get_projection(df) + if lowest_utc is None or utc < lowest_utc: + lowest_utc = utc + lowest_name = name + if highest_utc is None or utc > highest_utc: + highest_utc = utc + highest_name = name + + first_lonlat = projections[lowest_name].project(x1, y1) + last_lonlat = projections[highest_name].project(x2, y2) + movement = vstarstack.library.movement.sphere.Movement.build_by_single(last_lonlat, first_lonlat) + identity = vstarstack.library.movement.sphere.Movement.identity() + + delta = highest_utc - lowest_utc + shifts = {} + for name in utcs: + interpolation = (utcs[name] - lowest_utc) / delta + move = vstarstack.library.movement.sphere.Movement.interpolate([movement, identity], [interpolation, 1-interpolation]) + shifts[name] = move.serialize() + with open(shift_file, "w", encoding='utf8') as f: + json.dump(shifts, f, ensure_ascii=False, indent=4) + +commands = { + "linear-interpolation": (linear, "Interpolate moving object position linear between first and last images", "path/ X1 Y1 X2 Y2 shift-linear.json"), +} diff --git a/src/vstarstack/tool/process.py b/src/vstarstack/tool/process.py index 5f270a9..39f055e 100644 --- a/src/vstarstack/tool/process.py +++ b/src/vstarstack/tool/process.py @@ -53,6 +53,8 @@ "fine shift images"), "photometry": ("vstarstack.tool.photometry.photometry", "analyze images"), + "moving-object-align": ("vstarstack.tool.moving_object_shift", + "align moving objects, like comets"), "pipeline": ("vstarstack.tool.generators.generators", "generate pipelines for processing"), } diff --git a/src/vstarstack/tool/shift.py b/src/vstarstack/tool/shift.py index 7372bea..8bb3cf9 100644 --- a/src/vstarstack/tool/shift.py +++ b/src/vstarstack/tool/shift.py @@ -254,10 +254,10 @@ def apply_shift_extended(project: vstarstack.tool.cfg.Project, argv: list[str]): "shifts.json shift.json"), "apply-shift": (apply_shift, "Apply selected shifts", - "shift.json npy/ shifted/"), + "npy/ shift.json shifted/"), "apply-extended-shift": (apply_shift_extended, "Apply selected shifts and save to output with extended size (only perspective projection!)", - "shift.json npy/ shifted/"), + "npy/ shift.json shifted/"), } def run(project: vstarstack.tool.cfg.Project, argv: list[str]):