Compare commits
11 Commits
af2bbe93cc
...
master
Author | SHA1 | Date | |
---|---|---|---|
66d542f742 | |||
2a6498f117 | |||
447354266c | |||
a423ceb669 | |||
34cc28897d | |||
1a5abca8e1 | |||
bbedfe6d35 | |||
a0ba27b35c | |||
ac33ec5d8d | |||
9801f69d0b | |||
60d28e92d0 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
env
|
||||
env
|
||||
out.png
|
169
negative_raw.nef.xmp
Normal file
169
negative_raw.nef.xmp
Normal file
@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:exif="http://ns.adobe.com/exif/1.0/"
|
||||
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
||||
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
|
||||
xmlns:darktable="http://darktable.sf.net/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:lr="http://ns.adobe.com/lightroom/1.0/"
|
||||
exif:DateTimeOriginal="2025:07:26 16:46:04.370"
|
||||
xmp:Rating="0"
|
||||
xmpMM:DerivedFrom="negative_raw.nef"
|
||||
darktable:import_timestamp="63889663903342009"
|
||||
darktable:change_timestamp="-1"
|
||||
darktable:export_timestamp="63889663939611277"
|
||||
darktable:print_timestamp="-1"
|
||||
darktable:xmp_version="5"
|
||||
darktable:raw_params="0"
|
||||
darktable:auto_presets_applied="1"
|
||||
darktable:history_end="11"
|
||||
darktable:iop_order_version="4"
|
||||
darktable:history_auto_hash="d760eeba94580ffacc8656d2f1b16c05"
|
||||
darktable:history_current_hash="d760eeba94580ffacc8656d2f1b16c05">
|
||||
<darktable:masks_history>
|
||||
<rdf:Seq/>
|
||||
</darktable:masks_history>
|
||||
<darktable:history>
|
||||
<rdf:Seq>
|
||||
<rdf:li
|
||||
darktable:num="0"
|
||||
darktable:operation="rawprepare"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="2"
|
||||
darktable:params="000000000000000000000000000000005802580258025802143e000000000000"
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
<rdf:li
|
||||
darktable:num="1"
|
||||
darktable:operation="demosaic"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="4"
|
||||
darktable:params="0000000000000000000000000500000001000000cdcc4c3e"
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
<rdf:li
|
||||
darktable:num="2"
|
||||
darktable:operation="colorin"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="7"
|
||||
darktable:params="gz48eJzjZhgFowABWAbaAaNgwAEAOQAAEA=="
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
<rdf:li
|
||||
darktable:num="3"
|
||||
darktable:operation="colorout"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="5"
|
||||
darktable:params="gz35eJxjZBgFo4CBAQAEEAAC"
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
<rdf:li
|
||||
darktable:num="4"
|
||||
darktable:operation="gamma"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="1"
|
||||
darktable:params="0000000000000000"
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
<rdf:li
|
||||
darktable:num="5"
|
||||
darktable:operation="temperature"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="4"
|
||||
darktable:params="004003400000803f0080b33f0000000004000000"
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
<rdf:li
|
||||
darktable:num="6"
|
||||
darktable:operation="highlights"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="4"
|
||||
darktable:params="050000000000803f00000000000000000000803f000000001e00000006000000cdcccc3e000000400000000000000000"
|
||||
darktable:multi_name=""
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYGBgYARiCQYYOOHEgAZY0QWgejBBgz0Ej1Q+dcF/IADRAGwSHQY="/>
|
||||
<rdf:li
|
||||
darktable:num="7"
|
||||
darktable:operation="channelmixerrgb"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="3"
|
||||
darktable:params="gz04eJxjYGiwZ8AAxIqRD9iAmAmIWYCYEYifft9jZ6e11076Z6sryC5GqDwAs0IJJA=="
|
||||
darktable:multi_name="_builtin_scene-referred default"
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz08eJxjYGBgYAFiCQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dlAx68oBEMbFxwX+AwGIBgCbGCeh"/>
|
||||
<rdf:li
|
||||
darktable:num="8"
|
||||
darktable:operation="exposure"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="6"
|
||||
darktable:params="00000000000080b93333333f00004842000080c001000000"
|
||||
darktable:multi_name="_builtin_scene-referred default"
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz08eJxjYGBgYAFiCQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dlAx68oBEMbFxwX+AwGIBgCbGCeh"/>
|
||||
<rdf:li
|
||||
darktable:num="9"
|
||||
darktable:operation="filmicrgb"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="6"
|
||||
darktable:params="gz02eJybNXOy49kzXw60vp7owAAGDkD6hBMEQ8AsoBr9ZRU2IDG1yHQHruvKQHaDPUz+7BkfO2YgzQLEjFAxRiQ2DDBBaQC8LhQY"
|
||||
darktable:multi_name="_builtin_scene-referred default"
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz08eJxjYGBgYAFiCQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dlAx68oBEMbFxwX+AwGIBgCbGCeh"/>
|
||||
<rdf:li
|
||||
darktable:num="10"
|
||||
darktable:operation="flip"
|
||||
darktable:enabled="1"
|
||||
darktable:modversion="2"
|
||||
darktable:params="ffffffff"
|
||||
darktable:multi_name="_builtin_auto"
|
||||
darktable:multi_name_hand_edited="0"
|
||||
darktable:multi_priority="0"
|
||||
darktable:blendop_version="14"
|
||||
darktable:blendop_params="gz11eJxjYIAACQYYOOHEgAZY0QWAgBGLGANDgz0Ej1Q+dcF/IADRAGpyHQU="/>
|
||||
</rdf:Seq>
|
||||
</darktable:history>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>darktable</rdf:li>
|
||||
<rdf:li>exported</rdf:li>
|
||||
<rdf:li>format</rdf:li>
|
||||
<rdf:li>nef</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<lr:hierarchicalSubject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>darktable|exported</rdf:li>
|
||||
<rdf:li>darktable|format|nef</rdf:li>
|
||||
</rdf:Bag>
|
||||
</lr:hierarchicalSubject>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>
|
@ -9,10 +9,22 @@ class ImagePipeline:
|
||||
self.id_counter = 0
|
||||
self.stages = {}
|
||||
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
|
||||
self.stagedata_full[self.id_counter] = None
|
||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
||||
self.id_counter += 1
|
||||
return self.id_counter-1
|
||||
@ -22,18 +34,32 @@ class ImagePipeline:
|
||||
self.stages[id] = name
|
||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
||||
|
||||
def publish(self, id: int, img: np.ndarray):
|
||||
self.stagedata[id] = img.astype(np.float32)
|
||||
self.bus.publish_deferred("pipeline_stage", (id, self.stagedata[id]))
|
||||
def publish(self, id: int, img: np.ndarray, full_res=False):
|
||||
if img is None:
|
||||
return
|
||||
if full_res:
|
||||
self.stagedata_full[id] = img.astype(np.float32)
|
||||
self.bus.publish_deferred(
|
||||
"pipeline_stage_full", (id, self.stagedata_full[id]))
|
||||
else:
|
||||
self.stagedata[id] = img.astype(np.float32)
|
||||
self.bus.publish_deferred(
|
||||
"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)
|
||||
|
@ -13,8 +13,7 @@ from .layout_manager import LayoutManager
|
||||
|
||||
from .widgets.base_widget import BaseWidget
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s")
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -62,24 +61,51 @@ class EditorManager:
|
||||
and cls is not ModuleBaseWidget
|
||||
and cls.register
|
||||
):
|
||||
logging.info(
|
||||
f" -> Found and registered widget: {name}")
|
||||
logging.info(f" -> Found and registered widget: {name}")
|
||||
self._register_widget(name, cls)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to import widget '{py_file.name}': {e}")
|
||||
|
||||
def _register_widget(self, name: str, widget_class: object):
|
||||
if name in self.widget_classes:
|
||||
logging.warning(
|
||||
f"Widget '{name}' is already registered. Overwriting.")
|
||||
logging.warning(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)}')
|
||||
logger.info(f"Created instance: {str(instance)}")
|
||||
self.widgets.append(instance)
|
||||
instance.create()
|
||||
instance.set_config(config)
|
||||
|
||||
def _on_drag(self, sender, app_data, user_data):
|
||||
self.bus.publish_deferred(
|
||||
"mouse_dragged",
|
||||
{
|
||||
"button": (
|
||||
"right"
|
||||
if app_data[0] == 0
|
||||
else ("left" if app_data[0] == 1 else ("middle"))
|
||||
),
|
||||
"delta": (app_data[1], app_data[2]),
|
||||
},
|
||||
)
|
||||
|
||||
def _on_scroll(self, sender, app_data, user_data):
|
||||
self.bus.publish_deferred("mouse_scrolled", app_data)
|
||||
|
||||
def _on_release(self, sender, app_data, user_data):
|
||||
self.bus.publish_deferred(
|
||||
"mouse_released",
|
||||
{
|
||||
"button": (
|
||||
"right"
|
||||
if app_data == 0
|
||||
else ("left" if app_data == 1 else ("middle"))
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
self._discover_and_register_widgets(
|
||||
@ -96,8 +122,12 @@ class EditorManager:
|
||||
label="Save Layout", callback=self.layout_manager.save_layout
|
||||
)
|
||||
dpg.add_menu_item(
|
||||
label="Quit", callback=lambda: dpg.stop_dearpygui()
|
||||
label="Run full-res pipeline",
|
||||
callback=lambda: self.bus.publish_deferred(
|
||||
"process_full_res", None
|
||||
),
|
||||
)
|
||||
dpg.add_menu_item(label="Quit", callback=lambda: dpg.stop_dearpygui())
|
||||
|
||||
with dpg.menu(label="View"):
|
||||
for widget_name in sorted(self.widget_classes.keys()):
|
||||
@ -107,6 +137,11 @@ class EditorManager:
|
||||
user_data=widget_name,
|
||||
)
|
||||
|
||||
with dpg.handler_registry() as self.handler_registry:
|
||||
dpg.add_mouse_drag_handler(callback=self._on_drag, threshold=1.0)
|
||||
dpg.add_mouse_wheel_handler(callback=self._on_scroll)
|
||||
dpg.add_mouse_release_handler(callback=self._on_release)
|
||||
|
||||
def run(self):
|
||||
self.setup()
|
||||
dpg.setup_dearpygui()
|
||||
|
@ -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
|
||||
|
||||
|
142
negstation/widgets/export_widget.py
Normal file
142
negstation/widgets/export_widget.py
Normal file
@ -0,0 +1,142 @@
|
||||
# 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}")
|
||||
|
||||
|
||||
import os
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
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):
|
||||
# we don’t register an output stage — this widget only consumes
|
||||
super().__init__(manager, logger, default_stage_out="unused")
|
||||
# tags for our “Save As” dialog
|
||||
self._save_dialog_tag = dpg.generate_uuid()
|
||||
self._save_path = f"{os.getcwd()}/out.png"
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
# Button to pop up the file-save dialog
|
||||
dpg.add_button(label="Save As…",
|
||||
callback=lambda s,a,u: dpg.configure_item(self._save_dialog_tag, show=True))
|
||||
|
||||
# File dialog for choosing export path & extension
|
||||
with dpg.file_dialog(
|
||||
directory_selector=False,
|
||||
show=False,
|
||||
callback=self._on_save_selected,
|
||||
tag=self._save_dialog_tag,
|
||||
width=400,
|
||||
height=300
|
||||
):
|
||||
dpg.add_file_extension("PNG {.png}")
|
||||
dpg.add_file_extension("JPEG {.jpg,.jpeg}")
|
||||
dpg.add_file_extension("TIFF {.tif,.tiff}")
|
||||
dpg.add_file_extension("All files {.*}")
|
||||
|
||||
with dpg.child_window(autosize_x=True, autosize_y=True, horizontal_scrollbar=True):
|
||||
self.path_label = dpg.add_text("out.png")
|
||||
|
||||
def _on_save_selected(self, sender, app_data):
|
||||
"""
|
||||
Called when the user picks a filename in the Save As… dialog.
|
||||
Stores the path for the next full-res pass.
|
||||
"""
|
||||
# app_data is a dict with 'current_path' and 'selections'
|
||||
path = os.path.join(
|
||||
app_data["current_path"],
|
||||
app_data["file_name"]
|
||||
)
|
||||
self._save_path = path
|
||||
self.logger.info(f"Export path set to: {path}")
|
||||
dpg.set_value(self.path_label, path)
|
||||
|
||||
def on_pipeline_data(self, img: np.ndarray):
|
||||
# ignore all previews
|
||||
return
|
||||
|
||||
def on_full_res_pipeline_data(self, img: np.ndarray):
|
||||
"""
|
||||
Receives the full-resolution NumPy image when the user fires
|
||||
the “Run full-res pipeline” action. Saves via Pillow.
|
||||
"""
|
||||
if img is None:
|
||||
self.logger.error("on_full_res_pipeline_data called with None image")
|
||||
return
|
||||
if not self._save_path:
|
||||
self.logger.warning("No export path set — click Save As… first")
|
||||
return
|
||||
|
||||
# Decide bit depth by extension
|
||||
ext = os.path.splitext(self._save_path)[-1].lower()
|
||||
# Convert floats → uint; or leave ints alone
|
||||
if np.issubdtype(img.dtype, np.floating):
|
||||
if ext in (".tif", ".tiff"):
|
||||
arr = np.clip(img * 65535.0, 0, 65535).astype(np.uint16)
|
||||
else:
|
||||
arr = np.clip(img * 255.0, 0, 255).astype(np.uint8)
|
||||
else:
|
||||
arr = img
|
||||
|
||||
# Determine PIL mode
|
||||
mode = None
|
||||
if arr.ndim == 2:
|
||||
mode = "L"
|
||||
elif arr.ndim == 3:
|
||||
c = arr.shape[2]
|
||||
if c == 3:
|
||||
mode = "RGB"
|
||||
elif c == 4:
|
||||
mode = "RGBA"
|
||||
|
||||
try:
|
||||
im = Image.fromarray(arr, mode) if mode else Image.fromarray(arr)
|
||||
|
||||
# JPEG doesn’t support alpha — drop it
|
||||
if ext in (".jpg", ".jpeg") and im.mode == "RGBA":
|
||||
im = im.convert("RGB")
|
||||
|
||||
im.save(self._save_path)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to save image to {self._save_path}: {e}")
|
||||
else:
|
||||
self.logger.info(f"Saved full-resolution image to {self._save_path}")
|
178
negstation/widgets/framing_widget.py
Normal file
178
negstation/widgets/framing_widget.py
Normal file
@ -0,0 +1,178 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
import time
|
||||
import scipy.ndimage as snd
|
||||
|
||||
from .stage_viewer_widget import PipelineStageViewer
|
||||
|
||||
|
||||
class FramingWidget(PipelineStageViewer):
|
||||
name = "Framing"
|
||||
register = True
|
||||
has_pipeline_in = True
|
||||
has_pipeline_out = True
|
||||
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger)
|
||||
|
||||
self.needs_publishing = False
|
||||
|
||||
# Rotation line endpoints (canvas coords)
|
||||
self.rot_start = None # (x, y)
|
||||
self.rot_end = None # (x, y)
|
||||
self.angle = 0.0 # computed deskew angle
|
||||
|
||||
# Crop rect endpoints (canvas coords)
|
||||
self.crop_start = None # (x, y)
|
||||
self.crop_end = None # (x, y)
|
||||
|
||||
self.manager.bus.subscribe("img_clicked", self.on_click)
|
||||
self.manager.bus.subscribe("img_dragged", self.on_drag)
|
||||
self.manager.bus.subscribe("img_scrolled", self.on_scroll)
|
||||
self.manager.bus.subscribe("img_released", self.on_release)
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
super().create_pipeline_stage_content()
|
||||
|
||||
def on_full_res_pipeline_data(self, img):
|
||||
if img is None:
|
||||
return
|
||||
img = self.rotate(img)
|
||||
self.publish_stage(self.crop(img))
|
||||
|
||||
def on_pipeline_data(self, img):
|
||||
if img is None:
|
||||
return
|
||||
self.original_img = img.copy()
|
||||
self.needs_update = True
|
||||
|
||||
# Apply transformation and publish
|
||||
self.img = self.rotate(img)
|
||||
self.publish_stage(self.crop(self.img))
|
||||
|
||||
def update_texture(self, img):
|
||||
super().update_texture(img)
|
||||
|
||||
# Draw rotation guide
|
||||
if self.rot_start and self.rot_end:
|
||||
p0 = self._pos_to_canvas(self.rot_start)
|
||||
p1 = self._pos_to_canvas(self.rot_end)
|
||||
dpg.draw_line(
|
||||
p1=p0,
|
||||
p2=p1,
|
||||
color=(255, 0, 0, 255),
|
||||
thickness=2,
|
||||
parent=self.drawlist,
|
||||
)
|
||||
|
||||
# Draw crop rect
|
||||
if self.crop_start and self.crop_end:
|
||||
p0 = self._pos_to_canvas(self.crop_start)
|
||||
p1 = self._pos_to_canvas(self.crop_end)
|
||||
dpg.draw_rectangle(
|
||||
pmin=p0,
|
||||
pmax=p1,
|
||||
color=(255, 0, 255, 255),
|
||||
thickness=2,
|
||||
parent=self.drawlist,
|
||||
)
|
||||
|
||||
if self.needs_publishing:
|
||||
img = self.crop(self.img)
|
||||
self.publish_stage(img)
|
||||
|
||||
def _pos_to_canvas(self, img_pos):
|
||||
x, y = img_pos
|
||||
ix, iy = self.image_position
|
||||
iw, ih = self.img.shape[1], self.img.shape[0]
|
||||
sw, sh = self.scaled_size
|
||||
return (ix + x / iw * sw, iy + y / ih * sh)
|
||||
|
||||
def on_click(self, data):
|
||||
if data.get("obj") is not self:
|
||||
return
|
||||
x, y = data.get("pos")
|
||||
if data["button"] == "left":
|
||||
self.rot_start = (x, y)
|
||||
self.rot_end = None
|
||||
elif data["button"] == "right":
|
||||
self.crop_start = (x,y)
|
||||
self.crop_end = None
|
||||
self.needs_update = True
|
||||
|
||||
def on_drag(self, data):
|
||||
if data.get("obj") is not self:
|
||||
return
|
||||
x, y = data.get("pos")
|
||||
if data["button"] == "left":
|
||||
self.rot_end = (x, y)
|
||||
elif data["button"] == "right":
|
||||
self.crop_end = (x,y)
|
||||
self.needs_update = True
|
||||
|
||||
def on_scroll(self, data):
|
||||
if data.get("obj") is not self:
|
||||
return
|
||||
|
||||
def on_release(self, data):
|
||||
if data.get("obj") is not self:
|
||||
return
|
||||
if data["button"] == "left":
|
||||
# End of rotation line dragging
|
||||
dx = self.rot_end[0] - self.rot_start[0]
|
||||
dy = self.rot_end[1] - self.rot_start[1]
|
||||
self.angle += np.degrees(np.arctan2(dy, dx))
|
||||
# Do the rotation
|
||||
self.img = self.rotate(self.original_img)
|
||||
# Delete lines
|
||||
self.rot_start = self.rot_end = None
|
||||
self.needs_publishing = True
|
||||
elif data["button"] == "right":
|
||||
self.needs_publishing = True
|
||||
|
||||
self.needs_update = True
|
||||
|
||||
def rotate(self, img):
|
||||
img = snd.rotate(img, self.angle, reshape=False)
|
||||
return img
|
||||
|
||||
def crop(self, img):
|
||||
"""
|
||||
Crop `img` according to a rectangle drawn in the DISPLAY,
|
||||
by converting display‐coords to image‐coords via percentages.
|
||||
"""
|
||||
# only crop if user has dragged out a box
|
||||
if not (self.crop_start and self.crop_end):
|
||||
return img
|
||||
|
||||
# 1) get actual image dims
|
||||
h, w = img.shape[:2]
|
||||
|
||||
# 2) get display info (set in update_texture)
|
||||
disp_h, disp_w, _ = self.img.shape
|
||||
|
||||
# 3) unpack the two drag points (in screen coords)
|
||||
sx, sy = self.crop_start
|
||||
ex, ey = self.crop_end
|
||||
|
||||
# 4) normalize into [0..1]
|
||||
nx0 = np.clip(sx / disp_w, 0.0, 1.0)
|
||||
ny0 = np.clip(sy / disp_h, 0.0, 1.0)
|
||||
nx1 = np.clip(ex / disp_w, 0.0, 1.0)
|
||||
ny1 = np.clip(ey / disp_h, 0.0, 1.0)
|
||||
|
||||
# 5) map to image‐pixel coords
|
||||
x0 = int(nx0 * w)
|
||||
y0 = int(ny0 * h)
|
||||
x1 = int(nx1 * w)
|
||||
y1 = int(ny1 * h)
|
||||
|
||||
# 6) sort & clamp again just in case
|
||||
x0, x1 = sorted((max(0, x0), min(w, x1)))
|
||||
y0, y1 = sorted((max(0, y0), min(h, y1)))
|
||||
|
||||
# 7) avoid zero‐area
|
||||
if x0 == x1 or y0 == y1:
|
||||
return img
|
||||
|
||||
return img[y0:y1, x0:x1]
|
@ -41,6 +41,9 @@ class HistogramWidget(PipelineStageWidget):
|
||||
self.img = img
|
||||
self.needs_redraw = True
|
||||
|
||||
def on_full_res_pipeline_data(self, img):
|
||||
pass
|
||||
|
||||
def update(self):
|
||||
# TODO move calculations to on_pipeline_data
|
||||
if not self.needs_redraw or self.img is None:
|
||||
|
@ -14,7 +14,7 @@ class InvertStage(PipelineStageWidget):
|
||||
super().__init__(manager, logger, default_stage_out="inverted_image")
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
dpg.add_text("Inversion is happening here")
|
||||
pass
|
||||
|
||||
def on_pipeline_data(self, img):
|
||||
if img is None:
|
||||
|
@ -22,6 +22,8 @@ class LogWindowWidget(BaseWidget):
|
||||
self.initialized = False
|
||||
self.log_tag = dpg.generate_uuid()
|
||||
self.log_lines = []
|
||||
self.update_counter = 0
|
||||
self.need_update = False
|
||||
|
||||
# Create and attach handler
|
||||
self.handler = DPGLogHandler(self._on_log)
|
||||
@ -40,7 +42,7 @@ class LogWindowWidget(BaseWidget):
|
||||
self.log_lines.append(msg)
|
||||
if self.initialized:
|
||||
dpg.add_text(msg, parent=self.log_tag)
|
||||
dpg.set_y_scroll(self.log_tag, dpg.get_y_scroll_max(self.log_tag))
|
||||
self.need_update = True
|
||||
|
||||
def on_resize(self, width: int, height: int):
|
||||
# Optional: could resize child window here if needed
|
||||
@ -51,3 +53,11 @@ class LogWindowWidget(BaseWidget):
|
||||
self.logger.removeHandler(self.handler)
|
||||
self.handler = None
|
||||
super()._on_window_close()
|
||||
|
||||
def update(self):
|
||||
if self.need_update:
|
||||
self.update_counter += 1
|
||||
if self.update_counter == 10:
|
||||
dpg.set_y_scroll(self.log_tag, dpg.get_y_scroll_max(self.log_tag))
|
||||
self.update_counter = 0
|
||||
self.need_update = False
|
@ -14,7 +14,7 @@ class MonochromeStage(PipelineStageWidget):
|
||||
super().__init__(manager, logger, default_stage_out="monochrome")
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
dpg.add_text("Converting to grayscale...")
|
||||
pass
|
||||
|
||||
def on_pipeline_data(self, img):
|
||||
if img is None:
|
||||
|
@ -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,8 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import rawpy
|
||||
import numpy as np
|
||||
import ast
|
||||
from PIL import Image
|
||||
|
||||
from .pipeline_stage_widget import PipelineStageWidget
|
||||
|
||||
@ -18,7 +20,9 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
self.config_group = dpg.generate_uuid()
|
||||
self.busy_group = dpg.generate_uuid()
|
||||
self.raw_path = None
|
||||
self.config = {
|
||||
self.img = None
|
||||
self.img_full = None
|
||||
self.rawconfig = {
|
||||
# Demosaic algorithm
|
||||
"demosaic_algorithm": rawpy.DemosaicAlgorithm.AHD,
|
||||
# Output color space
|
||||
@ -39,8 +43,22 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
"four_color_rgb": False,
|
||||
}
|
||||
|
||||
def get_config(self):
|
||||
return {}
|
||||
self.demosaic_combo_tag = dpg.generate_uuid()
|
||||
self.color_space_combo_tag = dpg.generate_uuid()
|
||||
self.output_bps_combo_tag = dpg.generate_uuid()
|
||||
self.use_cam_wb_tag = dpg.generate_uuid()
|
||||
self.auto_wb_tag = dpg.generate_uuid()
|
||||
self.wb_r_slider_tag = dpg.generate_uuid()
|
||||
self.wb_g_slider_tag = dpg.generate_uuid()
|
||||
self.wb_b_slider_tag = dpg.generate_uuid()
|
||||
self.bright_slider_tag = dpg.generate_uuid()
|
||||
self.no_auto_bright_tag = dpg.generate_uuid()
|
||||
self.gamma_slider_tag = dpg.generate_uuid()
|
||||
self.half_size_tag = dpg.generate_uuid()
|
||||
self.four_color_tag = dpg.generate_uuid()
|
||||
|
||||
self.manager.bus.subscribe(
|
||||
"process_full_res", self._on_process_full_res, True)
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
with dpg.file_dialog(
|
||||
@ -57,90 +75,125 @@ class OpenRawWidget(PipelineStageWidget):
|
||||
dpg.add_file_extension(".*")
|
||||
|
||||
with dpg.group(tag=self.config_group):
|
||||
# -- Open / Reprocess buttons --
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(label="Open File...",
|
||||
callback=self._on_open_file)
|
||||
dpg.add_button(label="Reprocess",
|
||||
callback=self._process_and_publish)
|
||||
dpg.add_button(label="Open File...", callback=self._on_open_file)
|
||||
dpg.add_button(label="Reprocess", callback=self._process_and_publish)
|
||||
|
||||
# -- Demosaic combo --
|
||||
dpg.add_combo(
|
||||
label="Demosaic",
|
||||
items=[alg.name for alg in rawpy.DemosaicAlgorithm],
|
||||
default_value=rawpy.DemosaicAlgorithm.AHD.name,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"demosaic_algorithm", rawpy.DemosaicAlgorithm[a])
|
||||
default_value=self.rawconfig["demosaic_algorithm"].name,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"demosaic_algorithm", rawpy.DemosaicAlgorithm[a]
|
||||
),
|
||||
tag=self.demosaic_combo_tag
|
||||
)
|
||||
|
||||
# -- Color space combo --
|
||||
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__(
|
||||
"output_color", rawpy.ColorSpace[a])
|
||||
default_value=self.rawconfig["output_color"].name,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"output_color", rawpy.ColorSpace[a]
|
||||
),
|
||||
tag=self.color_space_combo_tag
|
||||
)
|
||||
|
||||
# -- Bits per sample --
|
||||
dpg.add_combo(
|
||||
label="Output Bits",
|
||||
items=["8", "16"],
|
||||
default_value="16",
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"output_bps", int(a))
|
||||
items=["8","16"],
|
||||
default_value=str(self.rawconfig["output_bps"]),
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"output_bps", int(a)
|
||||
),
|
||||
tag=self.output_bps_combo_tag
|
||||
)
|
||||
|
||||
# -- Checkboxes & sliders --
|
||||
dpg.add_checkbox(
|
||||
label="Use Camera WB",
|
||||
default_value=True,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"use_camera_wb", a)
|
||||
default_value=self.rawconfig["use_camera_wb"],
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__("use_camera_wb", a),
|
||||
tag=self.use_cam_wb_tag
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="Auto WB",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"use_auto_wb", a)
|
||||
default_value=self.rawconfig["use_auto_wb"],
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__("use_auto_wb", a),
|
||||
tag=self.auto_wb_tag
|
||||
)
|
||||
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]))
|
||||
default_value=self.rawconfig["user_wb"][0],
|
||||
min_value=0.1, max_value=4.0,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"user_wb", (a, self.rawconfig["user_wb"][1],
|
||||
self.rawconfig["user_wb"][2], self.rawconfig["user_wb"][3])
|
||||
),
|
||||
tag=self.wb_r_slider_tag
|
||||
)
|
||||
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]))
|
||||
default_value=self.rawconfig["user_wb"][1],
|
||||
min_value=0.1, max_value=4.0,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"user_wb", (self.rawconfig["user_wb"][0], a,
|
||||
self.rawconfig["user_wb"][2], self.rawconfig["user_wb"][3])
|
||||
),
|
||||
tag=self.wb_g_slider_tag
|
||||
)
|
||||
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))
|
||||
default_value=self.rawconfig["user_wb"][2],
|
||||
min_value=0.1, max_value=4.0,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"user_wb", (self.rawconfig["user_wb"][0],
|
||||
self.rawconfig["user_wb"][1], a,
|
||||
self.rawconfig["user_wb"][3])
|
||||
),
|
||||
tag=self.wb_b_slider_tag
|
||||
)
|
||||
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)
|
||||
default_value=self.rawconfig["bright"],
|
||||
min_value=0.1, max_value=4.0,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__("bright", a),
|
||||
tag=self.bright_slider_tag
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="No Auto Bright",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"no_auto_bright", a)
|
||||
default_value=self.rawconfig["no_auto_bright"],
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"no_auto_bright", a
|
||||
),
|
||||
tag=self.no_auto_bright_tag
|
||||
)
|
||||
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)
|
||||
default_value=self.rawconfig["gamma"],
|
||||
min_value=0.1, max_value=3.0,
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__("gamma", a),
|
||||
tag=self.gamma_slider_tag
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="Half-size",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"half_size", a)
|
||||
default_value=self.rawconfig["half_size"],
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"half_size", a
|
||||
),
|
||||
tag=self.half_size_tag
|
||||
)
|
||||
dpg.add_checkbox(
|
||||
label="4-color RGB",
|
||||
default_value=False,
|
||||
callback=lambda s, a, u: self.config.__setitem__(
|
||||
"four_color_rgb", a)
|
||||
default_value=self.rawconfig["four_color_rgb"],
|
||||
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||
"four_color_rgb", a
|
||||
),
|
||||
tag=self.four_color_tag
|
||||
)
|
||||
|
||||
with dpg.group(tag=self.busy_group, show=False):
|
||||
@ -173,28 +226,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)
|
||||
@ -203,6 +256,76 @@ 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)
|
||||
|
||||
def get_config(self):
|
||||
config = super().get_config()
|
||||
config["raw_config"] = { k:str(v) for k, v in self.rawconfig.items() }
|
||||
return config
|
||||
|
||||
def set_config(self, config):
|
||||
super().set_config(config)
|
||||
raw_cfg = config.get("raw_config", {})
|
||||
if raw_cfg:
|
||||
# parse each back into Python types
|
||||
for k, v in raw_cfg.items():
|
||||
if k == "demosaic_algorithm":
|
||||
# "DemosaicAlgorithm.AHD" → "AHD"
|
||||
name = v.split(".", 1)[1]
|
||||
self.rawconfig[k] = rawpy.DemosaicAlgorithm[name]
|
||||
elif k == "output_color":
|
||||
name = v.split(".", 1)[1]
|
||||
self.rawconfig[k] = rawpy.ColorSpace[name]
|
||||
elif k == "output_bps":
|
||||
self.rawconfig[k] = int(v)
|
||||
elif k in ("use_camera_wb","use_auto_wb",
|
||||
"no_auto_bright","half_size","four_color_rgb"):
|
||||
self.rawconfig[k] = (v == "True")
|
||||
elif k in ("bright","gamma"):
|
||||
self.rawconfig[k] = float(v)
|
||||
elif k == "user_wb":
|
||||
self.rawconfig[k] = tuple(ast.literal_eval(v))
|
||||
|
||||
# now that rawconfig is back to real types, update the UI
|
||||
self._update_raw_ui()
|
||||
|
||||
def _update_raw_ui(self):
|
||||
"""Push current self.rawconfig values back into all controls."""
|
||||
# combos want the enum.name or string
|
||||
dpg.set_value(self.demosaic_combo_tag, self.rawconfig["demosaic_algorithm"].name)
|
||||
dpg.set_value(self.color_space_combo_tag, self.rawconfig["output_color"].name)
|
||||
dpg.set_value(self.output_bps_combo_tag, str(self.rawconfig["output_bps"]))
|
||||
|
||||
# checkboxes & sliders
|
||||
dpg.set_value(self.use_cam_wb_tag, self.rawconfig["use_camera_wb"])
|
||||
dpg.set_value(self.auto_wb_tag, self.rawconfig["use_auto_wb"])
|
||||
dpg.set_value(self.wb_r_slider_tag, self.rawconfig["user_wb"][0])
|
||||
dpg.set_value(self.wb_g_slider_tag, self.rawconfig["user_wb"][1])
|
||||
dpg.set_value(self.wb_b_slider_tag, self.rawconfig["user_wb"][2])
|
||||
dpg.set_value(self.bright_slider_tag, self.rawconfig["bright"])
|
||||
dpg.set_value(self.no_auto_bright_tag, self.rawconfig["no_auto_bright"])
|
||||
dpg.set_value(self.gamma_slider_tag, self.rawconfig["gamma"])
|
||||
dpg.set_value(self.half_size_tag, self.rawconfig["half_size"])
|
||||
dpg.set_value(self.four_color_tag, self.rawconfig["four_color_rgb"])
|
121
negstation/widgets/orientation_widget.py
Normal file
121
negstation/widgets/orientation_widget.py
Normal file
@ -0,0 +1,121 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
|
||||
from .pipeline_stage_widget import PipelineStageWidget
|
||||
|
||||
|
||||
class OrientationStage(PipelineStageWidget):
|
||||
name = "Orient Image"
|
||||
register = True
|
||||
has_pipeline_in = True
|
||||
has_pipeline_out = True
|
||||
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger, default_stage_out="oriented_image")
|
||||
|
||||
self.rotation = 0
|
||||
self.mirror_h = False
|
||||
self.mirror_v = False
|
||||
|
||||
self.rotation_combo_tag = dpg.generate_uuid()
|
||||
self.mirror_h_tag = dpg.generate_uuid()
|
||||
self.mirror_v_tag = dpg.generate_uuid()
|
||||
|
||||
self.last_image = None
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
dpg.add_combo(
|
||||
label="Rotation",
|
||||
items=["0°", "90°", "180°", "270°"],
|
||||
default_value="0°",
|
||||
callback=self._on_rotation_change,
|
||||
tag=self.rotation_combo_tag
|
||||
)
|
||||
|
||||
dpg.add_checkbox(
|
||||
label="Mirror Horizontal",
|
||||
default_value=False,
|
||||
callback=self._on_mirror_h_change,
|
||||
tag=self.mirror_h_tag
|
||||
)
|
||||
|
||||
dpg.add_checkbox(
|
||||
label="Mirror Vertical",
|
||||
default_value=False,
|
||||
callback=self._on_mirror_v_change,
|
||||
tag=self.mirror_v_tag
|
||||
)
|
||||
|
||||
def _on_rotation_change(self, sender, value, user_data):
|
||||
degree_map = {
|
||||
"0°": 0,
|
||||
"90°": 90,
|
||||
"180°": 180,
|
||||
"270°": 270
|
||||
}
|
||||
self.rotation = degree_map.get(value, 0)
|
||||
self.on_pipeline_data(self.last_img)
|
||||
|
||||
def _on_mirror_h_change(self, sender, value, user_data):
|
||||
self.mirror_h = value
|
||||
self.on_pipeline_data(self.last_img)
|
||||
|
||||
def _on_mirror_v_change(self, sender, value, user_data):
|
||||
self.mirror_v = value
|
||||
self.on_pipeline_data(self.last_img)
|
||||
|
||||
def on_pipeline_data(self, img):
|
||||
if img is None:
|
||||
return
|
||||
|
||||
self.last_img = img
|
||||
img_out = img.copy()
|
||||
|
||||
# Apply rotation
|
||||
if self.rotation == 90:
|
||||
img_out = np.rot90(img_out, k=3)
|
||||
elif self.rotation == 180:
|
||||
img_out = np.rot90(img_out, k=2)
|
||||
elif self.rotation == 270:
|
||||
img_out = np.rot90(img_out, k=1)
|
||||
|
||||
# Apply mirroring
|
||||
if self.mirror_h:
|
||||
img_out = np.fliplr(img_out)
|
||||
if self.mirror_v:
|
||||
img_out = np.flipud(img_out)
|
||||
|
||||
self.publish_stage(img_out)
|
||||
|
||||
def get_config(self):
|
||||
config = super().get_config()
|
||||
config["orientation"] = {
|
||||
"rotation": self.rotation,
|
||||
"mirror_h": str(self.mirror_h),
|
||||
"mirror_v": str(self.mirror_v),
|
||||
}
|
||||
return config
|
||||
|
||||
def set_config(self, config):
|
||||
super().set_config(config)
|
||||
orient_cfg = config.get("orientation", {})
|
||||
|
||||
self.rotation = int(orient_cfg.get("rotation", 0))
|
||||
self.mirror_h = orient_cfg.get("mirror_h", "False") == "True"
|
||||
self.mirror_v = orient_cfg.get("mirror_v", "False") == "True"
|
||||
|
||||
self._update_ui()
|
||||
|
||||
def _update_ui(self):
|
||||
# Update rotation combo
|
||||
reverse_map = {
|
||||
0: "0°",
|
||||
90: "90°",
|
||||
180: "180°",
|
||||
270: "270°"
|
||||
}
|
||||
dpg.set_value(self.rotation_combo_tag, reverse_map.get(self.rotation, "0°"))
|
||||
|
||||
# Update checkboxes
|
||||
dpg.set_value(self.mirror_h_tag, self.mirror_h)
|
||||
dpg.set_value(self.mirror_v_tag, self.mirror_v)
|
@ -23,6 +23,8 @@ 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:
|
||||
self.pipeline_stage_out_id = self.manager.pipeline.register_stage(
|
||||
@ -33,6 +35,9 @@ class PipelineStageWidget(BaseWidget):
|
||||
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_full", self._on_stage_data_full, True
|
||||
)
|
||||
# force getting all available pipeline stages
|
||||
self.manager.pipeline.republish_stages()
|
||||
|
||||
@ -43,8 +48,9 @@ class PipelineStageWidget(BaseWidget):
|
||||
label="Stage In",
|
||||
items=[],
|
||||
callback=self._on_stage_in_select,
|
||||
default_value=f"{self.manager.pipeline.get_stage_name(0)} : 0",
|
||||
tag=self.stage_in_combo
|
||||
default_value=f"{
|
||||
self.manager.pipeline.get_stage_name(0)} : 0",
|
||||
tag=self.stage_in_combo,
|
||||
)
|
||||
if self.has_pipeline_out:
|
||||
dpg.add_input_text(
|
||||
@ -55,14 +61,11 @@ 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()
|
||||
|
||||
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)
|
||||
with dpg.group():
|
||||
self.create_pipeline_stage_content()
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
"""Must be implemented by the widget, creates the content of the window"""
|
||||
@ -72,6 +75,65 @@ 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
|
||||
)
|
||||
# Reset last_full
|
||||
self._last_full = False
|
||||
|
||||
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
|
||||
|
||||
def _on_window_close(self):
|
||||
@ -81,8 +143,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(" : ")
|
||||
@ -97,14 +158,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_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
|
||||
self.window_offset_y = group_h + group_y + 3
|
||||
self.on_resize(win_w, win_h)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from .pipeline_stage_widget import PipelineStageWidget
|
||||
|
||||
|
||||
@ -13,77 +12,219 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger, default_stage_in="pipeline_out")
|
||||
self.texture_tag = dpg.generate_uuid()
|
||||
self.drawlist = None
|
||||
self.img = None
|
||||
self.needs_update = False
|
||||
self.registry = manager.texture_registry
|
||||
self.needs_update = False
|
||||
self.canvas_handler = None
|
||||
self.scaled_size = (0, 0)
|
||||
self.image_position = (0, 0)
|
||||
self._last_tex_size = (0, 0)
|
||||
|
||||
self.manager.bus.subscribe("mouse_dragged", self._on_mouse_drag, False)
|
||||
self.manager.bus.subscribe("mouse_scrolled", self._on_mouse_scroll, False)
|
||||
self.manager.bus.subscribe("mouse_released", self._on_mouse_release, False)
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
# Create an empty dynamic texture
|
||||
dpg.add_dynamic_texture(
|
||||
1, 1, [0, 0, 0, 0], tag=self.texture_tag, parent=self.registry
|
||||
)
|
||||
self.image_item = dpg.add_image(self.texture_tag)
|
||||
|
||||
# Add drawlist
|
||||
with dpg.drawlist(width=-1, height=-1) as self.drawlist:
|
||||
pass
|
||||
|
||||
# Register click handler
|
||||
with dpg.item_handler_registry() as self.canvas_handler:
|
||||
dpg.add_item_clicked_handler(callback=self.on_canvas_click)
|
||||
dpg.bind_item_handler_registry(self.drawlist, self.canvas_handler)
|
||||
|
||||
def on_canvas_click(self, sender, app_data, user_data):
|
||||
mouse_x, mouse_y = dpg.get_mouse_pos(local=False)
|
||||
canvas_x, canvas_y = dpg.get_item_rect_min(self.drawlist)
|
||||
local_x = mouse_x - canvas_x
|
||||
local_y = mouse_y - canvas_y
|
||||
|
||||
img_x, img_y = self.image_position
|
||||
img_w, img_h = self.scaled_size
|
||||
|
||||
if (
|
||||
local_x >= img_x
|
||||
and local_x < img_x + img_w
|
||||
and local_y >= img_y
|
||||
and local_y < img_y + img_h
|
||||
and dpg.is_item_focused(self.window_tag)
|
||||
):
|
||||
# calculate the image coordinate
|
||||
x = int((local_x - img_x) * self.img.shape[1] / img_w)
|
||||
y = int((local_y - img_y) * self.img.shape[0] / img_h)
|
||||
self.manager.bus.publish_deferred(
|
||||
"img_clicked",
|
||||
{
|
||||
"stage_id": self.pipeline_stage_in_id,
|
||||
"pos": (x, y),
|
||||
"button": (
|
||||
"right"
|
||||
if app_data[0] == 0
|
||||
else ("left" if app_data[0] == 1 else ("middle"))
|
||||
),
|
||||
"obj":self,
|
||||
},
|
||||
)
|
||||
|
||||
def _on_mouse_drag(self, data):
|
||||
mouse_x, mouse_y = dpg.get_mouse_pos(local=False)
|
||||
canvas_x, canvas_y = dpg.get_item_rect_min(self.drawlist)
|
||||
local_x = mouse_x - canvas_x
|
||||
local_y = mouse_y - canvas_y
|
||||
|
||||
img_x, img_y = self.image_position
|
||||
img_w, img_h = self.scaled_size
|
||||
|
||||
if (
|
||||
local_x >= img_x
|
||||
and local_x < img_x + img_w
|
||||
and local_y >= img_y
|
||||
and local_y < img_y + img_h
|
||||
and dpg.is_item_focused(self.window_tag)
|
||||
):
|
||||
# calculate the image coordinate
|
||||
x = int((local_x - img_x) * self.img.shape[1] / img_w)
|
||||
y = int((local_y - img_y) * self.img.shape[0] / img_h)
|
||||
self.manager.bus.publish_deferred(
|
||||
"img_dragged",
|
||||
{
|
||||
"stage_id": self.pipeline_stage_in_id,
|
||||
"pos": (x, y),
|
||||
"button": data['button'],
|
||||
"delta": data['delta'],
|
||||
"obj":self,
|
||||
},
|
||||
)
|
||||
|
||||
def _on_mouse_scroll(self, data):
|
||||
mouse_x, mouse_y = dpg.get_mouse_pos(local=False)
|
||||
canvas_x, canvas_y = dpg.get_item_rect_min(self.drawlist)
|
||||
local_x = mouse_x - canvas_x
|
||||
local_y = mouse_y - canvas_y
|
||||
|
||||
img_x, img_y = self.image_position
|
||||
img_w, img_h = self.scaled_size
|
||||
|
||||
if (
|
||||
local_x >= img_x
|
||||
and local_x < img_x + img_w
|
||||
and local_y >= img_y
|
||||
and local_y < img_y + img_h
|
||||
and dpg.is_item_focused(self.window_tag)
|
||||
):
|
||||
# calculate the image coordinate
|
||||
x = int((local_x - img_x) * self.img.shape[1] / img_w)
|
||||
y = int((local_y - img_y) * self.img.shape[0] / img_h)
|
||||
self.manager.bus.publish_deferred(
|
||||
"img_scrolled",
|
||||
{
|
||||
"stage_id": self.pipeline_stage_in_id,
|
||||
"pos": (x, y),
|
||||
"delta": data,
|
||||
"obj":self,
|
||||
},
|
||||
)
|
||||
|
||||
def _on_mouse_release(self, data):
|
||||
mouse_x, mouse_y = dpg.get_mouse_pos(local=False)
|
||||
canvas_x, canvas_y = dpg.get_item_rect_min(self.drawlist)
|
||||
local_x = mouse_x - canvas_x
|
||||
local_y = mouse_y - canvas_y
|
||||
|
||||
img_x, img_y = self.image_position
|
||||
img_w, img_h = self.scaled_size
|
||||
|
||||
if (
|
||||
local_x >= img_x
|
||||
and local_x < img_x + img_w
|
||||
and local_y >= img_y
|
||||
and local_y < img_y + img_h
|
||||
and dpg.is_item_focused(self.window_tag)
|
||||
):
|
||||
# calculate the image coordinate
|
||||
x = int((local_x - img_x) * self.img.shape[1] / img_w)
|
||||
y = int((local_y - img_y) * self.img.shape[0] / img_h)
|
||||
self.manager.bus.publish_deferred(
|
||||
"img_released",
|
||||
{
|
||||
"stage_id": self.pipeline_stage_in_id,
|
||||
"pos": (x, y),
|
||||
"button": data['button'],
|
||||
"obj":self,
|
||||
},
|
||||
)
|
||||
|
||||
def on_resize(self, width, height):
|
||||
self.needs_update = True
|
||||
|
||||
def on_pipeline_data(self, img):
|
||||
# Resize if needed
|
||||
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
|
||||
if img is None:
|
||||
dpg.configure_item(self.image_item, show=False)
|
||||
return
|
||||
|
||||
# Only recreate the texture if its size changed
|
||||
h, w, _ = img.shape
|
||||
flat = img.flatten().tolist()
|
||||
if (w, h) != self._last_tex_size or not dpg.does_item_exist(self.texture_tag):
|
||||
# remove old one if present
|
||||
if dpg.does_item_exist(self.texture_tag):
|
||||
dpg.delete_item(self.texture_tag)
|
||||
# create a new dynamic texture at the new size
|
||||
dpg.add_dynamic_texture(
|
||||
width=w,
|
||||
height=h,
|
||||
default_value=flat,
|
||||
tag=self.texture_tag,
|
||||
parent=self.registry,
|
||||
)
|
||||
self._last_tex_size = (w, h)
|
||||
else:
|
||||
# just upload new pixels into the existing texture
|
||||
dpg.set_value(self.texture_tag, flat)
|
||||
|
||||
if dpg.does_item_exist(self.texture_tag):
|
||||
dpg.delete_item(self.texture_tag)
|
||||
dpg.add_dynamic_texture(
|
||||
width=w,
|
||||
height=h,
|
||||
default_value=flat,
|
||||
tag=self.texture_tag,
|
||||
parent=self.registry,
|
||||
)
|
||||
# Clear old drawings
|
||||
dpg.delete_item(self.drawlist, children_only=True)
|
||||
|
||||
# Draw the image to the screen
|
||||
win_w, win_h = self.window_width, self.window_height
|
||||
avail_w = win_w
|
||||
avail_h = win_h
|
||||
|
||||
scale = min(avail_w / w, avail_h / h) # , 1.0)
|
||||
scale = min(win_w / w, win_h / h)
|
||||
disp_w = int(w * scale)
|
||||
disp_h = int(h * scale)
|
||||
|
||||
x_off = (avail_w - disp_w) / 2
|
||||
y_off = self.window_offset_y
|
||||
x_off = (win_w - disp_w) / 2
|
||||
y_off = (win_h - disp_h) / 2
|
||||
|
||||
dpg.configure_item(
|
||||
self.image_item,
|
||||
texture_tag=self.texture_tag,
|
||||
pos=(x_off, y_off),
|
||||
width=disp_w,
|
||||
height=disp_h,
|
||||
show=True
|
||||
self.scaled_size = (disp_w, disp_h)
|
||||
self.image_position = (x_off, y_off)
|
||||
|
||||
# Draw image
|
||||
dpg.draw_image(
|
||||
self.texture_tag,
|
||||
pmin=(x_off, y_off),
|
||||
pmax=(x_off + disp_w, y_off + disp_h),
|
||||
uv_min=(0, 0),
|
||||
uv_max=(1, 1),
|
||||
parent=self.drawlist,
|
||||
)
|
||||
|
||||
# Resize drawlist
|
||||
dpg.configure_item(self.drawlist, width=win_w, height=win_h)
|
||||
|
||||
def update(self):
|
||||
if self.needs_update:
|
||||
self.needs_update = False
|
||||
|
@ -17,9 +17,9 @@ DockId=0x00000007,0
|
||||
|
||||
[Window][###51]
|
||||
Pos=0,19
|
||||
Size=270,469
|
||||
Size=270,691
|
||||
Collapsed=0
|
||||
DockId=0x0000001F,1
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###59]
|
||||
Pos=0,494
|
||||
@ -46,9 +46,9 @@ DockId=0x0000000E,0
|
||||
|
||||
[Window][###23]
|
||||
Pos=0,19
|
||||
Size=270,469
|
||||
Size=270,437
|
||||
Collapsed=0
|
||||
DockId=0x0000001F,0
|
||||
DockId=0x0000005B,0
|
||||
|
||||
[Window][###29]
|
||||
Pos=0,19
|
||||
@ -60,7 +60,7 @@ DockId=0x00000012,0
|
||||
Pos=198,19
|
||||
Size=602,425
|
||||
Collapsed=0
|
||||
DockId=0x00000011,0
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][###49]
|
||||
Pos=0,495
|
||||
@ -72,7 +72,7 @@ DockId=0x0000000C,0
|
||||
Pos=246,19
|
||||
Size=554,379
|
||||
Collapsed=0
|
||||
DockId=0x00000011,0
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][###60]
|
||||
Pos=0,19
|
||||
@ -81,16 +81,16 @@ Collapsed=0
|
||||
DockId=0x00000012,1
|
||||
|
||||
[Window][###107]
|
||||
Pos=246,400
|
||||
Size=554,200
|
||||
Pos=958,19
|
||||
Size=242,460
|
||||
Collapsed=0
|
||||
DockId=0x00000016,0
|
||||
DockId=0x00000059,0
|
||||
|
||||
[Window][###35]
|
||||
Pos=272,19
|
||||
Size=710,625
|
||||
Size=698,591
|
||||
Collapsed=0
|
||||
DockId=0x00000011,0
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][###43]
|
||||
Pos=0,490
|
||||
@ -99,8 +99,8 @@ Collapsed=0
|
||||
DockId=0x00000020,0
|
||||
|
||||
[Window][###81]
|
||||
Pos=272,646
|
||||
Size=710,154
|
||||
Pos=272,609
|
||||
Size=700,191
|
||||
Collapsed=0
|
||||
DockId=0x00000010,0
|
||||
|
||||
@ -108,25 +108,25 @@ DockId=0x00000010,0
|
||||
Pos=0,646
|
||||
Size=270,154
|
||||
Collapsed=0
|
||||
DockId=0x00000015,1
|
||||
DockId=0x00000013,1
|
||||
|
||||
[Window][###87]
|
||||
Pos=0,646
|
||||
Size=270,154
|
||||
Pos=272,646
|
||||
Size=710,154
|
||||
Collapsed=0
|
||||
DockId=0x00000015,0
|
||||
DockId=0x00000046,0
|
||||
|
||||
[Window][###111]
|
||||
Pos=900,19
|
||||
Size=300,781
|
||||
Pos=974,19
|
||||
Size=226,641
|
||||
Collapsed=0
|
||||
DockId=0x0000001A,0
|
||||
DockId=0x0000004B,0
|
||||
|
||||
[Window][###96]
|
||||
Pos=984,19
|
||||
Size=216,781
|
||||
Size=216,391
|
||||
Collapsed=0
|
||||
DockId=0x0000001D,0
|
||||
DockId=0x00000019,0
|
||||
|
||||
[Window][###127]
|
||||
Pos=984,365
|
||||
@ -134,38 +134,365 @@ Size=216,435
|
||||
Collapsed=0
|
||||
DockId=0x0000001E,0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=1200,781 Split=X
|
||||
DockNode ID=0x0000001B Parent=0x7C6B3D9B SizeRef=982,781 Split=X
|
||||
DockNode ID=0x00000019 Parent=0x0000001B SizeRef=898,781 Split=X
|
||||
DockNode ID=0x00000017 Parent=0x00000019 SizeRef=898,781 Split=X
|
||||
DockNode ID=0x00000009 Parent=0x00000017 SizeRef=270,581 Split=Y Selected=0x3BEDC6B0
|
||||
DockNode ID=0x0000000B Parent=0x00000009 SizeRef=196,474 Split=Y Selected=0x3BEDC6B0
|
||||
DockNode ID=0x0000000D Parent=0x0000000B SizeRef=196,99 Split=Y Selected=0x99D84869
|
||||
DockNode ID=0x00000012 Parent=0x0000000D SizeRef=196,423 Selected=0x0F59680E
|
||||
DockNode ID=0x00000013 Parent=0x0000000D SizeRef=196,156 Split=Y Selected=0xB4AD3310
|
||||
DockNode ID=0x00000014 Parent=0x00000013 SizeRef=244,625 Split=Y Selected=0xB4AD3310
|
||||
DockNode ID=0x0000001F Parent=0x00000014 SizeRef=270,469 Selected=0xB4AD3310
|
||||
DockNode ID=0x00000020 Parent=0x00000014 SizeRef=270,154 Selected=0x0531B3D5
|
||||
DockNode ID=0x00000015 Parent=0x00000013 SizeRef=244,154 Selected=0x8773D56E
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=196,373 Selected=0x3BEDC6B0
|
||||
DockNode ID=0x0000000C Parent=0x00000009 SizeRef=196,105 Selected=0x4F81AB74
|
||||
DockNode ID=0x0000000A Parent=0x00000017 SizeRef=710,581 Split=X
|
||||
DockNode ID=0x00000003 Parent=0x0000000A SizeRef=299,581 Split=Y Selected=0x52849BCC
|
||||
DockNode ID=0x00000005 Parent=0x00000003 SizeRef=299,473 Split=Y Selected=0x52849BCC
|
||||
DockNode ID=0x00000007 Parent=0x00000005 SizeRef=299,86 Selected=0x52849BCC
|
||||
DockNode ID=0x00000008 Parent=0x00000005 SizeRef=299,385 Selected=0xBD79B41E
|
||||
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=299,106 Selected=0x84DD78D1
|
||||
DockNode ID=0x00000004 Parent=0x0000000A SizeRef=499,581 Split=Y
|
||||
DockNode ID=0x00000001 Parent=0x00000004 SizeRef=800,379 Split=Y Selected=0x7FF1E0B5
|
||||
DockNode ID=0x0000000F Parent=0x00000001 SizeRef=602,425 Split=Y Selected=0x38519A65
|
||||
DockNode ID=0x00000011 Parent=0x0000000F SizeRef=554,379 CentralNode=1 Selected=0x977476CD
|
||||
DockNode ID=0x00000016 Parent=0x0000000F SizeRef=554,200 Selected=0x3A881EEF
|
||||
DockNode ID=0x00000010 Parent=0x00000001 SizeRef=602,154 Selected=0x083320CE
|
||||
DockNode ID=0x00000002 Parent=0x00000004 SizeRef=800,200 Selected=0x1834836D
|
||||
DockNode ID=0x00000018 Parent=0x00000019 SizeRef=300,781 Selected=0x7E9438EA
|
||||
DockNode ID=0x0000001A Parent=0x0000001B SizeRef=300,781 Selected=0x7E9438EA
|
||||
DockNode ID=0x0000001C Parent=0x7C6B3D9B SizeRef=216,781 Split=Y Selected=0x714F2F7B
|
||||
DockNode ID=0x0000001D Parent=0x0000001C SizeRef=216,344 Selected=0x714F2F7B
|
||||
DockNode ID=0x0000001E Parent=0x0000001C SizeRef=216,435 Selected=0x7740BFE4
|
||||
[Window][###125]
|
||||
Pos=984,600
|
||||
Size=216,200
|
||||
Collapsed=0
|
||||
DockId=0x00000022,0
|
||||
|
||||
[Window][###103]
|
||||
Pos=984,19
|
||||
Size=216,634
|
||||
Collapsed=0
|
||||
DockId=0x0000003F,0
|
||||
|
||||
[Window][###139]
|
||||
Pos=984,600
|
||||
Size=216,200
|
||||
Collapsed=0
|
||||
DockId=0x00000026,0
|
||||
|
||||
[Window][###101]
|
||||
Pos=984,19
|
||||
Size=216,609
|
||||
Collapsed=0
|
||||
DockId=0x00000029,0
|
||||
|
||||
[Window][###121]
|
||||
Pos=984,596
|
||||
Size=216,204
|
||||
Collapsed=0
|
||||
DockId=0x0000004E,0
|
||||
|
||||
[Window][###129]
|
||||
Pos=939,555
|
||||
Size=261,245
|
||||
Collapsed=0
|
||||
DockId=0x0000002C,0
|
||||
|
||||
[Window][###97]
|
||||
Pos=972,19
|
||||
Size=228,560
|
||||
Collapsed=0
|
||||
DockId=0x0000002F,0
|
||||
|
||||
[Window][###113]
|
||||
Pos=972,581
|
||||
Size=228,219
|
||||
Collapsed=0
|
||||
DockId=0x00000030,0
|
||||
|
||||
[Window][###44]
|
||||
Pos=0,712
|
||||
Size=270,88
|
||||
Collapsed=0
|
||||
DockId=0x00000036,0
|
||||
|
||||
[Window][###52]
|
||||
Pos=0,19
|
||||
Size=270,567
|
||||
Collapsed=0
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###82]
|
||||
Pos=272,601
|
||||
Size=710,199
|
||||
Collapsed=0
|
||||
DockId=0x00000034,0
|
||||
|
||||
[Window][###88]
|
||||
Pos=984,19
|
||||
Size=216,628
|
||||
Collapsed=0
|
||||
DockId=0x00000031,0
|
||||
|
||||
[Window][###95]
|
||||
Pos=0,700
|
||||
Size=270,100
|
||||
Collapsed=0
|
||||
DockId=0x00000042,0
|
||||
|
||||
[Window][###120]
|
||||
Pos=984,582
|
||||
Size=216,218
|
||||
Collapsed=0
|
||||
DockId=0x0000003A,0
|
||||
|
||||
[Window][###100]
|
||||
Pos=984,19
|
||||
Size=216,561
|
||||
Collapsed=0
|
||||
DockId=0x00000039,0
|
||||
|
||||
[Window][###123]
|
||||
Pos=984,655
|
||||
Size=216,145
|
||||
Collapsed=0
|
||||
DockId=0x0000003E,0
|
||||
|
||||
[Window][###102]
|
||||
Pos=984,19
|
||||
Size=216,467
|
||||
Collapsed=0
|
||||
DockId=0x00000051,0
|
||||
|
||||
[Window][###124]
|
||||
Pos=984,700
|
||||
Size=216,100
|
||||
Collapsed=0
|
||||
DockId=0x00000040,0
|
||||
|
||||
[Window][###122]
|
||||
Pos=987,597
|
||||
Size=216,100
|
||||
Collapsed=0
|
||||
|
||||
[Window][###46]
|
||||
Pos=0,700
|
||||
Size=270,100
|
||||
Collapsed=0
|
||||
DockId=0x00000042,1
|
||||
|
||||
[Window][###53]
|
||||
Pos=0,19
|
||||
Size=270,679
|
||||
Collapsed=0
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###83]
|
||||
Pos=272,612
|
||||
Size=698,188
|
||||
Collapsed=0
|
||||
DockId=0x00000044,0
|
||||
|
||||
[Window][###89]
|
||||
Pos=272,605
|
||||
Size=710,195
|
||||
Collapsed=0
|
||||
DockId=0x00000015,0
|
||||
|
||||
[Window][###119]
|
||||
Pos=984,653
|
||||
Size=216,147
|
||||
Collapsed=0
|
||||
DockId=0x0000004A,0
|
||||
|
||||
[Window][###36]
|
||||
Pos=0,458
|
||||
Size=270,254
|
||||
Collapsed=0
|
||||
DockId=0x0000005C,0
|
||||
|
||||
[Window][###48]
|
||||
Pos=0,714
|
||||
Size=270,86
|
||||
Collapsed=0
|
||||
DockId=0x00000036,1
|
||||
|
||||
[Window][###56]
|
||||
Pos=0,19
|
||||
Size=270,437
|
||||
Collapsed=0
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###93]
|
||||
Pos=0,714
|
||||
Size=270,86
|
||||
Collapsed=0
|
||||
DockId=0x00000036,0
|
||||
|
||||
[Window][###133]
|
||||
Pos=974,645
|
||||
Size=226,155
|
||||
Collapsed=0
|
||||
DockId=0x00000048,0
|
||||
|
||||
[Window][###110]
|
||||
Pos=974,19
|
||||
Size=226,624
|
||||
Collapsed=0
|
||||
DockId=0x00000047,0
|
||||
|
||||
[Window][###109]
|
||||
Pos=958,19
|
||||
Size=242,262
|
||||
Collapsed=0
|
||||
DockId=0x00000057,0
|
||||
|
||||
[Window][###131]
|
||||
Pos=958,664
|
||||
Size=242,136
|
||||
Collapsed=0
|
||||
DockId=0x00000016,0
|
||||
|
||||
[Window][###128]
|
||||
Pos=60,60
|
||||
Size=141,100
|
||||
Collapsed=0
|
||||
|
||||
[Window][###50]
|
||||
Pos=0,700
|
||||
Size=270,100
|
||||
Collapsed=0
|
||||
DockId=0x00000042,1
|
||||
|
||||
[Window][###58]
|
||||
Pos=0,19
|
||||
Size=270,679
|
||||
Collapsed=0
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###104]
|
||||
Pos=984,19
|
||||
Size=216,575
|
||||
Collapsed=0
|
||||
DockId=0x0000004D,0
|
||||
|
||||
[Window][###156]
|
||||
Pos=984,451
|
||||
Size=216,200
|
||||
Collapsed=0
|
||||
DockId=0x00000050,0
|
||||
|
||||
[Window][###136]
|
||||
Pos=984,488
|
||||
Size=216,163
|
||||
Collapsed=0
|
||||
DockId=0x00000052,0
|
||||
|
||||
[Window][###170]
|
||||
Pos=272,19
|
||||
Size=710,625
|
||||
Collapsed=0
|
||||
DockId=0x00000014,1
|
||||
|
||||
[Window][###148]
|
||||
Pos=272,19
|
||||
Size=710,625
|
||||
Collapsed=0
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][###169]
|
||||
Pos=272,19
|
||||
Size=684,625
|
||||
Collapsed=0
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][###152]
|
||||
Pos=958,283
|
||||
Size=242,368
|
||||
Collapsed=0
|
||||
DockId=0x00000058,0
|
||||
|
||||
[Window][###157]
|
||||
Pos=958,481
|
||||
Size=242,181
|
||||
Collapsed=0
|
||||
DockId=0x0000005A,0
|
||||
|
||||
[Window][###178]
|
||||
Pos=60,60
|
||||
Size=148,100
|
||||
Collapsed=0
|
||||
|
||||
[Window][###184]
|
||||
Pos=272,19
|
||||
Size=710,625
|
||||
Collapsed=0
|
||||
DockId=0x00000014,1
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=1200,781 Split=X
|
||||
DockNode ID=0x00000053 Parent=0x7C6B3D9B SizeRef=956,781 Split=X
|
||||
DockNode ID=0x00000037 Parent=0x00000053 SizeRef=982,781 Split=X
|
||||
DockNode ID=0x0000002D Parent=0x00000037 SizeRef=970,781 Split=X
|
||||
DockNode ID=0x0000002B Parent=0x0000002D SizeRef=937,781 Split=X
|
||||
DockNode ID=0x00000027 Parent=0x0000002B SizeRef=982,781 Split=X
|
||||
DockNode ID=0x00000023 Parent=0x00000027 SizeRef=982,781 Split=X
|
||||
DockNode ID=0x0000001B Parent=0x00000023 SizeRef=972,781 Split=X
|
||||
DockNode ID=0x00000017 Parent=0x0000001B SizeRef=898,781 Split=X
|
||||
DockNode ID=0x00000009 Parent=0x00000017 SizeRef=270,581 Split=Y Selected=0x3BEDC6B0
|
||||
DockNode ID=0x0000000B Parent=0x00000009 SizeRef=196,474 Split=Y Selected=0x3BEDC6B0
|
||||
DockNode ID=0x0000000D Parent=0x0000000B SizeRef=196,99 Split=Y Selected=0x99D84869
|
||||
DockNode ID=0x00000012 Parent=0x0000000D SizeRef=196,423 Selected=0x0F59680E
|
||||
DockNode ID=0x00000013 Parent=0x0000000D SizeRef=196,156 Split=Y Selected=0xB4AD3310
|
||||
DockNode ID=0x0000001F Parent=0x00000013 SizeRef=270,469 Split=Y Selected=0xD36850C8
|
||||
DockNode ID=0x00000035 Parent=0x0000001F SizeRef=270,693 Split=Y Selected=0xD36850C8
|
||||
DockNode ID=0x00000041 Parent=0x00000035 SizeRef=270,679 Split=Y Selected=0x068DEF00
|
||||
DockNode ID=0x0000005B Parent=0x00000041 SizeRef=270,437 Selected=0x068DEF00
|
||||
DockNode ID=0x0000005C Parent=0x00000041 SizeRef=270,254 Selected=0xD0D40C1D
|
||||
DockNode ID=0x00000042 Parent=0x00000035 SizeRef=270,100 Selected=0x89CD1AA0
|
||||
DockNode ID=0x00000036 Parent=0x0000001F SizeRef=270,86 Selected=0xB9AFA00B
|
||||
DockNode ID=0x00000020 Parent=0x00000013 SizeRef=270,154 Selected=0x0531B3D5
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=196,373 Selected=0x3BEDC6B0
|
||||
DockNode ID=0x0000000C Parent=0x00000009 SizeRef=196,105 Selected=0x4F81AB74
|
||||
DockNode ID=0x0000000A Parent=0x00000017 SizeRef=684,581 Split=X
|
||||
DockNode ID=0x00000003 Parent=0x0000000A SizeRef=299,581 Split=Y Selected=0x52849BCC
|
||||
DockNode ID=0x00000005 Parent=0x00000003 SizeRef=299,473 Split=Y Selected=0x52849BCC
|
||||
DockNode ID=0x00000007 Parent=0x00000005 SizeRef=299,86 Selected=0x52849BCC
|
||||
DockNode ID=0x00000008 Parent=0x00000005 SizeRef=299,385 Selected=0xBD79B41E
|
||||
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=299,106 Selected=0x84DD78D1
|
||||
DockNode ID=0x00000004 Parent=0x0000000A SizeRef=499,581 Split=Y
|
||||
DockNode ID=0x00000001 Parent=0x00000004 SizeRef=800,379 Split=Y Selected=0x7FF1E0B5
|
||||
DockNode ID=0x0000000F Parent=0x00000001 SizeRef=602,588 Split=Y Selected=0x977476CD
|
||||
DockNode ID=0x00000033 Parent=0x0000000F SizeRef=710,580 Split=Y Selected=0x977476CD
|
||||
DockNode ID=0x00000043 Parent=0x00000033 SizeRef=710,591 Split=Y Selected=0x4EE4732B
|
||||
DockNode ID=0x00000045 Parent=0x00000043 SizeRef=710,625 Split=Y Selected=0xD0D40C1D
|
||||
DockNode ID=0x00000014 Parent=0x00000045 SizeRef=710,584 CentralNode=1 Selected=0x2349CB28
|
||||
DockNode ID=0x00000015 Parent=0x00000045 SizeRef=710,195 Selected=0x38436B0F
|
||||
DockNode ID=0x00000046 Parent=0x00000043 SizeRef=710,154 Selected=0x8773D56E
|
||||
DockNode ID=0x00000044 Parent=0x00000033 SizeRef=710,188 Selected=0x72F373AE
|
||||
DockNode ID=0x00000034 Parent=0x0000000F SizeRef=710,199 Selected=0x4F935A1E
|
||||
DockNode ID=0x00000010 Parent=0x00000001 SizeRef=602,191 Selected=0x083320CE
|
||||
DockNode ID=0x00000002 Parent=0x00000004 SizeRef=800,200 Selected=0x1834836D
|
||||
DockNode ID=0x00000018 Parent=0x0000001B SizeRef=300,781 Selected=0x7E9438EA
|
||||
DockNode ID=0x0000001C Parent=0x00000023 SizeRef=226,781 Split=Y Selected=0x714F2F7B
|
||||
DockNode ID=0x0000001D Parent=0x0000001C SizeRef=216,344 Split=Y Selected=0x714F2F7B
|
||||
DockNode ID=0x00000021 Parent=0x0000001D SizeRef=216,579 Split=Y Selected=0x714F2F7B
|
||||
DockNode ID=0x00000019 Parent=0x00000021 SizeRef=216,391 Selected=0x714F2F7B
|
||||
DockNode ID=0x0000001A Parent=0x00000021 SizeRef=216,388 Split=Y Selected=0x7E9438EA
|
||||
DockNode ID=0x0000004B Parent=0x0000001A SizeRef=226,641 Selected=0x7E9438EA
|
||||
DockNode ID=0x0000004C Parent=0x0000001A SizeRef=226,138 Split=Y Selected=0x499CCA81
|
||||
DockNode ID=0x00000047 Parent=0x0000004C SizeRef=226,624 Selected=0x43F4115A
|
||||
DockNode ID=0x00000048 Parent=0x0000004C SizeRef=226,155 Selected=0x499CCA81
|
||||
DockNode ID=0x00000022 Parent=0x0000001D SizeRef=216,200 Selected=0x0D80EC84
|
||||
DockNode ID=0x0000001E Parent=0x0000001C SizeRef=216,435 Selected=0x7740BFE4
|
||||
DockNode ID=0x00000024 Parent=0x00000027 SizeRef=216,781 Split=Y Selected=0xCF08B82F
|
||||
DockNode ID=0x00000025 Parent=0x00000024 SizeRef=216,579 Split=Y Selected=0xCF08B82F
|
||||
DockNode ID=0x00000031 Parent=0x00000025 SizeRef=216,628 Selected=0x052342BF
|
||||
DockNode ID=0x00000032 Parent=0x00000025 SizeRef=216,151 Split=Y Selected=0xCF08B82F
|
||||
DockNode ID=0x0000003B Parent=0x00000032 SizeRef=216,634 Split=Y Selected=0xCF08B82F
|
||||
DockNode ID=0x0000003F Parent=0x0000003B SizeRef=216,679 Selected=0xCF08B82F
|
||||
DockNode ID=0x00000040 Parent=0x0000003B SizeRef=216,100 Selected=0x30E0C534
|
||||
DockNode ID=0x0000003C Parent=0x00000032 SizeRef=216,145 Split=Y Selected=0x82C01924
|
||||
DockNode ID=0x0000003D Parent=0x0000003C SizeRef=216,613 Split=Y Selected=0xF268919F
|
||||
DockNode ID=0x00000049 Parent=0x0000003D SizeRef=216,632 Split=Y Selected=0xF268919F
|
||||
DockNode ID=0x0000004F Parent=0x00000049 SizeRef=216,430 Split=Y Selected=0xF268919F
|
||||
DockNode ID=0x00000051 Parent=0x0000004F SizeRef=216,467 Selected=0xF268919F
|
||||
DockNode ID=0x00000052 Parent=0x0000004F SizeRef=216,163 Selected=0x817C45F1
|
||||
DockNode ID=0x00000050 Parent=0x00000049 SizeRef=216,200 Selected=0x5725A6EC
|
||||
DockNode ID=0x0000004A Parent=0x0000003D SizeRef=216,147 Selected=0x4EE4732B
|
||||
DockNode ID=0x0000003E Parent=0x0000003C SizeRef=216,166 Selected=0x82C01924
|
||||
DockNode ID=0x00000026 Parent=0x00000024 SizeRef=216,200 Selected=0x032CD220
|
||||
DockNode ID=0x00000028 Parent=0x0000002B SizeRef=216,781 Split=Y Selected=0xB5C8EB4F
|
||||
DockNode ID=0x00000029 Parent=0x00000028 SizeRef=216,609 Selected=0xB5C8EB4F
|
||||
DockNode ID=0x0000002A Parent=0x00000028 SizeRef=216,170 Split=Y Selected=0xF8004A44
|
||||
DockNode ID=0x0000004D Parent=0x0000002A SizeRef=216,575 Selected=0x7D28643F
|
||||
DockNode ID=0x0000004E Parent=0x0000002A SizeRef=216,204 Selected=0xF8004A44
|
||||
DockNode ID=0x0000002C Parent=0x0000002D SizeRef=261,781 Selected=0xC8700185
|
||||
DockNode ID=0x0000002E Parent=0x00000037 SizeRef=228,781 Split=Y Selected=0x4C2F06CB
|
||||
DockNode ID=0x0000002F Parent=0x0000002E SizeRef=216,560 Selected=0x4C2F06CB
|
||||
DockNode ID=0x00000030 Parent=0x0000002E SizeRef=216,219 Selected=0x04546B8A
|
||||
DockNode ID=0x00000038 Parent=0x00000053 SizeRef=216,781 Split=Y Selected=0x88A8C2FF
|
||||
DockNode ID=0x00000039 Parent=0x00000038 SizeRef=216,561 Selected=0x88A8C2FF
|
||||
DockNode ID=0x0000003A Parent=0x00000038 SizeRef=216,218 Selected=0xC56063F4
|
||||
DockNode ID=0x00000054 Parent=0x7C6B3D9B SizeRef=242,781 Split=Y Selected=0xA2A5002C
|
||||
DockNode ID=0x00000055 Parent=0x00000054 SizeRef=158,632 Split=Y Selected=0xA2A5002C
|
||||
DockNode ID=0x00000057 Parent=0x00000055 SizeRef=158,262 Selected=0x85B8A08E
|
||||
DockNode ID=0x00000058 Parent=0x00000055 SizeRef=158,368 Selected=0xA2A5002C
|
||||
DockNode ID=0x00000056 Parent=0x00000054 SizeRef=158,147 Split=Y Selected=0x335C99E1
|
||||
DockNode ID=0x00000011 Parent=0x00000056 SizeRef=242,643 Split=Y Selected=0x3A881EEF
|
||||
DockNode ID=0x00000059 Parent=0x00000011 SizeRef=242,460 Selected=0x3A881EEF
|
||||
DockNode ID=0x0000005A Parent=0x00000011 SizeRef=242,181 Selected=0x6A458F5C
|
||||
DockNode ID=0x00000016 Parent=0x00000056 SizeRef=242,136 Selected=0x335C99E1
|
||||
|
||||
|
@ -1,30 +1,115 @@
|
||||
[
|
||||
{
|
||||
"widget_type": "OpenImageWidget",
|
||||
"config": {}
|
||||
{
|
||||
"pipeline_order": {
|
||||
"0": "opened_image",
|
||||
"1": "inverted_image",
|
||||
"2": "opened_raw",
|
||||
"3": "monochrome",
|
||||
"4": "oriented_image",
|
||||
"6": "framed_image"
|
||||
},
|
||||
{
|
||||
"widget_type": "PipelineStageViewer",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "InvertStage",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "OpenRawWidget",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "LogWindowWidget",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "MonochromeStage",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "HistogramWidget",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
"widgets": [
|
||||
{
|
||||
"widget_type": "OpenImageWidget",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": null,
|
||||
"stage_out": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "PipelineStageViewer",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 6,
|
||||
"stage_out": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "InvertStage",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 3,
|
||||
"stage_out": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "OpenRawWidget",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": null,
|
||||
"stage_out": 2
|
||||
},
|
||||
"raw_config": {
|
||||
"demosaic_algorithm": "DemosaicAlgorithm.AHD",
|
||||
"output_color": "ColorSpace.sRGB",
|
||||
"output_bps": "16",
|
||||
"use_camera_wb": "True",
|
||||
"use_auto_wb": "False",
|
||||
"user_wb": "(1.0, 1.0, 1.0, 1.0)",
|
||||
"bright": "1.0",
|
||||
"no_auto_bright": "False",
|
||||
"gamma": "1.0",
|
||||
"half_size": "False",
|
||||
"four_color_rgb": "False"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "LogWindowWidget",
|
||||
"config": {}
|
||||
},
|
||||
{
|
||||
"widget_type": "MonochromeStage",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 2,
|
||||
"stage_out": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "HistogramWidget",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 6,
|
||||
"stage_out": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "ExportStage",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 6,
|
||||
"stage_out": null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "OrientationStage",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 1,
|
||||
"stage_out": 4
|
||||
},
|
||||
"orientation": {
|
||||
"rotation": 180,
|
||||
"mirror_h": "False",
|
||||
"mirror_v": "False"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"widget_type": "FramingWidget",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 4,
|
||||
"stage_out": 6
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -2,4 +2,5 @@ pillow
|
||||
gphoto2
|
||||
dearpygui
|
||||
numpy
|
||||
rawpy
|
||||
rawpy
|
||||
scipy
|
Reference in New Issue
Block a user