🌐 English|한국어

개발자 가이드

이 가이드는 Modan2에 기여하거나 아키텍처를 이해하고자 하는 개발자를 위한 정보를 제공합니다.

프로젝트 개요

Modan2는 다음 기술로 구축된 기하학적 형태측정학용 Python 데스크톱 애플리케이션입니다:

  • GUI 프레임워크: PyQt5

  • 데이터베이스: SQLite with Peewee ORM

  • 과학 컴퓨팅: NumPy, SciPy, Pandas, Statsmodels

  • 3D 그래픽: PyOpenGL, Trimesh

  • 이미지 처리: Pillow, OpenCV

프로젝트 구조:

Modan2/
├── main.py               Entry point (--debug, --db, --config, --lang, --no-splash)
├── Modan2.py             ModanMainWindow, imported by main.py
├── ModanController.py    Controller layer: DB/file I/O, analysis runs
├── MdModel.py            Peewee models + Procrustes/superimposition operations
├── MdStatistics.py       PCA, CVA, MANOVA
├── MdUtils.py            Utilities, paths, constants
├── MdHelpers.py          Shared helpers (guard_slot, geometry, …)
├── MdConstants.py        Shared constants
├── MdAppSetup.py         Application initialization
├── MdSplashScreen.py     Splash screen
├── MdLiveWire.py         Edge-following curve tracing
├── build.py              PyInstaller build script
├── migrate.py            Database migration tool
├── version.py            Single source of truth for the version
│
├── dialogs/              One module per dialog, all inheriting BaseDialog
├── components/
│   ├── formats/          TPS / NTS / X1Y1 / Morphologika readers
│   ├── viewers/          ObjectViewer2D, ObjectViewer3D
│   └── widgets/          Custom PyQt5 widgets
├── OBJFileLoader/        3D OBJ loading
│
├── tests/                pytest suite
├── migrations/           Database schema migrations
├── tools/                Code index builder and search (dev only)
├── scripts/              Benchmarks and profilers (dev only)
├── benchmarks/           Benchmark output
├── devlog/               Development log
├── docs/                 Repository-only Markdown notes
│   └── manual/           This manual (Sphinx, .rst)
├── config/               requirements-dev.txt
├── icons/                Application icons
└── translations/         Qt i18n files (.ts / .qm)

ModanComponents.pycomponents/ 를 다시 내보내는 하위 호환용 shim입니다. 새 코드는 components.<하위패키지>dialogs.<모듈> 에서 직접 import하세요. ModanDialogs.py 는 더 이상 존재하지 않습니다 — 모든 대화상자가 dialogs/ 로 이전되었습니다.

아키텍처

고수준 개요

Modan2는 수정된 Model-View-Controller (MVC) 패턴을 따릅니다:

┌──────────────────────────────────────────┐
│         ModanMainWindow (View)            │
│  ┌────────────┐  ┌──────────────────┐   │
│  │ TreeView   │  │  TableView       │   │
│  │ (Datasets) │  │  (Objects)       │   │
│  └────────────┘  └──────────────────┘   │
└──────────────┬───────────────────────────┘
               │
               ├─── Signals/Slots ───┐
               │                      │
┌──────────────▼─────────────┐  ┌────▼──────────────┐
│  ModanController           │  │  dialogs/         │
│  - Dataset operations      │  │  - ObjectDialog   │
│  - Object CRUD             │  │  - AnalysisDialog │
│  - Analysis coordination   │  │  - Preferences    │
└───────────┬────────────────┘  └───────────────────┘
            │
            │ Uses
            │
┌───────────▼────────────────────────────────┐
│         MdModel (Model - Peewee ORM)       │
│  ┌──────────┐  ┌─────────────┐            │
│  │MdDataset │  │ MdObject    │            │
│  │MdImage   │  │ MdAnalysis  │            │
│  └──────────┘  └─────────────┘            │
│                                             │
│  Database: Modan2.db (SQLite)              │
└────────────────────────────────────────────┘
                 │
                 │ Queries
                 │
