diff --git a/src/qoradfxm/cli/__init__.py b/src/qoradfxm/cli/__init__.py index beeb7ac..e6d9277 100644 --- a/src/qoradfxm/cli/__init__.py +++ b/src/qoradfxm/cli/__init__.py @@ -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) @@ -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) @@ -220,7 +218,6 @@ def cmd_gui(args) -> int: return 1 -# ---------------------------------------------------------------- parser def _version() -> str: from importlib.metadata import PackageNotFoundError, version diff --git a/src/qoradfxm/core/dataset.py b/src/qoradfxm/core/dataset.py index 7f3f3a5..4a218f7 100644 --- a/src/qoradfxm/core/dataset.py +++ b/src/qoradfxm/core/dataset.py @@ -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( @@ -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) @@ -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( @@ -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. @@ -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()) @@ -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).""" @@ -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: diff --git a/src/qoradfxm/core/profile.py b/src/qoradfxm/core/profile.py index 68f9f43..e8e01e1 100644 --- a/src/qoradfxm/core/profile.py +++ b/src/qoradfxm/core/profile.py @@ -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 diff --git a/src/qoradfxm/gui/control_dock.py b/src/qoradfxm/gui/control_dock.py index ad4459b..5451909 100644 --- a/src/qoradfxm/gui/control_dock.py +++ b/src/qoradfxm/gui/control_dock.py @@ -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") @@ -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;" @@ -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) @@ -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") @@ -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() @@ -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). diff --git a/src/qoradfxm/gui/document.py b/src/qoradfxm/gui/document.py index 6873342..eff74d8 100644 --- a/src/qoradfxm/gui/document.py +++ b/src/qoradfxm/gui/document.py @@ -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 diff --git a/src/qoradfxm/gui/documents.py b/src/qoradfxm/gui/documents.py index 96fc2a6..4094e54 100644 --- a/src/qoradfxm/gui/documents.py +++ b/src/qoradfxm/gui/documents.py @@ -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) @@ -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(): @@ -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() @@ -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} (범위 밖)") diff --git a/src/qoradfxm/gui/file_dock.py b/src/qoradfxm/gui/file_dock.py index 2f64e86..3407b20 100644 --- a/src/qoradfxm/gui/file_dock.py +++ b/src/qoradfxm/gui/file_dock.py @@ -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") @@ -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) @@ -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) @@ -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(): @@ -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 소스로 빼기 추가") @@ -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": @@ -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) diff --git a/src/qoradfxm/gui/icons.py b/src/qoradfxm/gui/icons.py index 7f0d08f..3fbbab2 100644 --- a/src/qoradfxm/gui/icons.py +++ b/src/qoradfxm/gui/icons.py @@ -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" @@ -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) diff --git a/src/qoradfxm/gui/image_view.py b/src/qoradfxm/gui/image_view.py index 922c5a8..a5e3649 100644 --- a/src/qoradfxm/gui/image_view.py +++ b/src/qoradfxm/gui/image_view.py @@ -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") @@ -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() @@ -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() @@ -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 @@ -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 @@ -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 @@ -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) @@ -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: @@ -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) @@ -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() @@ -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) diff --git a/src/qoradfxm/gui/main_window.py b/src/qoradfxm/gui/main_window.py index 66cde8b..da9d2c2 100644 --- a/src/qoradfxm/gui/main_window.py +++ b/src/qoradfxm/gui/main_window.py @@ -132,7 +132,6 @@ def __init__(self) -> None: self._restore_layout() self._restore_session() - # --------------------------------------------------- window lifecycle def _restore_layout(self) -> None: """Come back the size you left, with the docks where you put them.""" geom = self._settings.value("win_geometry") @@ -151,7 +150,6 @@ def _update_window_title(self) -> None: doc = self._cur() self.setWindowTitle(f"{doc.file_path.name} — {APP_NAME}" if doc else APP_NAME) - # ------------------------------------------------------ drag & drop def _new_doc_prefs(self) -> ViewPrefs: """What a newly opened document starts with: the app-scoped defaults.""" return self._app_prefs.copy(tool=getattr(self, "_current_tool", "select")) @@ -223,7 +221,6 @@ def _open_settings(self) -> None: self._cmap_combo.setCurrentText(vals["def_cmap"]) self._status.showMessage("설정 저장됨.", 3000) - # ------------------------------------------------ active-document access def _cur(self) -> DocumentSession | None: return self._docs.get(self._tabs.currentWidget()) @@ -233,8 +230,6 @@ def _cur_view(self) -> ImageView | None: return doc.view return None - # ---------------------------------------------------------- theme + icons - def _set_action_icon(self, action: QtGui.QAction, icon_name: str) -> None: self._icon_actions[action] = icon_name action.setIcon(AppIcons.get(icon_name, self._icon_color)) @@ -244,7 +239,6 @@ def apply_theme(self, theme: str) -> None: self._icon_color = "#d4d4d8" if theme == "Dark" else "#33333a" self.setStyleSheet(build_style(theme)) - # 등록된 Action 및 Button들 테마 색상 재적용 for act, name in self._icon_actions.items(): act.setIcon(AppIcons.get(name, self._icon_color)) for btn, name in self._icon_buttons.items(): @@ -285,7 +279,6 @@ def _do_log(self, b) -> None: self._log(f"Log scale {'ON' if b else 'OFF'}") self._after_preproc_change(doc) - # ------------------------------------------------------ session restore def _restore_session(self) -> None: """Reopen at the previously used folder / file.""" last_dir = self._settings.value("last_dir", "", type=str) diff --git a/src/qoradfxm/gui/models.py b/src/qoradfxm/gui/models.py index 4b4f881..5ac5dfe 100644 --- a/src/qoradfxm/gui/models.py +++ b/src/qoradfxm/gui/models.py @@ -46,7 +46,6 @@ def __init__(self, results: ResultsFrame | None = None, parent=None) -> None: super().__init__(parent) self._results = results or ResultsFrame() - # --- Core access ------------------------------------------------------- @property def results(self) -> ResultsFrame: return self._results @@ -60,7 +59,6 @@ def set_results(self, results: ResultsFrame) -> None: self._results = results self.endResetModel() - # --- Qt interface ------------------------------------------------------ def rowCount(self, parent=QtCore.QModelIndex()) -> int: return 0 if parent.isValid() else len(self._results) @@ -114,7 +112,6 @@ def data(self, index, role=QtCore.Qt.DisplayRole): return str(val) return None - # --- editing (manual data cleanup) ------------------------------------- def flags(self, index): if not index.isValid(): return QtCore.Qt.NoItemFlags @@ -145,7 +142,6 @@ def setData(self, index, value, role=QtCore.Qt.EditRole) -> bool: def data_for_edit(self, index): return self._results.df.iat[index.row(), index.column()] - # --- mutation (wraps Core, emits Qt signals) --------------------------- def add_row(self, row: dict) -> int: r = len(self._results) self.beginInsertRows(QtCore.QModelIndex(), r, r) diff --git a/src/qoradfxm/gui/pipeline.py b/src/qoradfxm/gui/pipeline.py index babb108..cb44465 100644 --- a/src/qoradfxm/gui/pipeline.py +++ b/src/qoradfxm/gui/pipeline.py @@ -13,10 +13,8 @@ class PipelineMixin: """Preprocessing chain, ROI handling and the pipeline object tree.""" - # ------------------------------------------------- preprocessing chain - # The recipe is Core state: doc.ds.history (immutable). It is shown / managed - # in the sidebar's 오브젝트 tab (each image root gets a ⚙ 전처리 group). - # Dark/Flat sources are chosen from the sidebar (구조/파일 tab context menu). + # The recipe is Core state: doc.ds.history (immutable). The sidebar object tree + # shows and edits it; Dark/Flat sources come from the file tree's context menu. # The GUI performs NO pixel math — Core computes doc.ds.image. def _cur_preproc(self): @@ -143,7 +141,7 @@ def _do_fit(self) -> None: rec = doc.ds.to_record() # If this shot ALREADY has a row (from populate/drop), update it in # place (Phase 2). Otherwise DON'T auto-add — the user picks fields - # in the 샷 정보 inspector and commits one row deliberately. + # in the shot inspector and commits one row deliberately. prev = self._master.index_of(rec["shot_id"]) if prev is not None: if str(self._master.status_at(prev)) == "EXCLUDE": @@ -160,7 +158,6 @@ def _do_fit(self) -> None: name = self._next_roi_name(doc, "ellipse", "Ellipse") self.add_roi(EllipseFitROI(geom, points=pts, name=name)) - # ------------------------------------------------------------- ROI (B-1) @staticmethod def _next_roi_name(doc: DocumentSession, roi_type: str, prefix: str) -> str: return f"{prefix}_{len(doc.rois_of_type(roi_type)) + 1}" @@ -234,7 +231,6 @@ def _on_rect_drawn(self, x0: float, y0: float, x1: float, y1: float) -> None: self.add_roi(roi) v.select_roi(roi) - # =============================================== Pipeline object tree (B-3) def _build_object_tree(self) -> QtWidgets.QTreeWidget: self._obj_updating = False self._roi_items: dict[str, QtWidgets.QTreeWidgetItem] = {} @@ -435,7 +431,7 @@ def _add_gamma(self, doc) -> None: self._preproc_add(doc, "gamma", {"gamma": float(g)}) def _add_scale(self, doc) -> None: - """비율/크기 변형 — 가로·세로 배율을 따로 받는다 (같게 주면 단순 확대·축소).""" + """Ask for separate x and y scale factors (equal values scale uniformly).""" sx, ok = QtWidgets.QInputDialog.getDouble( self, "크기·비율 변형", "가로 배율 sx (1.0 = 그대로):", 1.0, 0.01, 20.0, 3 ) @@ -463,7 +459,7 @@ def _on_obj_context_menu(self, pos: QtCore.QPoint) -> None: if doc is None: return menu = QtWidgets.QMenu(self) - if idx == -1: # ⚙ 전처리 group node + if idx == -1: # preprocessing group node menu.addAction("Log (adaptive) 추가", lambda: self._preproc_add(doc, "log")) menu.addAction( "Log (pure) 추가", lambda: self._preproc_add(doc, "pure_log") diff --git a/src/qoradfxm/gui/prefs.py b/src/qoradfxm/gui/prefs.py index 4a9e7a7..92b0e70 100644 --- a/src/qoradfxm/gui/prefs.py +++ b/src/qoradfxm/gui/prefs.py @@ -45,7 +45,6 @@ class ViewPrefs: unit: str = "µm" tool: str = "select" - # --------------------------------------------------------- persistence @classmethod def load(cls, settings) -> ViewPrefs: """Read the app-scoped defaults (``tool`` is deliberately not stored).""" @@ -65,7 +64,6 @@ def save(self, settings) -> None: for field_name, key in _SETTINGS_KEYS.items(): settings.setValue(key, getattr(self, field_name)) - # ---------------------------------------------------------------- use def copy(self, **changes) -> ViewPrefs: """A new instance — documents must never share one prefs object.""" return replace(self, **changes) diff --git a/src/qoradfxm/gui/results_table.py b/src/qoradfxm/gui/results_table.py index 350c354..ff5b617 100644 --- a/src/qoradfxm/gui/results_table.py +++ b/src/qoradfxm/gui/results_table.py @@ -22,7 +22,6 @@ class ResultsTableMixin: """Results table, shot inspector and ribbon-driven data table.""" - # ----------------------------------------------------- results table def _build_results_table_widget(self) -> None: """The QTableView itself (hosted later by the standalone ResultsWindow).""" self._results_table = DropTableView() @@ -40,7 +39,6 @@ def _build_results_table_widget(self) -> None: self._results_table.filesDropped.connect(self._add_dropped_files) self._results_window = None - # ------------------------------------------------- Phase 2: shot ↔ row def _shot_id_for_frame(self, path: Path, frame) -> str: return f"{Path(path).stem}:{frame}" @@ -103,7 +101,6 @@ def _toggle_exclude(self) -> None: self._master.set_status(r, new) self._log(f"상태 변경: 행 {r + 1} → {new}") - # ------------------------------------------- shot inspector (sidebar) _INSP_HINT_COLS: ClassVar[set[str]] = { "center_x", "center_y", @@ -218,7 +215,6 @@ def _inspector_commit(self) -> None: self._results_table.selectRow(r) self._log(f"샷 정보 → 표 {'추가' if created else '갱신'}: 행 {r + 1}") - # ------------------------------------------- data table (ribbon-driven) def _show_results_table(self) -> None: """Open the standalone, resizable results-table window (lazy).""" if self._results_window is None: diff --git a/src/qoradfxm/gui/ribbon.py b/src/qoradfxm/gui/ribbon.py index 1d7df81..a3a54b1 100644 --- a/src/qoradfxm/gui/ribbon.py +++ b/src/qoradfxm/gui/ribbon.py @@ -14,7 +14,6 @@ class RibbonMixin: """Ribbon UI, keyboard shortcuts and the tab bar commands.""" - # ============================================================ ribbon UI def _act(self, text: str, slot, tip: str = "", checkable: bool = False): a = QtGui.QAction(text, self, checkable=checkable) if tip: @@ -169,7 +168,6 @@ def _build_ribbon(self) -> None: self._cmap_syncing = False self._ribbon_cmap.currentTextChanged.connect(self._on_ribbon_cmap) - # --- Ribbon tabs --- ribbon = QtWidgets.QTabWidget() ribbon.setObjectName("Ribbon") ribbon.setFixedHeight(140) @@ -433,7 +431,6 @@ def _set_tool(self, name: str) -> None: else: self._analysis_dock.hide() - # ------------------------------------------------------- 단축키 / 탭 #: (key sequence, handler name, what it does) — also the help table's source. SHORTCUTS: ClassVar[tuple[tuple[str, str, str], ...]] = ( ("Ctrl+O", "_open_file", "파일 열기"), diff --git a/src/qoradfxm/gui/ring_panel.py b/src/qoradfxm/gui/ring_panel.py index 4f5ea96..3fad0e2 100644 --- a/src/qoradfxm/gui/ring_panel.py +++ b/src/qoradfxm/gui/ring_panel.py @@ -1,4 +1,4 @@ -"""링 프로파일 패널 — 타원 둘레 밝기 I(k) 실시간 플롯. +"""Ring profile panel: live plot of the brightness I(k) around the ellipse. The panel owns no pixel math: it asks its *source* callback for the current (linear) image plus the live ellipse geometry, hands both to @@ -37,7 +37,6 @@ def __init__(self, source=None, parent: QtWidgets.QWidget | None = None) -> None self._build_ui() - # ------------------------------------------------------------------ ui def _build_ui(self) -> None: lay = QtWidgets.QVBoxLayout(self) lay.setContentsMargins(6, 6, 6, 6) @@ -126,13 +125,12 @@ def _spin(lo, hi, val, decimals, tip) -> QtWidgets.QDoubleSpinBox: w.setMaximumWidth(90) return w - # --------------------------------------------------------------- logic def set_source(self, source) -> None: """``source() -> (image, geom) | None`` — linear image + live ellipse.""" self._source = source def request_update(self) -> None: - """Coalesced refresh; ignored while 실시간 is off (buttons still work).""" + """Coalesced refresh; ignored while the live checkbox is off (buttons still work).""" if self._live.isChecked() and self.isVisible(): self._timer.start() diff --git a/src/qoradfxm/gui/roi.py b/src/qoradfxm/gui/roi.py index 8c68c7d..de5cb18 100644 --- a/src/qoradfxm/gui/roi.py +++ b/src/qoradfxm/gui/roi.py @@ -44,7 +44,6 @@ def _init_analysis(self, name: str, roi_type: str) -> None: # Keep internal geometry current while the user drags the ROI. self.sigRegionChanged.connect(self._on_region_changed) - # --- visibility / lock ------------------------------------------------- @property def visible(self) -> bool: return self._visible @@ -67,7 +66,6 @@ def set_locked(self, on: bool) -> None: h.setVisible(not on) self.update() - # --- data --------------------------------------------------------------- def _on_region_changed(self, *_) -> None: self.update_data() diff --git a/src/qoradfxm/gui/theme.py b/src/qoradfxm/gui/theme.py index 67d2b98..c2069af 100644 --- a/src/qoradfxm/gui/theme.py +++ b/src/qoradfxm/gui/theme.py @@ -178,7 +178,7 @@ def build_style(theme: str) -> str: for k, v in toks.items(): s = s.replace("%" + k + "%", v) - # qtawesome 체크마크 아이콘을 임시 PNG로 저장하여 QSS에 주입 + # QSS can only reference a file, so write the check-mark icon to a temp PNG check_pix = AppIcons.get_pixmap(AppIcons.CHECK, size=12, color="#ffffff") tmp_check_path = Path(tempfile.gettempdir()) / "qoradfxm_qta_check.png" check_pix.save(str(tmp_check_path)) diff --git a/src/qoradfxm/gui/widgets.py b/src/qoradfxm/gui/widgets.py index 9e855e8..fcd6731 100644 --- a/src/qoradfxm/gui/widgets.py +++ b/src/qoradfxm/gui/widgets.py @@ -49,26 +49,21 @@ def __init__(self, parent: QtWidgets.QWidget): self.setAttribute(QtCore.Qt.WA_StyledBackground, True) self.setStyleSheet("background: rgba(0,0,0,0.65);") - # 1. 중앙 박스 (테두리 및 배경 카드 역할) self._card = QtWidgets.QFrame() self._card.setObjectName("DropCard") - # 2. 아이콘 표시용 QLabel self._icon_label = QtWidgets.QLabel() self._icon_label.setAlignment(QtCore.Qt.AlignCenter) - # 3. 텍스트 표시용 QLabel self._text_label = QtWidgets.QLabel() self._text_label.setAlignment(QtCore.Qt.AlignCenter) - # 카드 내부 레이아웃 card_lay = QtWidgets.QVBoxLayout(self._card) card_lay.setContentsMargins(60, 40, 60, 40) card_lay.setSpacing(16) card_lay.addWidget(self._icon_label, 0, QtCore.Qt.AlignCenter) card_lay.addWidget(self._text_label, 0, QtCore.Qt.AlignCenter) - # 전체 오버레이 레이아웃 lay = QtWidgets.QVBoxLayout(self) lay.addStretch(1) lay.addWidget(self._card, 0, QtCore.Qt.AlignCenter) @@ -78,12 +73,10 @@ def __init__(self, parent: QtWidgets.QWidget): def set_ok(self, ok: bool) -> None: color_hex = "#4da3ff" if ok else "#ff5252" - # 1. 아이콘 변경 (QtAwesome 픽스맵) icon_key = AppIcons.DOWNLOAD if ok else AppIcons.WARNING pixmap = AppIcons.get_pixmap(icon_key, size=56, color=color_hex) self._icon_label.setPixmap(pixmap) - # 2. 텍스트 변경 if ok: text = ( "" @@ -98,7 +91,6 @@ def set_ok(self, ok: bool) -> None: ) self._text_label.setText(text) - # 3. 카드 테두리 스타일 적용 (상황에 맞게 색상 동적 변경) self._card.setStyleSheet(f""" QFrame#DropCard {{ border: 2px dashed {color_hex}; diff --git a/tests/conftest.py b/tests/conftest.py index 74d1b8c..606a555 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -91,7 +91,6 @@ def points_json(tmp_path): return path -# ------------------------------------------------------------------ Qt @pytest.fixture(scope="session", autouse=True) def isolated_qsettings(tmp_path_factory): """Redirect QSettings to a temp dir — tests must not touch the real app state.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index af18af8..eb7f96b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -18,7 +18,6 @@ from qoradfxm.core import QoraDFXMDataset -# ------------------------------------------------------------- grammar @pytest.mark.parametrize( "spec", [ @@ -97,7 +96,6 @@ def test_argv_needs_a_source_path(): argv_for_dataset(QoraDFXMDataset.from_array(np.ones((2, 2), np.float32))) -# ------------------------------------------------------------ commands def test_info_lists_frames_and_shapes(ring_h5, capsys): assert main(["info", str(ring_h5)]) == 0 out = capsys.readouterr().out diff --git a/tests/test_core.py b/tests/test_core.py index 17c0e2a..c4b293b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -18,7 +18,6 @@ def img(): return np.arange(60, dtype=np.float32).reshape(6, 10) -# ------------------------------------------------------------------ warp def test_scale_changes_each_axis_independently(img): assert warp.scale(img, 2.0, 0.5).shape == (3, 20) assert warp.scale(img, 1.5).shape == (9, 15) # sy defaults to sx @@ -68,7 +67,6 @@ def test_rotate_fill_value_lands_in_the_corners(img): assert out[0, 0] == pytest.approx(-1.0) -# --------------------------------------------------------------- recipe def test_dataset_ops_are_immutable_and_ordered(img): ds = QoraDFXMDataset.from_array(img) grown = ds.scale(2.0, 0.5).rotate(15).flip("v") @@ -105,7 +103,6 @@ def test_replace_history_recomputes_the_image(img): assert plain.image.shape == img.shape -# ------------------------------------------------------- linear_view def test_linear_view_drops_only_the_nonlinear_ops(img): ds = QoraDFXMDataset.from_array(img).scale(2.0).apply_log().gamma(0.5).normalize() kinds = [op.kind for op in ds.linear_view().history] @@ -123,7 +120,6 @@ def test_ring_profile_requires_a_fit(img): QoraDFXMDataset.from_array(img).ring_profile() -# ----------------------------------------------------------- transform def test_adaptive_log_survives_nan_corners(): a = np.array([[1.0, 2.0], [np.nan, 4.0]], dtype=np.float32) out = adaptive_log(a) @@ -136,7 +132,6 @@ def test_adaptive_log_of_all_nan_does_not_raise(): assert np.isnan(out).all() -# ------------------------------------------------------- dark / flat refs def test_sub_bg_without_a_reference_is_refused(img): ds = QoraDFXMDataset.from_array(img) with pytest.raises(CorrectionError, match=r"dataset_path=\.\.\. or file_path="): diff --git a/tests/test_negative_inputs.py b/tests/test_negative_inputs.py index c0b1689..d314170 100644 --- a/tests/test_negative_inputs.py +++ b/tests/test_negative_inputs.py @@ -12,7 +12,6 @@ from qoradfxm.core.results import FitResult, ResultsFrame, UnknownColumnsWarning -# ------------------------------------------------------------------ ellipse fit def test_fewer_than_five_points_are_refused(): with pytest.raises(ValueError, match="at least 5"): FitResult.from_points([[0, 0], [1, 0], [0, 1], [1, 1]]) @@ -56,7 +55,6 @@ def test_a_non_elliptical_conic_is_refused(): fitting.conic_to_geometry(hyperbola) -# ----------------------------------------------------------------------- h5 / images def test_unreadable_h5_raises_oserror(tmp_path): bad = tmp_path / "broken.h5" bad.write_bytes(b"not hdf5") @@ -103,7 +101,6 @@ def test_a_multi_page_tif_stack_is_not_taken_for_one_image(tmp_path): io.load_image_file(stack) -# ------------------------------------------------------------------------ CSV def test_csv_with_no_results_columns_is_refused(tmp_path): path = tmp_path / "other.csv" path.write_text("foo,bar\n1,2\n", encoding="utf-8") diff --git a/tests/test_prefs.py b/tests/test_prefs.py index 244e5f7..84c8a2e 100644 --- a/tests/test_prefs.py +++ b/tests/test_prefs.py @@ -83,7 +83,6 @@ def test_tool_is_not_persisted(qapp, tmp_path): assert ViewPrefs.load(settings).tool == "select" -# ------------------------------------------------------- window semantics @pytest.fixture def two_tifs(tmp_path): paths = []