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)