diff --git a/src/vstarstack/library/loaders/classic.py b/src/vstarstack/library/loaders/classic.py
index a7512a6..45bbca9 100644
--- a/src/vstarstack/library/loaders/classic.py
+++ b/src/vstarstack/library/loaders/classic.py
@@ -56,21 +56,21 @@ def readjpeg(fname: str):
for channel_name, channel_index in [("R",0), ("G", 1), ("B", 2)]:
data = rgb[:,:,channel_index]
dataframe.add_channel(check_datatype(data), channel_name, brightness=True, signal=True)
- overlight_idx = np.where(data >= max_value*0.99)
- if len(overlight_idx) > 0:
- weight = np.ones(data.shape)*params["weight"]
- weight[overlight_idx] = 0
- dataframe.add_channel(weight, f"weight-{channel_name}", weight=True)
- dataframe.add_channel_link(channel_name, f"weight-{channel_name}", "weight")
+ saturated_idx = np.where(data >= max_value*0.99)
+ if len(saturated_idx) > 0:
+ saturated = np.zeros(rgb.shape, dtype=np.bool)
+ saturated[saturated_idx] = True
+ dataframe.add_channel(saturated, f"saturated-{channel_name}", saturation=True)
+ dataframe.add_channel_link(channel_name, f"saturated-{channel_name}", "saturation")
elif len(rgb.shape) == 2:
dataframe.add_channel(check_datatype(rgb), "L", brightness=True, signal=True)
- overlight_idx = np.where(rgb >= max_value*0.99)
- if len(overlight_idx) > 0:
- weight = np.ones(rgb.shape)*params["weight"]
- weight[overlight_idx] = 0
- dataframe.add_channel(weight, f"weight-L", weight=True)
- dataframe.add_channel_link("L", f"weight-L", "weight")
+ saturated_idx = np.where(rgb >= max_value*0.99)
+ if len(saturated_idx) > 0:
+ saturated = np.zeros(rgb.shape, dtype=np.bool)
+ saturated[saturated_idx] = True
+ dataframe.add_channel(saturated, "saturated-L", saturation=True)
+ dataframe.add_channel_link("L", "saturated-L", "saturation")
else:
# unknown shape!
pass
diff --git a/src/vstarstack/library/loaders/fits.py b/src/vstarstack/library/loaders/fits.py
index 962cfdd..4b64323 100644
--- a/src/vstarstack/library/loaders/fits.py
+++ b/src/vstarstack/library/loaders/fits.py
@@ -94,14 +94,14 @@ def readfits(filename: str):
for i, slice_name in enumerate(slice_names):
data = original[i, :, :]
- overlight_idx = np.where(data >= max_value*0.99)
+ saturated_idx = np.where(data >= max_value*0.99)
dataframe.add_channel(check_datatype(data), slice_name,
brightness=True, signal=True, encoded=encoded)
- if len(overlight_idx) > 0:
- weight = np.ones(data.shape)*params["weight"]
- weight[overlight_idx] = 0
- dataframe.add_channel(weight, f"weight-{slice_name}", weight=True)
- dataframe.add_channel_link(slice_name, f"weight-{slice_name}", "weight")
+ if len(saturated_idx) > 0:
+ saturated = np.zeros(data.shape, dtype=np.bool)
+ saturated[saturated_idx] = True
+ dataframe.add_channel(saturated, f"saturated-{slice_name}", saturation=True)
+ dataframe.add_channel_link(slice_name, f"saturated-{slice_name}", "saturation")
if bayer is not None:
dataframe.add_parameter(bayer, "format")
yield dataframe
diff --git a/src/vstarstack/library/loaders/nef.py b/src/vstarstack/library/loaders/nef.py
index dcaa2d6..a1ff5b8 100644
--- a/src/vstarstack/library/loaders/nef.py
+++ b/src/vstarstack/library/loaders/nef.py
@@ -59,11 +59,11 @@ def readnef(filename: str):
max_value = np.iinfo(image.dtype).max
dataframe = vstarstack.library.data.DataFrame(params, printable_tags)
dataframe.add_channel(check_datatype(image), "raw", encoded=True, brightness=True, signal=True)
- overlight_idx = np.where(image >= max_value*0.99)
- if len(overlight_idx) > 0:
- weight = np.ones(image.shape)*params["weight"]
- weight[overlight_idx] = 0
- dataframe.add_channel(weight, f"weight-raw", weight=True)
- dataframe.add_channel_link("raw", f"weight-raw", "weight")
+ saturated_idx = np.where(image >= max_value*0.99)
+ if len(saturated_idx) > 0:
+ saturated = np.zeros(image.shape, dtype=np.bool)
+ saturated[saturated_idx] = True
+ dataframe.add_channel(saturated, f"saturated-raw", saturation=True)
+ dataframe.add_channel_link("raw", f"saturated-raw", "saturation")
dataframe.add_parameter(bayer, "format")
yield dataframe
diff --git a/src/vstarstack/library/loaders/ser.py b/src/vstarstack/library/loaders/ser.py
index e0deadb..0588671 100644
--- a/src/vstarstack/library/loaders/ser.py
+++ b/src/vstarstack/library/loaders/ser.py
@@ -218,10 +218,10 @@ def readser(fname: str):
for index, channel in enumerate(channels):
data = frame[:, :, index]
dataframe.add_channel(check_datatype(data), channel, **opts)
- overlight_idx = np.where(data >= max_value*0.99)
- if len(overlight_idx) > 0:
- weight = np.ones(data.shape)*params["weight"]
- weight[overlight_idx] = 0
- dataframe.add_channel(weight, f"weight-{channel}", weight=True)
- dataframe.add_channel_link(channel, f"weight-{channel}", "weight")
+ saturated_idx = np.where(data >= max_value*0.99)
+ if len(saturated_idx) > 0:
+ saturated = np.zeros(data.shape, dtype=np.bool)
+ saturated[saturated_idx] = True
+ dataframe.add_channel(saturated, f"saturated-{channel}", saturation=True)
+ dataframe.add_channel_link(channel, f"saturated-{channel}", "saturation")
yield dataframe
diff --git a/src/vstarstack/library/loaders/video.py b/src/vstarstack/library/loaders/video.py
index 99b7118..f027448 100644
--- a/src/vstarstack/library/loaders/video.py
+++ b/src/vstarstack/library/loaders/video.py
@@ -45,12 +45,12 @@ def read_video(fname: str):
for channel_name, channel_index in [("R",0), ("G", 1), ("B", 2)]:
data = frame[:,:,channel_index]
dataframe.add_channel(check_datatype(data), channel_name, brightness=True, signal=True)
- overlight_idx = np.where(data >= max_value*0.99)
- if len(overlight_idx) > 0:
- weight = np.ones(data.shape)*params["weight"]
- weight[overlight_idx] = 0
- dataframe.add_channel(weight, f"weight-{channel_name}", weight=True)
- dataframe.add_channel_link(channel_name, f"weight-{channel_name}", "weight")
+ saturated_idx = np.where(data >= max_value*0.99)
+ if len(saturated_idx) > 0:
+ saturated = np.zeros(data.shape, dtype=np.bool)
+ saturated[saturated_idx] = True
+ dataframe.add_channel(saturated, f"saturated-{channel_name}", saturation=True)
+ dataframe.add_channel_link(channel_name, f"saturated-{channel_name}", "saturation")
yield dataframe
frame_id += 1
diff --git a/src/vstarstack/library/merge/simple_add.py b/src/vstarstack/library/merge/simple_add.py
index 9c52ea9..8a2195d 100644
--- a/src/vstarstack/library/merge/simple_add.py
+++ b/src/vstarstack/library/merge/simple_add.py
@@ -22,7 +22,7 @@
logger = logging.getLogger(__name__)
-def simple_add(images : vstarstack.library.common.IImageSource) -> DataFrame:
+def simple_add(images : vstarstack.library.common.IImageSource, ignore_saturated : bool = False) -> DataFrame:
"""Just add images"""
summary = {}
@@ -49,6 +49,13 @@ def simple_add(images : vstarstack.library.common.IImageSource) -> DataFrame:
weight_k = 1
weight = np.ones(channel.shape, dtype=np.float64) * weight_k
+ if ignore_saturated:
+ saturated, _, _ = img.get_linked_channel(channel_name, "saturation")
+ if saturated is not None:
+ mask = (saturated == 0).astype(np.uint)
+ weight = weight * mask
+ channel = channel * mask
+
if channel_name not in summary:
summary[channel_name] = deepcopy(channel.astype(np.float64))
summary_weight[channel_name] = deepcopy(weight)
diff --git a/src/vstarstack/library/movement/move_image.py b/src/vstarstack/library/movement/move_image.py
index 5446ded..6c01b9d 100644
--- a/src/vstarstack/library/movement/move_image.py
+++ b/src/vstarstack/library/movement/move_image.py
@@ -16,8 +16,6 @@
import numpy as np
import scipy.ndimage
-import vstarstack.library.common
-import vstarstack.library.data
from vstarstack.library.data import DataFrame
import vstarstack.library.projection.tools
@@ -36,8 +34,6 @@ def _generate_points(height, width):
def move_image(image: np.ndarray,
transformation: basic_movement.Movement,
input_proj, output_proj,*,
- image_weight: float = 1,
- image_weight_layer: np.ndarray | None = None,
output_shape: tuple | None = None,
interpolate : bool = True):
"""
@@ -64,10 +60,6 @@ def move_image(image: np.ndarray,
w = shape[1]
shifted = np.zeros(shape)
- shifted_weight_layer = np.zeros(shape)
-
- if image_weight_layer is None:
- image_weight_layer = np.ones(image.shape)*image_weight
positions = _generate_points(h, w)
original_positions = transformation.reverse(positions.astype('double'),
@@ -88,8 +80,7 @@ def move_image(image: np.ndarray,
shifted = scipy.ndimage.geometric_transform(image, crdtf, output_shape=shape, order=3)
else:
shifted = scipy.ndimage.geometric_transform(image, crdtf, output_shape=shape, order=0)
- shifted_weight_layer = scipy.ndimage.geometric_transform(image_weight_layer, crdtf, output_shape=shape, order=3)
- return shifted, shifted_weight_layer
+ return shifted
def move_dataframe(dataframe: DataFrame,
transformation: basic_movement.Movement,*,
@@ -137,21 +128,47 @@ def move_dataframe(dataframe: DataFrame,
else:
weight = np.ones(image.shape)
+ if channel in dataframe.links["saturation"]:
+ saturation_channel = dataframe.links["saturation"][channel]
+ saturation, _ = dataframe.get_channel(saturation_channel)
+ else:
+ saturation = None
+
if interpolate is not None:
apply_interpolate = interpolate
else:
apply_interpolate = not dataframe.get_channel_option(channel, "cfa")
- shifted, shifted_weight = move_image(image,
- transformation,
- input_proj,
- output_proj,
- image_weight_layer=weight,
- output_shape=output_shape,
- interpolate=apply_interpolate)
+ shifted = move_image(image,
+ transformation,
+ input_proj,
+ output_proj,
+ output_shape=output_shape,
+ interpolate=apply_interpolate)
+
+ shifted_weight = move_image(weight,
+ transformation,
+ input_proj,
+ output_proj,
+ output_shape=output_shape,
+ interpolate=apply_interpolate)
+
+ if saturation is not None:
+ shifted_saturation = move_image(saturation.astype(np.float32),
+ transformation,
+ input_proj,
+ output_proj,
+ output_shape=output_shape,
+ interpolate=apply_interpolate)
+ shifted_saturation = abs(shifted_saturation) > 1e-6
+ else:
+ shifted_saturation = None
output_dataframe.add_channel(shifted, channel, **opts)
output_dataframe.add_channel(shifted_weight, weight_channel, weight=True)
output_dataframe.add_channel_link(channel, weight_channel, "weight")
+ if shifted_saturation is not None:
+ output_dataframe.add_channel(shifted_saturation, saturation_channel, saturation=True)
+ output_dataframe.add_channel_link(channel, saturation_channel, "saturation")
return output_dataframe
diff --git a/src/vstarstack/library/photometry/star_profile.py b/src/vstarstack/library/photometry/star_profile.py
new file mode 100644
index 0000000..228c1b0
--- /dev/null
+++ b/src/vstarstack/library/photometry/star_profile.py
@@ -0,0 +1,46 @@
+#
+# 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 .
+#
+
+from typing import Tuple
+import numpy as np
+import math
+
+def find_star_profile(image : np.ndarray, npoints : int) -> Tuple[float, np.ndarray]:
+ """Find star profile"""
+ w = image.shape[1]
+ h = image.shape[0]
+ cx = int(w/2)
+ cy = int(h/2)
+ profile = np.zeros((w*h, 2))
+ for y in range(h):
+ dy = y - cy
+ for x in range(w):
+ dx = x - cx
+ r = math.sqrt(dx**2+dy**2)
+ profile[y*w+x, 0] = r
+ profile[y*w+x, 1] = image[y,x]
+ rmax = np.max(profile[:,0])
+ smooth_profile = np.zeros((npoints,))
+ for i in range(npoints):
+ r_1 = rmax * i / npoints
+ r_2 = rmax * (i+1) / npoints
+
+ idxs = np.where(np.logical_and(profile[:,0] >= r_1, profile[:,0] <= r_2))
+ smooth_profile[i] = np.mean(profile[idxs,1])
+
+ return rmax, smooth_profile
+
+def restore_star_profile(profile : np.ndarray) -> np.ndarray:
+ """If star profile has saturation, this function restores center"""
+
\ No newline at end of file
diff --git a/src/vstarstack/library/stars/restore_saturated.py b/src/vstarstack/library/stars/restore_saturated.py
new file mode 100644
index 0000000..7279f0c
--- /dev/null
+++ b/src/vstarstack/library/stars/restore_saturated.py
@@ -0,0 +1,14 @@
+#
+# 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 .
+#
+
diff --git a/src/vstarstack/tool/merge.py b/src/vstarstack/tool/merge.py
index 218c8aa..96f506c 100644
--- a/src/vstarstack/tool/merge.py
+++ b/src/vstarstack/tool/merge.py
@@ -24,6 +24,8 @@
from vstarstack.library.common import FilesImageSource
+IGNS = vstarstack.tool.cfg.get_param("ignore-saturated", bool, False)
+
def simple_add(project: vstarstack.tool.cfg.Project, argv: list):
"""Calculate simple sum of images"""
if len(argv) > 0:
@@ -35,7 +37,7 @@ def simple_add(project: vstarstack.tool.cfg.Project, argv: list):
imgs = vstarstack.tool.common.listfiles(path_images, ".zip")
filenames = [img[1] for img in imgs]
- dataframe = vstarstack.library.merge.simple_add.simple_add(FilesImageSource(filenames))
+ dataframe = vstarstack.library.merge.simple_add.simple_add(FilesImageSource(filenames), IGNS)
if dataframe is not None:
vstarstack.tool.common.check_dir_exists(out)
dataframe.store(out)