🌐 English|한국어

개발자 가이드

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

프로젝트 개요

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

  • GUI 프레임워크: PyQt5

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

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

  • 3D 그래픽: PyOpenGL, Trimesh

  • 이미지 처리: Pillow, OpenCV

프로젝트 구조:

Modan2/
├── Modan2.py                    # Main application entry point
├── MdModel.py                   # Database models (Peewee ORM)
├── MdUtils.py                   # Utility functions and constants
├── MdStatistics.py              # Statistical analysis functions
├── MdHelpers.py                 # Helper functions
├── MdConstants.py               # Application constants
├── MdLogger.py                  # Logging utilities
├── MdAppSetup.py                # Application initialization
├── MdSplashScreen.py            # Splash screen widget
├── ModanController.py           # MVC controller
├── ModanDialogs.py              # Legacy dialogs (being phased out)
├── ModanComponents.py           # Legacy components (being phased out)
├── ModanWidgets.py              # Reusable widget utilities
├── build.py                     # PyInstaller build script
├── migrate.py                   # Database migration tool
├── requirements.txt             # Python dependencies
│
├── dialogs/                     # Dialog modules (Phase 2+ refactoring)
│   ├── __init__.py
│   ├── base_dialog.py           # Base dialog class
│   ├── analysis_dialog.py       # New analysis dialog
│   ├── analysis_result_dialog.py # Analysis results
│   ├── calibration_dialog.py    # Image calibration
│   ├── data_exploration_dialog.py # Data visualization & exploration
│   ├── dataset_analysis_dialog.py # Dataset analysis configuration
│   ├── dataset_dialog.py        # Dataset create/edit
│   ├── export_dialog.py         # Data export (TPS, Morphologika, JSON+ZIP)
│   ├── import_dialog.py         # Data import (TPS, NTS, X1Y1, etc.)
│   ├── object_dialog.py         # Object/specimen editor with landmarks
│   ├── preferences_dialog.py    # Application preferences
│   └── progress_dialog.py       # Progress tracking
│
├── components/                  # Reusable components (Phase 3+ refactoring)
│   ├── __init__.py
│   ├── formats/                 # File format parsers
│   │   ├── tps.py              # TPS format support
│   │   ├── nts.py              # NTS format support
│   │   ├── x1y1.py             # X1Y1 format support
│   │   └── morphologika.py     # Morphologika format support
│   ├── viewers/                 # 2D/3D visualization widgets
│   │   ├── object_viewer_2d.py # 2D image viewer with landmarks
│   │   └── object_viewer_3d.py # 3D model viewer (OpenGL)
│   └── widgets/                 # UI widgets
│       ├── analysis_info.py     # Analysis info widget
│       ├── dataset_ops_viewer.py # Dataset operations viewer
│       ├── delegates.py         # Table/tree delegates
│       ├── drag_widgets.py      # Drag-and-drop widgets
│       ├── overlay_widget.py    # Overlay rendering widget
│       ├── pic_button.py        # Picture button widget
│       ├── shape_preference.py  # Shape visualization preferences
│       └── table_view.py        # Custom table view
│
├── OBJFileLoader/               # 3D OBJ file loading
│   ├── objloader.py
│   └── objviewer.py
│
├── tests/                       # Automated tests (pytest)
│   ├── conftest.py              # Test fixtures
│   ├── test_mdmodel.py          # Database model tests
│   ├── test_mdstatistics.py     # Statistical analysis tests
│   ├── test_mdutils.py          # Utility function tests
│   └── ...                      # Additional test modules
│
├── devlog/                      # Development log (142+ sessions)
├── docs/                        # Sphinx documentation
├── icons/                       # Application icons
├── migrations/                  # Database schema migrations
├── benchmarks/                  # Performance benchmarks
├── tools/                       # Development tools & scripts
├── config/                      # Configuration files
│   ├── pytest.ini
│   └── requirements-dev.txt
└── translations/                # i18n translation files

아키텍처

고수준 개요

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

