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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ scikit-image
scipy
imutils
matplotlib
pytest
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
'exifread',
'opencv-python',
'scikit-image',
'scipy',
'scipy >= 1.11.0',
'imutils',
'matplotlib',
]
Expand Down
78 changes: 48 additions & 30 deletions src/vstarstack/library/fine_shift/fine_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#

import numpy as np
import scipy

from vstarstack.library.fine_shift.image_wave import ImageWave
import vstarstack.library.data
Expand All @@ -29,6 +30,25 @@ def _cluster_average(cluster):

class Aligner:
"""Alignment calculator"""

def apply_alignment(self,
dataframe : vstarstack.library.data.DataFrame,
align : dict,
subpixels : int):
"""Apply alignment descriptor to file"""
wave = ImageWave.from_data(align)
for channel in dataframe.get_channels():
image, opts = dataframe.get_channel(channel)
if opts["encoded"]:
continue
image = image.astype('double')
fixed = wave.apply_shift(image, subpixels)
fixed[np.where(np.isnan(fixed))] = 0
dataframe.replace_channel(fixed, channel)
return dataframe

class ClusterAlignerBuilder:

def __init__(self, W, H, gridW, gridH, spk, num_steps, min_points, dh):
self.W = W
self.H = H
Expand All @@ -39,9 +59,7 @@ def __init__(self, W, H, gridW, gridH, spk, num_steps, min_points, dh):
self.min_points = min_points
self.dh = dh

