Added high-res pass

This commit is contained in:
2025-08-03 12:55:31 +02:00
parent 60d28e92d0
commit 9801f69d0b
9 changed files with 233 additions and 69 deletions

View File

@ -0,0 +1,33 @@
import dearpygui.dearpygui as dpg
import numpy as np
from .pipeline_stage_widget import PipelineStageWidget
class ExportStage(PipelineStageWidget):
name = "Export Image"
register = True
has_pipeline_in = True
has_pipeline_out = False
def __init__(self, manager, logger):
super().__init__(manager, logger, default_stage_out="opened_image")
self.manager.bus.subscribe(
"process_full_res", self._on_process_full_res, True)
def create_pipeline_stage_content(self):
dpg.add_text("Some export fields")
def _on_process_full_res(self, data):
self.logger.info("Starting full res pipeline export")
def on_pipeline_data(self, img):
if img is None:
return
self.logger.info("low res image received, ignore")
def on_full_res_pipeline_data(self, img):
if img is None:
return
h, w, _ = img.shape
self.logger.info(f"Full res image received: {w}x{h}")

View File

@ -15,6 +15,11 @@ class OpenImageWidget(PipelineStageWidget):
super().__init__(manager, logger, default_stage_out="opened_image")
self.dialog_tag = dpg.generate_uuid()
self.output_tag = dpg.generate_uuid()
self.img = None
self.img_full = None
self.manager.bus.subscribe(
"process_full_res", self._on_process_full_res, True)
def create_pipeline_stage_content(self):
with dpg.file_dialog(
@ -46,9 +51,30 @@ class OpenImageWidget(PipelineStageWidget):
self.logger.info(f"Selected file '{selection}'")
try:
img = Image.open(selection).convert("RGBA")
arr = np.asarray(img).astype(np.float32) / \
rgba = np.asarray(img).astype(np.float32) / \
255.0 # normalize to [0,1]
# Publish into pipeline
self.manager.pipeline.publish(self.pipeline_stage_out_id, arr)
h, w, _ = rgba.shape
# scale for small version
max_dim = 500
scale = min(1.0, max_dim / w, max_dim / h)
if scale < 1.0:
# convert to 0255 uint8, resize with PIL, back to float32 [01]
pil = Image.fromarray(
(rgba * 255).astype(np.uint8), mode="RGBA")
new_w, new_h = int(w * scale), int(h * scale)
pil = pil.resize((new_w, new_h), Image.LANCZOS)
rgba_small = np.asarray(pil).astype(np.float32) / 255.0
w_small, h_small = new_w, new_h
self.img_full = rgba
self.img = rgba_small
self.manager.pipeline.publish(
self.pipeline_stage_out_id, rgba_small)
except Exception as e:
self.logger.error(f"Failed to load image {selection}: {e}")
def _on_process_full_res(self, data):
self.manager.pipeline.publish(
self.pipeline_stage_out_id, self.img_full, True)

View File

@ -1,6 +1,7 @@
import dearpygui.dearpygui as dpg
import rawpy
import numpy as np
from PIL import Image
from .pipeline_stage_widget import PipelineStageWidget
@ -18,6 +19,8 @@ class OpenRawWidget(PipelineStageWidget):
self.config_group = dpg.generate_uuid()
self.busy_group = dpg.generate_uuid()
self.raw_path = None
self.img = None
self.img_full = None
self.config = {
# Demosaic algorithm
"demosaic_algorithm": rawpy.DemosaicAlgorithm.AHD,
@ -39,6 +42,9 @@ class OpenRawWidget(PipelineStageWidget):
"four_color_rgb": False,
}
self.manager.bus.subscribe(
"process_full_res", self._on_process_full_res, True)
def get_config(self):
return {}
@ -203,6 +209,26 @@ class OpenRawWidget(PipelineStageWidget):
rgba = np.concatenate([rgb_float, alpha], axis=2)
self.manager.pipeline.publish(self.pipeline_stage_out_id, rgba)
# scale for small version
max_dim = 500
scale = min(1.0, max_dim / w, max_dim / h)
if scale < 1.0:
# convert to 0255 uint8, resize with PIL, back to float32 [01]
pil = Image.fromarray((rgba * 255).astype(np.uint8), mode="RGBA")
new_w, new_h = int(w * scale), int(h * scale)
pil = pil.resize((new_w, new_h), Image.LANCZOS)
rgba_small = np.asarray(pil).astype(np.float32) / 255.0
w_small, h_small = new_w, new_h
self.img_full = rgba
self.img = rgba_small
self.manager.pipeline.publish(self.pipeline_stage_out_id, rgba_small)
dpg.configure_item(self.config_group, show=True)
dpg.configure_item(self.busy_group, show=False)
def _on_process_full_res(self, data):
if self.img_full is None:
return
self.manager.pipeline.publish(
self.pipeline_stage_out_id, self.img_full, True)

View File

@ -23,16 +23,21 @@ class PipelineStageWidget(BaseWidget):
self.pipeline_stage_out_id = None
self.pipeline_config_group_tag = dpg.generate_uuid()
self.stage_in_combo = dpg.generate_uuid()
self._last_full = False
if self.has_pipeline_out:
self.pipeline_stage_out_id = self.manager.pipeline.register_stage(
default_stage_out
)
self.manager.bus.subscribe("pipeline_stages", self._on_stage_list, True)
self.manager.bus.subscribe(
"pipeline_stages", self._on_stage_list, True)
if self.has_pipeline_in:
self.pipeline_stage_in_id = 0
self.manager.bus.subscribe("pipeline_stage", self._on_stage_data, True)
self.manager.bus.subscribe(
"pipeline_stage", self._on_stage_data, True)
self.manager.bus.subscribe(
"pipeline_stage_full", self._on_stage_data_full, True)
# force getting all available pipeline stages
self.manager.pipeline.republish_stages()
@ -43,7 +48,8 @@ class PipelineStageWidget(BaseWidget):
label="Stage In",
items=[],
callback=self._on_stage_in_select,
default_value=f"{self.manager.pipeline.get_stage_name(0)} : 0",
default_value=f"{
self.manager.pipeline.get_stage_name(0)} : 0",
tag=self.stage_in_combo
)
if self.has_pipeline_out:
@ -59,11 +65,6 @@ class PipelineStageWidget(BaseWidget):
dpg.add_separator()
self.create_pipeline_stage_content()
def publish_stage(self, img: np.ndarray):
"""Publishes an image to output stage"""
if self.has_pipeline_out:
self.manager.pipeline.publish(self.pipeline_stage_out_id, img)
def create_pipeline_stage_content(self):
"""Must be implemented by the widget, creates the content of the window"""
raise NotImplementedError
@ -72,6 +73,12 @@ class PipelineStageWidget(BaseWidget):
"""Must be implemented by the widget, is called when there is a new image published on the in stage"""
pass
def publish_stage(self, img):
"""Publishes an image to output stage"""
if self.has_pipeline_out:
self.manager.pipeline.publish(
self.pipeline_stage_out_id, img, full_res=self._last_full)
# Callbacks
def _on_window_close(self):
@ -97,12 +104,25 @@ class PipelineStageWidget(BaseWidget):
pipeline_id = data[0]
img = data[1]
if self.has_pipeline_in and pipeline_id == self.pipeline_stage_in_id:
self._last_full = False
self.on_pipeline_data(img)
def _on_stage_data_full(self, data):
pipeline_id = data[0]
img = data[1]
if self.has_pipeline_in and pipeline_id == self.pipeline_stage_in_id:
self._last_full = True
if hasattr(self, "on_full_res_pipeline_data"):
self.on_full_res_pipeline_data(img)
else:
self.on_pipeline_data(img)
# Override the window resize callback
def _on_window_resize(self, data):
win_w, win_h = dpg.get_item_rect_size(self.window_tag)
group_w, group_h = dpg.get_item_rect_size(self.pipeline_config_group_tag)
group_w, group_h = dpg.get_item_rect_size(
self.pipeline_config_group_tag)
group_x, group_y = dpg.get_item_pos(self.pipeline_config_group_tag)
self.window_height = win_h - group_h - group_y - 12
self.window_width = win_w - 7

View File

@ -31,19 +31,13 @@ class PipelineStageViewer(PipelineStageWidget):
if img is None:
return
h, w, _ = img.shape
max_dim = 500
scale = min(1.0, max_dim / w, max_dim / h)
if scale < 1.0:
# convert to 0255 uint8, resize with PIL, back to float32 [01]
pil = Image.fromarray((img * 255).astype(np.uint8), mode="RGBA")
new_w, new_h = int(w * scale), int(h * scale)
pil = pil.resize((new_w, new_h), Image.LANCZOS)
img = np.asarray(pil).astype(np.float32) / 255.0
w, h = new_w, new_h
self.img = img
self.needs_update = True
def on_full_res_pipeline_data(self, img):
pass
def update_texture(self, img: np.ndarray):
"""Only call from update function"""
# TODO show a smaller version of the image to speed things up