Framing widget done
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
env
|
||||
out.png
|
@ -13,8 +13,7 @@ from .layout_manager import LayoutManager
|
||||
|
||||
from .widgets.base_widget import BaseWidget
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(message)s")
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -62,16 +61,14 @@ class EditorManager:
|
||||
and cls is not ModuleBaseWidget
|
||||
and cls.register
|
||||
):
|
||||
logging.info(
|
||||
f" -> Found and registered widget: {name}")
|
||||
logging.info(f" -> Found and registered widget: {name}")
|
||||
self._register_widget(name, cls)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to import widget '{py_file.name}': {e}")
|
||||
|
||||
def _register_widget(self, name: str, widget_class: object):
|
||||
if name in self.widget_classes:
|
||||
logging.warning(
|
||||
f"Widget '{name}' is already registered. Overwriting.")
|
||||
logging.warning(f"Widget '{name}' is already registered. Overwriting.")
|
||||
self.widget_classes[name] = widget_class
|
||||
|
||||
def _add_widget(self, widget_type: str, config: dict = {}):
|
||||
@ -98,6 +95,18 @@ class EditorManager:
|
||||
def _on_scroll(self, sender, app_data, user_data):
|
||||
self.bus.publish_deferred("mouse_scrolled", app_data)
|
||||
|
||||
def _on_release(self, sender, app_data, user_data):
|
||||
self.bus.publish_deferred(
|
||||
"mouse_released",
|
||||
{
|
||||
"button": (
|
||||
"right"
|
||||
if app_data == 0
|
||||
else ("left" if app_data == 1 else ("middle"))
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
def setup(self):
|
||||
self._discover_and_register_widgets(
|
||||
f"{os.path.dirname(os.path.realpath(__file__))}/widgets"
|
||||
@ -118,8 +127,7 @@ class EditorManager:
|
||||
"process_full_res", None
|
||||
),
|
||||
)
|
||||
dpg.add_menu_item(
|
||||
label="Quit", callback=lambda: dpg.stop_dearpygui())
|
||||
dpg.add_menu_item(label="Quit", callback=lambda: dpg.stop_dearpygui())
|
||||
|
||||
with dpg.menu(label="View"):
|
||||
for widget_name in sorted(self.widget_classes.keys()):
|
||||
@ -130,16 +138,9 @@ class EditorManager:
|
||||
)
|
||||
|
||||
with dpg.handler_registry() as self.handler_registry:
|
||||
dpg.add_mouse_drag_handler(
|
||||
callback=self._on_drag, threshold=1.0, button=0
|
||||
)
|
||||
dpg.add_mouse_drag_handler(
|
||||
callback=self._on_drag, threshold=1.0, button=1
|
||||
)
|
||||
dpg.add_mouse_drag_handler(
|
||||
callback=self._on_drag, threshold=1.0, button=2
|
||||
)
|
||||
dpg.add_mouse_drag_handler(callback=self._on_drag, threshold=1.0)
|
||||
dpg.add_mouse_wheel_handler(callback=self._on_scroll)
|
||||
dpg.add_mouse_release_handler(callback=self._on_release)
|
||||
|
||||
def run(self):
|
||||
self.setup()
|
||||
|
@ -1,85 +0,0 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
from .stage_viewer_widget import PipelineStageViewer
|
||||
|
||||
|
||||
class CropWidget(PipelineStageViewer):
|
||||
name = "Crop Image"
|
||||
register = True
|
||||
has_pipeline_in = True
|
||||
has_pipeline_out = True
|
||||
|
||||
def __init__(self, manager, logger):
|
||||
super().__init__(manager, logger)
|
||||
self.crop_start = None # (x, y)
|
||||
self.crop_end = None # (x, y)
|
||||
self.crop_active = False
|
||||
|
||||
self.manager.bus.subscribe("img_clicked", self.on_click)
|
||||
self.manager.bus.subscribe("img_dragged", self.on_drag)
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
super().create_pipeline_stage_content()
|
||||
|
||||
# def on_full_res_pipeline_data(self, img):
|
||||
# pass
|
||||
|
||||
def on_pipeline_data(self, img):
|
||||
if img is None:
|
||||
return
|
||||
self.img = img
|
||||
|
||||
if self.crop_start and self.crop_end:
|
||||
x0, y0 = self.crop_start
|
||||
x1, y1 = self.crop_end
|
||||
x0, x1 = sorted((int(x0), int(x1)))
|
||||
y0, y1 = sorted((int(y0), int(y1)))
|
||||
|
||||
x0 = max(0, min(x0, img.shape[1]-1))
|
||||
x1 = max(0, min(x1, img.shape[1]-1))
|
||||
y0 = max(0, min(y0, img.shape[0]-1))
|
||||
y1 = max(0, min(y1, img.shape[0]-1))
|
||||
|
||||
cropped = img[y0:y1, x0:x1, :]
|
||||
self.publish_stage(cropped)
|
||||
else:
|
||||
self.publish_stage(img)
|
||||
|
||||
self.needs_update = True
|
||||
|
||||
def on_click(self, data):
|
||||
if data["obj"] is not self:
|
||||
return
|
||||
if data["button"] == "left":
|
||||
self.crop_start = data["pos"]
|
||||
self.crop_end = data["pos"]
|
||||
self.crop_active = True
|
||||
self.needs_update = True
|
||||
|
||||
def on_drag(self, data):
|
||||
if data["obj"] is not self or not self.crop_active:
|
||||
return
|
||||
self.crop_end = data["pos"]
|
||||
self.needs_update = True
|
||||
|
||||
def update_texture(self, img):
|
||||
super().update_texture(img)
|
||||
if self.crop_start and self.crop_end:
|
||||
# map image coords back to screen coords
|
||||
x0, y0 = self.crop_start
|
||||
x1, y1 = self.crop_end
|
||||
h, w, _ = self.img.shape
|
||||
img_x, img_y = self.image_position
|
||||
img_w, img_h = self.scaled_size
|
||||
|
||||
p0 = (
|
||||
img_x + x0 / w * img_w,
|
||||
img_y + y0 / h * img_h
|
||||
)
|
||||
p1 = (
|
||||
img_x + x1 / w * img_w,
|
||||
img_y + y1 / h * img_h
|
||||
)
|
||||
|
||||
dpg.draw_rectangle(pmin=p0, pmax=p1, color=(255, 255, 0, 255),
|
||||
fill=(255, 255, 0, 50), thickness=2, parent=self.drawlist)
|
@ -52,7 +52,7 @@ class ExportStage(PipelineStageWidget):
|
||||
super().__init__(manager, logger, default_stage_out="unused")
|
||||
# tags for our “Save As” dialog
|
||||
self._save_dialog_tag = dpg.generate_uuid()
|
||||
self._save_path = None
|
||||
self._save_path = f"{os.getcwd()}/out.png"
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
# Button to pop up the file-save dialog
|
||||
@ -74,7 +74,7 @@ class ExportStage(PipelineStageWidget):
|
||||
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("...")
|
||||
self.path_label = dpg.add_text("out.png")
|
||||
|
||||
def _on_save_selected(self, sender, app_data):
|
||||
"""
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
import numpy as np
|
||||
import time
|
||||
from scipy.ndimage import rotate
|
||||
import scipy.ndimage as snd
|
||||
|
||||
from .stage_viewer_widget import PipelineStageViewer
|
||||
|
||||
@ -15,74 +15,71 @@ class FramingWidget(PipelineStageViewer):
|
||||
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
|
||||
|
||||
# Throttle publishing to a few Hz
|
||||
self._last_pub_time = 0.0
|
||||
self._publish_interval = 0.5 # seconds
|
||||
# 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.img = img.copy()
|
||||
self._publish_rotated_and_cropped()
|
||||
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 active
|
||||
|
||||
# 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, 255, 0, 255), thickness=2, parent=self.drawlist)
|
||||
dpg.draw_line(
|
||||
p1=p0,
|
||||
p2=p1,
|
||||
color=(255, 0, 0, 255),
|
||||
thickness=2,
|
||||
parent=self.drawlist,
|
||||
)
|
||||
|
||||
def on_click(self, data):
|
||||
if data.get("obj") is not self:
|
||||
return
|
||||
x, y = data.get("pos")
|
||||
button = data.get("button")
|
||||
if button == "right":
|
||||
self.rot_start = (x, y)
|
||||
self.rot_end = (x, y)
|
||||
self.needs_update = True
|
||||
# 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,
|
||||
)
|
||||
|
||||
def on_drag(self, data):
|
||||
if data.get("obj") is not self:
|
||||
return
|
||||
x, y = data.get("pos")
|
||||
button = data.get("button")
|
||||
if button == "right":
|
||||
self.rot_end = (x, y)
|
||||
# Update angle on rotation drag
|
||||
if self.rot_start and self.rot_end:
|
||||
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))
|
||||
# Throttle publishes
|
||||
now = time.time()
|
||||
if now - self._last_pub_time >= self._publish_interval:
|
||||
self._publish_rotated_and_cropped()
|
||||
self._last_pub_time = now
|
||||
self.needs_update = True
|
||||
|
||||
def on_scroll(self, data):
|
||||
print(data)
|
||||
|
||||
def _publish_rotated_and_cropped(self):
|
||||
w, h, _ = self.img.shape
|
||||
out = self.rotate_and_crop(self.img, self.angle, (0, 0, w, h))
|
||||
self.publish_stage(out)
|
||||
if self.needs_publishing:
|
||||
img = self.crop(self.img)
|
||||
self.publish_stage(img)
|
||||
|
||||
def _pos_to_canvas(self, img_pos):
|
||||
x, y = img_pos
|
||||
@ -91,28 +88,91 @@ class FramingWidget(PipelineStageViewer):
|
||||
sw, sh = self.scaled_size
|
||||
return (ix + x / iw * sw, iy + y / ih * sh)
|
||||
|
||||
def rotate_and_crop(
|
||||
self,
|
||||
img: np.ndarray,
|
||||
angle: float,
|
||||
rect: tuple[int, int, int, int],
|
||||
cval: float = 0.0
|
||||
) -> np.ndarray:
|
||||
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]
|
||||
x, y, cw, ch = rect
|
||||
rotated = np.empty_like(img)
|
||||
for c in range(img.shape[2]):
|
||||
rotated[..., c] = rotate(
|
||||
img[..., c],
|
||||
angle,
|
||||
reshape=False,
|
||||
order=1, # bilinear interpolation
|
||||
mode='constant',
|
||||
cval=cval,
|
||||
prefilter=False
|
||||
)
|
||||
x0 = max(0, min(int(x), w - 1))
|
||||
y0 = max(0, min(int(y), h - 1))
|
||||
x1 = max(0, min(int(x + cw), w))
|
||||
y1 = max(0, min(int(y + ch), h))
|
||||
return rotated[y0:y1, x0:x1]
|
||||
|
||||
# 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]
|
@ -81,6 +81,8 @@ class PipelineStageWidget(BaseWidget):
|
||||
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 {
|
||||
|
@ -19,9 +19,11 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
self.canvas_handler = None
|
||||
self.scaled_size = (0, 0)
|
||||
self.image_position = (0, 0)
|
||||
self._last_tex_size = (0, 0)
|
||||
|
||||
self.manager.bus.subscribe("mouse_dragged", self._on_mouse_drag, False)
|
||||
self.manager.bus.subscribe("mouse_scrolled", self._on_mouse_scroll, False)
|
||||
self.manager.bus.subscribe("mouse_released", self._on_mouse_release, False)
|
||||
|
||||
def create_pipeline_stage_content(self):
|
||||
# Create an empty dynamic texture
|
||||
@ -52,6 +54,7 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
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)
|
||||
@ -84,6 +87,7 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
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)
|
||||
@ -113,6 +117,7 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
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)
|
||||
@ -127,6 +132,35 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
},
|
||||
)
|
||||
|
||||
def _on_mouse_release(self, data):
|
||||
mouse_x, mouse_y = dpg.get_mouse_pos(local=False)
|
||||
canvas_x, canvas_y = dpg.get_item_rect_min(self.drawlist)
|
||||
local_x = mouse_x - canvas_x
|
||||
local_y = mouse_y - canvas_y
|
||||
|
||||
img_x, img_y = self.image_position
|
||||
img_w, img_h = self.scaled_size
|
||||
|
||||
if (
|
||||
local_x >= img_x
|
||||
and local_x < img_x + img_w
|
||||
and local_y >= img_y
|
||||
and local_y < img_y + img_h
|
||||
and dpg.is_item_focused(self.window_tag)
|
||||
):
|
||||
# calculate the image coordinate
|
||||
x = int((local_x - img_x) * self.img.shape[1] / img_w)
|
||||
y = int((local_y - img_y) * self.img.shape[0] / img_h)
|
||||
self.manager.bus.publish_deferred(
|
||||
"img_released",
|
||||
{
|
||||
"stage_id": self.pipeline_stage_in_id,
|
||||
"pos": (x, y),
|
||||
"button": data['button'],
|
||||
"obj":self,
|
||||
},
|
||||
)
|
||||
|
||||
def on_resize(self, width, height):
|
||||
self.needs_update = True
|
||||
|
||||
@ -143,12 +177,14 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
if img is None:
|
||||
return
|
||||
|
||||
# Only recreate the texture if its size changed
|
||||
h, w, _ = img.shape
|
||||
flat = img.flatten().tolist()
|
||||
|
||||
# Replace texture
|
||||
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,
|
||||
@ -156,7 +192,15 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
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)
|
||||
|
||||
# Clear old drawings
|
||||
dpg.delete_item(self.drawlist, children_only=True)
|
||||
|
||||
# Draw the image to the screen
|
||||
win_w, win_h = self.window_width, self.window_height
|
||||
scale = min(win_w / w, win_h / h)
|
||||
disp_w = int(w * scale)
|
||||
@ -168,9 +212,6 @@ class PipelineStageViewer(PipelineStageWidget):
|
||||
self.scaled_size = (disp_w, disp_h)
|
||||
self.image_position = (x_off, y_off)
|
||||
|
||||
# Clear old drawings
|
||||
dpg.delete_item(self.drawlist, children_only=True)
|
||||
|
||||
# Draw image
|
||||
dpg.draw_image(
|
||||
self.texture_tag,
|
||||
|
@ -19,7 +19,7 @@ DockId=0x00000007,0
|
||||
Pos=0,19
|
||||
Size=270,691
|
||||
Collapsed=0
|
||||
DockId=0x00000041,1
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###59]
|
||||
Pos=0,494
|
||||
@ -46,9 +46,9 @@ DockId=0x0000000E,0
|
||||
|
||||
[Window][###23]
|
||||
Pos=0,19
|
||||
Size=270,693
|
||||
Size=270,437
|
||||
Collapsed=0
|
||||
DockId=0x00000041,0
|
||||
DockId=0x0000005B,0
|
||||
|
||||
[Window][###29]
|
||||
Pos=0,19
|
||||
@ -192,7 +192,7 @@ DockId=0x00000036,0
|
||||
Pos=0,19
|
||||
Size=270,567
|
||||
Collapsed=0
|
||||
DockId=0x00000041,1
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###82]
|
||||
Pos=272,601
|
||||
@ -257,7 +257,7 @@ DockId=0x00000042,1
|
||||
Pos=0,19
|
||||
Size=270,679
|
||||
Collapsed=0
|
||||
DockId=0x00000041,1
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###83]
|
||||
Pos=272,612
|
||||
@ -278,10 +278,10 @@ Collapsed=0
|
||||
DockId=0x0000004A,0
|
||||
|
||||
[Window][###36]
|
||||
Pos=272,19
|
||||
Size=710,625
|
||||
Pos=0,458
|
||||
Size=270,254
|
||||
Collapsed=0
|
||||
DockId=0x00000014,0
|
||||
DockId=0x0000005C,0
|
||||
|
||||
[Window][###48]
|
||||
Pos=0,714
|
||||
@ -291,9 +291,9 @@ DockId=0x00000036,1
|
||||
|
||||
[Window][###56]
|
||||
Pos=0,19
|
||||
Size=270,693
|
||||
Size=270,437
|
||||
Collapsed=0
|
||||
DockId=0x00000041,1
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###93]
|
||||
Pos=0,714
|
||||
@ -340,7 +340,7 @@ DockId=0x00000042,1
|
||||
Pos=0,19
|
||||
Size=270,679
|
||||
Collapsed=0
|
||||
DockId=0x00000041,1
|
||||
DockId=0x0000005B,1
|
||||
|
||||
[Window][###104]
|
||||
Pos=984,19
|
||||
@ -370,7 +370,7 @@ DockId=0x00000014,1
|
||||
Pos=272,19
|
||||
Size=710,625
|
||||
Collapsed=0
|
||||
DockId=0x00000014,1
|
||||
DockId=0x00000014,0
|
||||
|
||||
[Window][###169]
|
||||
Pos=272,19
|
||||
@ -418,7 +418,9 @@ DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,
|
||||
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 Selected=0x068DEF00
|
||||
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
|
||||
@ -436,7 +438,7 @@ DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,
|
||||
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=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
|
||||
|
@ -83,7 +83,7 @@
|
||||
"widget_type": "ExportStage",
|
||||
"config": {
|
||||
"pipeline_config": {
|
||||
"stage_in": 5,
|
||||
"stage_in": 6,
|
||||
"stage_out": null
|
||||
}
|
||||
}
|
||||
|
@ -3,3 +3,4 @@ gphoto2
|
||||
dearpygui
|
||||
numpy
|
||||
rawpy
|
||||
scipy
|
Reference in New Issue
Block a user