┌────────────────▼──────────────────┐
│    MdStatistics                    │
│  - Procrustes superimposition      │
│  - PCA, CVA, MANOVA                │
│  - Missing landmark imputation     │
└────────────────────────────────────┘

데이터베이스 스키마

핵심 모델 (MdModel.py 에 정의됨):

  1. MdDataset:

    • 계층적 구조 (부모/자식 관계)

    • 차원 (2D/3D), 설명 저장

    • MdObject와 일대다 관계

  2. MdObject:

    • 표본 (이미지 또는 3D 모델) 표현

    • 랜드마크 좌표를 JSON 문자열로 저장 (landmark_str)

    • MdDataset에 대한 외래 키

    • 변수 데이터를 JSON으로 저장 (propertyvalue_str)

  3. MdImage:

    • 2D 이미지를 객체에 연결

    • 파일 경로, EXIF 데이터, 너비/높이 저장

  4. MdThreeDModel:

    • 3D 모델을 객체에 연결

    • 파일 경로, 메시 메타데이터 저장

  5. MdAnalysis:

    • 분석 결과 저장 (PCA, CVA, MANOVA)

    • MdDataset에 연결됨

    • 결과를 JSON으로 저장

관계:

MdDataset (1) ──< (many) MdObject
MdDataset (1) ──< (many) MdAnalysis
MdObject (1) ──< (0 or 1) MdImage
MdObject (1) ──< (0 or 1) MdThreeDModel

주요 필드:

  • landmark_str: 직렬화된 랜드마크 좌표 (형식: “x,y\nx,y\n…”)

  • propertyvalue_str: 직렬화된 변수 값 (JSON)

임시 작업: MdObjectOpsMdDatasetOps 클래스는 데이터베이스를 수정하지 않고 메모리 내 작업 (예: Procrustes 정렬)을 위해 데이터베이스 모델을 래핑합니다.

Modan2의 MVC 패턴

모델 (MdModel.py):

  • Peewee ORM 모델

  • 데이터베이스 쿼리 및 CRUD 작업

  • 데이터 검증

