Advanced Features Guide
This guide covers advanced features and techniques for power users of Modan2.
Performance Optimization
Database Optimization
Modan2 uses SQLite for data storage. Regular maintenance improves performance.
Optimize database:
# Using SQLite command line
sqlite3 ~/PaleoBytes/Modan2/Modan2.db "VACUUM;"
# Or using Python
import sqlite3
conn = sqlite3.connect('Modan2.db')
conn.execute('VACUUM')
conn.close()
Benefits:
Reduces database file size
Improves query performance
Reclaims unused space
When to optimize:
After deleting many datasets
Database file larger than expected
Performance noticeably slower
Large Dataset Strategies
For datasets with 1000+ objects:
1. Hierarchical Organization:
Master Dataset (all objects)
├── Subset 1 (species A)
├── Subset 2 (species B)
└── Subset 3 (time period 1)
Benefits:
Analyze subsets independently
Faster individual analyses
Organized workflow
2. Batch Processing:
# Process multiple datasets programmatically
from MdModel import MdDataset, MdDatasetOps
from MdStatistics import PerformPCA
datasets = MdDataset.select()
for dataset in datasets:
obj_count = len(dataset.get_object_list())
if obj_count > 50:
dataset_ops = MdDatasetOps()
dataset_ops.read_from_dataset(dataset)
pca = PerformPCA(dataset_ops)
# Use pca results (pca.rotated_matrix, pca.eigen_value_percentages, etc.)
3. Memory Management:
Close unused object viewers
Don’t keep all analyses open
Export results and close tabs
Performance Benchmarks
Expected performance (Phase 7 validation):
Operation |
100 objects |
1000 objects |
|---|---|---|
Dataset load |
< 50ms |
277ms |
PCA |
< 10ms |
60ms |
CVA |
< 5ms |
2.5ms |
MANOVA |
< 10ms |
28ms |
Object table |
5ms |
12.63ms |
Memory usage:
~4KB per object (linear scaling)
1000 objects: ~4MB
10000 objects: ~40MB
Scalability:
Tested up to 2000 objects
Linear O(n) performance
Production-ready for 100,000+ objects
Advanced Data Management
Hierarchical Datasets
Parent-child relationships allow flexible data organization:
Creating child datasets:
Right-click the parent dataset in the tree
Select “Add child dataset”
Fill in the new dataset’s dialog as usual — the parent is preset for you
A child dataset is a new, empty dataset nested under the parent; objects are not copied into it. Use it to organise a study into subsets you populate yourself.
Use cases:
Example 1: Taxonomic subsets
All Specimens (parent)
├── Species A (child)
├── Species B (child)
└── Species C (child)
Example 2: Time periods
Complete Dataset (parent)
├── Pleistocene (child)
├── Holocene (child)
└── Modern (child)
Batch Operations
Batch landmark editing:
# Example: Apply transformation to all objects
from MdModel import MdDataset, MdObject
import numpy as np
dataset = MdDataset.get_by_id(dataset_id)
for obj in dataset.get_object_list():
coords = obj.get_landmark_list()
# Apply transformation
coords = coords * 2.0 # Scale example
obj.save_landmark_list(coords)
Batch variable editing:
Select the dataset
Edit cells directly in the object table
Copy/paste to and from a spreadsheet with
Ctrl+C/Ctrl+V
Batch import:
Drag several landmark files onto the window at once, or select them together in File → Import — each becomes an object in the dataset.
Semi-landmark Curves at Scale
Curves are defined once for the dataset (a name and a point count N) and then
traced per specimen, so the semi-landmark count stays consistent across the whole
dataset by construction.
Change ``N`` after tracing. Editing
Nin the curve table re-resamples the stored trace — you do not have to re-trace anything.Trace fewer clicks. With “Snap to curve” on (the default in Curve mode), the trace follows the strongest image edge between clicks, so a clean outline usually needs only a handful of points.
Remove a curve everywhere. Right-click a row in the curve table → “Delete Curve (all specimens)”.
Round-trip.
CURVES=/POINTS=blocks are read from and written to TPS, and the JSON+ZIP package carries the curve scheme and every traced curve.
Semi-landmark curves are a 2D feature. In analysis the derived points are treated as ordinary landmarks, appended after the fixed (anatomical) ones.
Database Direct Access
Access database directly for advanced operations:
from MdModel import MdDataset, MdObject, database
# Query datasets
datasets = MdDataset.select().where(
MdDataset.dimension == 2
)
# Complex queries
from peewee import fn
large_datasets = MdDataset.select().where(
fn.COUNT(MdObject.id) > 100
).join(MdObject)
# Bulk operations
with database.atomic():
for obj in objects:
obj.save()
Advanced Statistical Analysis
Custom Analysis Workflows
A single analysis run always computes PCA, CVA, and MANOVA together; the exploration below happens afterwards, in the Data Exploration dialog.
Regression overlay:
Open a completed analysis in Data Exploration
Tick “Show regression”
Choose the grouping with Group by, and whether the line is fitted to All points, By group, or a Select group
Shape grid:
Tick “Shape grid” in Data Exploration to draw the shape reconstructed at positions across the plot, so you can see how shape changes along each axis.
Superimposition Methods
Modan2 offers three superimposition methods, chosen in the analysis dialog. All three impute missing landmarks first (see Missing Landmark Handling).
Procrustes (Generalized Procrustes Analysis):
Translation + rotation + scaling
Standardises centroid size, leaving pure shape
The default, and the right choice unless you have a specific reason otherwise
Bookstein (baseline registration):
Re-expresses each shape as Bookstein shape coordinates by fixing the dataset’s baseline landmarks to a standard position
2D: the baseline endpoints go to (-0.5, 0) and (0.5, 0); 3D uses a 3-point baseline
Requires a baseline defined on the dataset
Useful when a well-defined anatomical axis should anchor the comparison
Resistant Fit (RFTRA):
Robust alignment built on repeated medians of pairwise landmark relationships
A few displaced (outlier) landmarks do not drag the whole fit the way they can under Procrustes
Works for both 2D and 3D
Choosing a method:
Research Goal |
Recommended |
Notes |
|---|---|---|
Pure shape |
Procrustes |
The default |
Anchored on an anatomical axis |
Bookstein |
Needs a dataset baseline |
A few unreliable landmarks |
Resistant Fit |
Resists outlier landmarks |
Missing Landmark Handling
Modan2 fills missing landmarks automatically during analysis with an EM-style loop that interleaves alignment and imputation:
Align every specimen with the gaps left open — the mean shape ignores them, and each specimen is aligned on the landmarks it actually has.
For each specimen with missing data, fit the current mean shape onto its observed landmarks by a similarity transform (rotation, scale, translation) and read the missing positions off the fitted mean.
Re-align with the filled-in values, then re-open the original gaps and re-estimate them from the improved mean.
Repeat step 3 a few times, so estimates improve as the alignment settles and are never fitted on previous estimates.
Imputed values exist only in the analysis working copy — they are never written back to the database. The same shape-fitting drives the “Show Estimated” and “Show Expected” previews in the Object Dialog.
Best practices:
Limit missing data to < 10% of landmarks
Keep a good number of complete (or near-complete) specimens
Document which landmarks were missing
Sensitivity analysis (compare with/without the affected specimens)
3D Visualization Techniques
Advanced 3D Controls
Navigation:
Left-drag: Rotate around centre
Middle-drag: Pan (translate)
Right-drag or scroll wheel: Zoom in/out
Display toggles (checkboxes beside the viewer):
3D Model: show the mesh
Rotate: spin the model continuously
Wireframe / Polygon / Baseline: draw the dataset’s landmark connections, filled polygons, and baseline
Landmark Visualization
Customization — set in Edit → Preferences, separately for 2D and 3D:
Landmark size: Small / Medium / Large
Wireframe thickness: Thin / Medium / Thick
Index (label) size: Small / Medium / Large
Background Color for the viewer
Landmark labels:
The Show checkbox toggles labels
The Index / Name radio buttons choose whether the label is the landmark’s number or its dataset-wide name
Wireframe display:
Define the connections in the dataset dialog’s wireframe tab
Model Import and Processing
Supported 3D formats:
OBJ: Wavefront format (most common)
PLY: Polygon File Format
STL: Stereolithography
Pre-processing models:
For best performance, prepare models:
Reduce polygon count (MeshLab/Blender):
Original: 1,000,000 polygons Decimated: 100,000 polygons Quality: Still excellent Performance: 10x faster
Center model:
Model centered at origin
Easier landmark placement
Scale appropriately:
Reasonable coordinate range
Not too large/small
Batch model processing:
# Example: Decimate models with Trimesh
import trimesh
mesh = trimesh.load('model.obj')
# Reduce to 10% of original faces
simplified = mesh.simplify_quadratic_decimation(
len(mesh.faces) // 10
)
simplified.export('model_simplified.obj')
Integration with External Tools
R Integration
Export data for analysis in R:
Export landmarks:
# Modan2: Export as Morphologika or TPS
# R: Import with geomorph
library(geomorph)
data <- readland.tps("export.tps", specID = "ID")
# Or Morphologika
data <- read.morphologika("export.txt")
# Perform analysis in geomorph
gpa <- gpagen(data)
pca <- gm.prcomp(gpa$coords)
Import R results:
Save R results as CSV
Import as variables in Modan2
Visualize in Data Exploration
Python/NumPy Integration
Access data programmatically:
from MdModel import MdDataset, MdObject
import numpy as np
# Load dataset
dataset = MdDataset.get_by_id(1)
objects = dataset.get_object_list()
# Extract landmark coordinates
coords = []
for obj in objects:
landmarks = obj.get_landmark_list()
coords.append(landmarks)
# Convert to NumPy array
data = np.array(coords) # Shape: (n_objects, n_landmarks, n_dims)
# Perform custom analysis
from scipy.spatial.distance import pdist, squareform
from sklearn.decomposition import PCA
# Flatten for PCA
flat_data = data.reshape(len(objects), -1)
# Custom PCA
pca = PCA(n_components=10)
scores = pca.fit_transform(flat_data)
# Save results back to Modan2
# (Add as variables to objects)
MorphoJ Compatibility
Export for MorphoJ:
Export as Morphologika format
Open in MorphoJ
Perform additional analyses
Compare results
Import from MorphoJ:
Export from MorphoJ as Morphologika
Import into Modan2
Continue workflow
Scripting and Automation
Python API
Use Modan2 modules in scripts:
#!/usr/bin/env python
"""
Example: Batch PCA analysis
"""
from MdModel import MdDataset, MdDatasetOps
from MdStatistics import PerformPCA
import json
# Get all 2D datasets with sufficient objects
datasets = MdDataset.select().where(MdDataset.dimension == 2)
results = []
for dataset in datasets:
# Check object count
obj_count = len(dataset.get_object_list())
if obj_count < 50:
continue
print(f"Processing {dataset.dataset_name}...")
# Create dataset ops and perform PCA
dataset_ops = MdDatasetOps()
dataset_ops.read_from_dataset(dataset)
pca = PerformPCA(dataset_ops)
if pca is None:
continue
# Save results
results.append({
'dataset': dataset.dataset_name,
'n_components': len(pca.eigen_value_percentages),
'variance_ratios': pca.eigen_value_percentages[:5] # First 5 PCs
})
# Export summary
with open('pca_summary.json', 'w') as f:
json.dump(results, f, indent=2)
print(f"Processed {len(results)} datasets")
Database Queries
Advanced database operations:
from MdModel import MdDataset, MdObject, MdAnalysis
from peewee import fn
# Find datasets with most objects
top_datasets = (MdDataset
.select(MdDataset, fn.COUNT(MdObject.id).alias('count'))
.join(MdObject)
.group_by(MdDataset)
.order_by(fn.COUNT(MdObject.id).desc())
.limit(10))
# Find objects with missing landmarks
incomplete_objects = (MdObject
.select()
.where(MdObject.missing_landmarks.is_null(False)))
# Get all PCA analyses
pca_analyses = (MdAnalysis
.select()
.where(MdAnalysis.analysis_type == 'PCA'))
# Complex join query
results = (MdDataset
.select(MdDataset.name, fn.AVG(MdObject.size).alias('mean_size'))
.join(MdObject)
.group_by(MdDataset)
.having(fn.COUNT(MdObject.id) > 30))
Batch Export
Export multiple datasets:
#!/usr/bin/env python
"""
Export all datasets as JSON+ZIP
"""
from MdModel import MdDataset
from MdUtils import create_zip_package
import os
output_dir = "exports"
os.makedirs(output_dir, exist_ok=True)
datasets = MdDataset.select()
for dataset in datasets:
filename = f"{dataset.dataset_name.replace(' ', '_')}.zip"
filepath = os.path.join(output_dir, filename)
print(f"Exporting {dataset.dataset_name}...")
create_zip_package(
dataset.id,
filepath,
include_files=True
)
print(f"Exported {len(datasets)} datasets to {output_dir}")
Custom Visualizations
Matplotlib Integration
Create custom plots from Modan2 data:
import matplotlib.pyplot as plt
from MdModel import MdDataset, MdDatasetOps
from MdStatistics import PerformPCA
import numpy as np
# Load dataset and run PCA
dataset = MdDataset.get_by_id(1)
dataset_ops = MdDatasetOps()
dataset_ops.read_from_dataset(dataset)
pca = PerformPCA(dataset_ops)
if pca is None:
print("PCA failed")
exit(1)
# Extract PC scores and variance
scores = np.array(pca.rotated_matrix)
variance = [v * 100 for v in pca.eigen_value_percentages] # Convert to percentages
# Create custom scatter plot
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# PC1 vs PC2
ax1.scatter(scores[:, 0], scores[:, 1], alpha=0.6)
ax1.set_xlabel(f'PC1 ({variance[0]:.1f}%)')
ax1.set_ylabel(f'PC2 ({variance[1]:.1f}%)')
ax1.set_title('PCA Scores')
ax1.grid(True, alpha=0.3)
# Scree plot
n_components = min(10, len(variance)) # Show first 10 components
ax2.bar(range(1, n_components+1), variance[:n_components])
ax2.set_xlabel('Component')
ax2.set_ylabel('Variance Explained (%)')
ax2.set_title('Scree Plot')
plt.tight_layout()
plt.savefig('pca_custom.png', dpi=300)
plt.show()
Shape Deformation Grids
Visualize shape changes along PC axes:
import matplotlib.pyplot as plt
import numpy as np
from MdModel import MdDataset, MdDatasetOps
from MdStatistics import PerformPCA
# Load dataset and run PCA
dataset = MdDataset.get_by_id(1)
dataset_ops = MdDatasetOps()
dataset_ops.read_from_dataset(dataset)
pca = PerformPCA(dataset_ops)
if pca is None:
print("PCA failed")
exit(1)
# Calculate mean shape (already centered in PCA)
n_landmarks = len(dataset_ops.object_list[0].landmark_list)
dimension = dataset.dimension
# Get PC1 loadings (rotation matrix column 0)
pc1_loadings = pca.rotation_matrix[:, 0]
# Reconstruct shapes at -2SD, mean (0), +2SD along PC1
sd = np.sqrt(pca.raw_eigen_values[0])
shapes = []
for multiplier in [-2, 0, 2]:
# Apply PC1 loadings scaled by SD
shape_vector = pc1_loadings * multiplier * sd
# Reshape to landmarks
shape = shape_vector.reshape(n_landmarks, dimension)
shapes.append(shape)
# Plot deformation
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
wireframe = dataset.unpack_wireframe()
for ax, shape, title in zip(
axes,
shapes,
['PC1 -2SD', 'Mean', 'PC1 +2SD']
):
ax.scatter(shape[:, 0], shape[:, 1], c='red', s=50, zorder=2)
# Add wireframe if defined
if wireframe:
for connection in wireframe:
idx1, idx2 = connection
ax.plot([shape[idx1, 0], shape[idx2, 0]],
[shape[idx1, 1], shape[idx2, 1]], 'b-', alpha=0.6)
ax.set_title(title)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('shape_deformation.png', dpi=300)
Settings and Configuration
Preferences File Format
Modan2 keeps its preferences in a JSON file written when the application exits.
Location — the standard settings folder for your operating system:
Platform |
Location |
|---|---|
Windows |
|
macOS |
|
Linux |
|
This is separate from your data (~/PaleoBytes/Modan2/, which holds the
database, images, 3D models, logs and backups). Preferences are settings the
application can recreate; your data cannot be recreated, so the two are kept
apart.
Preferences are copied forward automatically when the location changes, so
upgrading never loses them. Earlier versions kept them in
~/PaleoBytes/Modan2/preferences.json and, before that,
~/.modan2/config.json; those files are left in place and can be deleted once
you have upgraded.
Everything in the file is written by the Preferences dialog and by the window geometry that is remembered between sessions, so the normal way to change a setting is through the UI. To reset Modan2 to defaults, quit it and delete the file — it is recreated on the next launch.
# Backup (Linux; adjust the path for your platform, see the table above)
cp ~/.config/PaleoBytes/Modan2/preferences.json{,.backup}
# Restore
cp ~/.config/PaleoBytes/Modan2/preferences.json{.backup,}
Command-Line Options
The application accepts a few options at startup:
--db <path>— open a specific database instead of the default--config <path>— use a different configuration file--lang <en|ko>— start in the given language--debug— verbose logging--no-splash— skip the splash screen--version— print the version and exit
Note
Modan2 is not configured through environment variables; use these options (or the Preferences dialog) instead.
Tips and Tricks
Keyboard Shortcuts
Main window:
Ctrl+N: New datasetCtrl+Shift+N: New objectCtrl+Shift+O: Edit objectCtrl+S: Save changesCtrl+I: ImportCtrl+E: ExportCtrl+G: AnalyzeCtrl+P: Toggle the object previewCtrl+W: ExitF1: About
Object Dialog (Curve mode):
Enter/ double-click: accept the current traceEsc/ right-click: cancel the current trace
Tables:
Ctrl+C/Ctrl+V: copy and paste cells (e.g. to and from a spreadsheet)
Workflow Optimization
Efficient data entry:
Template datasets:
Create dataset with complete structure
Duplicate for new studies
Pre-defined variables and wireframes
Keyboard navigation:
Tab between fields
Enter to confirm
Escape to cancel
Batch operations:
Select multiple objects
Edit variables in table
Copy/paste from spreadsheet
Analysis workflows:
Quick exploration:
1. Import data
2. Quick PCA (no grouping)
3. Identify outliers
4. Refine dataset
Publication workflow:
1. Clean data
2. Document variables
3. Multiple analyses
4. Export high-quality figures
5. Export data for R/Python
Further Resources
Documentation:
Installation Guide: Detailed setup
User Guide: Basic usage
Troubleshooting Guide: Problem-solving
FAQ: Common questions
Community:
GitHub Discussions: Ask questions
GitHub Issues: Report bugs
Development:
Developer Guide: Architecture
CONTRIBUTING.md: Contribution guide
GitHub Repository: Source code
Contact:
Email: jikhanjung@gmail.com
GitHub: @jikhanjung