def process_alignment_by_clusters(self,
name : str,
clusters : list):
def find_alignment(self, name : str, clusters : list) -> dict:
"""Find alignment of image `name` using clusters"""
points = []
targets = []
Expand All @@ -67,8 +85,7 @@ def process_alignment_by_clusters(self,
descriptor = wave.data()
return descriptor

def find_all_alignments_by_clusters(self,
clusters : list):
def find_all_alignments(self, clusters : list) -> dict:
"""Build alignment descriptor using clusters"""
names = []
for cluster in clusters:
Expand All @@ -81,18 +98,22 @@ def find_all_alignments_by_clusters(self,
descs[name] = desc
return descs

def process_alignment_by_correlation(self,
image : np.ndarray,
mask : np.ndarray,
pre_align : dict | None,
image_ref : np.ndarray,
mask_ref : np.ndarray,
pre_align_ref : dict | None):
class CorrelationAlignedBuilder:

def __init__(self, radius : int, maximal_shift : float, subpixels : int):
self.r = radius
self.shift = maximal_shift
self.subp = subpixels

def find_alignment(self,
image : np.ndarray,
pre_align : dict | None,
image_ref : np.ndarray,
pre_align_ref : dict | None,
smooth : int | None):
"""Build alignment descriptor of image using correlations"""
if image.shape != image_ref.shape:
return None
h = image.shape[0]
w = image.shape[1]
if pre_align is not None:
pre_wave = ImageWave.from_data(pre_align)
else:
Expand All @@ -102,21 +123,18 @@ def process_alignment_by_correlation(self,
pre_wave_ref = ImageWave.from_data(pre_align_ref)
else:
pre_wave_ref = None
wave = ImageWave.find_shift_array(image, pre_wave, image_ref, pre_wave_ref, 5, 3, 4)
wave = ImageWave.find_shift_array(image, pre_wave,
image_ref, pre_wave_ref,
self.r, self.shift, self.subp)
align = wave.data()
print(f"smooth = {smooth}")
if smooth is not None:
data = align["data"]
Nw = align["Nw"]
Nh = align["Nh"]
data = np.array(data, dtype='double')
data = data.reshape((Nh, Nw, 2))
data = scipy.ndimage.gaussian_filter(data, sigma=smooth, axes=(0,1))
data = list(data.reshape((Nh*Nw*2,)))
align["data"] = data
return align

def apply_alignment(self,
dataframe : vstarstack.library.data.DataFrame,
align : dict):
"""Apply alignment descriptor to file"""
wave = ImageWave.from_data(align)
for channel in dataframe.get_channels():
image, opts = dataframe.get_channel(channel)
if opts["encoded"]:
continue
image = image.astype('double')
fixed = wave.apply_shift(image)
fixed[np.where(np.isnan(fixed))] = 0
dataframe.replace_channel(fixed, channel)
return dataframe
3 changes: 2 additions & 1 deletion src/vstarstack/library/fine_shift/image_wave.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ double image_wave_interpolation(const struct ImageWaveGrid *array,
void image_wave_shift_image(struct ImageWave *self,
const struct ImageWaveGrid *array,
const struct ImageWaveGrid *input_image,
struct ImageWaveGrid *output_image);
struct ImageWaveGrid *output_image,
int subpixels);


/* Approximation by targets methods */
Expand Down
8 changes: 6 additions & 2 deletions src/vstarstack/library/fine_shift/image_wave_image.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@ static double image_wave_get_pixel(const struct ImageWaveGrid *image, double x,
void image_wave_shift_image(struct ImageWave *self,
const struct ImageWaveGrid *array,
const struct ImageWaveGrid *input_image,
struct ImageWaveGrid *output_image)
struct ImageWaveGrid *output_image,
int subpixels)
{
int y, x;
for (y = 0; y < output_image->h; y++)
for (x = 0; x < output_image->w; x++)
{
double orig_y, orig_x;
image_wave_shift_interpolate(self, array, x, y, &orig_x, &orig_y);
image_wave_shift_interpolate(self, array,
(double)x/subpixels, (double)y/subpixels,
&orig_x, &orig_y);

double val = image_wave_get_pixel(input_image, orig_x, orig_y);
image_wave_set_pixel(output_image, x, y, val);
}
Expand Down
31 changes: 19 additions & 12 deletions src/vstarstack/library/fine_shift/image_wave_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,12 @@ static PyObject *ImageWave_apply_shift(PyObject *_self,
PyObject *args,
PyObject *kwds)
{
int subpixels;
PyArrayObject *image;
struct ImageWaveObject *self = (struct ImageWaveObject *)_self;
static char *kwlist[] = {"image", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist,
&image))
static char *kwlist[] = {"image", "subpixels", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oi", kwlist,
&image, &subpixels))
{
PyErr_SetString(PyExc_ValueError, "invalid function arguments");
Py_INCREF(Py_None);
Expand Down Expand Up @@ -260,6 +261,9 @@ static PyObject *ImageWave_apply_shift(PyObject *_self,
.h = dims[0],
};

dims[1] *= subpixels;
dims[0] *= subpixels;

PyArrayObject *output_image = (PyArrayObject *)PyArray_ZEROS(2, dims, NPY_DOUBLE, 0);
struct ImageWaveGrid out = {
.array = PyArray_DATA(output_image),
Expand All @@ -268,25 +272,27 @@ static PyObject *ImageWave_apply_shift(PyObject *_self,
.h = dims[0],
};

image_wave_shift_image(&self->wave, &self->wave.array, &img, &out);
image_wave_shift_image(&self->wave, &self->wave.array, &img, &out, subpixels);
return (PyObject *)output_image;
}

static PyObject *ImageWave_data(PyObject *_self, PyObject *args, PyObject *kwds)
{
struct ImageWaveObject *self = (struct ImageWaveObject *)_self;
int xi, yi;
PyObject *data = PyList_New(self->wave.array.w * self->wave.array.h * 2);
PyObject *data = PyList_New(0);
for (yi = 0; yi < self->wave.array.h; yi++)
for (xi = 0; xi < self->wave.array.w; xi++)
{
double vx = image_wave_get_array(&self->wave.array,
xi, yi, 0);
double vy = image_wave_get_array(&self->wave.array,
xi, yi, 1);

PyList_SetItem(data, yi*self->wave.array.w*2 + xi*2, PyFloat_FromDouble(vx));
PyList_SetItem(data, yi*self->wave.array.w*2 + xi*2 + 1, PyFloat_FromDouble(vy));
double vx = image_wave_get_array(&self->wave.array, xi, yi, 0);
double vy = image_wave_get_array(&self->wave.array, xi, yi, 1);

PyObject *vxv = PyFloat_FromDouble(vx);
PyObject *vyv = PyFloat_FromDouble(vy);
PyList_Append(data, vxv);
PyList_Append(data, vyv);
Py_DECREF(vxv);
Py_DECREF(vyv);
}
PyObject *result = Py_BuildValue("{s:i,s:i,s:i,s:i,s:d,s:O}",
"Nw", self->wave.array.w,
Expand All @@ -295,6 +301,7 @@ static PyObject *ImageWave_data(PyObject *_self, PyObject *args, PyObject *kwds)
"h", self->wave.h,
"spk", self->wave.stretch_penalty_k,
"data", data);
Py_DECREF(data);
return result;
}

Expand Down
35 changes: 16 additions & 19 deletions src/vstarstack/tool/fine_shift/align_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,28 @@

ncpu = vstarstack.tool.cfg.nthreads

def create_aligner(project: vstarstack.tool.cfg.Project, W: int, H: int):
"""Create aligner for the project"""
num_steps = project.config.fine_shift.Nsteps
dh = project.config.fine_shift.dh
gridW = project.config.fine_shift.gridW
gridH = project.config.fine_shift.gridH
spk = project.config.fine_shift.stretchPenaltyCoefficient
min_points = project.config.fine_shift.points_min_len

aligner = Aligner(W, H, gridW, gridH, spk, num_steps, min_points, dh)
return aligner

def align_file(project : vstarstack.tool.cfg.Project,
name : str,
input_image_f : str,
desc_f : str,
output_image_f : str):
output_image_f : str,
subpixels : int):
"""Apply alignment to each file"""
print(name)
print(f"{name}: {input_image_f} : {desc_f} -> {output_image_f} [{subpixels}]")

if not os.path.exists(input_image_f):
return
if not os.path.exists(desc_f):
return

with open(desc_f, encoding='utf8') as f:
descriptor = json.load(f)

df = vstarstack.library.data.DataFrame.load(input_image_f)
w = df.params["w"]
h = df.params["h"]
aligner = create_aligner(project, w, h)
aligner = Aligner()

# apply alignment to file
df = aligner.apply_alignment(df, descriptor)
df = aligner.apply_alignment(df, descriptor, subpixels)
print(f"{name} - aligned")

vstarstack.tool.common.check_dir_exists(output_image_f)
Expand All @@ -72,9 +63,14 @@ def apply(project: vstarstack.tool.cfg.Project, argv: list):
npys = argv[0]
aligns = argv[1]
outputs = argv[2]
if len(argv) >= 4:
subpixels = int(argv[3])
else:
subpixels = 1
else:
npys = project.config.paths.npy_fixed
aligns = project.config.fine_shift.aligns
subpixels = project.config.fine_shift.subpixels
outputs = project.config.paths.aligned

files = vstarstack.tool.common.listfiles(npys, ".zip")
Expand All @@ -83,7 +79,8 @@ def apply(project: vstarstack.tool.cfg.Project, argv: list):
name,
input_image_f,
os.path.join(aligns, name + ".json"),
os.path.join(outputs, name + ".zip"))
os.path.join(outputs, name + ".zip"),
subpixels)
for name, input_image_f in files]
for _ in pool.imap_unordered(_align_file_wrapper, args):
pass
12 changes: 6 additions & 6 deletions src/vstarstack/tool/fine_shift/align_clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import json
import multiprocessing as mp

