Config saving of pipeline in/out
This commit is contained in:
@ -11,6 +11,16 @@ class ImagePipeline:
|
||||
self.stagedata = {}
|
||||
self.stagedata_full = {}
|
||||
|
||||
def load_stages(self, stages:dict):
|
||||
self.stages = stages
|
||||
self.stagedata.clear()
|
||||
self.stagedata_full.clear()
|
||||
self.id_counter = len(stages)
|
||||
for id, stage in self.stages.items():
|
||||
print(id, stage)
|
||||
self.stagedata[id] = None
|
||||
self.stagedata_full[id] = None
|
||||
|
||||
def register_stage(self, name: str):
|
||||
self.stages[self.id_counter] = name
|
||||
self.stagedata[self.id_counter] = None
|
||||
@ -37,13 +47,19 @@ class ImagePipeline:
|
||||
"pipeline_stage", (id, self.stagedata[id]))
|
||||
|
||||
def get_stage_data(self, id: int):
|
||||
if id >= 0 and id < len(self.stages):
|
||||
if id in self.stagedata:
|
||||
return self.stagedata[id]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_stage_data_full(self, id: int):
|
||||
if id in self.stagedata_full:
|
||||
return self.stagedata_full[id]
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_stage_name(self, id: int):
|
||||
if id >= 0 and id < len(self.stages):
|
||||
if id in self.stages:
|
||||
return self.stages[id]
|
||||
else:
|
||||
return None
|
||||
|
@ -20,12 +20,15 @@ class LayoutManager:
|
||||
def save_layout(self):
|
||||
self.logger.info("Saving layout...")
|
||||
dpg.save_init_file(self.INI_PATH)
|
||||
widget_data = [
|
||||
{"widget_type": type(w).__name__, "config": w.get_config()}
|
||||
for w in self.manager.widgets
|
||||
]
|
||||
layout_data = {
|
||||
"pipeline_order" : { k:v for k, v in self.manager.pipeline.stages.items() },
|
||||
"widgets": [
|
||||
{"widget_type": type(w).__name__, "config": w.get_config()}
|
||||
for w in self.manager.widgets
|
||||
]
|
||||
}
|
||||
with open(self.WIDGET_DATA_PATH, "w") as f:
|
||||
json.dump(widget_data, f, indent=4)
|
||||
json.dump(layout_data, f, indent=4)
|
||||
self.logger.info("Layout saved successfully.")
|
||||
|
||||
def load_layout(self):
|
||||
@ -33,10 +36,17 @@ class LayoutManager:
|
||||
if not os.path.exists(self.WIDGET_DATA_PATH):
|
||||
return
|
||||
with open(self.WIDGET_DATA_PATH, "r") as f:
|
||||
widget_data = json.load(f)
|
||||
layout_data = json.load(f)
|
||||
|
||||
# Load all widgets
|
||||
widget_data = layout_data["widgets"]
|
||||
for data in widget_data:
|
||||
if data.get("widget_type") in self.manager.widget_classes:
|
||||
self.manager._add_widget(widget_type=data.get("widget_type"))
|
||||
self.manager._add_widget(widget_type=data.get("widget_type"), config=data.get("config"))
|
||||
|
||||
# Reset the image pipeline and reload it
|
||||
pipelinestages = { int(k):v for k, v in layout_data["pipeline_order"].items() }
|
||||
self.manager.pipeline.load_stages(pipelinestages)
|
||||
|
||||
if os.path.exists(self.INI_PATH):
|
||||
dpg.configure_app(init_file=self.INI_PATH)
|
||||
|
@ -74,12 +74,13 @@ class EditorManager:
|
||||
f"Widget '{name}' is already registered. Overwriting.")
|
||||
self.widget_classes[name] = widget_class
|
||||
|
||||
def _add_widget(self, widget_type: str):
|
||||
def _add_widget(self, widget_type: str, config:dict = {}):
|
||||
WidgetClass = self.widget_classes[widget_type]
|
||||
instance = WidgetClass(self, logger)
|
||||
logger.info(f'Created instance: {str(instance)}')
|
||||
self.widgets.append(instance)
|
||||
instance.create()
|
||||
instance.set_config(config)
|
||||
|
||||
def setup(self):
|
||||
self._discover_and_register_widgets(
|
||||
|
@ -63,6 +63,10 @@ class BaseWidget:
|
||||
def get_config(self):
|
||||
"""Caled by negstation itself, returns the saved widget config"""
|
||||
return self.config
|
||||
|
||||
def set_config(self, config):
|
||||
"""Called by negstation itself but can be overridden by a widget"""
|
||||
self.config = config
|
||||
|
||||
# Callbacks
|
||||
|
||||
|
@ -21,7 +21,7 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
self.raw_path = None
|
||||
self.img = None
|
||||
self.img_full = None
|
||||
self.config = {
|
||||
self.rawconfig = {
|
||||
# Demosaic algorithm
|
||||
"demosaic_algorithm": rawpy.DemosaicAlgorithm.AHD,
|
||||
# Output color space
|
||||
@ -45,9 +45,6 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
self.manager.bus.subscribe(
|
||||
"process_full_res", self._on_process_full_res, True)
|
||||
|
||||
def get_config(self):
|
||||
return {}
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
with dpg.file_dialog(
|
||||
directory_selector=False,
|
||||
@ -73,79 +70,79 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
label="Demosaic",
|
||||
items=[alg.name for alg in rawpy.DemosaicAlgorithm],
|
||||
default_value=rawpy.DemosaicAlgorithm.AHD.name,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"demosaic_algorithm", rawpy.DemosaicAlgorithm[a])
|
||||
)
|
||||
dpg.add_combo(
|
||||
label="Color Space",
|
||||
items=[cs.name for cs in rawpy.ColorSpace],
|
||||
default_value=rawpy.ColorSpace.sRGB.name,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"output_color", rawpy.ColorSpace[a])
|
||||
)
|
||||
dpg.add_combo(
|
||||
label="Output Bits",
|
||||
items=["8", "16"],
|
||||
default_value="16",
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"output_bps", int(a))
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="Use Camera WB",
|
||||
default_value=True,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"use_camera_wb", a)
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="Auto WB",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"use_auto_wb", a)
|
||||
)
|
||||
dpg.add_slider_float(
|
||||
label="Manual WB R Gain",
|
||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"user_wb", (a, self.config["user_wb"][1], self.config["user_wb"][2], self.config["user_wb"][3]))
|
||||
"user_wb", (a, self.rawconfig["user_wb"][1], self.rawconfig["user_wb"][2], self.rawconfig["user_wb"][3]))
|
||||
)
|
||||
dpg.add_slider_float(
|
||||
label="Manual WB G Gain",
|
||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"user_wb", (self.config["user_wb"][0], a, a, self.config["user_wb"][3]))
|
||||
"user_wb", (self.rawconfig["user_wb"][0], a, a, self.rawconfig["user_wb"][3]))
|
||||
)
|
||||
dpg.add_slider_float(
|
||||
label="Manual WB B Gain",
|
||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"user_wb", (self.config["user_wb"][0], self.config["user_wb"][1], self.config["user_wb"][2], a))
|
||||
"user_wb", (self.rawconfig["user_wb"][0], self.rawconfig["user_wb"][1], self.rawconfig["user_wb"][2], a))
|
||||
)
|
||||
dpg.add_slider_float(
|
||||
label="Bright",
|
||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
||||
callback=lambda s, a, u: self.config.__setitem__("bright", a)
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__("bright", a)
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="No Auto Bright",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"no_auto_bright", a)
|
||||
)
|
||||
dpg.add_slider_float(
|
||||
label="Gamma",
|
||||
default_value=1.0, min_value=0.1, max_value=3.0,
|
||||
callback=lambda s, a, u: self.config.__setitem__("gamma", a)
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__("gamma", a)
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="Half-size",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"half_size", a)
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="4-color RGB",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
callback=lambda s, a, u: self.rawconfig.__setitem__(
|
||||
"four_color_rgb", a)
|
||||
)
|
||||
|
||||
@ -179,28 +176,28 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
with rawpy.imread(self.raw_path) as raw:
|
||||
# Prepare postprocess kwargs from config
|
||||
postprocess_args = {
|
||||
'demosaic_algorithm': self.config["demosaic_algorithm"],
|
||||
'output_color': self.config["output_color"],
|
||||
'output_bps': self.config["output_bps"],
|
||||
'bright': self.config["bright"],
|
||||
'no_auto_bright': self.config["no_auto_bright"],
|
||||
'gamma': (1.0, self.config["gamma"]),
|
||||
'half_size': self.config["half_size"],
|
||||
'four_color_rgb': self.config["four_color_rgb"],
|
||||
'demosaic_algorithm': self.rawconfig["demosaic_algorithm"],
|
||||
'output_color': self.rawconfig["output_color"],
|
||||
'output_bps': self.rawconfig["output_bps"],
|
||||
'bright': self.rawconfig["bright"],
|
||||
'no_auto_bright': self.rawconfig["no_auto_bright"],
|
||||
'gamma': (1.0, self.rawconfig["gamma"]),
|
||||
'half_size': self.rawconfig["half_size"],
|
||||
'four_color_rgb': self.rawconfig["four_color_rgb"],
|
||||
}
|
||||
|
||||
if self.config["use_camera_wb"]:
|
||||
if self.rawconfig["use_camera_wb"]:
|
||||
postprocess_args['use_camera_wb'] = True
|
||||
elif self.config["use_auto_wb"]:
|
||||
elif self.rawconfig["use_auto_wb"]:
|
||||
postprocess_args['use_auto_wb'] = True
|
||||
else:
|
||||
postprocess_args['user_wb'] = self.config["user_wb"]
|
||||
postprocess_args['user_wb'] = self.rawconfig["user_wb"]
|
||||
|
||||
# Postprocess into RGB
|
||||
rgb = raw.postprocess(**postprocess_args)
|
||||
|
||||
# Normalize to float32 in 0.0-1.0 range depending on output_bps
|
||||
max_val = (2 ** self.config["output_bps"]) - 1
|
||||
max_val = (2 ** self.rawconfig["output_bps"]) - 1
|
||||
rgb_float = rgb.astype(np.float32) / max_val
|
||||
|
||||
# Add alpha channel (fully opaque)
|
||||
|
@ -23,6 +23,7 @@ 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.stage_out_input = dpg.generate_uuid()
|
||||
self._last_full = False
|
||||
|
||||
if self.has_pipeline_out:
|
||||
@ -30,14 +31,13 @@ class PipelineStageWidget(BaseWidget):
|
||||
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)
|
||||
"pipeline_stage_full", self._on_stage_data_full, True
|
||||
)
|
||||
# force getting all available pipeline stages
|
||||
self.manager.pipeline.republish_stages()
|
||||
|
||||
@ -50,7 +50,7 @@ class PipelineStageWidget(BaseWidget):
|
||||
callback=self._on_stage_in_select,
|
||||
default_value=f"{
|
||||
self.manager.pipeline.get_stage_name(0)} : 0",
|
||||
tag=self.stage_in_combo
|
||||
tag=self.stage_in_combo,
|
||||
)
|
||||
if self.has_pipeline_out:
|
||||
dpg.add_input_text(
|
||||
@ -61,6 +61,7 @@ class PipelineStageWidget(BaseWidget):
|
||||
callback=lambda s, a, u: self.manager.pipeline.rename_stage(
|
||||
self.pipeline_stage_out_id, a
|
||||
),
|
||||
tag=self.stage_out_input,
|
||||
)
|
||||
dpg.add_separator()
|
||||
self.create_pipeline_stage_content()
|
||||
@ -77,7 +78,58 @@ class PipelineStageWidget(BaseWidget):
|
||||
"""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)
|
||||
self.pipeline_stage_out_id, img, full_res=self._last_full
|
||||
)
|
||||
|
||||
def get_config(self):
|
||||
return {
|
||||
"pipeline_config": {
|
||||
"stage_in": self.pipeline_stage_in_id,
|
||||
"stage_out": self.pipeline_stage_out_id,
|
||||
}
|
||||
}
|
||||
|
||||
def set_config(self, config):
|
||||
# Set pipelinedata
|
||||
if "pipeline_config" in config:
|
||||
if self.has_pipeline_in:
|
||||
self.pipeline_stage_in_id = config["pipeline_config"]["stage_in"]
|
||||
if self.has_pipeline_out:
|
||||
self.pipeline_stage_out_id = config["pipeline_config"]["stage_out"]
|
||||
self._update_ui_from_state()
|
||||
|
||||
def _update_ui_from_state(self):
|
||||
"""
|
||||
Refresh the ‘Stage In’ combo (and ‘Stage Out’ input) so
|
||||
that:
|
||||
1. its items list matches the current pipeline stages, and
|
||||
2. its displayed value matches the saved ID.
|
||||
"""
|
||||
# --- Build the ordered list of "name : id" labels ---
|
||||
ordered = sorted(self.manager.pipeline.stages.items(), key=lambda kv: kv[0])
|
||||
labels = [f"{name} : {sid}" for sid, name in ordered]
|
||||
|
||||
# --- Update Stage In combo ---
|
||||
if self.has_pipeline_in:
|
||||
dpg.configure_item(self.stage_in_combo, items=labels)
|
||||
# set the combo's value if the saved ID still exists
|
||||
sid = self.pipeline_stage_in_id
|
||||
if sid in self.manager.pipeline.stages:
|
||||
name = self.manager.pipeline.get_stage_name(sid)
|
||||
dpg.set_value(self.stage_in_combo, f"{name} : {sid}")
|
||||
else:
|
||||
# clear if it no longer exists
|
||||
dpg.set_value(self.stage_in_combo, "")
|
||||
|
||||
# --- Update Stage Out input text ---
|
||||
if self.has_pipeline_out:
|
||||
# show the stage name (without ID) or blank if missing
|
||||
sid = self.pipeline_stage_out_id
|
||||
if sid in self.manager.pipeline.stages:
|
||||
name = self.manager.pipeline.get_stage_name(sid)
|
||||
dpg.set_value(self.stage_out_input, name)
|
||||
else:
|
||||
dpg.set_value(self.stage_out_input, "")
|
||||
|
||||
# Callbacks
|
||||
|
||||
@ -88,8 +140,7 @@ class PipelineStageWidget(BaseWidget):
|
||||
|
||||
def _on_stage_list(self, stagelist):
|
||||
if self.has_pipeline_in:
|
||||
stages = [f"{stage} : {id}" for id, stage in stagelist.items()]
|
||||
dpg.configure_item(self.stage_in_combo, items=stages)
|
||||
self._update_ui_from_state()
|
||||
|
||||
def _on_stage_in_select(self, sender, selected_stage: str):
|
||||
d = selected_stage.split(" : ")
|
||||
@ -121,8 +172,7 @@ class PipelineStageWidget(BaseWidget):
|
||||
|
||||
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
|
||||
|
Reference in New Issue
Block a user