Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
66d542f742 | |||
2a6498f117 | |||
447354266c | |||
a423ceb669 | |||
34cc28897d | |||
1a5abca8e1 | |||
bbedfe6d35 | |||
a0ba27b35c | |||
ac33ec5d8d | |||
9801f69d0b | |||
60d28e92d0 | |||
af2bbe93cc | |||
bc4f10a7ad |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
__pycache__
|
__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.id_counter = 0
|
||||||
self.stages = {}
|
self.stages = {}
|
||||||
self.stagedata = {}
|
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):
|
def register_stage(self, name: str):
|
||||||
self.stages[self.id_counter] = name
|
self.stages[self.id_counter] = name
|
||||||
self.stagedata[self.id_counter] = None
|
self.stagedata[self.id_counter] = None
|
||||||
|
self.stagedata_full[self.id_counter] = None
|
||||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
self.bus.publish_deferred("pipeline_stages", self.stages)
|
||||||
self.id_counter += 1
|
self.id_counter += 1
|
||||||
return self.id_counter-1
|
return self.id_counter-1
|
||||||
@ -22,18 +34,32 @@ class ImagePipeline:
|
|||||||
self.stages[id] = name
|
self.stages[id] = name
|
||||||
self.bus.publish_deferred("pipeline_stages", self.stages)
|
self.bus.publish_deferred("pipeline_stages", self.stages)
|
||||||
|
|
||||||
def publish(self, id: int, img: np.ndarray):
|
def publish(self, id: int, img: np.ndarray, full_res=False):
|
||||||
self.stagedata[id] = img.astype(np.float32)
|
if img is None:
|
||||||
self.bus.publish_deferred("pipeline_stage", (id, self.stagedata[id]))
|
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):
|
def get_stage_data(self, id: int):
|
||||||
if id >= 0 and id < len(self.stages):
|
if id in self.stagedata:
|
||||||
return self.stagedata[id]
|
return self.stagedata[id]
|
||||||
else:
|
else:
|
||||||
return None
|
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):
|
def get_stage_name(self, id: int):
|
||||||
if id >= 0 and id < len(self.stages):
|
if id in self.stages:
|
||||||
return self.stages[id]
|
return self.stages[id]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -20,12 +20,15 @@ class LayoutManager:
|
|||||||
def save_layout(self):
|
def save_layout(self):
|
||||||
self.logger.info("Saving layout...")
|
self.logger.info("Saving layout...")
|
||||||
dpg.save_init_file(self.INI_PATH)
|
dpg.save_init_file(self.INI_PATH)
|
||||||
widget_data = [
|
layout_data = {
|
||||||
{"widget_type": type(w).__name__, "config": w.get_config()}
|
"pipeline_order" : { k:v for k, v in self.manager.pipeline.stages.items() },
|
||||||
for w in self.manager.widgets
|
"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:
|
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.")
|
self.logger.info("Layout saved successfully.")
|
||||||
|
|
||||||
def load_layout(self):
|
def load_layout(self):
|
||||||
@ -33,10 +36,17 @@ class LayoutManager:
|
|||||||
if not os.path.exists(self.WIDGET_DATA_PATH):
|
if not os.path.exists(self.WIDGET_DATA_PATH):
|
||||||
return
|
return
|
||||||
with open(self.WIDGET_DATA_PATH, "r") as f:
|
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:
|
for data in widget_data:
|
||||||
if data.get("widget_type") in self.manager.widget_classes:
|
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):
|
if os.path.exists(self.INI_PATH):
|
||||||
dpg.configure_app(init_file=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
|
from .widgets.base_widget import BaseWidget
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO,
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||||
format="%(asctime)s %(levelname)s %(message)s")
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -62,24 +61,51 @@ class EditorManager:
|
|||||||
and cls is not ModuleBaseWidget
|
and cls is not ModuleBaseWidget
|
||||||
and cls.register
|
and cls.register
|
||||||
):
|
):
|
||||||
logging.info(
|
logging.info(f" -> Found and registered widget: {name}")
|
||||||
f" -> Found and registered widget: {name}")
|
|
||||||
self._register_widget(name, cls)
|
self._register_widget(name, cls)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to import widget '{py_file.name}': {e}")
|
logging.error(f"Failed to import widget '{py_file.name}': {e}")
|
||||||
|
|
||||||
def _register_widget(self, name: str, widget_class: object):
|
def _register_widget(self, name: str, widget_class: object):
|
||||||
if name in self.widget_classes:
|
if name in self.widget_classes:
|
||||||
logging.warning(
|
logging.warning(f"Widget '{name}' is already registered. Overwriting.")
|
||||||
f"Widget '{name}' is already registered. Overwriting.")
|
|
||||||
self.widget_classes[name] = widget_class
|
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]
|
WidgetClass = self.widget_classes[widget_type]
|
||||||
instance = WidgetClass(self, logger)
|
instance = WidgetClass(self, logger)
|
||||||
logger.info(f'Created instance: {str(instance)}')
|
logger.info(f"Created instance: {str(instance)}")
|
||||||
self.widgets.append(instance)
|
self.widgets.append(instance)
|
||||||
instance.create()
|
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):
|
def setup(self):
|
||||||
self._discover_and_register_widgets(
|
self._discover_and_register_widgets(
|
||||||
@ -87,7 +113,7 @@ class EditorManager:
|
|||||||
)
|
)
|
||||||
self.layout_manager.load_layout()
|
self.layout_manager.load_layout()
|
||||||
|
|
||||||
dpg.create_viewport(title="NegStation", width=800, height=600)
|
dpg.create_viewport(title="NegStation", width=1200, height=800)
|
||||||
dpg.configure_app(docking=True, docking_space=True)
|
dpg.configure_app(docking=True, docking_space=True)
|
||||||
|
|
||||||
with dpg.viewport_menu_bar():
|
with dpg.viewport_menu_bar():
|
||||||
@ -96,8 +122,12 @@ class EditorManager:
|
|||||||
label="Save Layout", callback=self.layout_manager.save_layout
|
label="Save Layout", callback=self.layout_manager.save_layout
|
||||||
)
|
)
|
||||||
dpg.add_menu_item(
|
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"):
|
with dpg.menu(label="View"):
|
||||||
for widget_name in sorted(self.widget_classes.keys()):
|
for widget_name in sorted(self.widget_classes.keys()):
|
||||||
@ -107,6 +137,11 @@ class EditorManager:
|
|||||||
user_data=widget_name,
|
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):
|
def run(self):
|
||||||
self.setup()
|
self.setup()
|
||||||
dpg.setup_dearpygui()
|
dpg.setup_dearpygui()
|
||||||
|
@ -63,6 +63,10 @@ class BaseWidget:
|
|||||||
def get_config(self):
|
def get_config(self):
|
||||||
"""Caled by negstation itself, returns the saved widget config"""
|
"""Caled by negstation itself, returns the saved widget config"""
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
|
def set_config(self, config):
|
||||||
|
"""Called by negstation itself but can be overridden by a widget"""
|
||||||
|
self.config = config
|
||||||
|
|
||||||
# Callbacks
|
# 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]
|
72
negstation/widgets/histogram_widget.py
Normal file
72
negstation/widgets/histogram_widget.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
import numpy as np
|
||||||
|
from .pipeline_stage_widget import PipelineStageWidget
|
||||||
|
|
||||||
|
|
||||||
|
class HistogramWidget(PipelineStageWidget):
|
||||||
|
name = "Histogram"
|
||||||
|
register = True
|
||||||
|
has_pipeline_in = True
|
||||||
|
has_pipeline_out = False
|
||||||
|
|
||||||
|
def __init__(self, manager, logger):
|
||||||
|
super().__init__(manager, logger, default_stage_in="monochrome")
|
||||||
|
self.plot_tag = dpg.generate_uuid()
|
||||||
|
self.axis_x = dpg.generate_uuid()
|
||||||
|
self.axis_y = dpg.generate_uuid()
|
||||||
|
self.needs_redraw = False
|
||||||
|
self.img = None
|
||||||
|
self.series_tags = {
|
||||||
|
"R": dpg.generate_uuid(),
|
||||||
|
"G": dpg.generate_uuid(),
|
||||||
|
"B": dpg.generate_uuid(),
|
||||||
|
"L": dpg.generate_uuid(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_pipeline_stage_content(self):
|
||||||
|
with dpg.plot(label="Histogram", height=200, width=-1, tag=self.plot_tag):
|
||||||
|
dpg.add_plot_legend()
|
||||||
|
dpg.add_plot_axis(
|
||||||
|
dpg.mvXAxis, tag=self.axis_x)
|
||||||
|
with dpg.plot_axis(dpg.mvYAxis, tag=self.axis_y):
|
||||||
|
for channel, tag in self.series_tags.items():
|
||||||
|
dpg.add_line_series([], [], label=channel, tag=tag)
|
||||||
|
dpg.set_axis_limits(self.axis_x, 0.0, 1.0)
|
||||||
|
dpg.set_axis_limits(self.axis_y, 0.0, 1.0)
|
||||||
|
|
||||||
|
def on_pipeline_data(self, img: np.ndarray):
|
||||||
|
if img is None or img.ndim != 3 or img.shape[2] < 3:
|
||||||
|
return
|
||||||
|
|
||||||
|
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:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.needs_redraw = False
|
||||||
|
img = np.clip(self.img, 0.0, 1.0)
|
||||||
|
|
||||||
|
r, g, b = img[..., 0], img[..., 1], img[..., 2]
|
||||||
|
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||||
|
|
||||||
|
bins = 64
|
||||||
|
hist_range = (0.0, 1.0)
|
||||||
|
bin_edges = np.linspace(*hist_range, bins)
|
||||||
|
|
||||||
|
def compute_hist(channel):
|
||||||
|
hist, _ = np.histogram(channel, bins=bin_edges)
|
||||||
|
x = bin_edges[:-1]
|
||||||
|
y = np.log1p(hist)
|
||||||
|
y = y / np.max(y)
|
||||||
|
return x.tolist(), y.tolist()
|
||||||
|
|
||||||
|
dpg.set_value(self.series_tags["R"], compute_hist(r))
|
||||||
|
dpg.set_value(self.series_tags["G"], compute_hist(g))
|
||||||
|
dpg.set_value(self.series_tags["B"], compute_hist(b))
|
||||||
|
dpg.set_value(self.series_tags["L"], compute_hist(luminance))
|
@ -14,7 +14,7 @@ class InvertStage(PipelineStageWidget):
|
|||||||
super().__init__(manager, logger, default_stage_out="inverted_image")
|
super().__init__(manager, logger, default_stage_out="inverted_image")
|
||||||
|
|
||||||
def create_pipeline_stage_content(self):
|
def create_pipeline_stage_content(self):
|
||||||
dpg.add_text("Inversion is happening here")
|
pass
|
||||||
|
|
||||||
def on_pipeline_data(self, img):
|
def on_pipeline_data(self, img):
|
||||||
if img is None:
|
if img is None:
|
||||||
|
63
negstation/widgets/log_widget.py
Normal file
63
negstation/widgets/log_widget.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
import logging
|
||||||
|
from .base_widget import BaseWidget
|
||||||
|
|
||||||
|
|
||||||
|
class DPGLogHandler(logging.Handler):
|
||||||
|
def __init__(self, callback):
|
||||||
|
super().__init__()
|
||||||
|
self.callback = callback
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
msg = self.format(record)
|
||||||
|
self.callback(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class LogWindowWidget(BaseWidget):
|
||||||
|
name = "Log Window"
|
||||||
|
register = True
|
||||||
|
|
||||||
|
def __init__(self, manager, logger):
|
||||||
|
super().__init__(manager, logger)
|
||||||
|
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)
|
||||||
|
self.handler.setFormatter(logging.Formatter(
|
||||||
|
'%(asctime)s [%(levelname)s] %(message)s'))
|
||||||
|
self.logger.addHandler(self.handler)
|
||||||
|
|
||||||
|
def create_content(self):
|
||||||
|
dpg.add_text("Live Log Output")
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_child_window(tag=self.log_tag, autosize_x=True,
|
||||||
|
autosize_y=True, horizontal_scrollbar=True)
|
||||||
|
self.initialized = True
|
||||||
|
|
||||||
|
def _on_log(self, msg: str):
|
||||||
|
self.log_lines.append(msg)
|
||||||
|
if self.initialized:
|
||||||
|
dpg.add_text(msg, parent=self.log_tag)
|
||||||
|
self.need_update = True
|
||||||
|
|
||||||
|
def on_resize(self, width: int, height: int):
|
||||||
|
# Optional: could resize child window here if needed
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _on_window_close(self):
|
||||||
|
if self.initialized:
|
||||||
|
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
|
31
negstation/widgets/monochrome_widget.py
Normal file
31
negstation/widgets/monochrome_widget.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import dearpygui.dearpygui as dpg
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from .pipeline_stage_widget import PipelineStageWidget
|
||||||
|
|
||||||
|
|
||||||
|
class MonochromeStage(PipelineStageWidget):
|
||||||
|
name = "Monochrome"
|
||||||
|
register = True
|
||||||
|
has_pipeline_in = True
|
||||||
|
has_pipeline_out = True
|
||||||
|
|
||||||
|
def __init__(self, manager, logger):
|
||||||
|
super().__init__(manager, logger, default_stage_out="monochrome")
|
||||||
|
|
||||||
|
def create_pipeline_stage_content(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_pipeline_data(self, img):
|
||||||
|
if img is None:
|
||||||
|
return
|
||||||
|
gray = img.copy()
|
||||||
|
rgb = gray[..., :3]
|
||||||
|
alpha = gray[..., 3:] if gray.shape[2] == 4 else np.ones_like(
|
||||||
|
rgb[..., :1])
|
||||||
|
|
||||||
|
luminance = np.dot(rgb, [0.2126, 0.7152, 0.0722])[..., np.newaxis]
|
||||||
|
gray_rgba = np.concatenate(
|
||||||
|
[luminance, luminance, luminance, alpha], axis=-1)
|
||||||
|
|
||||||
|
self.publish_stage(gray_rgba.astype(np.float32))
|
@ -15,6 +15,11 @@ class OpenImageWidget(PipelineStageWidget):
|
|||||||
super().__init__(manager, logger, default_stage_out="opened_image")
|
super().__init__(manager, logger, default_stage_out="opened_image")
|
||||||
self.dialog_tag = dpg.generate_uuid()
|
self.dialog_tag = dpg.generate_uuid()
|
||||||
self.output_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):
|
def create_pipeline_stage_content(self):
|
||||||
with dpg.file_dialog(
|
with dpg.file_dialog(
|
||||||
@ -46,9 +51,30 @@ class OpenImageWidget(PipelineStageWidget):
|
|||||||
self.logger.info(f"Selected file '{selection}'")
|
self.logger.info(f"Selected file '{selection}'")
|
||||||
try:
|
try:
|
||||||
img = Image.open(selection).convert("RGBA")
|
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]
|
255.0 # normalize to [0,1]
|
||||||
# Publish into pipeline
|
h, w, _ = rgba.shape
|
||||||
self.manager.pipeline.publish(self.pipeline_stage_out_id, arr)
|
|
||||||
|
# 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:
|
except Exception as e:
|
||||||
self.logger.error(f"Failed to load image {selection}: {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 dearpygui.dearpygui as dpg
|
||||||
import rawpy
|
import rawpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import ast
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from .pipeline_stage_widget import PipelineStageWidget
|
from .pipeline_stage_widget import PipelineStageWidget
|
||||||
|
|
||||||
@ -18,7 +20,9 @@ class OpenRawWidget(PipelineStageWidget):
|
|||||||
self.config_group = dpg.generate_uuid()
|
self.config_group = dpg.generate_uuid()
|
||||||
self.busy_group = dpg.generate_uuid()
|
self.busy_group = dpg.generate_uuid()
|
||||||
self.raw_path = None
|
self.raw_path = None
|
||||||
self.config = {
|
self.img = None
|
||||||
|
self.img_full = None
|
||||||
|
self.rawconfig = {
|
||||||
# Demosaic algorithm
|
# Demosaic algorithm
|
||||||
"demosaic_algorithm": rawpy.DemosaicAlgorithm.AHD,
|
"demosaic_algorithm": rawpy.DemosaicAlgorithm.AHD,
|
||||||
# Output color space
|
# Output color space
|
||||||
@ -39,6 +43,23 @@ class OpenRawWidget(PipelineStageWidget):
|
|||||||
"four_color_rgb": False,
|
"four_color_rgb": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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):
|
def create_pipeline_stage_content(self):
|
||||||
with dpg.file_dialog(
|
with dpg.file_dialog(
|
||||||
directory_selector=False,
|
directory_selector=False,
|
||||||
@ -54,93 +75,128 @@ class OpenRawWidget(PipelineStageWidget):
|
|||||||
dpg.add_file_extension(".*")
|
dpg.add_file_extension(".*")
|
||||||
|
|
||||||
with dpg.group(tag=self.config_group):
|
with dpg.group(tag=self.config_group):
|
||||||
|
# -- Open / Reprocess buttons --
|
||||||
with dpg.group(horizontal=True):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_button(label="Open File...",
|
dpg.add_button(label="Open File...", callback=self._on_open_file)
|
||||||
callback=self._on_open_file)
|
dpg.add_button(label="Reprocess", callback=self._process_and_publish)
|
||||||
dpg.add_button(label="Reprocess",
|
|
||||||
callback=self._process_and_publish)
|
|
||||||
|
|
||||||
|
# -- Demosaic combo --
|
||||||
dpg.add_combo(
|
dpg.add_combo(
|
||||||
label="Demosaic",
|
label="Demosaic",
|
||||||
items=[alg.name for alg in rawpy.DemosaicAlgorithm],
|
items=[alg.name for alg in rawpy.DemosaicAlgorithm],
|
||||||
default_value=rawpy.DemosaicAlgorithm.AHD.name,
|
default_value=self.rawconfig["demosaic_algorithm"].name,
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||||
"demosaic_algorithm", rawpy.DemosaicAlgorithm[a])
|
"demosaic_algorithm", rawpy.DemosaicAlgorithm[a]
|
||||||
|
),
|
||||||
|
tag=self.demosaic_combo_tag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -- Color space combo --
|
||||||
dpg.add_combo(
|
dpg.add_combo(
|
||||||
label="Color Space",
|
label="Color Space",
|
||||||
items=[cs.name for cs in rawpy.ColorSpace],
|
items=[cs.name for cs in rawpy.ColorSpace],
|
||||||
default_value=rawpy.ColorSpace.sRGB.name,
|
default_value=self.rawconfig["output_color"].name,
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||||
"output_color", rawpy.ColorSpace[a])
|
"output_color", rawpy.ColorSpace[a]
|
||||||
|
),
|
||||||
|
tag=self.color_space_combo_tag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -- Bits per sample --
|
||||||
dpg.add_combo(
|
dpg.add_combo(
|
||||||
label="Output Bits",
|
label="Output Bits",
|
||||||
items=["8", "16"],
|
items=["8","16"],
|
||||||
default_value="16",
|
default_value=str(self.rawconfig["output_bps"]),
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||||
"output_bps", int(a))
|
"output_bps", int(a)
|
||||||
|
),
|
||||||
|
tag=self.output_bps_combo_tag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -- Checkboxes & sliders --
|
||||||
dpg.add_checkbox(
|
dpg.add_checkbox(
|
||||||
label="Use Camera WB",
|
label="Use Camera WB",
|
||||||
default_value=True,
|
default_value=self.rawconfig["use_camera_wb"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__("use_camera_wb", a),
|
||||||
"use_camera_wb", a)
|
tag=self.use_cam_wb_tag
|
||||||
)
|
)
|
||||||
dpg.add_checkbox(
|
dpg.add_checkbox(
|
||||||
label="Auto WB",
|
label="Auto WB",
|
||||||
default_value=False,
|
default_value=self.rawconfig["use_auto_wb"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__("use_auto_wb", a),
|
||||||
"use_auto_wb", a)
|
tag=self.auto_wb_tag
|
||||||
)
|
)
|
||||||
dpg.add_slider_float(
|
dpg.add_slider_float(
|
||||||
label="Manual WB R Gain",
|
label="Manual WB R Gain",
|
||||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
default_value=self.rawconfig["user_wb"][0],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
min_value=0.1, max_value=4.0,
|
||||||
"user_wb", (a, self.config["user_wb"][1], self.config["user_wb"][2], self.config["user_wb"][3]))
|
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(
|
dpg.add_slider_float(
|
||||||
label="Manual WB G Gain",
|
label="Manual WB G Gain",
|
||||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
default_value=self.rawconfig["user_wb"][1],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
min_value=0.1, max_value=4.0,
|
||||||
"user_wb", (self.config["user_wb"][0], a, a, self.config["user_wb"][3]))
|
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(
|
dpg.add_slider_float(
|
||||||
label="Manual WB B Gain",
|
label="Manual WB B Gain",
|
||||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
default_value=self.rawconfig["user_wb"][2],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
min_value=0.1, max_value=4.0,
|
||||||
"user_wb", (self.config["user_wb"][0], self.config["user_wb"][1], self.config["user_wb"][2], a))
|
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(
|
dpg.add_slider_float(
|
||||||
label="Bright",
|
label="Bright",
|
||||||
default_value=1.0, min_value=0.1, max_value=4.0,
|
default_value=self.rawconfig["bright"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__("bright", a)
|
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(
|
dpg.add_checkbox(
|
||||||
label="No Auto Bright",
|
label="No Auto Bright",
|
||||||
default_value=False,
|
default_value=self.rawconfig["no_auto_bright"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||||
"no_auto_bright", a)
|
"no_auto_bright", a
|
||||||
|
),
|
||||||
|
tag=self.no_auto_bright_tag
|
||||||
)
|
)
|
||||||
dpg.add_slider_float(
|
dpg.add_slider_float(
|
||||||
label="Gamma",
|
label="Gamma",
|
||||||
default_value=1.0, min_value=0.1, max_value=3.0,
|
default_value=self.rawconfig["gamma"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__("gamma", a)
|
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(
|
dpg.add_checkbox(
|
||||||
label="Half-size",
|
label="Half-size",
|
||||||
default_value=False,
|
default_value=self.rawconfig["half_size"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||||
"half_size", a)
|
"half_size", a
|
||||||
|
),
|
||||||
|
tag=self.half_size_tag
|
||||||
)
|
)
|
||||||
dpg.add_checkbox(
|
dpg.add_checkbox(
|
||||||
label="4-color RGB",
|
label="4-color RGB",
|
||||||
default_value=False,
|
default_value=self.rawconfig["four_color_rgb"],
|
||||||
callback=lambda s, a, u: self.config.__setitem__(
|
callback=lambda s,a,u: self.rawconfig.__setitem__(
|
||||||
"four_color_rgb", a)
|
"four_color_rgb", a
|
||||||
|
),
|
||||||
|
tag=self.four_color_tag
|
||||||
)
|
)
|
||||||
|
|
||||||
with dpg.group(tag=self.busy_group):
|
with dpg.group(tag=self.busy_group, show=False):
|
||||||
dpg.add_text("Processing...")
|
dpg.add_text("Processing...")
|
||||||
|
|
||||||
def _on_open_file(self):
|
def _on_open_file(self):
|
||||||
@ -170,28 +226,28 @@ class OpenRawWidget(PipelineStageWidget):
|
|||||||
with rawpy.imread(self.raw_path) as raw:
|
with rawpy.imread(self.raw_path) as raw:
|
||||||
# Prepare postprocess kwargs from config
|
# Prepare postprocess kwargs from config
|
||||||
postprocess_args = {
|
postprocess_args = {
|
||||||
'demosaic_algorithm': self.config["demosaic_algorithm"],
|
'demosaic_algorithm': self.rawconfig["demosaic_algorithm"],
|
||||||
'output_color': self.config["output_color"],
|
'output_color': self.rawconfig["output_color"],
|
||||||
'output_bps': self.config["output_bps"],
|
'output_bps': self.rawconfig["output_bps"],
|
||||||
'bright': self.config["bright"],
|
'bright': self.rawconfig["bright"],
|
||||||
'no_auto_bright': self.config["no_auto_bright"],
|
'no_auto_bright': self.rawconfig["no_auto_bright"],
|
||||||
'gamma': (1.0, self.config["gamma"]),
|
'gamma': (1.0, self.rawconfig["gamma"]),
|
||||||
'half_size': self.config["half_size"],
|
'half_size': self.rawconfig["half_size"],
|
||||||
'four_color_rgb': self.config["four_color_rgb"],
|
'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
|
postprocess_args['use_camera_wb'] = True
|
||||||
elif self.config["use_auto_wb"]:
|
elif self.rawconfig["use_auto_wb"]:
|
||||||
postprocess_args['use_auto_wb'] = True
|
postprocess_args['use_auto_wb'] = True
|
||||||
else:
|
else:
|
||||||
postprocess_args['user_wb'] = self.config["user_wb"]
|
postprocess_args['user_wb'] = self.rawconfig["user_wb"]
|
||||||
|
|
||||||
# Postprocess into RGB
|
# Postprocess into RGB
|
||||||
rgb = raw.postprocess(**postprocess_args)
|
rgb = raw.postprocess(**postprocess_args)
|
||||||
|
|
||||||
# Normalize to float32 in 0.0-1.0 range depending on output_bps
|
# 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
|
rgb_float = rgb.astype(np.float32) / max_val
|
||||||
|
|
||||||
# Add alpha channel (fully opaque)
|
# Add alpha channel (fully opaque)
|
||||||
@ -200,6 +256,76 @@ class OpenRawWidget(PipelineStageWidget):
|
|||||||
|
|
||||||
rgba = np.concatenate([rgb_float, alpha], axis=2)
|
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.config_group, show=True)
|
||||||
dpg.configure_item(self.busy_group, show=False)
|
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_stage_out_id = None
|
||||||
self.pipeline_config_group_tag = dpg.generate_uuid()
|
self.pipeline_config_group_tag = dpg.generate_uuid()
|
||||||
self.stage_in_combo = 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:
|
if self.has_pipeline_out:
|
||||||
self.pipeline_stage_out_id = self.manager.pipeline.register_stage(
|
self.pipeline_stage_out_id = self.manager.pipeline.register_stage(
|
||||||
@ -33,6 +35,9 @@ class PipelineStageWidget(BaseWidget):
|
|||||||
if self.has_pipeline_in:
|
if self.has_pipeline_in:
|
||||||
self.pipeline_stage_in_id = 0
|
self.pipeline_stage_in_id = 0
|
||||||
self.manager.bus.subscribe("pipeline_stage", self._on_stage_data, True)
|
self.manager.bus.subscribe("pipeline_stage", self._on_stage_data, True)
|
||||||
|
self.manager.bus.subscribe(
|
||||||
|
"pipeline_stage_full", self._on_stage_data_full, True
|
||||||
|
)
|
||||||
# force getting all available pipeline stages
|
# force getting all available pipeline stages
|
||||||
self.manager.pipeline.republish_stages()
|
self.manager.pipeline.republish_stages()
|
||||||
|
|
||||||
@ -43,8 +48,9 @@ class PipelineStageWidget(BaseWidget):
|
|||||||
label="Stage In",
|
label="Stage In",
|
||||||
items=[],
|
items=[],
|
||||||
callback=self._on_stage_in_select,
|
callback=self._on_stage_in_select,
|
||||||
default_value=f"{self.manager.pipeline.get_stage_name(0)} : 0",
|
default_value=f"{
|
||||||
tag=self.stage_in_combo
|
self.manager.pipeline.get_stage_name(0)} : 0",
|
||||||
|
tag=self.stage_in_combo,
|
||||||
)
|
)
|
||||||
if self.has_pipeline_out:
|
if self.has_pipeline_out:
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
@ -55,14 +61,11 @@ class PipelineStageWidget(BaseWidget):
|
|||||||
callback=lambda s, a, u: self.manager.pipeline.rename_stage(
|
callback=lambda s, a, u: self.manager.pipeline.rename_stage(
|
||||||
self.pipeline_stage_out_id, a
|
self.pipeline_stage_out_id, a
|
||||||
),
|
),
|
||||||
|
tag=self.stage_out_input,
|
||||||
)
|
)
|
||||||
dpg.add_separator()
|
dpg.add_separator()
|
||||||
self.create_pipeline_stage_content()
|
with dpg.group():
|
||||||
|
self.create_pipeline_stage_content()
|
||||||
def publish_stage(self, img: np.ndarray):
|
|
||||||
"""Publishes an image to output stage"""
|
|
||||||
if self.has_pipeline_out:
|
|
||||||
self.manager.pipeline.publish(self.pipeline_stage_out_id, img)
|
|
||||||
|
|
||||||
def create_pipeline_stage_content(self):
|
def create_pipeline_stage_content(self):
|
||||||
"""Must be implemented by the widget, creates the content of the window"""
|
"""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"""
|
"""Must be implemented by the widget, is called when there is a new image published on the in stage"""
|
||||||
pass
|
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
|
# Callbacks
|
||||||
|
|
||||||
def _on_window_close(self):
|
def _on_window_close(self):
|
||||||
@ -81,8 +143,7 @@ class PipelineStageWidget(BaseWidget):
|
|||||||
|
|
||||||
def _on_stage_list(self, stagelist):
|
def _on_stage_list(self, stagelist):
|
||||||
if self.has_pipeline_in:
|
if self.has_pipeline_in:
|
||||||
stages = [f"{stage} : {id}" for id, stage in stagelist.items()]
|
self._update_ui_from_state()
|
||||||
dpg.configure_item(self.stage_in_combo, items=stages)
|
|
||||||
|
|
||||||
def _on_stage_in_select(self, sender, selected_stage: str):
|
def _on_stage_in_select(self, sender, selected_stage: str):
|
||||||
d = selected_stage.split(" : ")
|
d = selected_stage.split(" : ")
|
||||||
@ -97,14 +158,25 @@ class PipelineStageWidget(BaseWidget):
|
|||||||
pipeline_id = data[0]
|
pipeline_id = data[0]
|
||||||
img = data[1]
|
img = data[1]
|
||||||
if self.has_pipeline_in and pipeline_id == self.pipeline_stage_in_id:
|
if self.has_pipeline_in and pipeline_id == self.pipeline_stage_in_id:
|
||||||
|
self._last_full = False
|
||||||
self.on_pipeline_data(img)
|
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
|
# Override the window resize callback
|
||||||
|
|
||||||
def _on_window_resize(self, data):
|
def _on_window_resize(self, data):
|
||||||
win_w, win_h = dpg.get_item_rect_size(self.window_tag)
|
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)
|
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_height = win_h - group_h - group_y - 12
|
||||||
self.window_width = win_w - 7
|
self.window_width = win_w - 7
|
||||||
self.window_offset_y = group_h + group_y + 3
|
|
||||||
self.on_resize(win_w, win_h)
|
self.on_resize(win_w, win_h)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import dearpygui.dearpygui as dpg
|
import dearpygui.dearpygui as dpg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
|
||||||
from .pipeline_stage_widget import PipelineStageWidget
|
from .pipeline_stage_widget import PipelineStageWidget
|
||||||
|
|
||||||
|
|
||||||
@ -13,77 +12,219 @@ class PipelineStageViewer(PipelineStageWidget):
|
|||||||
def __init__(self, manager, logger):
|
def __init__(self, manager, logger):
|
||||||
super().__init__(manager, logger, default_stage_in="pipeline_out")
|
super().__init__(manager, logger, default_stage_in="pipeline_out")
|
||||||
self.texture_tag = dpg.generate_uuid()
|
self.texture_tag = dpg.generate_uuid()
|
||||||
|
self.drawlist = None
|
||||||
self.img = None
|
self.img = None
|
||||||
self.needs_update = False
|
|
||||||
self.registry = manager.texture_registry
|
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):
|
def create_pipeline_stage_content(self):
|
||||||
|
# Create an empty dynamic texture
|
||||||
dpg.add_dynamic_texture(
|
dpg.add_dynamic_texture(
|
||||||
1, 1, [0, 0, 0, 0], tag=self.texture_tag, parent=self.registry
|
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):
|
def on_resize(self, width, height):
|
||||||
self.needs_update = True
|
self.needs_update = True
|
||||||
|
|
||||||
def on_pipeline_data(self, img):
|
def on_pipeline_data(self, img):
|
||||||
# Resize if needed
|
|
||||||
if img is None:
|
if img is None:
|
||||||
return
|
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.img = img
|
||||||
self.needs_update = True
|
self.needs_update = True
|
||||||
|
|
||||||
|
def on_full_res_pipeline_data(self, img):
|
||||||
|
pass
|
||||||
|
|
||||||
def update_texture(self, img: np.ndarray):
|
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:
|
if img is None:
|
||||||
dpg.configure_item(self.image_item, show=False)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Only recreate the texture if its size changed
|
||||||
h, w, _ = img.shape
|
h, w, _ = img.shape
|
||||||
flat = img.flatten().tolist()
|
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):
|
# Clear old drawings
|
||||||
dpg.delete_item(self.texture_tag)
|
dpg.delete_item(self.drawlist, children_only=True)
|
||||||
dpg.add_dynamic_texture(
|
|
||||||
width=w,
|
|
||||||
height=h,
|
|
||||||
default_value=flat,
|
|
||||||
tag=self.texture_tag,
|
|
||||||
parent=self.registry,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# Draw the image to the screen
|
||||||
win_w, win_h = self.window_width, self.window_height
|
win_w, win_h = self.window_width, self.window_height
|
||||||
avail_w = win_w
|
scale = min(win_w / w, win_h / h)
|
||||||
avail_h = win_h
|
|
||||||
|
|
||||||
scale = min(avail_w / w, avail_h / h) # , 1.0)
|
|
||||||
disp_w = int(w * scale)
|
disp_w = int(w * scale)
|
||||||
disp_h = int(h * scale)
|
disp_h = int(h * scale)
|
||||||
|
|
||||||
x_off = (avail_w - disp_w) / 2
|
x_off = (win_w - disp_w) / 2
|
||||||
y_off = self.window_offset_y
|
y_off = (win_h - disp_h) / 2
|
||||||
|
|
||||||
dpg.configure_item(
|
self.scaled_size = (disp_w, disp_h)
|
||||||
self.image_item,
|
self.image_position = (x_off, y_off)
|
||||||
texture_tag=self.texture_tag,
|
|
||||||
pos=(x_off, y_off),
|
# Draw image
|
||||||
width=disp_w,
|
dpg.draw_image(
|
||||||
height=disp_h,
|
self.texture_tag,
|
||||||
show=True
|
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):
|
def update(self):
|
||||||
if self.needs_update:
|
if self.needs_update:
|
||||||
self.needs_update = False
|
self.needs_update = False
|
||||||
|
@ -1,171 +1,498 @@
|
|||||||
[Window][WindowOverViewport_11111111]
|
[Window][WindowOverViewport_11111111]
|
||||||
Pos=0,19
|
Pos=0,19
|
||||||
Size=800,581
|
Size=1200,781
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][###33]
|
||||||
|
Pos=301,400
|
||||||
|
Size=499,200
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000002,0
|
||||||
|
|
||||||
|
[Window][###39]
|
||||||
|
Pos=0,19
|
||||||
|
Size=299,86
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000007,0
|
||||||
|
|
||||||
|
[Window][###51]
|
||||||
|
Pos=0,19
|
||||||
|
Size=270,691
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x0000005B,1
|
||||||
|
|
||||||
|
[Window][###59]
|
||||||
|
Pos=0,494
|
||||||
|
Size=299,106
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000006,0
|
||||||
|
|
||||||
|
[Window][###67]
|
||||||
|
Pos=0,107
|
||||||
|
Size=299,385
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000008,0
|
||||||
|
|
||||||
[Window][Debug##Default]
|
[Window][Debug##Default]
|
||||||
Pos=60,60
|
Pos=60,60
|
||||||
Size=400,400
|
Size=400,400
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][###28]
|
[Window][###57]
|
||||||
Pos=0,19
|
Pos=0,120
|
||||||
Size=299,379
|
Size=196,373
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000003,0
|
|
||||||
|
|
||||||
[Window][###27]
|
|
||||||
Pos=0,19
|
|
||||||
Size=300,200
|
|
||||||
Collapsed=0
|
|
||||||
|
|
||||||
[Window][###34]
|
|
||||||
Pos=0,480
|
|
||||||
Size=250,120
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000010,0
|
|
||||||
|
|
||||||
[Window][###33]
|
|
||||||
Pos=0,445
|
|
||||||
Size=250,383
|
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000E,0
|
DockId=0x0000000E,0
|
||||||
|
|
||||||
[Window][###37]
|
|
||||||
Pos=0,400
|
|
||||||
Size=299,200
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000004,0
|
|
||||||
|
|
||||||
[Window][###22]
|
|
||||||
Pos=0,19
|
|
||||||
Size=288,427
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000007,0
|
|
||||||
|
|
||||||
[Window][###31]
|
|
||||||
Pos=0,448
|
|
||||||
Size=288,152
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000008,0
|
|
||||||
|
|
||||||
[Window][###23]
|
[Window][###23]
|
||||||
Pos=0,19
|
Pos=0,19
|
||||||
Size=305,85
|
Size=270,437
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000021,0
|
DockId=0x0000005B,0
|
||||||
|
|
||||||
[Window][###32]
|
[Window][###29]
|
||||||
Pos=0,376
|
Pos=0,19
|
||||||
Size=250,224
|
Size=244,423
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000012,0
|
||||||
|
|
||||||
|
[Window][###41]
|
||||||
|
Pos=198,19
|
||||||
|
Size=602,425
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000014,0
|
||||||
|
|
||||||
|
[Window][###49]
|
||||||
|
Pos=0,495
|
||||||
|
Size=196,105
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000C,0
|
DockId=0x0000000C,0
|
||||||
|
|
||||||
[Window][###46]
|
|
||||||
Pos=252,19
|
|
||||||
Size=569,605
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000002,0
|
|
||||||
|
|
||||||
[Window][###39]
|
|
||||||
Pos=252,19
|
|
||||||
Size=663,581
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000002,0
|
|
||||||
|
|
||||||
[Window][###53]
|
|
||||||
Pos=252,19
|
|
||||||
Size=548,581
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000002,0
|
|
||||||
|
|
||||||
[Window][###41]
|
|
||||||
Pos=252,19
|
|
||||||
Size=857,809
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000002,0
|
|
||||||
|
|
||||||
[Window][###42]
|
[Window][###42]
|
||||||
Pos=252,19
|
Pos=246,19
|
||||||
Size=548,581
|
Size=554,379
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000002,0
|
DockId=0x00000014,0
|
||||||
|
|
||||||
[Window][###57]
|
|
||||||
Pos=0,400
|
|
||||||
Size=250,200
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000018,0
|
|
||||||
|
|
||||||
[Window][###65]
|
|
||||||
Pos=0,400
|
|
||||||
Size=250,200
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000016,0
|
|
||||||
|
|
||||||
[Window][###73]
|
|
||||||
Pos=237,120
|
|
||||||
Size=300,200
|
|
||||||
Collapsed=0
|
|
||||||
|
|
||||||
[Window][###43]
|
|
||||||
Pos=307,19
|
|
||||||
Size=493,581
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000002,0
|
|
||||||
|
|
||||||
[Window][###35]
|
|
||||||
Pos=0,500
|
|
||||||
Size=305,100
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x0000001A,0
|
|
||||||
|
|
||||||
[Window][###60]
|
[Window][###60]
|
||||||
Pos=0,192
|
Pos=0,19
|
||||||
Size=250,306
|
Size=244,423
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000012,1
|
||||||
|
|
||||||
|
[Window][###107]
|
||||||
|
Pos=958,19
|
||||||
|
Size=242,460
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000059,0
|
||||||
|
|
||||||
|
[Window][###35]
|
||||||
|
Pos=272,19
|
||||||
|
Size=698,591
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000014,0
|
||||||
|
|
||||||
|
[Window][###43]
|
||||||
|
Pos=0,490
|
||||||
|
Size=270,154
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000020,0
|
DockId=0x00000020,0
|
||||||
|
|
||||||
[Window][###51]
|
[Window][###81]
|
||||||
Pos=0,106
|
Pos=272,609
|
||||||
Size=305,392
|
Size=700,191
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000010,0
|
||||||
|
|
||||||
|
[Window][###99]
|
||||||
|
Pos=0,646
|
||||||
|
Size=270,154
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000013,1
|
||||||
|
|
||||||
|
[Window][###87]
|
||||||
|
Pos=272,646
|
||||||
|
Size=710,154
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000046,0
|
||||||
|
|
||||||
|
[Window][###111]
|
||||||
|
Pos=974,19
|
||||||
|
Size=226,641
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x0000004B,0
|
||||||
|
|
||||||
|
[Window][###96]
|
||||||
|
Pos=984,19
|
||||||
|
Size=216,391
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000019,0
|
||||||
|
|
||||||
|
[Window][###127]
|
||||||
|
Pos=984,365
|
||||||
|
Size=216,435
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x0000001E,0
|
||||||
|
|
||||||
|
[Window][###125]
|
||||||
|
Pos=984,600
|
||||||
|
Size=216,200
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000022,0
|
DockId=0x00000022,0
|
||||||
|
|
||||||
[Docking][Data]
|
[Window][###103]
|
||||||
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=800,581 Split=X
|
Pos=984,19
|
||||||
DockNode ID=0x00000009 Parent=0x7C6B3D9B SizeRef=305,581 Split=Y Selected=0xD36850C8
|
Size=216,634
|
||||||
DockNode ID=0x0000000B Parent=0x00000009 SizeRef=147,355 Split=Y Selected=0xD36850C8
|
Collapsed=0
|
||||||
DockNode ID=0x0000000D Parent=0x0000000B SizeRef=250,304 Split=Y Selected=0xD36850C8
|
DockId=0x0000003F,0
|
||||||
DockNode ID=0x0000000F Parent=0x0000000D SizeRef=250,459 Split=Y Selected=0xD36850C8
|
|
||||||
DockNode ID=0x00000011 Parent=0x0000000F SizeRef=250,379 Split=Y Selected=0xD36850C8
|
[Window][###139]
|
||||||
DockNode ID=0x00000013 Parent=0x00000011 SizeRef=250,379 Split=Y Selected=0xD36850C8
|
Pos=984,600
|
||||||
DockNode ID=0x00000015 Parent=0x00000013 SizeRef=250,379 Split=Y Selected=0xD36850C8
|
Size=216,200
|
||||||
DockNode ID=0x00000017 Parent=0x00000015 SizeRef=250,379 Split=Y Selected=0xD36850C8
|
Collapsed=0
|
||||||
DockNode ID=0x00000019 Parent=0x00000017 SizeRef=250,479 Split=Y Selected=0xD36850C8
|
DockId=0x00000026,0
|
||||||
DockNode ID=0x0000001B Parent=0x00000019 SizeRef=250,161 Split=Y Selected=0xD36850C8
|
|
||||||
DockNode ID=0x0000001D Parent=0x0000001B SizeRef=250,192 Split=Y Selected=0xD36850C8
|
[Window][###101]
|
||||||
DockNode ID=0x0000001F Parent=0x0000001D SizeRef=250,171 Split=Y Selected=0xD36850C8
|
Pos=984,19
|
||||||
DockNode ID=0x00000021 Parent=0x0000001F SizeRef=250,85 Selected=0xD36850C8
|
Size=216,609
|
||||||
DockNode ID=0x00000022 Parent=0x0000001F SizeRef=250,392 Selected=0xB4AD3310
|
Collapsed=0
|
||||||
DockNode ID=0x00000020 Parent=0x0000001D SizeRef=250,306 Selected=0x0F59680E
|
DockId=0x00000029,0
|
||||||
DockNode ID=0x0000001E Parent=0x0000001B SizeRef=250,285 Selected=0x0F59680E
|
|
||||||
DockNode ID=0x0000001C Parent=0x00000019 SizeRef=250,316 Selected=0x0F59680E
|
[Window][###121]
|
||||||
DockNode ID=0x0000001A Parent=0x00000017 SizeRef=250,100 Selected=0x977476CD
|
Pos=984,596
|
||||||
DockNode ID=0x00000018 Parent=0x00000015 SizeRef=250,200 Selected=0x3BEDC6B0
|
Size=216,204
|
||||||
DockNode ID=0x00000016 Parent=0x00000013 SizeRef=250,200 Selected=0xC7B9E77E
|
Collapsed=0
|
||||||
DockNode ID=0x00000014 Parent=0x00000011 SizeRef=250,200 Selected=0x3BEDC6B0
|
DockId=0x0000004E,0
|
||||||
DockNode ID=0x00000012 Parent=0x0000000F SizeRef=250,200 Selected=0x83A5C17B
|
|
||||||
DockNode ID=0x00000010 Parent=0x0000000D SizeRef=250,120 Selected=0xAA145F7D
|
[Window][###129]
|
||||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=250,275 Selected=0x1834836D
|
Pos=939,555
|
||||||
DockNode ID=0x0000000C Parent=0x00000009 SizeRef=147,224 Selected=0x2554AADD
|
Size=261,245
|
||||||
DockNode ID=0x0000000A Parent=0x7C6B3D9B SizeRef=493,581 Split=X
|
Collapsed=0
|
||||||
DockNode ID=0x00000005 Parent=0x0000000A SizeRef=288,581 Split=Y Selected=0xEE087978
|
DockId=0x0000002C,0
|
||||||
DockNode ID=0x00000007 Parent=0x00000005 SizeRef=147,427 Selected=0xEE087978
|
|
||||||
DockNode ID=0x00000008 Parent=0x00000005 SizeRef=147,152 Selected=0x62F4D00D
|
[Window][###97]
|
||||||
DockNode ID=0x00000006 Parent=0x0000000A SizeRef=510,581 Split=X
|
Pos=972,19
|
||||||
DockNode ID=0x00000001 Parent=0x00000006 SizeRef=299,581 Split=Y Selected=0xA4B861D9
|
Size=228,560
|
||||||
DockNode ID=0x00000003 Parent=0x00000001 SizeRef=299,379 Selected=0xA4B861D9
|
Collapsed=0
|
||||||
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=299,200 Selected=0xEDB425AD
|
DockId=0x0000002F,0
|
||||||
DockNode ID=0x00000002 Parent=0x00000006 SizeRef=499,581 CentralNode=1 Selected=0x0531B3D5
|
|
||||||
|
[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,18 +1,115 @@
|
|||||||
[
|
{
|
||||||
{
|
"pipeline_order": {
|
||||||
"widget_type": "OpenImageWidget",
|
"0": "opened_image",
|
||||||
"config": {}
|
"1": "inverted_image",
|
||||||
|
"2": "opened_raw",
|
||||||
|
"3": "monochrome",
|
||||||
|
"4": "oriented_image",
|
||||||
|
"6": "framed_image"
|
||||||
},
|
},
|
||||||
{
|
"widgets": [
|
||||||
"widget_type": "InvertStage",
|
{
|
||||||
"config": {}
|
"widget_type": "OpenImageWidget",
|
||||||
},
|
"config": {
|
||||||
{
|
"pipeline_config": {
|
||||||
"widget_type": "PipelineStageViewer",
|
"stage_in": null,
|
||||||
"config": {}
|
"stage_out": 0
|
||||||
},
|
}
|
||||||
{
|
}
|
||||||
"widget_type": "OpenRawWidget",
|
},
|
||||||
"config": {}
|
{
|
||||||
}
|
"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
|
gphoto2
|
||||||
dearpygui
|
dearpygui
|
||||||
numpy
|
numpy
|
||||||
rawpy
|
rawpy
|
||||||
|
scipy
|
Reference in New Issue
Block a user