개발자 가이드
이 가이드는 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
에 정의됨):
MdDataset:
계층적 구조 (부모/자식 관계)
차원 (2D/3D), 설명 저장
MdObject와 일대다 관계
MdObject:
표본 (이미지 또는 3D 모델) 표현
랜드마크 좌표를 JSON 문자열로 저장 (
landmark_str
)MdDataset에 대한 외래 키
변수 데이터를 JSON으로 저장 (
propertyvalue_str
)
MdImage:
2D 이미지를 객체에 연결
파일 경로, EXIF 데이터, 너비/높이 저장
MdThreeDModel:
3D 모델을 객체에 연결
파일 경로, 메시 메타데이터 저장
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 viewsDialog 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 워크플로우
GitHub에서 저장소 포크
포크 복제:
git clone https://github.com/YOUR_USERNAME/Modan2.git cd Modan2
기능 브랜치 생성:
git checkout -b feature/my-new-feature
변경사항 작성 및 커밋:
git add . git commit -m "Add new feature: description"
포크에 푸시:
git push origin feature/my-new-feature
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 프로세스
PR 설명에 변경사항을 명확하게 설명
관련 이슈 참조 (예: “Fixes #42”)
테스트 통과 확인: 제출 전 로컬에서
pytest
실행새로운 기능 추가 시 문서 업데이트
리뷰 코멘트에 신속하게 응답
요청 시 커밋 스쿼시 (히스토리 정리)
코드 리뷰 체크리스트
리뷰어가 확인할 항목:
[ ] 코드가 스타일 가이드라인을 따름
[ ] 새로운 기능에 테스트가 있음
[ ] 문서가 업데이트됨 (필요한 경우)
[ ] 호환성 깨는 변경사항 없음 (또는 명확히 문서화됨)
[ ] 성능 고려사항이 다뤄짐
[ ] 보안 취약점이 도입되지 않음
실행 파일 빌드
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 설치 프로그램의 경우:
https://jrsoftware.org/isinfo.php에서 InnoSetup 설치
실행 파일 빌드:
python build.py
설치 프로그램 컴파일:
iscc InnoSetup/Modan2.iss
출력:
Output/Modan2-Setup.exe
릴리스 생성
MdUtils.py
에서 버전 업데이트 :PROGRAM_VERSION = "0.1.5"
릴리스 노트로 CHANGELOG.md 업데이트
변경사항 커밋:
git commit -am "Release v0.1.5" git tag v0.1.5 git push origin main --tags
Windows, macOS, Linux용 실행 파일 빌드
GitHub 릴리스 생성:
Releases → Draft a new release로 이동
태그:
v0.1.5
제목:
Modan2 v0.1.5
설명: CHANGELOG.md에서 복사
빌드된 실행 파일 첨부
릴리스 게시
데이터베이스 마이그레이션
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 supportcomponents/widgets/drag_widgets.py
: Drag-and-drop list widgetscomponents/viewers/object_viewer_2d.py
: Complex 2D viewer with landmark editingcomponents/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
참고 자료
문서
형태측정학 분석
Geometric Morphometrics for Biologists (Zelditch 외 저)
Morphometrics with R (Claude 저)
커뮤니티
라이선스
Modan2는 MIT 라이선스 하에 배포됩니다.
다음 권한이 부여됩니다:
상업적 사용
수정
배포
서브라이선스
원본 저작권 및 라이선스 표기를 포함해야 합니다.
자세한 내용은 LICENSE 파일을 참조하세요.