from vstarstack.library.fine_shift.fine_shift import Aligner
from vstarstack.library.fine_shift.fine_shift import ClusterAlignerBuilder
import vstarstack.tool.usage
import vstarstack.tool.cfg
import vstarstack.tool.configuration
Expand All @@ -37,8 +37,8 @@ def create_aligner(project: vstarstack.tool.cfg.Project, W: int, H: int):
spk = project.config.fine_shift.stretchPenaltyCoefficient
min_points = project.config.fine_shift.points_min_len

aligner = Aligner(W, H, gridW, gridH, spk, num_steps, min_points, dh)
return aligner
aligner_factory = ClusterAlignerBuilder(W, H, gridW, gridH, spk, num_steps, min_points, dh)
return aligner_factory

def align_file(project : vstarstack.tool.cfg.Project,
name : str,
Expand All @@ -55,14 +55,14 @@ def align_file(project : vstarstack.tool.cfg.Project,
df = vstarstack.library.data.DataFrame.load(input_image_f)
w = df.params["w"]
h = df.params["h"]
aligner = create_aligner(project, w, h)
aligner_factory = create_aligner(project, w, h)

# find alignment
desc = aligner.process_alignment_by_clusters(name, clusters)
alignment = aligner_factory.find_alignment(name, clusters)
print(f"{name} - align found")
vstarstack.tool.common.check_dir_exists(desc_f)
with open(desc_f, "w", encoding='utf8') as f:
json.dump(desc, f, ensure_ascii=False, indent=2)
json.dump(alignment, f, ensure_ascii=False, indent=2)

def _align_file_wrapper(arg):
align_file(*arg)
Expand Down
Loading