(Modan2.py, dialogs/, components/):

  • ModanMainWindow (Modan2.py): 트리/테이블 뷰를 갖춘 메인 애플리케이션 창

  • 대화상자 클래스 (dialogs/*.py): ObjectDialog, NewAnalysisDialog, DataExplorationDialog

  • 뷰어 위젯 (components/viewers/): ObjectViewer2D, ObjectViewer3D

  • 사용자 정의 위젯 (components/widgets/): 분석, 데이터 표시 등을 위한 UI 구성 요소

  • 사용자 작업 시 Qt 시그널 발생

컨트롤러 (ModanController.py):

  • 뷰의 시그널을 모델 작업에 연결

  • UI와 비즈니스 로직 간 조정

  • 분석 워크플로우 처리

예제 흐름:

User clicks "New Dataset" button
→ MainWindow emits signal
→ Controller receives signal
→ Controller opens DatasetDialog
→ User fills form, clicks OK
→ Controller creates MdDataset in database
→ Controller refreshes TreeView
→ TreeView displays new dataset

파일 형식

TPS 형식 (형태측정학 표준):

LM=5
12.5 34.2
45.6 78.9
...
IMAGE=specimen_001.jpg
ID=1
SCALE=1.0

NTS 형식 (레거시):

5
12.5 34.2
45.6 78.9
...

CSV 형식 (사용자 정의):

object,lm1_x,lm1_y,lm2_x,lm2_y
spec_001,12.5,34.2,45.6,78.9

내부 저장소 (데이터베이스 내):

  • 랜드마크는 줄바꿈으로 구분된 “x,y” 또는 “x,y,z” 문자열로 저장

  • MdObject.unpack_landmark() 에서 파싱 수행

  • MdObject.pack_landmark() 에서 패킹 수행

개발 환경 설정

사전 요구사항

  • Python: 3.12 이상

  • Git: 버전 관리용

  • IDE: VSCode, PyCharm 또는 모든 Python IDE

  • 운영 체제: Windows, macOS 또는 Linux

저장소 복제

git clone https://github.com/jikhanjung/Modan2.git
cd Modan2

시스템 의존성 (Linux)

PyQt5는 자체 libqxcb.so 를 포함하지만 시스템 XCB 라이브러리에 링크하므로, 아래 패키지는 선택이 아니라 필수입니다 — 없으면 QApplication([]) 이 예외를 던지는 대신 인터프리터를 중단시킵니다.

Ubuntu/Debian:

sudo apt-get install -y libxcb-xinerama0 libxcb-icccm4 libxcb-image0 \
  libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xfixes0 \
  libxcb-shape0 libxcb-cursor0 libxkbcommon-x11-0 \
  qt5-qmake qtbase5-dev libqt5gui5 libqt5core5a libqt5widgets5 python3-pyqt5 \
  libglut-dev libglut3.12 python3-opengl \
  xvfb fonts-nanum

xvfb 는 GUI 테스트를 헤드리스로 실행하는 데, fonts-nanum 은 차트의 한글이 렌더링되는 데 필요합니다. 둘 다 CI가 설치하는 것과 같습니다.

Fedora/RHEL: sudo dnf install -y python3-qt5 qt5-qtbase mesa-libGLU freeglut xorg-x11-server-Xvfb

Arch: sudo pacman -S python-pyqt5 qt5-base freeglut xorg-server-xvfb

가상 환경 설정

Linux/macOS:

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -r config/requirements-dev.txt

Windows:

python -m venv venv
venv\\Scripts\\activate
pip install -r requirements.txt
pip install -r config/requirements-dev.txt

소스에서 실행

python main.py

main.py 가 엔트리포인트입니다. Modan2.py 는 그것이 import하는 모듈이며 스크립트가 아닙니다. 유용한 옵션: --debug, --db <경로>, --config <경로>, --lang <en|ko>, --no-splash.

Linux/WSL: Qt가 xcb 플랫폼 플러그인을 불러오지 못하는 경우:

python fix_qt_import.py

개발 의존성

config/requirements-dev.txt 를 통해 설치됨:

  • pytest, pytest-cov, pytest-qt, pytest-mock: 테스트 스위트

  • ruff: 린팅과 포매팅 (CI에서 강제)

  • mypy: 타입 검사

  • pre-commit: 커밋 훅

코드 품질 도구

Ruff가 린팅과 포매팅을 모두 담당하며, 설정은 pyproject.toml 에 있습니다(줄 길이 120, 대상 Python 3.12).

ruff format .          # format
ruff check .           # lint
ruff check --fix .     # lint and auto-fix

타입 검사는 로컬에서는 선택이지만 CI에서는 실행됩니다:

mypy MdStatistics.py MdUtils.py

pre-commit이 매 커밋 전에 같은 검사를 실행합니다:

pre-commit install         # one-time setup
pre-commit run --all-files # run manually

푸시하기 전에 요약하면: ruff check . && ruff format . && pytest.

테스트

테스트 프레임워크

Modan2는 자동화된 테스트를 위해 pytest 를 사용합니다.

테스트 구조:

tests/
├── conftest.py            # Shared fixtures
├── test_mdutils.py        # Utility function tests
├── test_mdmodel.py        # Database model tests
└── test_statistics.py     # Statistical function tests

테스트 실행

모든 테스트 실행:

pytest

중요

Linux(WSL 포함)에서 GUI 테스트에는 X 서버 PyQt5의 플랫폼 플러그인이 링크하는 xcb 라이브러리가 모두 필요합니다. 둘 중 하나라도 없으면 스위트가 깔끔하게 실패하지 않고, 도중에 인터프리터가 Fatal Python error: Aborted 로 중단됩니다 — 코드 문제처럼 보이지만 그렇지 않습니다.

sudo apt-get install -y xvfb fonts-nanum \
  libxcb-xinerama0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
  libxcb-randr0 libxcb-render-util0 libxcb-xfixes0 libxcb-shape0 \
  libxcb-cursor0 libxkbcommon-x11-0

Xvfb :99 -screen 0 1024x768x24 >/tmp/xvfb.log 2>&1 &
export DISPLAY=:99
pytest -p no:xvfb

-p no:xvfbpytest-xvfb 플러그인을 꺼서, 이미 띄운 서버 위에 두 번째 서버를 시작하고 종료하지 않게 합니다. 이 옵션을 빼면 pytest가 실패가 아니라 멈춥니다 — 플러그인이 자체 Xvfb를 띄우려다 돌아오지 않아, 수집이 끝나기도 전에 정지합니다.

그래도 중단된다면, 어떤 라이브러리를 열지 못했는지 Qt에 물어보세요:

QT_DEBUG_PLUGINS=1 python -c "from PyQt5.QtWidgets import QApplication; QApplication([])"

대개는 오류 메시지에 나오는 libxcb-* 패키지 하나가 빠진 것입니다.

특정 테스트 파일 실행:

pytest tests/test_mdutils.py

커버리지와 함께 실행:

pytest --cov=. --cov-report=html
# Open htmlcov/index.html

상세 출력:

pytest -v

테스트 작성

테스트 예제 (tests/test_mdutils.py):

import pytest
from MdUtils import normalize_path, is_valid_dimension

def test_normalize_path():
    assert normalize_path("C:\\\\Users\\\\test") == "C:/Users/test"

def test_is_valid_dimension():
    assert is_valid_dimension(2) == True
    assert is_valid_dimension(3) == True
    assert is_valid_dimension(4) == False

픽스처 사용 (tests/conftest.py):

import pytest
from peewee import SqliteDatabase
from MdModel import MdDataset, MdObject

@pytest.fixture
def test_db():
    test_database = SqliteDatabase(':memory:')
    with test_database.bind_ctx([MdDataset, MdObject]):
        test_database.create_tables([MdDataset, MdObject])
        yield test_database
        test_database.drop_tables([MdDataset, MdObject])

def test_create_dataset(test_db):
    dataset = MdDataset.create(name="Test", dimension=2)
    assert dataset.name == "Test"

코드 스타일 가이드라인

일반 원칙

  • PEP 8 규칙 준수

  • 설명적인 변수명 사용

  • 클래스와 함수에 독스트링 추가

  • 함수를 집중적으로 유지 (단일 책임)

명명 규칙

  • 클래스: PascalCase (예: ModanController, ObjectDialog)

  • 함수/메서드: snake_case (예: create_dataset, pack_landmark)

  • 상수: UPPER_SNAKE_CASE (예: PROGRAM_NAME, DEFAULT_COLOR)

  • 비공개 메서드: _leading_underscore (예: _update_view)

  • Qt 슬롯: on_<widget>_<action> (예: on_btnOK_clicked)

독스트링 형식

Google 스타일 독스트링 사용:

def estimate_missing_landmarks(self, obj_index, reference_shape):
    """Estimate missing landmarks using aligned mean shape.

    The mean shape is computed from Procrustes-aligned complete specimens,
    then transformed to match the scale and position of the current object.

    Args:
        obj_index (int): Index of object in object_list
        reference_shape (MdObjectOps): Reference shape with complete landmarks

    Returns:
        list: Estimated landmark coordinates, or None if estimation fails

    Raises:
        ValueError: If obj_index is out of range
    """
    # Implementation...

PyQt5 패턴

시그널/슬롯 연결:

# In __init__
self.btnOK.clicked.connect(self.on_btnOK_clicked)

# Slot method
def on_btnOK_clicked(self):
    # Handle button click
    pass

긴 작업에 대기 커서 사용:

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication

def long_operation(self):
    QApplication.setOverrideCursor(Qt.WaitCursor)
    try:
        # Perform operation
        result = self.compute_something()
    finally:
        QApplication.restoreOverrideCursor()
    return result

자주 하는 작업

새 대화상자 추가하기

대화상자는 dialogs/ 아래에 모듈 하나당 하나씩 두며 BaseDialog 를 상속합니다. BaseDialog 는 제목, 창 위치·크기 저장/복원, show_error / show_warning / show_info, with_wait_cursor, create_button_box 를 제공합니다.

# dialogs/my_new_dialog.py
from PyQt5.QtWidgets import QLabel, QVBoxLayout

from dialogs.base_dialog import BaseDialog


class MyNewDialog(BaseDialog):
    """Dialog for the new feature."""

    def __init__(self, parent=None):
        super().__init__(parent, title="My New Dialog")
        self._create_widgets()
        self._create_layout()
        self._connect_signals()

    def _create_widgets(self):
        self.lblInfo = QLabel("Information goes here")

    def _create_layout(self):
        layout = QVBoxLayout()
        layout.addWidget(self.lblInfo)
        layout.addWidget(self.create_button_box())
        self.setLayout(layout)

    def _connect_signals(self):
        pass

dialogs/__init__.py 에서 내보낸 뒤(import하고 이름을 __all__ 에 추가), 메인 창에서 엽니다:

from dialogs import MyNewDialog

@guard_slot("Failed to open the new feature")
def on_action_new_feature_triggered(self):
    dialog = MyNewDialog(self)
    if dialog.exec_() == QDialog.Accepted:
        ...
    dialog.deleteLater()

참고

슬롯은 @guard_slot 으로 감싸세요. 예외가 창을 조용히 닫는 대신 오류 대화상자로 표시됩니다. 그리고 exec_() 뒤에 deleteLater() 를 호출하세요 — 부모가 지정된 대화상자는 그렇게 하지 않으면 해제되지 않습니다.

tests/dialogs/ 아래에 테스트를 추가합니다:

def test_dialog_creation(qtbot):
    dialog = MyNewDialog()
    qtbot.addWidget(dialog)
    assert dialog.windowTitle() == "My New Dialog"

새 분석 방법 추가하기

한 번의 분석 실행은 중첩정렬을 수행한 뒤 PCA, CVA, MANOVA를 함께 계산합니다 — 분석 종류별로 분기하는 스위치가 있어 그것을 확장하는 구조가 아닙니다. 통계 루틴은 MdStatistics.py 에 있고 do_*_analysis 규약을 따릅니다(do_pca_analysis, do_cva_analysis, do_manova_analysis). 랜드마크 데이터와 그룹 정보를 받아 결과 딕셔너리를 반환합니다.

# MdStatistics.py
def do_new_analysis(landmarks_data, groups=None):
    """Perform the new analysis.

    Args:
        landmarks_data: sequence of (n_landmarks, n_dims) arrays
        groups: per-object group labels, when the method needs them

    Returns:
        dict with the results and any summary statistics
    """
    if not landmarks_data:
        raise ValueError("landmarks_data cannot be empty")
    ...

이미 superimposition_method, cva_group_by, manova_group_by 를 받는 ModanController.run_analysis 에서 호출하고, 결과는 _persist_analysis_results 에서 다른 결과들과 함께 저장하세요. 보관하려는 값에는 MdAnalysis 의 필드와 마이그레이션이 필요합니다(Database Migrations 참조).

새 루틴은 tests/test_mdstatistics.py 에서 다루세요. 이 모듈이 프로젝트에서 커버리지가 가장 높으며, 테스트를 두기에 적절한 위치입니다.

새 파일 형식 추가하기

리더는 components/formats/ 에 있습니다 — 형식마다 모듈 하나이고, 각각 클래스를 노출합니다(TPS, NTS, X1Y1, Morphologika).

# components/formats/newformat.py
from components.formats._encoding import open_text


class NewFormat:
    def __init__(self, filename, datasetname, invertY=False):
        self.filename = filename
        self.dataset_name = datasetname
        self.invertY = invertY
        self.nlandmarks = 0
        self.object_name_list = []
        self.landmark_data = {}

    def read(self):
        with open_text(self.filename) as f:
            ...

중요

파일은 일반 open() 대신 components/formats/_encoding.pyopen_text 로 여세요. UTF-8, 플랫폼 기본 인코딩, latin-1 순으로 시도하므로 비ASCII 표본 이름이 있는 파일도 어떤 로케일에서든 가져올 수 있습니다.

nlandmarks 를 0으로 두지 말고 데이터에서 채우세요 — X1Y1 리더에 실제로 있었던 버그입니다.

components/formats/__init__.py 에서 클래스를 내보내고, dialogs/import_dialog.py 에 라디오 버튼과 분기를 추가한 뒤, tests/ 아래에 파서 테스트를 추가하세요. 정상 파일, 잘못된 파일(크래시가 아니라 명확한 오류를 내야 합니다), 비ASCII 표본 이름을 포함해야 합니다.

기여하기

Git 워크플로우

  1. GitHub에서 저장소 포크

  2. 포크 복제:

    git clone https://github.com/YOUR_USERNAME/Modan2.git
    cd Modan2
    
  3. 기능 브랜치 생성:

    git checkout -b feature/my-new-feature
    
  4. 변경사항 작성 및 커밋:

    git add .
    git commit -m "Add new feature: description"
    
  5. 포크에 푸시:

    git push origin feature/my-new-feature
    
  6. GitHub에서 Pull Request 열기

커밋 메시지 가이드라인

Conventional Commits 규칙 준수:

<type>: <subject>

<body (optional)>

<footer (optional)>

타입:

  • feat: 새로운 기능

  • fix: 버그 수정

  • docs: 문서 변경

  • style: 코드 스타일 (포맷팅, 로직 변경 없음)

  • refactor: 코드 리팩토링

  • test: 테스트 추가/업데이트

  • chore: 유지보수 작업

예제:

feat: Add hollow circle visualization for estimated landmarks

fix: Resolve scale mismatch in missing landmark estimation

docs: Update user guide with missing landmark section

test: Add tests for Procrustes with missing data

Pull Request 프로세스

  1. PR 설명에 변경사항을 명확하게 설명

  2. 관련 이슈 참조 (예: “Fixes #42”)

  3. 테스트 통과 확인: 제출 전 로컬에서 pytest 실행

  4. 새로운 기능 추가 시 문서 업데이트

  5. 리뷰 코멘트에 신속하게 응답

  6. 요청 시 커밋 스쿼시 (히스토리 정리)

코드 리뷰 체크리스트

리뷰어가 확인할 항목:

  • [ ] 코드가 스타일 가이드라인을 따름

  • [ ] 새로운 기능에 테스트가 있음

  • [ ] 문서가 업데이트됨 (필요한 경우)

  • [ ] 호환성 깨는 변경사항 없음 (또는 명확히 문서화됨)

  • [ ] 성능 고려사항이 다뤄짐

  • [ ] 보안 취약점이 도입되지 않음

실행 파일 빌드

PyInstaller 설정

Modan2는 독립 실행 파일을 생성하기 위해 PyInstaller를 사용합니다.

빌드 스크립트: build.py

빌드 실행:

python build.py

출력:

  • dist/Modan2/ - 독립 실행 애플리케이션 폴더

  • dist/Modan2.exe - 실행 파일 (Windows)

  • dist/Modan2 - 실행 파일 (Linux/macOS)

플랫폼별 빌드

Windows:

python build.py
# Creates dist/Modan2.exe

macOS:

python build.py
# Creates dist/Modan2.app

Linux:

python build.py
# Creates dist/Modan2

참고: 크로스 플랫폼 빌드는 지원되지 않습니다 - 대상 플랫폼에서 빌드하세요.

InnoSetup 설치 프로그램 (Windows)

Windows 설치 프로그램의 경우:

  1. https://jrsoftware.org/isinfo.php에서 InnoSetup 설치

  2. python build.py 를 실행하세요 — 실행 파일을 빌드하고, InnoSetup/Modan2.iss.template 에 현재 버전과 빌드 번호를 채운 뒤, 설치 프로그램을 컴파일합니다

  3. 산출물: InnoSetup/Output/Modan2_v<version>_build<build>_Installer.exe

릴리스 생성

v*.*.* 태그를 푸시하는 것이 곧 릴리스 게시입니다. 그러면 release.yml 이 세 플랫폼에서 테스트를 돌리고 패키지를 빌드해 자산과 함께 GitHub 릴리스를 만듭니다. 손으로 빌드하거나 업로드하는 것은 없습니다.

  1. 버전 올리기. version.py 가 단일 출처입니다 — 다른 모든 곳(앱, conf.py, 설치 파일 이름)이 여기서 파생되며, 어딘가에 값을 하드코딩하면 tests/test_version_consistency.py 가 실패합니다. 직접 편집하지 말고 도우미 스크립트를 사용하세요:

    python manage_version.py patch        # or minor / major
    python manage_version.py prerelease   # 0.2.0-beta.1 -> beta.2
    python manage_version.py prepatch beta   # start a pre-release cycle
    python manage_version.py stage rc      # 0.2.0-beta.2 -> 0.2.0-rc.1
    python manage_version.py release       # drop the suffix: 0.2.0-rc.1 -> 0.2.0
    

    이 스크립트는 확인을 묻기 때문에 무인 실행이 되지 않습니다. 한 줄을 직접 고치는 것과 결과는 같습니다.

  2. CHANGELOG.md 절을 작성합니다. 이것은 릴리스에 대한 문서가 아니라 릴리스 본문 그 자체 입니다. release.yml 이 태그와 일치하는 헤더의 절을 추출해 그대로 게시합니다. 태그를 만들기 전에 워크플로와 같은 awk로 추출을 확인하십시오:

    VERSION=0.2.0-beta.2
    awk -v hdr="## [$VERSION]" '
      index($0, hdr) == 1 { found=1; next }
      found && /^## \[/   { exit }
      found               { print }
    ' CHANGELOG.md
    

    결과가 비어 있으면 릴리스가 체크섬만 담은 채 게시된다는 뜻입니다.

  3. 태그 없이 버전 범프만 먼저 푸시하고 CI를 기다립니다. 태그를 밀면 릴리스가 곧바로 나가므로, 범프를 먼저 보내고 그 트리에서 다섯 개 워크플로가 green인 것을 확인한 뒤에 태그를 만듭니다.

  4. 태그를 만들어 푸시합니다:

    git tag -a v<version> -m "Modan2 v<version>"
    git push origin v<version>
    

    릴리스가 pre-release로 표시되는지는 태그에서 결정됩니다. 이름에 -alpha, -beta, -rc 중 하나가 있으면 플래그가 설정됩니다.

데이터베이스 마이그레이션

Modan2는 스키마 변경을 위해 peewee-migrate 를 사용합니다.

마이그레이션 생성

데이터베이스 모델을 수정할 때:

python migrate.py create <migration_name>

예제:

python migrate.py create add_missing_landmark_flag

이는 migrations/ 에 새 마이그레이션 파일을 생성합니다.

변경사항을 정의하기 위해 마이그레이션 파일 편집:

def migrate(migrator, database, fake=False, **kwargs):
    migrator.add_column('mdobject', 'has_missing', BooleanField(default=False))

def rollback(migrator, database, fake=False, **kwargs):
    migrator.drop_column('mdobject', 'has_missing')

마이그레이션 실행

대기 중인 마이그레이션 적용:

python migrate.py

마지막 마이그레이션 롤백:

python migrate.py rollback

고급 주제

사용자 정의 위젯

사용자 정의 PyQt5 위젯 생성 (예제는 components/widgets/ 참조):

from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import pyqtSignal

class CustomWidget(QWidget):
    # Define custom signals
    valueChanged = pyqtSignal(int)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.initUI()

    def initUI(self):
        # Setup UI components
        pass

    def setValue(self, value):
        # Custom logic
        self.valueChanged.emit(value)

코드베이스의 예제:

  • components/widgets/pic_button.py: 이미지를 지원하는 사용자 정의 버튼

  • components/widgets/drag_widgets.py: 드래그 앤 드롭 리스트 위젯

  • components/viewers/object_viewer_2d.py: 랜드마크 편집 기능을 갖춘 복합 2D 뷰어

  • components/viewers/object_viewer_3d.py: OpenGL 기반 3D 뷰어

통계 확장

새로운 통계 방법 추가 (MdStatistics.py 에서):

def perform_new_analysis(dataset_ops, options):
    """Perform new statistical analysis.

    Args:
        dataset_ops (MdDatasetOps): Dataset with aligned shapes
        options (dict): Analysis parameters

    Returns:
        dict: Results including scores, statistics, etc.
    """
    # Extract shape data
    coords = extract_coordinates(dataset_ops)

    # Perform analysis
    result = compute_something(coords, **options)

    return {
        'scores': result.scores,
        'statistics': result.stats,
    }

플러그인 시스템 (향후)

Modan2는 향후 버전에서 플러그인을 지원할 수 있습니다:

# plugins/my_plugin.py
class MyPlugin:
    name = "My Analysis Plugin"
    version = "1.0"

    def run(self, dataset):
        # Plugin logic
        return result

프로파일링 및 최적화

cProfile로 프로파일링:

python -m cProfile -o profile.stats main.py
# Analyze with snakeviz
pip install snakeviz
snakeviz profile.stats

메모리 프로파일링:

pip install memory_profiler
python -m memory_profiler main.py

디버깅

로깅. 각 모듈은 표준 모듈 레벨 로거를 사용하고, 핸들러는 main.pysetup_logging() 에서 설정합니다. --debug 는 로그 레벨을 올립니다.

import logging

logger = logging.getLogger(__name__)

logger.debug("Detailed debugging info")
logger.error("Something failed", exc_info=True)

로그 파일은 ~/PaleoBytes/Modan2/logs/ 에 기록됩니다.

Qt 디버깅:

export QT_DEBUG_PLUGINS=1
python main.py --debug

데이터베이스 디버깅. Peewee는 실행하는 SQL을 로그로 남깁니다:

import logging

logging.getLogger("peewee").addHandler(logging.StreamHandler())
logging.getLogger("peewee").setLevel(logging.DEBUG)

데이터베이스를 직접 살펴보려면:

sqlite3 ~/PaleoBytes/Modan2/Modan2.db "PRAGMA integrity_check"

유용한 명령어

# Development
pytest                              # run the suite
pytest --cov=. --cov-report=html    # coverage report
pytest --lf                         # re-run last failures
ruff check . && ruff format .       # lint and format
pre-commit run --all-files          # all hooks

# Performance
python scripts/benchmark_analysis.py     # analysis benchmarks
python scripts/benchmark_large_scale.py  # large-dataset benchmarks
python scripts/profile_detailed.py       # profiling
snakeviz benchmarks/*.prof               # view a profile

# Database
python migrate.py                        # run migrations

# Build
python build.py                          # build the executable

참고 자료

문서

형태측정학 분석

커뮤니티

라이선스

Modan2는 MIT 라이선스 하에 배포됩니다.

다음 권한이 부여됩니다:

  • 상업적 사용

  • 수정

  • 배포

  • 서브라이선스

원본 저작권 및 라이선스 표기를 포함해야 합니다.

자세한 내용은 LICENSE 파일을 참조하세요.