From af2bbe93cc492d23628135024bfc387d7bbd081c Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Sat, 2 Aug 2025 17:12:31 +0200 Subject: [PATCH] Added histogram widget --- negstation/negstation.py | 2 +- negstation/widgets/histogram_widget.py | 69 +++++++++++++++ negstation/widgets/monochrome_widget.py | 31 +++++++ negstation_layout.ini | 112 ++++++++++++++++-------- negstation_widgets.json | 8 ++ 5 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 negstation/widgets/histogram_widget.py create mode 100644 negstation/widgets/monochrome_widget.py diff --git a/negstation/negstation.py b/negstation/negstation.py index 35f5c86..ab3c92b 100644 --- a/negstation/negstation.py +++ b/negstation/negstation.py @@ -87,7 +87,7 @@ class EditorManager: ) 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) with dpg.viewport_menu_bar(): diff --git a/negstation/widgets/histogram_widget.py b/negstation/widgets/histogram_widget.py new file mode 100644 index 0000000..38308c7 --- /dev/null +++ b/negstation/widgets/histogram_widget.py @@ -0,0 +1,69 @@ +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 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)) diff --git a/negstation/widgets/monochrome_widget.py b/negstation/widgets/monochrome_widget.py new file mode 100644 index 0000000..79dc413 --- /dev/null +++ b/negstation/widgets/monochrome_widget.py @@ -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): + dpg.add_text("Converting to grayscale...") + + 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)) diff --git a/negstation_layout.ini b/negstation_layout.ini index 400a68f..e496bf2 100644 --- a/negstation_layout.ini +++ b/negstation_layout.ini @@ -1,6 +1,6 @@ [Window][WindowOverViewport_11111111] Pos=0,19 -Size=800,581 +Size=1200,781 Collapsed=0 [Window][###33] @@ -17,9 +17,9 @@ DockId=0x00000007,0 [Window][###51] Pos=0,19 -Size=244,474 +Size=270,469 Collapsed=0 -DockId=0x00000014,1 +DockId=0x0000001F,1 [Window][###59] Pos=0,494 @@ -46,9 +46,9 @@ DockId=0x0000000E,0 [Window][###23] Pos=0,19 -Size=244,474 +Size=270,469 Collapsed=0 -DockId=0x00000014,0 +DockId=0x0000001F,0 [Window][###29] Pos=0,19 @@ -87,45 +87,85 @@ Collapsed=0 DockId=0x00000016,0 [Window][###35] -Pos=246,19 -Size=554,425 +Pos=272,19 +Size=710,625 Collapsed=0 DockId=0x00000011,0 [Window][###43] -Pos=0,495 -Size=244,105 +Pos=0,490 +Size=270,154 Collapsed=0 -DockId=0x00000015,0 +DockId=0x00000020,0 [Window][###81] -Pos=246,446 -Size=554,154 +Pos=272,646 +Size=710,154 Collapsed=0 DockId=0x00000010,0 -[Docking][Data] -DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=800,581 Split=X - DockNode ID=0x00000009 Parent=0x7C6B3D9B SizeRef=244,581 Split=Y Selected=0x3BEDC6B0 - DockNode ID=0x0000000B Parent=0x00000009 SizeRef=196,474 Split=Y Selected=0x3BEDC6B0 - DockNode ID=0x0000000D Parent=0x0000000B SizeRef=196,99 Split=Y Selected=0x99D84869 - DockNode ID=0x00000012 Parent=0x0000000D SizeRef=196,423 Selected=0x0F59680E - DockNode ID=0x00000013 Parent=0x0000000D SizeRef=196,156 Split=Y Selected=0xB4AD3310 - DockNode ID=0x00000014 Parent=0x00000013 SizeRef=244,474 Selected=0xB4AD3310 - DockNode ID=0x00000015 Parent=0x00000013 SizeRef=244,105 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=0x7C6B3D9B SizeRef=554,581 Split=X - DockNode ID=0x00000003 Parent=0x0000000A SizeRef=299,581 Split=Y Selected=0x52849BCC - DockNode ID=0x00000005 Parent=0x00000003 SizeRef=299,473 Split=Y Selected=0x52849BCC - DockNode ID=0x00000007 Parent=0x00000005 SizeRef=299,86 Selected=0x52849BCC - DockNode ID=0x00000008 Parent=0x00000005 SizeRef=299,385 Selected=0xBD79B41E - DockNode ID=0x00000006 Parent=0x00000003 SizeRef=299,106 Selected=0x84DD78D1 - DockNode ID=0x00000004 Parent=0x0000000A SizeRef=499,581 Split=Y - DockNode ID=0x00000001 Parent=0x00000004 SizeRef=800,379 Split=Y Selected=0x7FF1E0B5 - DockNode ID=0x0000000F Parent=0x00000001 SizeRef=602,425 Split=Y Selected=0x38519A65 - DockNode ID=0x00000011 Parent=0x0000000F SizeRef=554,379 CentralNode=1 Selected=0x977476CD - DockNode ID=0x00000016 Parent=0x0000000F SizeRef=554,200 Selected=0x3A881EEF - DockNode ID=0x00000010 Parent=0x00000001 SizeRef=602,154 Selected=0x083320CE - DockNode ID=0x00000002 Parent=0x00000004 SizeRef=800,200 Selected=0x1834836D +[Window][###99] +Pos=0,646 +Size=270,154 +Collapsed=0 +DockId=0x00000015,1 + +[Window][###87] +Pos=0,646 +Size=270,154 +Collapsed=0 +DockId=0x00000015,0 + +[Window][###111] +Pos=900,19 +Size=300,781 +Collapsed=0 +DockId=0x0000001A,0 + +[Window][###96] +Pos=984,19 +Size=216,781 +Collapsed=0 +DockId=0x0000001D,0 + +[Window][###127] +Pos=984,365 +Size=216,435 +Collapsed=0 +DockId=0x0000001E,0 + +[Docking][Data] +DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,19 Size=1200,781 Split=X + DockNode ID=0x0000001B Parent=0x7C6B3D9B SizeRef=982,781 Split=X + DockNode ID=0x00000019 Parent=0x0000001B SizeRef=898,781 Split=X + DockNode ID=0x00000017 Parent=0x00000019 SizeRef=898,781 Split=X + DockNode ID=0x00000009 Parent=0x00000017 SizeRef=270,581 Split=Y Selected=0x3BEDC6B0 + DockNode ID=0x0000000B Parent=0x00000009 SizeRef=196,474 Split=Y Selected=0x3BEDC6B0 + DockNode ID=0x0000000D Parent=0x0000000B SizeRef=196,99 Split=Y Selected=0x99D84869 + DockNode ID=0x00000012 Parent=0x0000000D SizeRef=196,423 Selected=0x0F59680E + DockNode ID=0x00000013 Parent=0x0000000D SizeRef=196,156 Split=Y Selected=0xB4AD3310 + DockNode ID=0x00000014 Parent=0x00000013 SizeRef=244,625 Split=Y Selected=0xB4AD3310 + DockNode ID=0x0000001F Parent=0x00000014 SizeRef=270,469 Selected=0xB4AD3310 + DockNode ID=0x00000020 Parent=0x00000014 SizeRef=270,154 Selected=0x0531B3D5 + DockNode ID=0x00000015 Parent=0x00000013 SizeRef=244,154 Selected=0x8773D56E + DockNode ID=0x0000000E Parent=0x0000000B SizeRef=196,373 Selected=0x3BEDC6B0 + DockNode ID=0x0000000C Parent=0x00000009 SizeRef=196,105 Selected=0x4F81AB74 + DockNode ID=0x0000000A Parent=0x00000017 SizeRef=710,581 Split=X + DockNode ID=0x00000003 Parent=0x0000000A SizeRef=299,581 Split=Y Selected=0x52849BCC + DockNode ID=0x00000005 Parent=0x00000003 SizeRef=299,473 Split=Y Selected=0x52849BCC + DockNode ID=0x00000007 Parent=0x00000005 SizeRef=299,86 Selected=0x52849BCC + DockNode ID=0x00000008 Parent=0x00000005 SizeRef=299,385 Selected=0xBD79B41E + DockNode ID=0x00000006 Parent=0x00000003 SizeRef=299,106 Selected=0x84DD78D1 + DockNode ID=0x00000004 Parent=0x0000000A SizeRef=499,581 Split=Y + DockNode ID=0x00000001 Parent=0x00000004 SizeRef=800,379 Split=Y Selected=0x7FF1E0B5 + DockNode ID=0x0000000F Parent=0x00000001 SizeRef=602,425 Split=Y Selected=0x38519A65 + DockNode ID=0x00000011 Parent=0x0000000F SizeRef=554,379 CentralNode=1 Selected=0x977476CD + DockNode ID=0x00000016 Parent=0x0000000F SizeRef=554,200 Selected=0x3A881EEF + DockNode ID=0x00000010 Parent=0x00000001 SizeRef=602,154 Selected=0x083320CE + DockNode ID=0x00000002 Parent=0x00000004 SizeRef=800,200 Selected=0x1834836D + DockNode ID=0x00000018 Parent=0x00000019 SizeRef=300,781 Selected=0x7E9438EA + DockNode ID=0x0000001A Parent=0x0000001B SizeRef=300,781 Selected=0x7E9438EA + DockNode ID=0x0000001C Parent=0x7C6B3D9B SizeRef=216,781 Split=Y Selected=0x714F2F7B + DockNode ID=0x0000001D Parent=0x0000001C SizeRef=216,344 Selected=0x714F2F7B + DockNode ID=0x0000001E Parent=0x0000001C SizeRef=216,435 Selected=0x7740BFE4 diff --git a/negstation_widgets.json b/negstation_widgets.json index d6213dc..2290fa7 100644 --- a/negstation_widgets.json +++ b/negstation_widgets.json @@ -18,5 +18,13 @@ { "widget_type": "LogWindowWidget", "config": {} + }, + { + "widget_type": "MonochromeStage", + "config": {} + }, + { + "widget_type": "HistogramWidget", + "config": {} } ] \ No newline at end of file