개발자 가이드
이 가이드는 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.py 는 components/ 를 다시 내보내는 하위 호환용 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 에 정의됨):
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 작업
데이터 검증
뷰 (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:xvfb 는 pytest-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.py 의 open_text 로 여세요. UTF-8, 플랫폼 기본 인코딩, latin-1 순으로 시도하므로 비ASCII 표본 이름이 있는 파일도 어떤 로케일에서든 가져올 수 있습니다.
nlandmarks 를 0으로 두지 말고 데이터에서 채우세요 — X1Y1 리더에 실제로 있었던 버그입니다.
components/formats/__init__.py 에서 클래스를 내보내고, dialogs/import_dialog.py 에 라디오 버튼과 분기를 추가한 뒤, tests/ 아래에 파서 테스트를 추가하세요. 정상 파일, 잘못된 파일(크래시가 아니라 명확한 오류를 내야 합니다), 비ASCII 표본 이름을 포함해야 합니다.
기여하기
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를 실행하세요 — 실행 파일을 빌드하고,InnoSetup/Modan2.iss.template에 현재 버전과 빌드 번호를 채운 뒤, 설치 프로그램을 컴파일합니다산출물:
InnoSetup/Output/Modan2_v<version>_build<build>_Installer.exe
릴리스 생성
v*.*.* 태그를 푸시하는 것이 곧 릴리스 게시입니다. 그러면 release.yml 이 세 플랫폼에서 테스트를 돌리고 패키지를 빌드해 자산과 함께 GitHub 릴리스를 만듭니다. 손으로 빌드하거나 업로드하는 것은 없습니다.
버전 올리기.
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
이 스크립트는 확인을 묻기 때문에 무인 실행이 되지 않습니다. 한 줄을 직접 고치는 것과 결과는 같습니다.
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
결과가 비어 있으면 릴리스가 체크섬만 담은 채 게시된다는 뜻입니다.
태그 없이 버전 범프만 먼저 푸시하고 CI를 기다립니다. 태그를 밀면 릴리스가 곧바로 나가므로, 범프를 먼저 보내고 그 트리에서 다섯 개 워크플로가 green인 것을 확인한 뒤에 태그를 만듭니다.
태그를 만들어 푸시합니다:
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.py 의 setup_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
참고 자료
문서
형태측정학 분석
Geometric Morphometrics for Biologists (Zelditch 외 저)
Morphometrics with R (Claude 저)
커뮤니티
라이선스
Modan2는 MIT 라이선스 하에 배포됩니다.
다음 권한이 부여됩니다:
상업적 사용
수정
배포
서브라이선스
원본 저작권 및 라이선스 표기를 포함해야 합니다.
자세한 내용은 LICENSE 파일을 참조하세요.