Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
2b55b97
initial geometry/rendering functions
themattinthehatt May 27, 2026
3d9e1ec
initial transformer functions
themattinthehatt May 27, 2026
4d6606a
numpy docstrings
themattinthehatt May 27, 2026
9bcdc83
move erayzer helper functions
themattinthehatt May 27, 2026
834229f
implement erayzer
themattinthehatt May 27, 2026
fb6627c
remove lpips
themattinthehatt May 27, 2026
16c02e3
remove xformers
themattinthehatt May 27, 2026
f2ecece
remove unused functions
themattinthehatt May 27, 2026
37690dc
remove unused functions
themattinthehatt May 27, 2026
dd7923d
remove unused functions
themattinthehatt May 28, 2026
ec0bf47
multiview data handling
themattinthehatt Jun 3, 2026
be1c7b7
more erayzer helpers
themattinthehatt Jun 3, 2026
e9f387e
erayzer implementation
themattinthehatt Jun 4, 2026
3ff0a41
erayzer api wiring
themattinthehatt Jun 4, 2026
a4f4005
beast_resnet port
themattinthehatt Jun 4, 2026
e27130d
beast_vit port
themattinthehatt Jun 4, 2026
8729340
erayzer port
themattinthehatt Jun 4, 2026
cd37fa3
auto-validate erayzer config
themattinthehatt Jun 4, 2026
f1f9699
helper docs
themattinthehatt Jun 4, 2026
abc326c
nn module
themattinthehatt Jun 4, 2026
03b1a72
refactor perceptual losses
themattinthehatt Jun 4, 2026
6a5d62b
vgg perceptual tests
themattinthehatt Jun 4, 2026
859249b
increase test coverage
themattinthehatt Jun 4, 2026
5bafa09
erayzer integration test
themattinthehatt Jun 4, 2026
3c46011
increase test coverage
themattinthehatt Jun 4, 2026
14f90be
additional gsplat install docs
themattinthehatt Jun 4, 2026
6549a18
doc cleanup
themattinthehatt Jun 4, 2026
ec3e24e
Merge branch 'main' into erayzer
themattinthehatt Jun 22, 2026
cf5199e
Erayzer verify (#50)
PPWangyc Jun 23, 2026
ad7a2cb
Merge branch 'main' into erayzer
themattinthehatt Jun 23, 2026
98669f2
update docs
themattinthehatt Jun 23, 2026
2935978
update build script for CI
themattinthehatt Jun 23, 2026
6c2a626
update build script for CI
themattinthehatt Jun 23, 2026
53081f5
update build script for CI
themattinthehatt Jun 23, 2026
511d6ce
update build script for CI
themattinthehatt Jun 23, 2026
02a846f
update build script for CI
themattinthehatt Jun 23, 2026
f9aebce
update build script for CI
themattinthehatt Jun 23, 2026
5d28522
update build script for CI
themattinthehatt Jun 23, 2026
fcb859d
send tests to non-1080
themattinthehatt Jun 23, 2026
a860ae3
update build script for CI
themattinthehatt Jun 23, 2026
a8782d9
update build script for CI
themattinthehatt Jun 23, 2026
3c78aec
update build instructions
themattinthehatt Jun 23, 2026
c5b7cd8
update installation docs
themattinthehatt Jun 23, 2026
d0fbfc7
only use 1 GPU for CI
themattinthehatt Jun 23, 2026
fa618cf
gaussian renderer tests
themattinthehatt Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,16 @@ app.log

# data
/data/
*.png
*.png

# machine-specific run / inspection scripts (private paths, accounts, node names)
scripts/*.sbatch
scripts/export_pointcloud.py
scripts/preview_pointcloud.py
scripts/smoke_erayzer_load.py
scripts/viz_erayzer.py

# fine-tune config holds a machine-specific init_checkpoint path (kept local)
configs/multiview/erayzer_cheese3d_ft.yaml
# local model checkpoints (machine-specific, large)
checkpoints/
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ See our [code of conduct](CODE_OF_CONDUCT.md) for more information.
install in editable mode with dev dependencies:

```bash
pip install -e ".[dev]"
pip install lightning poetry-core
pip install -e ".[dev]" --no-build-isolation
pre-commit install
```

Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,23 @@ For Github cloning:
```commandline
git clone https://github.com/paninski-lab/beast
cd beast
pip install -e .
pip install lightning poetry-core
pip install -e . --no-build-isolation
```

For installation through PyPI:

```commandline
pip install beast-backbones
pip install lightning poetry-core
pip install beast-backbones --no-build-isolation
```

> **Note:** `beast` depends on a custom fork of
> [gsplat](https://github.com/QitaoZhao/gsplat) that must be compiled from source. The
> `gsplat` build requires `torch` (provided by `lightning`) and the build backend requires
> `poetry-core`. Installing these first and using `--no-build-isolation` lets the build find
> them in your environment.

## Usage

> The commands below are for the **single-view BEAST model**.
Expand Down
27 changes: 11 additions & 16 deletions beast/api/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@

import torch

from beast.config import BeastConfig
from beast.config import get_beast_config_class
from beast.inference import predict_images, predict_video
from beast.io import load_config
from beast.logging import log_step
from beast.models.base import BaseLightningModel
from beast.models.resnets import ResnetAutoencoder
from beast.models.vits import VisionTransformer
from beast.train import train
from beast.models.registry import MODEL_REGISTRY, TRAIN_REGISTRY

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -46,12 +44,6 @@ class Model:
This class manages both the model and the training/inference processes.
"""

MODEL_REGISTRY = {
'vit': VisionTransformer,
'resnet': ResnetAutoencoder,
# Add more models as needed
}

def __init__(
self,
model: BaseLightningModel,
Expand Down Expand Up @@ -83,11 +75,11 @@ def from_dir(cls, model_dir: str | Path) -> 'Model':
config = load_config(config_path)

model_type = config['model'].get('model_class', '').lower()
if model_type not in cls.MODEL_REGISTRY:
if model_type not in MODEL_REGISTRY:
raise ValueError(f'Unknown model type: {model_type}')

# Initialize the LightningModule
model_class = cls.MODEL_REGISTRY[model_type]
model_class = MODEL_REGISTRY[model_type]
model = model_class(config)

_logger.info(f'Loaded a {model_class} model')
Expand Down Expand Up @@ -116,14 +108,15 @@ def from_config(cls, config_path: str | Path | dict) -> 'Model':
if not isinstance(config_path, dict):
config = load_config(config_path)
else:
config = BeastConfig.model_validate(config_path).model_dump()
model_class = (config_path.get('model') or {}).get('model_class', '')
config = get_beast_config_class(model_class).model_validate(config_path).model_dump()

model_type = config['model'].get('model_class', '').lower()
if model_type not in cls.MODEL_REGISTRY:
if model_type not in MODEL_REGISTRY:
raise ValueError(f'Unknown model type: {model_type}')

# Initialize the LightningModule
model_class = cls.MODEL_REGISTRY[model_type]
model_class = MODEL_REGISTRY[model_type]
log_step(f"Creating {model_type} model instance", level='debug')
log_step(
f"About to call {model_class.__name__}.__init__() - this may take several"
Expand All @@ -148,8 +141,10 @@ def train(self, output_dir: str | Path = 'runs/default') -> None:

"""
self.model_dir = Path(output_dir)
model_type = self.config['model'].get('model_class', '').lower()
train_fn = TRAIN_REGISTRY[model_type]
with chdir(self.model_dir):
self.model = train(self.config, self.model, output_dir=self.model_dir)
self.model = train_fn(self.config, self.model, output_dir=self.model_dir)

def predict_images(
self,
Expand Down
125 changes: 68 additions & 57 deletions beast/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
"""Pydantic models for BEAST configuration validation."""
"""Pydantic schemas for BEAST configuration validation and per-model dispatch.

Top-level config classes
------------------------
BeastConfig
Used for models that share the standard training loop (resnet, vit).
Contains TrainingConfig, OptimizerConfig, and DataConfig.

ERayZerBeastConfig
Used for ERayZer and its subclasses (e.g. BEAST3D if they share ERayZer's
training schema). Contains ERayZerTrainingConfig and ERayZerOptimizerConfig.

Shared schemas (reusable for new models)
-----------------------------------------
TrainingConfig — standard epoch-based training fields (batch sizes, epochs, imgaug, …)
OptimizerConfig — AdamW/Adam + cosine/step scheduler fields
DataConfig — data_dir only; extend or replace for richer data configs

Per-model dispatch
------------------
get_beast_config_class(model_class) returns the correct top-level config class for a
given model_class string. Both beast.io.load_config and beast.api.model.Model.from_config
use this function so that YAML files and raw dicts are validated against the right schema.

To add a new model with a divergent training schema:
1. Define <Model>TrainingConfig and <Model>OptimizerConfig in the model's *_config.py
2. Define <Model>BeastConfig here (avoids circular imports since DataConfig lives here)
3. Add 'model_class': <Model>BeastConfig to _MODEL_CONFIG_CLASSES
"""

from __future__ import annotations

Expand All @@ -7,68 +35,21 @@

from pydantic import BaseModel, Field

from beast.models.beast_resnet.beast_resnet_config import ResnetModelConfig
from beast.models.beast_vit.beast_vit_config import VitModelConfig
from beast.models.erayzer.erayzer_config import (
ERayZerModelConfig,
ERayZerOptimizerConfig,
ERayZerTrainingConfig,
)


class BeastConfig(BaseModel):
model: ModelConfig
training: TrainingConfig
optimizer: OptimizerConfig
data: DataConfig


class ResnetModelConfig(BaseModel):
model_class: Literal['resnet']
model_params: ResnetModelParams
seed: int = 0
checkpoint: str | None = None


class ResnetModelParams(BaseModel):
backbone: Literal[
'resnet18',
'resnet34',
'resnet50',
'resnet101',
'resnet152',
] = 'resnet18'
num_latents: int | None = None
image_size: int = 224
num_channels: int = 3


class VitModelConfig(BaseModel):
model_class: Literal['vit']
model_params: VitModelParams
seed: int = 0
checkpoint: str | None = None


class VitModelParams(BaseModel):
hidden_size: int = 768
num_hidden_layers: int = 12
num_attention_heads: int = 12
intermediate_size: int = 3072
hidden_act: str = 'gelu'
hidden_dropout_prob: float = 0.0
attention_probs_dropout_prob: float = 0.0
initializer_range: float = 0.02
layer_norm_eps: float = 1e-12
image_size: int = 224
patch_size: int = 16
num_channels: int = 3
qkv_bias: bool = True
decoder_num_attention_heads: int = 16
decoder_hidden_size: int = 512
decoder_num_hidden_layers: int = 8
decoder_intermediate_size: int = 2048
mask_ratio: float = 0.75
norm_pix_loss: bool = False
embed_size: int = 768
temp_scale: bool = False
random_init: bool = False
use_infoNCE: bool = False
infoNCE_weight: float = 0.03
use_perceptual_loss: bool = False
lambda_perceptual: float = 10.0
inference: bool = False


ModelConfig = Annotated[
Expand Down Expand Up @@ -113,4 +94,34 @@ class DataConfig(BaseModel):
data_dir: str | Path


class ERayZerBeastConfig(BaseModel):
"""Complete top-level config for ERayZer training runs."""

model: ERayZerModelConfig
training: ERayZerTrainingConfig
optimizer: ERayZerOptimizerConfig
data: DataConfig
inference: bool = False


_MODEL_CONFIG_CLASSES: dict[str, type[BaseModel]] = {
'erayzer': ERayZerBeastConfig,
}


def get_beast_config_class(model_class: str) -> type[BaseModel]:
"""Return the top-level Pydantic config class for the given model_class identifier.

Parameters
----------
model_class: model type identifier string (e.g., 'resnet', 'vit', 'erayzer')

Returns
-------
Pydantic model class for the full top-level config

"""
return _MODEL_CONFIG_CLASSES.get(model_class, BeastConfig)


BeastConfig.model_rebuild()
Loading