┌──────────────────────────────────────────┐
│         ModanMainWindow (View)            │
│  ┌────────────┐  ┌──────────────────┐   │
│  │ TreeView   │  │  TableView       │   │
│  │ (Datasets) │  │  (Objects)       │   │
│  └────────────┘  └──────────────────┘   │
└──────────────┬───────────────────────────┘
               │
               ├─── Signals/Slots ───┐
               │                      │
┌──────────────▼─────────────┐  ┌────▼──────────────┐
│  ModanController           │  │  ModanDialogs     │
│  - Dataset operations      │  │  - ObjectDialog   │
│  - Object CRUD             │  │  - AnalysisDialog │
│  - Analysis coordination   │  │  - Preferences    │
└───────────┬────────────────┘  └───────────────────┘
            │
            │ Uses
            │
┌───────────▼────────────────────────────────┐
│         MdModel (Model - Peewee ORM)       │
│  ┌──────────┐  ┌─────────────┐            │
│  │MdDataset │  │ MdObject    │            │
│  │MdImage   │  │ MdAnalysis  │            │
│  └──────────┘  └─────────────┘            │
│                                             │
│  Database: modan.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)

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

Modan2의 MVC 패턴

모델 (MdModel.py):

  • Peewee ORM 모델

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

  • 데이터 검증

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

  • ModanMainWindow (Modan2.py): Main application window with tree/table views

  • Dialog classes (dialogs/*.py): ObjectDialog, NewAnalysisDialog, DataExplorationDialog, etc.

  • Viewer widgets (components/viewers/): ObjectViewer2D, ObjectViewer3D

  • Custom widgets (components/widgets/): UI components for analysis, data display, etc.

  • 사용자 작업 시 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.11 이상

  • Git: 버전 관리용

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

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

저장소 복제

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

가상 환경 설정

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 Modan2.py

Linux/WSL: Qt 오류가 발생하는 경우:

python fix_qt_import.py

개발 의존성

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

  • pytest: 테스트 프레임워크

  • pytest-cov: 코드 커버리지

  • pytest-qt: PyQt5 테스트 지원 (향후)

  • ruff: 린팅 (향후)

테스트

테스트 프레임워크

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

특정 테스트 파일 실행:

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

기여하기

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

  3. 설치 프로그램 컴파일:

    iscc InnoSetup/Modan2.iss
    
  4. 출력: Output/Modan2-Setup.exe

릴리스 생성

  1. MdUtils.py 에서 버전 업데이트 :

    PROGRAM_VERSION = "0.1.5"
    
  2. 릴리스 노트로 CHANGELOG.md 업데이트

  3. 변경사항 커밋:

    git commit -am "Release v0.1.5"
    git tag v0.1.5
    git push origin main --tags
    
  4. Windows, macOS, Linux용 실행 파일 빌드

  5. GitHub 릴리스 생성:

    • Releases → Draft a new release로 이동

    • 태그: v0.1.5

    • 제목: Modan2 v0.1.5

    • 설명: CHANGELOG.md에서 복사

    • 빌드된 실행 파일 첨부

  6. 릴리스 게시

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

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

고급 주제

사용자 정의 위젯

Creating custom PyQt5 widgets (see components/widgets/ for examples):

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)

Examples from codebase:

  • components/widgets/pic_button.py: Custom button with image support

  • components/widgets/drag_widgets.py: Drag-and-drop list widgets

  • components/viewers/object_viewer_2d.py: Complex 2D viewer with landmark editing

  • components/viewers/object_viewer_3d.py: OpenGL-based 3D viewer

통계 확장

새로운 통계 방법 추가 (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 Modan2.py
# Analyze with snakeviz
pip install snakeviz
snakeviz profile.stats

메모리 프로파일링:

pip install memory_profiler
python -m memory_profiler Modan2.py

디버깅

상세 로깅 활성화:

# In Modan2.py
logging.basicConfig(level=logging.DEBUG)

Qt 디버깅:

export QT_DEBUG_PLUGINS=1
python Modan2.py

참고 자료

문서

형태측정학 분석

커뮤니티

라이선스

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

다음 권한이 부여됩니다:

  • 상업적 사용

  • 수정

  • 배포

  • 서브라이선스

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

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