Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/qoradfxm/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
__all__ = ["build_parser", "main"]


# --------------------------------------------------------------- helpers
def _load_dataset(path: Path, dataset_path: str | None) -> QoraDFXMDataset:
if dataset_path:
return QoraDFXMDataset.from_h5(path, dataset_path)
Expand All @@ -53,7 +52,6 @@ def _apply_ops(ds: QoraDFXMDataset, op_specs) -> QoraDFXMDataset:
return ds


# -------------------------------------------------------------- commands
def cmd_fit(args) -> int:
ds = _load_dataset(Path(args.file), args.dataset)
ds = _apply_ops(ds, args.op)
Expand Down Expand Up @@ -220,7 +218,6 @@ def cmd_gui(args) -> int:
return 1


# ---------------------------------------------------------------- parser
def _version() -> str:
from importlib.metadata import PackageNotFoundError, version

Expand Down
7 changes: 0 additions & 7 deletions src/qoradfxm/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ class QoraDFXMDataset:
# memoized processed image; excluded from equality, reset by _with()
_cache: np.ndarray | None = field(default=None, repr=False, compare=False)

# ------------------------------------------------------ constructors
@classmethod
def from_array(cls, arr, source_path=None, meta=None) -> QoraDFXMDataset:
return cls(
Expand Down Expand Up @@ -83,7 +82,6 @@ def from_image_file(cls, path) -> QoraDFXMDataset:
arr = _io.load_image_file(path)
return cls.from_array(arr, source_path=path, meta={"shot_id": path.stem})

# ------------------------------------------------------ fluent core
def _with(self, **kw) -> QoraDFXMDataset:
"""Return a copy with the processed-image cache invalidated."""
return replace(self, _cache=None, **kw)
Expand Down Expand Up @@ -114,7 +112,6 @@ def gamma(self, g: float = 0.5) -> QoraDFXMDataset:
def normalize(self) -> QoraDFXMDataset:
return self._add_op("normalize")

# -------------------------------------------------- geometric (warp)
# These change the image SHAPE, so anything picked on the old grid (fit
# points, ROIs) no longer lines up — apply them before fitting.
def scale(
Expand All @@ -141,7 +138,6 @@ def fit_ellipse(self, points) -> QoraDFXMDataset:
"""Fit an ellipse to picked points (on the PROCESSED image)."""
return self._with(fit=FitResult.from_points(points))

# ------------------------------------------------------ measurements
def linear_view(self) -> QoraDFXMDataset:
"""Same recipe with the intensity-bending ops (log/sqrt/gamma) dropped.

Expand All @@ -163,7 +159,6 @@ def ring_profile(self, **kw):
raise ValueError("no ellipse fit on this dataset — fit first")
return _ring_profile(self.linear_view().image, self.fit, **kw)

# ------------------------------------------------------ history ops
def undo(self) -> QoraDFXMDataset:
return self._with(history=self.history.pop())

Expand All @@ -176,7 +171,6 @@ def replay(self, history: History) -> QoraDFXMDataset:
self.raw, source_path=self.source_path, meta=dict(self.meta)
)._with(history=history)

# ------------------------------------------------------ derived data
@property
def image(self) -> np.ndarray:
"""raw with the full history applied, in order (memoized)."""
Expand All @@ -198,7 +192,6 @@ def bg_applied(self) -> bool:
def log_scale(self) -> bool:
return any(op.kind in ("log", "pure_log") for op in self.history)

# ------------------------------------------------------ export
def to_record(self, status: str = "OK") -> dict | None:
"""One Master-table row, or None if not yet fitted."""
if self.fit is None:
Expand Down
1 change: 0 additions & 1 deletion src/qoradfxm/core/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ def to_dataframe(self) -> pd.DataFrame:
def to_csv(self, path) -> None:
self.to_dataframe().to_csv(path, index=False, encoding="utf-8-sig")

# ------------------------------------------------------------- summary
def peak(self) -> tuple[float, float]:
"""(k, intensity) at the maximum, parabola-refined between samples."""
y = self.mean
Expand Down
9 changes: 2 additions & 7 deletions src/qoradfxm/gui/control_dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
class ControlDockMixin:
"""Control dock, analysis dock and the ring profile panel."""

# ------------------------------------------------------ control dock
def _build_control_dock(self) -> None:
dock = self._control_dock = QtWidgets.QDockWidget("컨트롤", self)
dock.setObjectName("ControlDock")
Expand Down Expand Up @@ -139,7 +138,6 @@ def _build_control_dock(self) -> None:
dock.setMinimumWidth(330)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)

# ----- overmax color -----
def _update_overmax_btn_style(self) -> None:
self._overmax_color_btn.setStyleSheet(
f"background-color: {self._overmax_color.name()}; border: 1px solid #555; border-radius: 3px;"
Expand All @@ -157,7 +155,6 @@ def _choose_overmax_color(self) -> None:
v.set_overmax_color(color)
self._log(f"Over-max highlight color: {color.name()}")

# ----- tool pages -----
def _build_view_page(self) -> QtWidgets.QWidget:
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
Expand Down Expand Up @@ -253,7 +250,6 @@ def _build_ellipse_page(self) -> QtWidgets.QWidget:
fl.addWidget(self._results, 1)
return w

# ----------------------------------------------------- analysis dock
def _build_analysis_dock(self) -> None:
self._analysis_dock = QtWidgets.QDockWidget("분석 / 로그", self)
self._analysis_dock.setObjectName("AnalysisDock")
Expand All @@ -278,11 +274,11 @@ def _build_analysis_dock(self) -> None:
)
tabs.addTab(self._profile_plot, "라인 프로파일")

# 링 프로파일: 타원 둘레 밝기 I(k). Core measures, this only draws.
# Core measures the ring profile I(k); this only draws it.
self._ring_panel = RingProfilePanel(source=self._ring_source)
tabs.addTab(self._ring_panel, "링 프로파일")

# 결과 표 lives in its own resizable window (see _show_results_table),
# The results table lives in its own resizable window (see _show_results_table),
# not this cramped bottom dock. Build the table widget here though.
self._build_results_table_widget()

Expand All @@ -296,7 +292,6 @@ def _build_analysis_dock(self) -> None:
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self._analysis_dock)
self._analysis_dock.hide()

# ------------------------------------------------------- 링 프로파일
def _linear_image(self, doc):
"""Current image with the nonlinear display ops stripped (cached).

Expand Down
1 change: 0 additions & 1 deletion src/qoradfxm/gui/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def add_log(self, message: str) -> None:
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.logs.append(f"[{ts}] {message}")

# --- ROI management ----------------------------------------------------
def add_roi(self, roi) -> None:
self.roi_objects[roi.roi_id] = roi

Expand Down
4 changes: 0 additions & 4 deletions src/qoradfxm/gui/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
class DocumentsMixin:
"""Document and tab system, file logic, fit logic and cursor readout."""

# =================================================== document / tab system
def _make_image_view(self) -> ImageView:
v = ImageView()
v.cursorMoved.connect(self._on_cursor)
Expand Down Expand Up @@ -404,7 +403,6 @@ def _on_fit_reported(self, text: str) -> None:
if text.startswith("ELLIPSE FIT RESULT"):
self._log("타원 피팅 완료")

# -------------------------------------------------------- file logic
def _dialog_start_dir(self) -> str:
start = self._settings.value("last_dir", "", type=str)
if not start or not Path(start).is_dir():
Expand Down Expand Up @@ -591,7 +589,6 @@ def _on_frame_selected(self, *_) -> None:
self._info_label.setText(doc.info)
self._results.clear()

# --------------------------------------------------------- fit logic
def _on_clear_fit(self) -> None:
if v := self._cur_view():
v.clear_fit()
Expand Down Expand Up @@ -629,7 +626,6 @@ def _save_fit(self) -> None:
p.write_text(json.dumps(payload, indent=2), encoding="utf-8")
self._status.showMessage(f"저장됨: {p}", 4000)

# ------------------------------------------------------------ cursor
def _on_cursor(self, x: float, y: float, val: float) -> None:
if np.isnan(val):
self._pos_label.setText(f"커서: x={x:.1f} y={y:.1f} (범위 밖)")
Expand Down
18 changes: 6 additions & 12 deletions src/qoradfxm/gui/file_dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
class FileDockMixin:
"""File explorer dock: tree, name filter and context menu."""

# --------------------------------------------------------- file dock
def _build_file_dock(self) -> None:
dock = self._file_dock = QtWidgets.QDockWidget("탐색기 (Explorer)", self)
dock.setObjectName("FileDock")
Expand All @@ -39,7 +38,8 @@ def _build_file_dock(self) -> None:
self._tree.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
self._tree.clicked.connect(self._on_tree_preview)
self._tree.doubleClicked.connect(self._on_tree_open)
self._tree.activated.connect(self._on_tree_open) # Enter = 확정 열기
# Enter opens for good; a click only previews.
self._tree.activated.connect(self._on_tree_open)
# Arrow keys move the preview with the selection (VS Code style), but
# only while something IS previewing — see _on_tree_current_changed.
self._preview_timer = QtCore.QTimer(self)
Expand Down Expand Up @@ -178,7 +178,6 @@ def _close_all_tabs(self) -> None:
for i in reversed(range(self._tabs.count())):
self._on_tab_close(i)

# ------------------------------------------------------- 이름 필터
def _focus_filter(self) -> None:
self._file_dock.show()
self._filter_edit.setFocus(QtCore.Qt.ShortcutFocusReason)
Expand Down Expand Up @@ -248,7 +247,6 @@ def on_loaded(loaded: str) -> None:

self._fs_model.directoryLoaded.connect(on_loaded)

# -------------------------------------------------- 파일 트리 컨텍스트 메뉴
def _on_tree_context_menu(self, pos: QtCore.QPoint) -> None:
index = self._tree.indexAt(pos)
if not index.isValid():
Expand All @@ -259,13 +257,12 @@ def _on_tree_context_menu(self, pos: QtCore.QPoint) -> None:

menu = QtWidgets.QMenu(self)

# 1. 파일 열기 (파일일 경우만)
if path.is_file():
icn_file = AppIcons.get(AppIcons.FILE, self._icon_color)
act_open = menu.addAction(icn_file, "파일 열기")
act_open.triggered.connect(lambda: self._open_document(path, preview=False))

# 전처리 소스 지정 (이미지 파일만) — 현재 문서 체인에 추가
# Images can be added to the current document's chain as Dark/Flat sources
if path.suffix.lower() in io.IMAGE_SUFFIXES:
src = {"file_path": str(path)}
a_dark = menu.addAction("→ Dark 소스로 빼기 추가")
Expand All @@ -278,24 +275,21 @@ def _on_tree_context_menu(self, pos: QtCore.QPoint) -> None:
)
menu.addSeparator()

# 2. 파일 탐색기에서 열기
icn_folder = AppIcons.get(AppIcons.FOLDER, self._icon_color)
act_explorer = menu.addAction(icn_folder, "파일 탐색기에서 열기")
act_explorer.triggered.connect(lambda: self._show_in_file_manager(path))

# 3. 경로 복사
icn_copy = AppIcons.get(AppIcons.COPY, self._icon_color)
act_copy_path = menu.addAction(icn_copy, "경로 복사")
act_copy_path.triggered.connect(lambda: self._copy_path_to_clipboard(path))

# 메뉴 출력
menu.exec_(self._tree.viewport().mapToGlobal(pos))

def _show_in_file_manager(self, path: Path) -> None:
"""시스템 파일 탐색기(Windows Explorer 등)에서 해당 파일/폴더를 엽니다."""
"""Show the file or folder in the system file manager."""
abs_path = path.resolve()
if abs_path.is_file():
# Windows의 경우 파일 선택 상태로 탐색기 열기 지원
# Only Windows Explorer can open with the file selected
import platform

if platform.system() == "Windows":
Expand All @@ -310,7 +304,7 @@ def _show_in_file_manager(self, path: Path) -> None:
QtGui.QDesktopServices.openUrl(target_url)

def _copy_path_to_clipboard(self, path: Path) -> None:
"""클립보드에 파일/폴더 절대 경로를 복사합니다."""
"""Copy the absolute path to the clipboard."""
abs_path = str(path.resolve())
QtWidgets.QApplication.clipboard().setText(abs_path)
self._status.showMessage(f"경로 복사됨: {abs_path}", 3000)
5 changes: 2 additions & 3 deletions src/qoradfxm/gui/icons.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ def wordmark_pixmap(height: int = 72, on_dark: bool = True) -> QtGui.QPixmap:


class AppIcons:
# 검증된 QtAwesome 아이콘 키값들
HAND = "fa5s.hand-paper"
FOLDER = "ph.folder-open-bold"
CAMERA = "fa5s.camera" # <--- 'msc.camera' 수정!
CAMERA = "fa5s.camera"
DEFAULT_ZOOM = "fa5s.expand-arrows-alt"
PAN = "fa5s.hand-paper"
ZOOM_IN = "fa5s.search-plus"
Expand All @@ -86,6 +85,6 @@ def get(name_or_qta: str, color: str = "#d4d4d8") -> QtGui.QIcon:
def get_pixmap(
name_or_qta: str, size: int = 14, color: str = "#ffffff"
) -> QtGui.QPixmap:
"""QSS 또는 QPainter용 Pixmap 반환"""
"""Pixmap of the icon, for QSS or QPainter."""
icon = AppIcons.get(name_or_qta, color=color)
return icon.pixmap(size, size)
11 changes: 0 additions & 11 deletions src/qoradfxm/gui/image_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:

self._build_ui()

# ------------------------------------------------------------------ UI
def _build_ui(self) -> None:
self._glw = pg.GraphicsLayoutWidget()
self._glw.setBackground("#1a1a1a")
Expand Down Expand Up @@ -316,7 +315,6 @@ def _build_ui(self) -> None:
self._image_item.scene().sigMouseClicked.connect(self._on_mouse_clicked)
self._vb.sigRangeChanged.connect(self._on_range_changed)

# -------------------------------------------------------------- loading
def set_image(self, img: np.ndarray) -> None:
"""Load a new raw frame, resetting fit state and autoscaling levels."""
self._hide_error()
Expand All @@ -335,7 +333,6 @@ def has_image(self) -> bool:
def image_shape(self) -> tuple[int, int]:
return (0, 0) if self._raw is None else self._raw.shape

# ----------------------------------------------------------- zoom/error
def reset_zoom(self) -> None:
"""Fit the whole image (or error marker) back into the view."""
self._vb.autoRange()
Expand Down Expand Up @@ -415,7 +412,6 @@ def _hide_error(self) -> None:
self._error_curve.setVisible(False)
self._error_text.setVisible(False)

# ------------------------------------------------------------- scale bar
def set_scale(self, px_size: float, unit: str) -> None:
"""Calibrate: each pixel spans ``px_size`` of the given real unit."""
self._px_size = float(px_size) if px_size > 0 else 1.0
Expand Down Expand Up @@ -521,7 +517,6 @@ def save_display(self, path: str) -> None:
if self._image_item.qimage is None or not self._image_item.qimage.save(path):
raise OSError("이미지 렌더 저장 실패")

# ------------------------------------------------------- histogram panel
def histogram_visible(self) -> bool:
return self._hist_visible

Expand Down Expand Up @@ -608,7 +603,6 @@ def _refresh_overlay(self, *_) -> None:
rgba[mask] = (*self._overmax_color, 255)
self._overlay_item.setImage(rgba, autoLevels=False)

# --------------------------------------------------------------- cursor
def _on_mouse_moved(self, scene_pos) -> None:
if self._raw is None:
return
Expand All @@ -626,7 +620,6 @@ def _on_mouse_moved(self, scene_pos) -> None:
x0, y0 = self._line_pending
self._line_preview.setData([x0, x], [y0, y])

# ---- region rubber-band (rect tool) ----
def _on_rect_draw_start(self, p0) -> None:
self._rect_preview.setVisible(True)

Expand All @@ -639,7 +632,6 @@ def _on_rect_draw_finish(self, p0, p1) -> None:
if abs(p1.x() - p0.x()) > 2 and abs(p1.y() - p0.y()) > 2:
self.rectDrawn.emit(p0.x(), p0.y(), p1.x(), p1.y())

# -------------------------------------------------------------- tools
def set_tool(self, name: str) -> None:
"""Switch the active interaction tool (state machine)."""
if name not in self.TOOLS:
Expand Down Expand Up @@ -735,7 +727,6 @@ def clear_fit(self) -> None:
self._major_axis.setData([], [])
self._minor_axis.setData([], [])

# -------------------------------------------------------- ROI objects (B)
def add_roi_item(self, roi) -> None:
"""Draw a managed ROI on the canvas and emit signals on edits."""
roi.setZValue(25)
Expand Down Expand Up @@ -779,7 +770,6 @@ def select_roi(self, roi) -> None:
def selected_roi(self):
return self._selected_roi

# ---- active line binding: the bottom profile follows this line ROI ----
def set_active_line(self, roi) -> None:
if self._active_line is roi:
self._emit_active_profile()
Expand Down Expand Up @@ -812,7 +802,6 @@ def _emit_active_profile(self, *_) -> None:
)
self.lineProfileChanged.emit(axis, vals, unit)

# ---------------------------------------------------------- distance tool
def _redraw_distance(self) -> None:
for t in self._dist_labels:
self._vb.removeItem(t)
Expand Down
Loading
Loading