Added high-res pass
This commit is contained in:
33
negstation/widgets/export_widget.py
Normal file
33
negstation/widgets/export_widget.py
Normal 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}")
|
@ -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 0–255 uint8, resize with PIL, back to float32 [0–1]
|
||||
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)
|
||||
|
@ -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 0–255 uint8, resize with PIL, back to float32 [0–1]
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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 0–255 uint8, resize with PIL, back to float32 [0–1]
|
||||
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
|
||||
|
Reference in New Issue
Block a user