Skip to content

Commit 68baf60

Browse files
committed
before refactor to use modern nicegui approaches, and nicewidgets
1 parent 8735a41 commit 68baf60

File tree

6 files changed

+52
-28
lines changed

6 files changed

+52
-28
lines changed

readme-dev.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,20 @@ uv run python -m kymflow.gui.main
2121
```bash
2222
uv sync --extra test --extra gui
2323
```
24+
25+
26+
## Run nicegui with environment variables
27+
28+
src/kymflow/gui/main.py has two environment variables to contrtol the nicegui app
29+
30+
`KYMFLOW_GUI_RELOAD`:
31+
- 1 will run in reload mode
32+
- 0 will not (use for distributing the app)
33+
34+
`KYMFLOW_GUI_NATIVE`:
35+
- 1 will run nicegui in a native standalone browser (load folder is flakey!!!)
36+
- 0 will run nicegui in a browser (chrome) tab
37+
38+
```
39+
KYMFLOW_GUI_NATIVE=1 uv run python -m kymflow.gui.main
40+
```

src/kymflow/core/utils/logging.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""
2-
Simple, reliable logging utilities for the kymflow project.
2+
Simple, reliable logging utilities for the kymflow application.
33
4-
- Configure logging once via `setup_logging(...)` at app startup.
4+
- Configure logging via `setup_logging(...)` at app startup.
55
- Get module-specific loggers via `get_logger(__name__)`.
6+
- Reconfigure anytime by calling `setup_logging(...)` again.
67
78
This uses the *root logger* so it plays nicely with most frameworks.
9+
Logs automatically go to ~/.kymflow/logs/kymflow.log by default.
810
"""
911

1012
from __future__ import annotations
@@ -16,9 +18,6 @@
1618
from pathlib import Path
1719
from typing import Optional, Union
1820

19-
# Internal flag to avoid double-configuration
20-
_CONFIGURED = False
21-
2221
# Store the log file path for retrieval
2322
_LOG_FILE_PATH: Optional[Path] = None
2423

@@ -29,35 +28,39 @@ def _expand_path(path: Union[str, Path]) -> Path:
2928

3029
def setup_logging(
3130
level: Union[str, int] = "INFO",
32-
log_file: Optional[Union[str, Path]] = None,
31+
log_file: Optional[Union[str, Path]] = "~/.kymflow/logs/kymflow.log",
3332
max_bytes: int = 5_000_000,
3433
backup_count: int = 5,
3534
) -> None:
3635
"""
3736
Configure root logging with console + optional rotating file handler.
3837
39-
Calling this multiple times is safe; handlers are only added once.
38+
Calling this multiple times will reconfigure logging (removes old handlers first).
4039
4140
Parameters
4241
----------
4342
level:
4443
Logging level for console (e.g. "DEBUG", "INFO").
4544
log_file:
46-
Optional path to a log file. If None, no file handler is added.
45+
Path to a log file. Defaults to "~/.kymflow/logs/kymflow.log".
46+
Set to None to disable file logging (console only).
4747
max_bytes:
4848
Max size in bytes for rotating log file.
4949
backup_count:
5050
Number of rotated log files to keep.
5151
"""
52-
global _CONFIGURED
53-
if _CONFIGURED:
54-
return
55-
5652
# Convert string levels like "INFO" to logging.INFO
5753
if isinstance(level, str):
5854
level = getattr(logging, level.upper(), logging.INFO)
5955

6056
root = logging.getLogger()
57+
58+
# Remove existing handlers to allow reconfiguration
59+
# This prevents duplicate handlers while allowing logging to be reconfigured
60+
for handler in root.handlers[:]:
61+
handler.close()
62+
root.removeHandler(handler)
63+
6164
root.setLevel(level)
6265

6366
# -------- Formatter --------
@@ -87,8 +90,9 @@ def setup_logging(
8790
file_handler.setLevel(logging.DEBUG) # capture everything to file
8891
file_handler.setFormatter(formatter)
8992
root.addHandler(file_handler)
90-
91-
_CONFIGURED = True
93+
else:
94+
# Clear log file path when file logging is disabled
95+
_LOG_FILE_PATH = None
9296

9397

9498
def get_logger(name: Optional[str] = None) -> logging.Logger:

src/kymflow/v2/core/image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from PIL import Image
1010
import matplotlib.cm as cm
1111

12-
from .viewport import KymViewport
12+
from kymflow.v2.core.viewport import KymViewport
1313

1414

1515
@dataclass

src/kymflow/v2/core/roi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import numpy as np
99

10-
from .image import KymImage
10+
from kymflow.v2.core.image import KymImage
1111

1212
@dataclass
1313
class KymRoi:

src/kymflow/v2/core/session.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import numpy as np
99
from PIL import Image
1010

11-
from .image import KymImage, array_to_pil
12-
from .viewport import KymViewport
13-
from .roi import KymRoiSet, KymRoi
11+
from kymflow.v2.core.image import KymImage, array_to_pil
12+
from kymflow.v2.core.viewport import KymViewport
13+
from kymflow.v2.core.roi import KymRoiSet, KymRoi
1414

1515

1616
@dataclass

src/kymflow/v2/gui/nicegui/image_widget.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class KymImageWidget:
7171
def __init__(
7272
self,
7373
engine: KymEngine,
74-
parent: Optional[ui.element] = None,
74+
parent=None,
7575
edge_tolerance: float = 5.0,
7676
cmap: str = "gray",
7777
) -> None:
@@ -80,8 +80,8 @@ def __init__(
8080
Args:
8181
engine: Backend KymEngine instance providing image, viewport, and
8282
ROI management.
83-
parent: Optional NiceGUI parent element to contain the widget.
84-
If None, the widget is created in the current context.
83+
parent: Optional NiceGUI container element (e.g. ui.row(), ui.column()).
84+
If None, the widget will create its own container div.
8585
edge_tolerance: Pixel tolerance for hit-testing ROI edges.
8686
cmap: Name of the Matplotlib colormap used for rendering.
8787
"""
@@ -98,18 +98,22 @@ def __init__(
9898
self.last_pan_y_full: Optional[float] = None
9999
self.last_mouse_x_full: Optional[float] = None
100100
self.last_mouse_y_full: Optional[float] = None
101-
self.last_image_update: float = 0.0 # simple throttle; set by caller if needed
101+
self.last_image_update: float = 0.0
102102

103-
# For move operations we keep a copy of the ROI at drag start
104103
self._orig_roi_dict: Optional[dict] = None
105104

106105
# Logical display size from the engine
107106
self.DISPLAY_W = self.engine.display_width
108107
self.DISPLAY_H = self.engine.display_height
109108

110-
# Build UI elements
111-
with (parent or ui) as container: # noqa: F841 - container unused, but keeps context
112-
# Initial rendering of the viewport
109+
# Decide which container to use: either a provided parent, or a fresh div
110+
if parent is not None:
111+
container = parent
112+
else:
113+
container = ui.element("div").classes("w-full")
114+
115+
# Build UI elements inside the container
116+
with container:
113117
pil_img = self.engine.render_view_pil(
114118
vmin=None,
115119
vmax=None,
@@ -124,7 +128,6 @@ def __init__(
124128
f"aspect-ratio: {self.engine.image.width} / {self.engine.image.height}; "
125129
"object-fit: contain; border: 1px solid #666;"
126130
)
127-
# Use NiceGUI 3.3.1 pattern: on_mouse + generic "wheel" event
128131
self.interactive.on_mouse(self._on_mouse)
129132
self.interactive.on("wheel", self._on_wheel)
130133

0 commit comments

Comments
 (0)