Metadata-Version: 2.2
Name: wxCvModule
Version: 0.1.0
Summary: High-performance OpenCV + wxWidgets integration for Python
Author-Email: wxCvRoot <karatow2022@outlook.com>
License:                                  Apache License
                                    Version 2.0, January 2004
                                 http://www.apache.org/licenses/
         
            TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
         
            1. Definitions.
         
               "License" shall mean the terms and conditions for use, reproduction,
               and distribution as defined by Sections 1 through 9 of this document.
         
               "Licensor" shall mean the copyright owner or entity authorized by
               the copyright owner that is granting the License.
         
               "Legal Entity" shall mean the union of the acting entity and all
               other entities that control, are controlled by, or are under common
               control with that entity. For the purposes of this definition,
               "control" means (i) the power, direct or indirect, to cause the
               direction or management of such entity, whether by contract or
               otherwise, or (ii) ownership of fifty percent (50%) or more of the
               outstanding shares, or (iii) beneficial ownership of such entity.
         
               "You" (or "Your") shall mean an individual or Legal Entity
               exercising permissions granted by this License.
         
               "Source" form shall mean the preferred form for making modifications,
               including but not limited to software source code, documentation
               source, and configuration files.
         
               "Object" form shall mean any form resulting from mechanical
               transformation or translation of a Source form, including but
               not limited to compiled object code, generated documentation,
               and conversions to other data formats.
         
               "Work" shall mean the work of authorship, whether in Source or
               Object form, made available under the License, as indicated by a
               copyright notice that is included in or attached to the work
               (an example is provided in the Appendix below).
         
               "Derivative Works" shall mean any work, whether in Source or Object
               form, that is based on (or derived from) the Work and for which the
               editorial revisions, annotations, elaborations, or other modifications
               represent, as a whole, an original work of authorship. For the purposes
               of this License, Derivative Works shall not include works that remain
               separable from, or merely link (or bind by name) to the interfaces of,
               the Work and Derivative Works thereof.
         
               "Contribution" shall mean any work of authorship, including
               the original version of the Work and any modifications or additions
               to that Work or Derivative Works thereof, that is intentionally
               submitted to Licensor for inclusion in the Work by the copyright owner
               or by an individual or Legal Entity authorized to submit on behalf of
               the copyright owner. For the purposes of this definition, "submitted"
               means any form of electronic, verbal, or written communication sent
               to the Licensor or its representatives, including but not limited to
               communication on electronic mailing lists, source code control systems,
               and issue tracking systems that are managed by, or on behalf of, the
               Licensor for the purpose of discussing and improving the Work, but
               excluding communication that is conspicuously marked or otherwise
               designated in writing by the copyright owner as "Not a Contribution."
         
               "Contributor" shall mean Licensor and any individual or Legal Entity
               on behalf of whom a Contribution has been received by Licensor and
               subsequently incorporated within the Work.
         
            2. Grant of Copyright License. Subject to the terms and conditions of
               this License, each Contributor hereby grants to You a perpetual,
               worldwide, non-exclusive, no-charge, royalty-free, irrevocable
               copyright license to reproduce, prepare Derivative Works of,
               publicly display, publicly perform, sublicense, and distribute the
               Work and such Derivative Works in Source or Object form.
         
            3. Grant of Patent License. Subject to the terms and conditions of
               this License, each Contributor hereby grants to You a perpetual,
               worldwide, non-exclusive, no-charge, royalty-free, irrevocable
               (except as stated in this section) patent license to make, have made,
               use, offer to sell, sell, import, and otherwise transfer the Work,
               where such license applies only to those patent claims licensable
               by such Contributor that are necessarily infringed by their
               Contribution(s) alone or by combination of their Contribution(s)
               with the Work to which such Contribution(s) was submitted. If You
               institute patent litigation against any entity (including a
               cross-claim or counterclaim in a lawsuit) alleging that the Work
               or a Contribution incorporated within the Work constitutes direct
               or contributory patent infringement, then any patent licenses
               granted to You under this License for that Work shall terminate
               as of the date such litigation is filed.
         
            4. Redistribution. You may reproduce and distribute copies of the
               Work or Derivative Works thereof in any medium, with or without
               modifications, and in Source or Object form, provided that You
               meet the following conditions:
         
               (a) You must give any other recipients of the Work or
                   Derivative Works a copy of this License; and
         
               (b) You must cause any modified files to carry prominent notices
                   stating that You changed the files; and
         
               (c) You must retain, in the Source form of any Derivative Works
                   that You distribute, all copyright, patent, trademark, and
                   attribution notices from the Source form of the Work,
                   excluding those notices that do not pertain to any part of
                   the Derivative Works; and
         
               (d) If the Work includes a "NOTICE" text file as part of its
                   distribution, then any Derivative Works that You distribute must
                   include a readable copy of the attribution notices contained
                   within such NOTICE file, excluding those notices that do not
                   pertain to any part of the Derivative Works, in at least one
                   of the following places: within a NOTICE text file distributed
                   as part of the Derivative Works; within the Source form or
                   documentation, if provided along with the Derivative Works; or,
                   within a display generated by the Derivative Works, if and
                   wherever such attribution notices normally appear. The contents
                   of the NOTICE file are for informational purposes only and
                   do not modify the License. You may add Your own attribution
                   notices within Derivative Works that You distribute, alongside
                   or as an addendum to the NOTICE text from the Work, provided
                   that such additional attribution notices cannot be construed
                   as modifying the License.
         
               You may add Your own copyright statement to Your modifications and
               may provide additional or different license terms and conditions
               for use, reproduction, or distribution of Your modifications, or
               for any such Derivative Works as a whole, provided Your use,
               reproduction, and distribution of the Work otherwise complies with
               the conditions stated in this License.
         
            5. Submission of Contributions. Unless You explicitly declare otherwise,
               any Contribution intentionally submitted for inclusion in the Work
               by You to the Licensor shall be under the terms and conditions of
               this License, without any additional terms or conditions.
               Notwithstanding the above, nothing herein shall supersede or modify
               the terms of any separate license agreement you may have executed
               with Licensor regarding such Contributions.
         
            6. Trademarks. This License does not grant permission to use the trade
               names, trademarks, service marks, or product names of the Licensor,
               except as required for reasonable and customary use in describing the
               origin of the Work and reproducing the content of the NOTICE file.
         
            7. Disclaimer of Warranty. Unless required by applicable law or
               agreed to in writing, Licensor provides the Work (and each
               Contributor provides its Contributions) on an "AS IS" BASIS,
               WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
               implied, including, without limitation, any warranties or conditions
               of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
               PARTICULAR PURPOSE. You are solely responsible for determining the
               appropriateness of using or redistributing the Work and assume any
               risks associated with Your exercise of permissions under this License.
         
            8. Limitation of Liability. In no event and under no legal theory,
               whether in tort (including negligence), contract, or otherwise,
               unless required by applicable law (such as deliberate and grossly
               negligent acts) or agreed to in writing, shall any Contributor be
               liable to You for damages, including any direct, indirect, special,
               incidental, or consequential damages of any character arising as a
               result of this License or out of the use or inability to use the
               Work (including but not limited to damages for loss of goodwill,
               work stoppage, computer failure or malfunction, or any and all
               other commercial damages or losses), even if such Contributor
               has been advised of the possibility of such damages.
         
            9. Accepting Warranty or Additional Liability. While redistributing
               the Work or Derivative Works thereof, You may choose to offer,
               and charge a fee for, acceptance of support, warranty, indemnity,
               or other liability obligations and/or rights consistent with this
               License. However, in accepting such obligations, You may act only
               on Your own behalf and on Your sole responsibility, not on behalf
               of any other Contributor, and only if You agree to indemnify,
               defend, and hold each Contributor harmless for any liability
               incurred by, or claims asserted against, such Contributor by reason
               of your accepting any such warranty or additional liability.
         
            END OF TERMS AND CONDITIONS
         
            APPENDIX: How to apply the Apache License to your work.
         
               To apply the Apache License to your work, attach the following
               boilerplate notice, with the fields enclosed by brackets "[]"
               replaced with your own identifying information. (Don't include
               the brackets!)  The text should be enclosed in the appropriate
               comment syntax for the file format. We also recommend that a
               file or class name and description of purpose be included on the
               same "printed page" as the copyright notice for easier
               identification within third-party archives.
         
            Copyright [2026] [wxCvRoot]
         
            Licensed under the Apache License, Version 2.0 (the "License");
            you may not use this file except in compliance with the License.
            You may obtain a copy of the License at
         
                http://www.apache.org/licenses/LICENSE-2.0
         
            Unless required by applicable law or agreed to in writing, software
            distributed under the License is distributed on an "AS IS" BASIS,
            WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
            See the License for the specific language governing permissions and
            limitations under the License.
         
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: C++
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Multimedia :: Graphics :: Viewers
Project-URL: Homepage, https://github.com/wxCvRoot
Project-URL: Documentation, https://github.com/wxCvRoot/wxCvModule-docs
Project-URL: Repository, https://github.com/wxCvRoot/wxCvModule
Project-URL: Issues, https://github.com/wxCvRoot/wxCvModule/issues
Requires-Python: >=3.7
Requires-Dist: numpy>=1.19.0
Requires-Dist: wxPython>=4.1.0; platform_system == "Windows" or platform_system == "Darwin"
Description-Content-Type: text/markdown

