diff --git a/src/vstarstack/library/loaders/classic.py b/src/vstarstack/library/loaders/classic.py index d315c2fe..a7512a6b 100644 --- a/src/vstarstack/library/loaders/classic.py +++ b/src/vstarstack/library/loaders/classic.py @@ -18,10 +18,11 @@ import exifread import vstarstack.library.data +from vstarstack.library.loaders.datatype import check_datatype def readjpeg(fname: str): """Read single image (jpg, png, tiff) file""" - rgb = np.asarray(Image.open(fname)).astype(np.float32) + rgb = np.asarray(Image.open(fname)) shape = rgb.shape shape = (shape[0], shape[1]) @@ -47,14 +48,29 @@ def readjpeg(fname: str): params["weight"] = params["exposure"] * params["gain"] + max_value = np.iinfo(rgb.dtype).max + dataframe = vstarstack.library.data.DataFrame(params, tags) if len(rgb.shape) == 3: - dataframe.add_channel(rgb[:, :, 0], "R", brightness=True, signal=True) - dataframe.add_channel(rgb[:, :, 1], "G", brightness=True, signal=True) - dataframe.add_channel(rgb[:, :, 2], "B", brightness=True, signal=True) + 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") + elif len(rgb.shape) == 2: - dataframe.add_channel(rgb[:, :], "L", brightness=True, signal=True) + 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") else: # unknown shape! pass diff --git a/src/vstarstack/library/loaders/datatype.py b/src/vstarstack/library/loaders/datatype.py new file mode 100644 index 00000000..37584056 --- /dev/null +++ b/src/vstarstack/library/loaders/datatype.py @@ -0,0 +1,23 @@ +"""convert 8 and 16 bit data to 32 bit""" +# +# 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 numpy as np +def check_datatype(data : np.ndarray) -> np.ndarray: + """If 8 bit or 16 bit convert to 32 bit""" + if data.dtype == np.int8 or data.dtype == np.int16: + return data.astype(np.int32) + if data.dtype == np.uint8 or data.dtype == np.uint16: + return data.astype(np.uint32) + return data diff --git a/src/vstarstack/library/loaders/fits.py b/src/vstarstack/library/loaders/fits.py index 1333ab3d..962cfdd9 100644 --- a/src/vstarstack/library/loaders/fits.py +++ b/src/vstarstack/library/loaders/fits.py @@ -13,10 +13,13 @@ # along with this program. If not, see . # +import numpy as np import logging from astropy.io import fits import vstarstack.library.data +from vstarstack.library.loaders.datatype import check_datatype + logger = logging.getLogger(__name__) def readfits(filename: str): @@ -32,11 +35,13 @@ def readfits(filename: str): shape = plane.data.shape if len(shape) == 2: - original = plane.data.reshape((1, shape[0], shape[1])) + original : np.ndarray = plane.data.reshape((1, shape[0], shape[1])) else: - original = plane.data + original : np.ndarray = plane.data shape = original.shape + max_value = np.iinfo(original.dtype).max + params = { "w": shape[2], "h": shape[1], @@ -67,6 +72,7 @@ def readfits(filename: str): slice_names = [] params["weight"] = params["exposure"]*params["gain"] + dataframe = vstarstack.library.data.DataFrame(params, tags) if shape[0] == 1: @@ -87,8 +93,15 @@ def readfits(filename: str): yield None for i, slice_name in enumerate(slice_names): - dataframe.add_channel(original[i, :, :], slice_name, + data = original[i, :, :] + overlight_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 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 1007cac2..dcaa2d6d 100644 --- a/src/vstarstack/library/loaders/nef.py +++ b/src/vstarstack/library/loaders/nef.py @@ -15,9 +15,10 @@ import rawpy import exifread +import numpy as np import vstarstack.library.data - +from vstarstack.library.loaders.datatype import check_datatype def readnef(filename: str): """Read NEF file""" @@ -55,7 +56,14 @@ def readnef(filename: str): for tag_name in tags: printable_tags[tag_name] = tags[tag_name].printable + max_value = np.iinfo(image.dtype).max dataframe = vstarstack.library.data.DataFrame(params, printable_tags) - dataframe.add_channel(image, "raw", encoded=True, brightness=True, signal=True) + 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") dataframe.add_parameter(bayer, "format") yield dataframe diff --git a/src/vstarstack/library/loaders/ser.py b/src/vstarstack/library/loaders/ser.py index 0da622d7..e0deadb5 100644 --- a/src/vstarstack/library/loaders/ser.py +++ b/src/vstarstack/library/loaders/ser.py @@ -20,6 +20,7 @@ import numpy as np import vstarstack.library.data +from vstarstack.library.loaders.datatype import check_datatype logger = logging.getLogger(__name__) @@ -43,7 +44,7 @@ def _serread8(file): """Read 8-byte integer""" return _serread(file, 8, True) -def _read_to_npy(file, bpp, little_endian, shape): +def _read_to_npy(file, bpp, little_endian, shape) -> np.ndarray: """Read block of SER file""" num = 1 for _, dim in enumerate(shape): @@ -200,6 +201,8 @@ def readser(fname: str): "weight" : 1, } + max_value = 2**depth - 1 + with open(fname, "rb") as trailer_f: trailer_offset = 178 + frames * width * height * (bpp * vpp) trailer_f.seek(trailer_offset, 0) @@ -213,5 +216,12 @@ def readser(fname: str): dataframe = vstarstack.library.data.DataFrame(params, tags) index = 0 for index, channel in enumerate(channels): - dataframe.add_channel(frame[:, :, index], channel, **opts) + 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") yield dataframe diff --git a/src/vstarstack/library/loaders/video.py b/src/vstarstack/library/loaders/video.py index a0b9eae8..99b7118a 100644 --- a/src/vstarstack/library/loaders/video.py +++ b/src/vstarstack/library/loaders/video.py @@ -14,8 +14,10 @@ # import cv2 +import numpy as np import vstarstack.library.data +from vstarstack.library.loaders.datatype import check_datatype def read_video(fname: str): """Read frames from video file""" @@ -38,9 +40,17 @@ def read_video(fname: str): "weight" : 1, } + max_value = np.iinfo(frame.dtype).max dataframe = vstarstack.library.data.DataFrame(params, tags) - dataframe.add_channel(frame[:, :, 0], "R", brightness=True, signal=True) - dataframe.add_channel(frame[:, :, 1], "G", brightness=True, signal=True) - dataframe.add_channel(frame[:, :, 2], "B", brightness=True, signal=True) + 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") + yield dataframe frame_id += 1 diff --git a/src/vstarstack/library/loaders/yuv.py b/src/vstarstack/library/loaders/yuv.py index 3d15ec7a..6fdbba15 100644 --- a/src/vstarstack/library/loaders/yuv.py +++ b/src/vstarstack/library/loaders/yuv.py @@ -17,6 +17,8 @@ import numpy as np import vstarstack.library.data +from vstarstack.library.loaders.datatype import check_datatype + logger = logging.getLogger(__name__) def readyuv(fname: str, width: int, height: int): @@ -47,6 +49,6 @@ def readyuv(fname: str, width: int, height: int): logger.info(f"processing frame {frame_id}") dataframe = vstarstack.library.data.DataFrame(params, tags) - dataframe.add_channel(yuv, "raw", encoded=True, signal=True) + dataframe.add_channel(check_datatype(yuv), "raw", encoded=True, signal=True) yield dataframe frame_id += 1 diff --git a/src/vstarstack/library/stars/detect.py b/src/vstarstack/library/stars/detect.py index bc3828a1..edd886e4 100644 --- a/src/vstarstack/library/stars/detect.py +++ b/src/vstarstack/library/stars/detect.py @@ -50,6 +50,7 @@ def _find_stars(gray_image : np.ndarray): """Find stars on image""" shape = gray_image.shape + gray_image = (gray_image.astype(np.float32) / np.amax(gray_image) * 255).astype(np.uint8) gray_image = cv2.GaussianBlur(gray_image, (3, 3), 0) thresh = _threshold(gray_image, _detector_cfg["THRESHOLD_BLOCK_SIZE"],