Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/vstarstack/library/movement/basic_movement.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

from typing import List
import numpy as np
from abc import ABC, abstractmethod

Expand Down Expand Up @@ -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"""
18 changes: 18 additions & 0 deletions src/vstarstack/library/movement/flat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 26 additions & 1 deletion src/vstarstack/library/movement/sphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down Expand Up @@ -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
Expand Down
89 changes: 89 additions & 0 deletions src/vstarstack/tool/moving_object_shift.py
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
#

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"),
}
2 changes: 2 additions & 0 deletions src/vstarstack/tool/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
4 changes: 2 additions & 2 deletions src/vstarstack/tool/shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]):
Expand Down