# wxCvModule 使用者指南

[![PyPI version](https://badge.fury.io/py/wxcvmodule.svg)](https://badge.fury.io/py/wxcvmodule)
[![Python Versions](https://img.shields.io/pypi/pyversions/wxcvmodule.svg)](https://pypi.org/project/wxcvmodule/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

> **版本**：v1.5　**最後更新**：2026-02-19

---

## 目錄

1. [簡介](#1-簡介)
2. [安裝](#2-安裝)
3. [各平台注意事項](#3-各平台注意事項)
4. [快速開始](#4-快速開始)
5. [核心類別說明](#5-核心類別說明)
6. [ROI 工具詳解](#6-roi-工具詳解)
7. [疊加層系統（Overlay）](#7-疊加層系統overlay)
8. [事件回調](#8-事件回調)
9. [視窗嵌入與 Resize](#9-視窗嵌入與-resize)
10. [API 快速參考](#10-api-快速參考)
11. [常見問題](#11-常見問題)

---

## 1. 簡介

`wxCvModule` 是一個高效能的 C++ + Python 混合函式庫，將 **OpenCV 影像處理**與 **wxWidgets GUI** 無縫整合進 Python wxPython 應用程式。

### 主要特色

| 特色 | 說明 |
|------|------|
| **高效能渲染** | 直接 C++ 渲染管線，處理大圖不卡頓 |
| **豐富的 ROI 工具** | 支援矩形、旋轉矩形、圓形、環形、多邊形、點、線段 |
| **互動操作** | Ctrl+滾輪縮放、滾輪平移、中鍵拖曳、右鍵選單 |
| **Python 友好** | 直接傳遞 NumPy array，零拷貝共享記憶體 |
| **事件委派** | 右鍵點擊、滑鼠移動、雙擊事件可委派給 Python 處理 |
| **跨平台** | Windows、Linux、macOS 均支援 |

---

## 2. 安裝

### 2.1 透過 pip 安裝（推薦）

```bash
pip install wxcvmodule
```

安裝必要的 Python 依賴：

```bash
pip install wxPython numpy opencv-python
```

### 2.2 從原始碼編譯

若要從原始碼編譯，請參閱 `CLAUDE.md` 中的編譯指令，以及各平台對應的 Build Guide：

- **Windows**：`docs/Windows_Local_Build_Guide.md`
- **Linux**：`docs/Linux_Wheel_Build_Guide.md`
- **macOS**：CLAUDE.md 的 macOS 編譯章節

編譯完成後，可透過以下指令打包 Wheel：

```bash
pip install build scikit-build-core
python -m build --wheel
```

---

## 3. 各平台注意事項

> 此章節是使用 wxCvModule 前最重要的閱讀內容，特別是 macOS 使用者。

### 3.1 Windows

Windows 是**最簡單**的平台，沒有特殊限制。

- **Import 順序**：無限制，`import wx` 與 `import wxCvModule` 順序不影響功能。
- **面板座標（x, y）**：建議使用 `-1`（讓系統自動決定）。
- **依賴管理**：Wheel 已包含所有 DLL（OpenCV、wxWidgets），安裝後即可使用，不需額外安裝任何套件。
- **路徑格式**：`LoadImage()` 完整支援中文等 Unicode 路徑（內部使用 `std::wstring`）。
- **GUI 後端**：Win32 API（`__WXMSW__`）

**典型初始化範例：**

```python
import wx
import wxCvModule  # Windows 上 import 順序不影響

handle = container.GetHandle()
cv_panel = wxCvModule.wxCvROIAdvPanel(handle, wx.ID_ANY, -1, -1, width, height)
```

---

### 3.2 Linux

Linux 使用 GTK3 後端，需要系統預先安裝 GTK3 執行環境。

- **Import 順序**：無限制。
- **面板座標（x, y）**：使用 `-1`。
- **系統依賴**：GTK3 必須存在於系統中。

**安裝 GTK3（若尚未安裝）：**

```bash
# Ubuntu / Debian
sudo apt install libgtk-3-0

# Fedora / RHEL
sudo dnf install gtk3

# Arch Linux
sudo pacman -S gtk3
```

- **路徑格式**：`LoadImage()` 使用 UTF-8 字串處理路徑，支援 Unicode 路徑。
- **Wheel 大小**：約 11–12 MB。OpenCV 與 wxWidgets 靜態連結進 `.so`，GTK3 則使用系統版本。
- **GUI 後端**：GTK3 (`__WXGTK__`)
- **圖像編解碼**：HEIF（`.heic`）與 JPEG XL（`.jxl`）需要 libheif / libjxl（Wheel 已包含）。

**典型初始化範例：**

```python
import wx
import wxCvModule

handle = container.GetHandle()
cv_panel = wxCvModule.wxCvROIAdvPanel(handle, wx.ID_ANY, -1, -1, width, height)
```

---

### 3.3 macOS（重要差異）

macOS 與 Windows/Linux 有**根本性的架構差異**，使用前務必閱讀以下說明。

#### ⚠️ 關鍵規則：Import 順序

```python
# ✅ 正確：先 import wx，再 import wxCvModule
import wx
import wxCvModule

# ❌ 錯誤：會拋出 RuntimeError
import wxCvModule  # wx 尚未載入，crash！
import wx
```

**原因**：macOS 版本的 wxCvModule 使用 `-undefined dynamic_lookup` 技術，wx 符號在執行時才從 wxPython 的 dylib 解析。若 wxPython 未先載入，C++ 端的 `wxAppConsole::GetInstance()` 找不到 wxApp，會拋出明確的 `RuntimeError`。

#### ⚠️ 面板座標必須為 (0, 0)

```python
# ✅ macOS 正確：使用 0, 0
cv_panel = wxCvModule.wxCvROIAdvPanel(handle, wx.ID_ANY, 0, 0, width, height)

# ❌ macOS 錯誤：使用 -1, -1 可能導致面板無法正確嵌入
cv_panel = wxCvModule.wxCvROIAdvPanel(handle, wx.ID_ANY, -1, -1, width, height)
```

#### ⚠️ 關閉視窗時必須手動釋放 C++ panel

```python
def on_close(self, event):
    self.cv_panel = None   # 觸發 C++ 析構 → 避免 wxApp 結束卡住
    self.roi_panel = None
    self.Destroy()
```

**原因**：macOS 使用隱藏框架（hidden wxFrame）作為 C++ panel 的臨時父視窗。若不釋放，wxApp 結束時會因 top-level window 仍存在而卡住不退出。

#### 平台比較摘要

| 項目 | Windows | Linux | macOS |
|------|---------|-------|-------|
| Import 順序限制 | 無 | 無 | **必須先 `import wx`** |
| 面板 x, y 參數 | `-1, -1` | `-1, -1` | **`0, 0`** |
| 關閉時需釋放 panel | 建議 | 建議 | **必須** |
| wxWidgets 連結方式 | 動態（獨立 DLL） | 靜態（內嵌） | dynamic_lookup（共用 wxPython） |
| GUI 後端 | Win32 | GTK3 | Cocoa |
| 路徑 Unicode 支援 | `std::wstring` | UTF-8 | UTF-8 |

#### 跨平台最佳寫法

若要讓同一份程式碼在三個平台都能正確執行，建議：

```python
import sys
import os

# 自動搜尋 wxCvModule 位置
_dir = os.path.dirname(os.path.abspath(__file__))
for _p in [_dir, os.path.join(_dir, "..", "build"),
           os.path.join(_dir, "..", "build", "Release")]:
    if os.path.isdir(_p) and _p not in sys.path:
        sys.path.insert(0, os.path.normpath(_p))

# wx 必須在 wxCvModule 之前（macOS 強制要求，其他平台無影響）
import wx
import wxCvModule

# 面板座標：macOS 用 0,0；其他平台用 -1,-1
_X = 0 if sys.platform == "darwin" else -1
_Y = 0 if sys.platform == "darwin" else -1

# 建立面板
cv_panel = wxCvModule.wxCvROIAdvPanel(handle, wx.ID_ANY, _X, _Y, w, h)
```

---

## 4. 快速開始

### 4.1 基本圖像顯示（wxCvPanel）

```python
import sys
import wx
import numpy as np
import wxCvModule

_X = 0 if sys.platform == "darwin" else -1
_Y = 0 if sys.platform == "darwin" else -1

class BasicViewerFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="wxCvModule Basic Viewer", size=(800, 600))
        self.cv_panel = None

        # 建立容器 Panel
        self.container = wx.Panel(self)
        self.container.SetBackgroundColour(wx.Colour(40, 40, 40))

        # 綁定事件
        self.Bind(wx.EVT_SHOW,  self.on_show)
        self.Bind(wx.EVT_CLOSE, self.on_close)
        self.container.Bind(wx.EVT_SIZE, self.on_resize)
        self.Centre()

    def on_show(self, event):
        if event.IsShown() and self.cv_panel is None:
            wx.CallAfter(self.init_panel)
        event.Skip()

    def on_close(self, event):
        self.cv_panel = None  # macOS 必要
        self.Destroy()

    def init_panel(self):
        handle = self.container.GetHandle()
        size   = self.container.GetSize()
        self.cv_panel = wxCvModule.wxCvPanel(
            handle, wx.ID_ANY, _X, _Y, size.width, size.height
        )
        self.cv_panel.SetCenterImageEnable(True)

        # 建立測試圖像
        img = np.zeros((480, 640, 3), dtype=np.uint8)
        img[:, :, 0] = 100  # B channel
        self.cv_panel.SetMat(img)
        self.cv_panel.SetZoomToFit()

    def on_resize(self, event):
        if self.cv_panel and self.cv_panel.IsOk():
            sz = self.container.GetSize()
            self.cv_panel.SetSize(sz.width, sz.height)
            self.cv_panel.Refresh()
        event.Skip()

if __name__ == "__main__":
    app = wx.App()
    BasicViewerFrame().Show()
    app.MainLoop()
```

### 4.2 ROI 編輯（wxCvROIAdvPanel）

```python
class ROIEditorFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title="ROI Editor", size=(900, 650))
        self.roi_panel = None

        panel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)

        self.container = wx.Panel(panel)
        sizer.Add(self.container, 1, wx.EXPAND | wx.ALL, 4)

        # ROI 模式選擇
        mode_sizer = wx.BoxSizer(wx.HORIZONTAL)
        mode_sizer.Add(wx.StaticText(panel, label="ROI Mode:"), 0,
                       wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 6)
        self.mode_choice = wx.Choice(panel, choices=[
            "Nothing", "Point", "Line", "Rectangle",
            "RotatedRect", "Circle", "Annulus", "Polygon"
        ])
        self.mode_choice.SetSelection(0)
        self.mode_choice.Bind(wx.EVT_CHOICE, self.on_mode_change)
        mode_sizer.Add(self.mode_choice)
        sizer.Add(mode_sizer, 0, wx.ALL, 4)

        panel.SetSizer(sizer)

        self.Bind(wx.EVT_SHOW,  self.on_show)
        self.Bind(wx.EVT_CLOSE, self.on_close)
        self.container.Bind(wx.EVT_SIZE, self.on_resize)
        self.Centre()

    def on_show(self, event):
        if event.IsShown() and self.roi_panel is None:
            wx.CallAfter(self.init_panel)
        event.Skip()

    def on_close(self, event):
        self.roi_panel = None  # macOS 必要
        self.Destroy()

    def init_panel(self):
        handle = self.container.GetHandle()
        size   = self.container.GetSize()
        self.roi_panel = wxCvModule.wxCvROIAdvPanel(
            handle, wx.ID_ANY, _X, _Y, size.width, size.height
        )
        # 啟用所有 ROI 工具及右鍵選單
        self.roi_panel.SetFuncEnable(True, True, True)
        self.roi_panel.SetMenuROIEnable(True, True, True, True, True, True, True)
        self.roi_panel.SetCenterImageEnable(True)

        # 設定 Crop 回調：使用者完成 ROI 後觸發
        self.roi_panel.SetOnCropCallback(self.on_crop)

        # 載入圖像
        img = np.zeros((480, 640, 3), dtype=np.uint8)
        self.roi_panel.SetMat(img)
        self.roi_panel.SetZoomToFit()

    def on_mode_change(self, event):
        if self.roi_panel:
            self.roi_panel.SetROIMode(self.mode_choice.GetSelection())

    def on_resize(self, event):
        if self.roi_panel and self.roi_panel.IsOk():
            sz = self.container.GetSize()
            self.roi_panel.SetSize(sz.width, sz.height)
            self.roi_panel.Refresh()
        event.Skip()

    def on_crop(self, rect):
        # rect = (x, y, width, height)，原圖座標
        wx.CallAfter(print, f"ROI Crop: {rect}")
```

### 4.3 純邏輯引擎（wxCvEngine，無需 GUI）

`wxCvEngine` 適合批次影像處理或命令列工具，**不需要 wxPython 也不需要顯示視窗**。

```python
import wxCvModule
import numpy as np

engine = wxCvModule.wxCvEngine()

# 設定圖像
img = np.zeros((480, 640, 3), dtype=np.uint8)
engine.SetMat(img)
print(f"Image size: {engine.GetImageSize()}")  # (width, height)

# 影像處理操作
engine.ConvertToGray()
engine.Resize(320, 240)
engine.GaussianBlur(5, 1.5)
edges = engine.Canny(50, 150)  # 直接回傳結果 numpy array

# 取得處理後的圖像
result = engine.GetMat()  # 回傳 numpy.ndarray

# 從檔案載入 / 儲存（支援 Unicode 路徑）
engine.LoadImage("/path/to/image.png")
engine.SaveImage("/output/result.jpg")
engine.LoadImage("/path/with/unicode/圖片.png", wxCvModule.IMREAD_GRAYSCALE)
```

---

## 5. 核心類別說明

### 5.1 wxCvEngine — 純邏輯影像引擎

不依賴 GUI，適用於影像前處理、批次轉換等場景。

| 方法 | 說明 |
|------|------|
| `SetMat(img)` | 設定圖像（NumPy BGR/BGRA/Gray array） |
| `GetMat()` | 取得當前圖像（NumPy array） |
| `HasImage()` | 是否有圖像 |
| `GetImageSize()` | 回傳 `(width, height)` |
| `LoadImage(path, flag?)` | 從檔案載入（支援 Unicode 路徑） |
| `SaveImage(path)` | 儲存到檔案 |
| `ConvertToGray()` | 轉灰階 |
| `Resize(w, h)` | 縮放 |
| `GaussianBlur(ksize, sigma)` | 高斯模糊 |
| `Canny(t1, t2)` | Canny 邊緣偵測，回傳結果圖像 |
| `Clear()` | 清除圖像 |

**imread flag 常數：**

```python
wxCvModule.IMREAD_COLOR       # 彩色（預設）
wxCvModule.IMREAD_GRAYSCALE   # 灰階
wxCvModule.IMREAD_UNCHANGED   # 保留 Alpha 通道
```

### 5.2 wxCvPanel — 基本圖像顯示面板

繼承 wxScrolledCanvas，提供縮放、平移、置中顯示功能。

| 方法 | 說明 |
|------|------|
| `SetMat(img)` | 設定顯示圖像 |
| `GetMat()` | 取得當前圖像 |
| `LoadImage(path)` | 從檔案載入（支援 Unicode 路徑） |
| `SetZoomToFit()` | 縮放至適合視窗 |
| `SetOriginal()` | 恢復 1:1 原始大小 |
| `SetZoomIn()` | 放大 |
| `SetZoomOut()` | 縮小 |
| `SetCenterImageEnable(bool)` | 啟用圖像置中顯示 |
| `SetCanvasBgColor(r, g, b)` | 設定畫布背景顏色 |
| `SetSize(w, h)` | 調整面板大小 |
| `Refresh()` | 強制重新繪製 |
| `IsOk()` | 面板是否正常初始化 |
| `GetHandle()` | 取得原生視窗 Handle |

### 5.2.1 滑鼠操作快捷鍵

| 操作 | 行為 |
|------|------|
| 滾輪上 / 下 | 畫面垂直捲動（圖像放大時生效） |
| 水平傾斜滾輪 | 畫面水平捲動（支援水平滾輪的滑鼠） |
| **Ctrl + 滾輪上** | 以滑鼠位置為中心**放大**圖像 |
| **Ctrl + 滾輪下** | 以滑鼠位置為中心**縮小**圖像 |
| 中鍵按住拖曳 | 自由平移畫面（圖像放大時生效） |
| 右鍵點擊 | 開啟 ROI 右鍵選單（或觸發 Python 回調） |

> 此設計符合 CVAT、LabelMe 等主流標注軟體的操作慣例。

### 5.3 wxCvROIAdvPanel — 進階 ROI 編輯面板

繼承 `wxCvPanel`，增加完整的 ROI 工具集與事件系統。

除了繼承 wxCvPanel 的所有方法外，還提供：

| 方法 | 說明 |
|------|------|
| `SetFuncEnable(menu, crop, move)` | 啟用右鍵選單 / Crop 功能 / 移動功能 |
| `SetMenuROIEnable(...)` | 控制右鍵選單中各 ROI 工具的顯示 |
| `SetROIMode(mode)` | 設定當前 ROI 工具（0–7） |
| `GetROIMode()` | 取得當前 ROI 工具 |
| `GetRect()` | 取得 ROI 邊界框 `(x, y, w, h)` |
| `GetPolygonPoints()` | 取得多邊形頂點 `[(x,y), ...]` |
| `GetRotateAngle()` | 取得旋轉角度 |
| `GetInnerRadius()` | 取得環形內半徑 |
| `GetOuterRadius()` | 取得環形外半徑 |
| `GetStartAngle()` | 取得環形起始角度 |
| `GetEndAngle()` | 取得環形結束角度 |
| `GetLeftMouseDownPoint()` | 取得拖曳起點（原圖座標） |
| `GetLeftMouseUpPoint()` | 取得拖曳終點（原圖座標） |
| `SetEditingROI(mode, pts, angle)` | 以程式設定 ROI |
| `AppendOverlay(mode, pts, color, size, angle?)` | 新增疊加層 |
| `AppendMaskOverlay(mask, color, alpha)` | 新增遮罩疊加層 |
| `ClearOverlay()` | 清除所有疊加層 |
| `SetDisplayOverlay(bool)` | 顯示 / 隱藏疊加層 |
| `UpdateDrawImage(bool)` | 強制更新渲染（加上 True 重建快取） |
| `ConvertMaskToPolygon(mask)` | 將二值遮罩轉為多邊形點集 |

---

## 6. ROI 工具詳解

### 6.1 ROI 模式編號

| 編號 | 名稱 | 說明 | 互動方式 |
|------|------|------|----------|
| 0 | Nothing | 無工具（清除 ROI） | — |
| 1 | Point | 點 | 左鍵點擊 |
| 2 | Line | 線段 | 左鍵拖曳 |
| 3 | Rectangle | 矩形 | 左鍵拖曳 |
| 4 | RotatedRect | 旋轉矩形 | 左鍵拖曳，拖曳邊緣旋轉 |
| 5 | Circle | 圓形 | 左鍵拖曳 |
| 6 | Annulus | 環形（扇環） | 左鍵拖曳 |
| 7 | Polygon | 多邊形 | 左鍵逐點點擊，**雙擊結束**（或點擊第一個頂點閉合） |

```python
roi_panel.SetROIMode(3)  # 切換到矩形工具
```

### 6.2 ROI 參數格式

以下是 `SetEditingROI` 與 `AppendOverlay` 使用的點集格式（**非常重要，格式不正確會靜默失敗**）：

| 模式 | SetEditingROI pts 格式 | AppendOverlay pts 格式 | 備註 |
|------|----------------------|----------------------|------|
| Point (1) | `[(x, y)]` | `[(x, y), ...]` 多點 | — |
| Line (2) | `[(x1, y1), (x2, y2)]` | 同左 | — |
| Rectangle (3) | `[(左上x, 左上y), (寬, 高)]` | 同左 | — |
| RotatedRect (4) | `[(左上x, 左上y), (寬, 高)]` + `angle` 參數 | 同左 + `angle` 參數 | — |
| Circle (5) | `[(cx, cy), (radius, 0)]` | 同左 | `GetRect` 回傳的 (x,y) 是**圓心** |
| Annulus (6) | `[(cx, cy), (outer_r, 0), (inner_r, 0), (start, end)]` | `[(cx, cy), (0, 0), (inner_r, outer_r), (start, end)]` | ⚠️ 兩者格式略有不同 |
| Polygon (7) | `[(x1, y1), (x2, y2), ..., (xn, yn)]` | 同左 | — |

> **環形（Annulus）注意**：`SetEditingROI` 與 `AppendOverlay` 的 pts 格式在 pts[1], pts[2] 欄位順序不同，使用前請仔細確認。

### 6.3 取得 ROI 資料

```python
mode = roi_panel.GetROIMode()
rect = roi_panel.GetRect()  # (x, y, w, h)

if mode == 3:   # Rectangle
    x, y, w, h = rect
    print(f"矩形: ({x}, {y}) 寬={w} 高={h}")

elif mode == 4: # RotatedRect
    x, y, w, h = rect
    angle = roi_panel.GetRotateAngle()
    print(f"旋轉矩形: ({x}, {y}) 寬={w} 高={h} 角度={angle:.1f}°")

elif mode == 5: # Circle
    # 注意：Circle 的 GetRect 回傳 (cx, cy, ?, ?)，x,y 是圓心
    cx, cy = rect[0], rect[1]
    radius = roi_panel.GetOuterRadius()
    print(f"圓形: 圓心=({cx}, {cy}) 半徑={radius}")

elif mode == 6: # Annulus
    cx, cy = rect[0], rect[1]
    inner_r = roi_panel.GetInnerRadius()
    outer_r = roi_panel.GetOuterRadius()
    start   = roi_panel.GetStartAngle()
    end     = roi_panel.GetEndAngle()
    print(f"環形: 圓心=({cx}, {cy}) 內徑={inner_r} 外徑={outer_r} "
          f"角度={start:.1f}°~{end:.1f}°")

elif mode == 7: # Polygon
    pts = roi_panel.GetPolygonPoints()
    print(f"多邊形: {len(pts)} 個頂點")
    for i, (x, y) in enumerate(pts):
        print(f"  [{i}] ({x:.1f}, {y:.1f})")
```

### 6.4 以程式設定 ROI（SetEditingROI）

```python
# 設定矩形 ROI（左上角 100,80，寬 200，高 150）
roi_panel.SetROIMode(3)
roi_panel.SetEditingROI(3, [(100, 80), (200, 150)], 0.0)

# 設定圓形 ROI（圓心 320,240，半徑 80）
roi_panel.SetROIMode(5)
roi_panel.SetEditingROI(5, [(320, 240), (80, 0.1)], 0.0)
# 注意：第二個點的 y 值用 0.1 而非 0，避免 C++ 有效性檢查拒絕

# 設定多邊形 ROI（五芒星）
import math
pts = []
for j in range(10):
    a = -math.pi/2 + j * math.pi/5
    r = 100 if j % 2 == 0 else 40
    pts.append((320 + r*math.cos(a), 240 + r*math.sin(a)))
roi_panel.SetROIMode(7)
roi_panel.SetEditingROI(7, pts, 0.0)
```

---

## 7. 疊加層系統（Overlay）

Overlay 系統允許在圖像上疊加多個 ROI 形狀，每個可指定不同顏色，用於同時顯示多個標注結果。

### 7.1 基本使用

```python
# 清除所有既有的 overlay
roi_panel.ClearOverlay()

# 顏色格式為 OpenCV BGR：(Blue, Green, Red)
red    = (0, 0, 255)
green  = (0, 255, 0)
blue   = (255, 0, 0)
yellow = (0, 255, 255)

# AppendOverlay(mode, points, color, line_width, angle=0.0)

# 矩形：[(左上x, 左上y), (寬, 高)]
roi_panel.AppendOverlay(3, [(50, 50), (200, 100)], red, 2)

# 圓形：[(圓心x, 圓心y), (半徑, 0)]
roi_panel.AppendOverlay(5, [(320, 240), (80, 0)], green, 2)

# 旋轉矩形：帶 angle 參數
roi_panel.AppendOverlay(4, [(150, 150), (180, 90)], blue, 2, 30.0)

# 環形：[(圓心), (0, 0), (inner_r, outer_r), (start_angle, end_angle)]
roi_panel.AppendOverlay(6, [(400, 300), (0, 0), (40, 80), (30, 210)], yellow, 2)

# 顯示 overlay
roi_panel.SetDisplayOverlay(True)

# 更新畫面（True = 重建快取）
roi_panel.UpdateDrawImage(True)
```

### 7.2 遮罩疊加（Mask Overlay）

適合顯示語義分割模型的輸出結果（如 SAM 分割遮罩）：

```python
import numpy as np
import cv2

# 建立二值遮罩（0 = 背景，255 = 前景）
mask = np.zeros((480, 640), dtype=np.uint8)
cv2.fillPoly(mask, [np.array([(100,100),(300,100),(300,300),(100,300)])], 255)

# AppendMaskOverlay(mask, color_bgr, alpha)
# alpha: 0.0 = 完全透明，1.0 = 完全不透明
roi_panel.AppendMaskOverlay(mask, (0, 0, 255), 0.5)  # 半透明紅色

roi_panel.SetDisplayOverlay(True)
roi_panel.UpdateDrawImage(True)
```

### 7.3 遮罩轉多邊形

```python
# 將 AI 模型輸出的 pixel mask 轉換為可編輯的多邊形
polygon_pts = roi_panel.ConvertMaskToPolygon(mask)

# 設定為可編輯的 ROI
roi_panel.SetEditingROI(7, polygon_pts, 0.0)
roi_panel.SetROIMode(7)
```

---

## 8. 事件回調

### 8.1 Crop 事件（使用者完成 ROI）

使用者拖曳完成 ROI 後觸發：

```python
def on_crop(rect):
    # rect = (x, y, w, h)，原圖座標
    x, y, w, h = rect
    print(f"ROI 完成: x={x:.0f} y={y:.0f} w={w:.0f} h={h:.0f}")

roi_panel.SetOnCropCallback(on_crop)
```

> **注意**：回調在 C++ 執行緒觸發，若要更新 GUI（如 `wx.TextCtrl`），必須使用 `wx.CallAfter`：
> ```python
> def on_crop(rect):
>     wx.CallAfter(self.info_label.SetLabel, f"Rect: {rect}")
> ```

### 8.2 右鍵點擊委派（REQ-006）

攔截右鍵點擊，由 Python 自定義行為：

```python
def on_right_click(pt, hit_index):
    """
    pt         : (x, y) 原圖座標（浮點數）
    hit_index  : 點擊到的 overlay 索引；-1 表示沒有點到任何 ROI
    return True  → 攔截（隱藏 C++ 原生右鍵選單）
    return False → 不攔截（顯示 C++ 原生右鍵選單）
    """
    x, y = pt
    if hit_index >= 0:
        print(f"點擊到第 {hit_index} 個 ROI，座標: ({x:.1f}, {y:.1f})")
        # 在此實作自定義選單
        return True   # 攔截原生選單
    return False      # 未點到 ROI，顯示原生選單

roi_panel.SetOnRightClickCallback(on_right_click)

# 恢復 C++ 原生選單
roi_panel.SetOnRightClickCallback(None)
```

### 8.3 滑鼠移動與雙擊（REQ-010）

即時追蹤滑鼠在圖像上的座標，以及雙擊事件（適合 SAM 互動標注、快速刪除等）：

```python
def on_mouse_move(pt):
    """pt = (x, y) 原圖座標，高頻觸發"""
    # 使用 CallAfter 更新 GUI，避免跨執行緒問題
    wx.CallAfter(status_bar.SetStatusText, f"座標: ({pt[0]:.1f}, {pt[1]:.1f})")

def on_left_dclick(pt):
    """左鍵雙擊，適合快速選取 / SAM 前景點"""
    wx.CallAfter(print, f"左鍵雙擊: ({pt[0]:.1f}, {pt[1]:.1f})")

def on_right_dclick(pt):
    """右鍵雙擊，適合快速刪除 / SAM 背景點"""
    wx.CallAfter(print, f"右鍵雙擊: ({pt[0]:.1f}, {pt[1]:.1f})")

roi_panel.SetOnMouseMoveCallback(on_mouse_move)
roi_panel.SetOnLeftDClickCallback(on_left_dclick)
roi_panel.SetOnRightDClickCallback(on_right_dclick)

# 取消回調
roi_panel.SetOnMouseMoveCallback(None)
```

> **效能說明**：`on_mouse_move` 為高頻回調，只在有註冊回調時才有 Python GIL 開銷。未註冊時完全無效能影響。

---

## 9. 視窗嵌入與 Resize

### 9.1 Resize 手動同步

由於 C++ panel 使用原生 API 嵌入（非 wxSizer 管理），**不會自動跟隨父容器縮放**，需手動同步：

```python
class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, size=(800, 600))
        self.container = wx.Panel(self)
        self.cv_panel  = None

        # 綁定容器的 resize 事件（注意是 container，不是 frame）
        self.container.Bind(wx.EVT_SIZE, self.on_container_resize)

    def on_container_resize(self, event):
        if self.cv_panel and self.cv_panel.IsOk():
            sz = self.container.GetSize()
            self.cv_panel.SetSize(sz.width, sz.height)
            self.cv_panel.Refresh()
        event.Skip()  # 必須呼叫，讓 sizer 繼續處理
```

### 9.2 延遲初始化（必須在視窗顯示後）

C++ panel 需要有效的原生 Handle 才能初始化。Handle 在視窗顯示後才穩定可用，因此推薦使用 `EVT_SHOW` + `wx.CallAfter`：

```python
def on_show(self, event):
    if event.IsShown() and self.cv_panel is None:
        wx.CallAfter(self.init_panel)  # 延遲一個事件循環，確保 Handle 有效
    event.Skip()
```

---

## 10. API 快速參考

### 顯示控制

| 方法 | 說明 |
|------|------|
| `SetMat(img)` | 設定 NumPy array 圖像（BGR/BGRA/Gray） |
| `GetMat()` | 取得圖像 |
| `LoadImage(path)` | 從檔案載入（支援 Unicode 路徑） |
| `SetZoomToFit()` | 縮放至適合 |
| `SetOriginal()` | 1:1 原始大小 |
| `SetZoomIn()` | 放大 |
| `SetZoomOut()` | 縮小 |
| `SetCenterImageEnable(bool)` | 啟用圖像置中 |
| `SetCanvasBgColor(r, g, b)` | 設定背景顏色 |

### ROI 控制

| 方法 | 說明 |
|------|------|
| `SetROIMode(mode)` | 設定工具（0–7） |
| `GetROIMode()` | 取得當前工具 |
| `SetFuncEnable(m, c, mv)` | 啟用選單 / Crop / 移動 |
| `SetMenuROIEnable(...)` | 控制選單中各工具可見性（7個 bool） |
| `SetEditingROI(mode, pts, angle)` | 以程式設定 ROI |
| `GetRect()` | 取得邊界框 `(x, y, w, h)` |
| `GetPolygonPoints()` | 取得多邊形頂點 |
| `GetRotateAngle()` | 取得旋轉角度 |
| `GetInnerRadius()` | 取得環形內半徑 |
| `GetOuterRadius()` | 取得環形外半徑 |
| `GetStartAngle()` | 取得環形起始角度 |
| `GetEndAngle()` | 取得環形結束角度 |

### Overlay 控制

| 方法 | 說明 |
|------|------|
| `AppendOverlay(mode, pts, color, size, angle?)` | 新增疊加形狀 |
| `AppendMaskOverlay(mask, color, alpha)` | 新增遮罩疊加 |
| `ClearOverlay()` | 清除所有疊加 |
| `SetDisplayOverlay(bool)` | 顯示 / 隱藏疊加 |
| `UpdateDrawImage(True)` | 強制更新渲染 |
| `ConvertMaskToPolygon(mask)` | 遮罩轉多邊形 |

### 回調設定

| 方法 | 說明 |
|------|------|
| `SetOnCropCallback(fn)` | ROI 完成回調 `fn(rect)` |
| `SetOnRightClickCallback(fn)` | 右鍵回調 `fn(pt, hit_index) → bool` |
| `SetOnMouseMoveCallback(fn)` | 滑鼠移動回調 `fn(pt)` |
| `SetOnLeftDClickCallback(fn)` | 左鍵雙擊回調 `fn(pt)` |
| `SetOnRightDClickCallback(fn)` | 右鍵雙擊回調 `fn(pt)` |

---

## 11. 常見問題

### Q1：macOS 出現 `RuntimeError: wxCvModule on macOS requires 'import wx' before 'import wxCvModule'`

**原因**：macOS 版本的 wxCvModule 使用 `dynamic_lookup` 在執行時從 wxPython 解析 wx 符號。若 wxPython 未先載入，C++ 找不到 wxApp 實例。

**解決**：確保程式碼中 `import wx` 在 `import wxCvModule` 之前執行，包含所有間接匯入路徑。

---

### Q2：面板建立後空白（白色或黑色畫面）

常見原因與解決方式：

| 症狀 | 可能原因 | 解決方式 |
|------|----------|----------|
| 白色畫面（macOS） | 未使用 Reparent，直接 NSView 嵌入 | 使用 wxCvModule 提供的 API，不要自行做 Cocoa 嵌入 |
| 黑色畫面（macOS） | `setWantsLayer:YES` 衝突 | 不要對容器 NSView 手動設定 layer |
| 空白（所有平台） | `init_panel` 在視窗顯示前執行 | 使用 `EVT_SHOW` + `wx.CallAfter` 延遲初始化 |
| 空白（所有平台） | Handle 取得時機過早 | 確保 `GetHandle()` 在 `Show()` 之後才呼叫 |

---

### Q3：視窗關閉後 Python 程式不退出（macOS）

**原因**：C++ 隱藏框架（hidden wxFrame）是 top-level window，會阻止 wxApp 結束。

**解決**：在 `EVT_CLOSE` 中手動釋放 panel：

```python
def on_close(self, event):
    self.cv_panel  = None  # 觸發 C++ 析構 → 銷毀隱藏框架
    self.roi_panel = None
    self.Destroy()
```

---

### Q4：Resize 後圖像沒有跟著縮放

C++ panel 不使用 wxSizer 管理，必須手動同步：

```python
def on_resize(self, event):
    if self.cv_panel and self.cv_panel.IsOk():
        sz = self.container.GetSize()
        self.cv_panel.SetSize(sz.width, sz.height)
        self.cv_panel.Refresh()
    event.Skip()
```

---

### Q5：Linux 上出現 `libgtk-3.so: cannot open shared object file`

安裝 GTK3 執行時函式庫：

```bash
sudo apt install libgtk-3-0          # Ubuntu/Debian
sudo dnf install gtk3                 # Fedora/RHEL
sudo pacman -S gtk3                   # Arch Linux
```

---

### Q6：回調函數更新 GUI 時出現 Assertion 或崩潰

C++ 回調可能在非 GUI 執行緒觸發。使用 `wx.CallAfter` 確保 GUI 更新在主執行緒執行：

```python
def on_mouse_move(pt):
    # ❌ 直接更新可能崩潰
    # self.label.SetLabel(f"{pt}")

    # ✅ 透過 CallAfter 安全更新
    wx.CallAfter(self.label.SetLabel, f"({pt[0]:.0f}, {pt[1]:.0f})")
```

---

### Q7：`AppendOverlay` 沒有效果

常見原因：
1. 忘記呼叫 `SetDisplayOverlay(True)` 開啟 overlay 顯示。
2. 忘記呼叫 `UpdateDrawImage(True)` 觸發重繪。
3. 點集格式不正確（請對照第 6.2 節的格式表）。

```python
roi_panel.ClearOverlay()
roi_panel.AppendOverlay(3, [(100, 100), (200, 150)], (0, 0, 255), 2)
roi_panel.SetDisplayOverlay(True)   # 必須！
roi_panel.UpdateDrawImage(True)     # 必須！
```

---

*本指南對應 wxCvModule v1.5 及以上版本。*
*完整範例程式請參閱 `examples/python_demo.py`。*