Justin21523/3d-animation-lora-pipeline

A full pipeline for 3D character processing — including video-to-frames extraction, layered segmentation, CLIP/HDBSCAN clustering, pose/view sub-clustering, schema-guided VLM captioning, dataset assembly, LoRA training, automated checkpoint evaluation, and batch video synthesis.

1

stars

31

commits

Python

primary language

Jun 28, 2026

updated

README

3D Animation LoRA Pipeline

Portfolio-ready data engineering and training pipeline for animation LoRA datasets. The project turns animation footage into staged artifacts: frames, detections, foreground/background cutouts, pose records, embeddings, LoRA-ready datasets, inference samples, animation frames, and a public demo layer that is safe to show without private media or model weights.

The repository is intentionally file-driven. There is no production database or hosted API server. YAML/TOML configs, parquet metadata, image artifacts, and static JSON manifests are the contracts between stages.

AssetURL / PathPurpose
Public demo sitehttps://justin21523.github.io/3d-animation-lora-pipeline/Static product-style demo for interview review
Portfolio case studyhttps://justin21523.github.io/zh-TW/projects/3d-animation-lora-pipeline/Main portfolio page with card, screenshots, recording, and links
Local static siteportfolio-web/index.htmlSource for the published demo
Demo manifestportfolio-web/demo-data/manifest.jsonFrontend data contract for stages, scenarios, metrics, and media
Demo screenshotsportfolio-web/assets/screenshots/Review-ready screenshots
Demo videoportfolio-web/assets/video/demo-walkthrough.mp4Short walkthrough recording

Quick Reviewer Path

pip install -r requirements/core.txt -r requirements/dev.txt
python scripts/demo/run_demo_pipeline.py --skip-pipeline
python -m pytest tests/demo -q
python -m http.server 8080 -d portfolio-web

Open http://localhost:8080.

If you want the full demo-safe smoke suite:

./tests/run_tests.sh

What This Project Demonstrates

AreaWhat is shownWhy it matters in an interview
ML data engineeringStage outputs are tracked as files and metadataShows reproducible data contracts, not only model prompting
Computer vision pipelineExtraction, tracking, segmentation, pose, embeddingsShows multi-stage CV orchestration and artifact handling
Training readinessLoRA/ControlNet dataset assembly and training adaptersShows awareness of real training constraints
Demo safetyCPU-safe stub mode and synthetic public assetsLets reviewers run it without private media, GPUs, or secrets
Product presentationStatic site, screenshots, video, portfolio integrationMakes the work understandable in a few minutes
DeploymentGitHub Pages, Docker/Nginx static hostingShows practical release packaging

Product Demo Flow

flowchart LR
    A[Reviewer opens portfolio card] --> B[Project detail page]
    B --> C[Demo video]
    B --> D[Screenshot gallery]
    B --> E[GitHub README]
    B --> F[Live demo site]
    F --> G[Pipeline status]
    F --> H[Product-style results]
    F --> I[Runbook commands]

The first screen of the demo shows the product itself: stage readiness, metrics, and result artifacts. It is not just a marketing landing page.

System Architecture

flowchart TB
    subgraph Inputs
        V[Animation footage]
        C[Project YAML/TOML configs]
        M[Local model warehouse]
    end

    subgraph Pipeline_Core[Python pipeline core]
        O[Stage orchestrator]
        R[Resource monitor]
        S[Stage manager]
        IO[Metadata IO]
    end

    subgraph CV_Stages[Computer vision stages]
        F[Frame extraction]
        D[Detection and tracking]
        G[Segmentation and matting]
        P[Pose extraction]
        E[Embedding index]
    end

    subgraph Training_Stages[Training and evaluation stages]
        LD[LoRA dataset builder]
        CD[ControlNet dataset builder]
        T[LoRA / ControlNet training adapters]
        Q[Evaluation and sample generation]
    end

    subgraph Demo_Layer[Public demo layer]
        Stub[CPU-safe stub artifacts]
        Manifest[Static manifest JSON]
        Site[portfolio-web static site]
        Media[Screenshots and video]
    end

    V --> F
    C --> O
    M --> D
    O --> R
    O --> S
    S --> IO
    F --> D --> G --> P --> E --> LD --> CD --> T --> Q
    IO --> Manifest
    Stub --> Manifest
    Manifest --> Site
    Media --> Site

Data Contracts

flowchart LR
    Frames[frames.parquet] --> Detections[detections.parquet]
    Detections --> Foreground[fg.parquet + RGBA cutouts]
    Foreground --> Pose[poses.parquet + pose images]
    Foreground --> Embeddings[embeddings.parquet]
    Pose --> ControlNet[controlnet metadata]
    Embeddings --> LoRA[LoRA dataset metadata]
    LoRA --> Samples[inference sample metadata]
    ControlNet --> Samples
    Samples --> Animation[animation frames metadata]
    Animation --> Manifest[portfolio manifest JSON]

Each stage produces inspectable metadata. The public demo reads the final manifest and renders the same stage model in the browser.

Pipeline Stage Matrix

OrderStageOutput contractDemo-safe?Real workflow dependency
1Frame extractionmetadata/frames.parquet, frame imagesYesVideo decoder, scene detection
2Perceptual dedupemetadata/frames_dedupe.parquetYesCPU image hashing
3Detection and trackingmetadata/detections.parquetYesYOLO/ByteTrack or stub detections
4Foreground/background splitmetadata/fg.parquet, RGBA cutoutsYesSAM/ToonOut/LaMa-style components
5Pose conditioningmetadata/poses.parquet, pose previewsYesMediaPipe/DWPose for real data
6Embedding indexmetadata/embeddings.parquetYesCLIP embeddings for real data
7LoRA datasetlora_datasets/.../metadata.parquetYesCaptioning and dataset assembly
8ControlNet datasetcontrolnet_datasets/.../metadata.parquetYesPose-conditioned dataset assembly
9Inference samplesoutputs/inference/metadata.parquetYesDiffusers/ComfyUI for real samples
10Animation exportoutputs/animation/.../metadata.parquetYesUpscale/interpolation stack for real video

Repository Map

.
|-- anime_pipeline/          Packaged 2D/3D pipeline library
|-- scripts/
|   |-- core/pipeline/       Main staged orchestrator CLI
|   |-- demo/                Demo manifest and public asset generator
|   |-- generic/             Reusable video, segmentation, clustering, quality, training tools
|   |-- batch/               Long-running generation/training automation
|   |-- training/            Training launchers, monitoring, checkpoint evaluation
|   `-- monitoring/          Progress and health scripts
|-- configs/                 Global, stage, project, batch, training, and evaluation configs
|-- portfolio-web/           Public static demo website
|-- docker/                  Nginx static hosting package
|-- tests/                   Demo-safe smoke tests and focused pytest suites
|-- requirements/            Modular dependency sets
`-- .github/workflows/       GitHub Pages demo deployment workflow

Frontend, Backend, Data, API, Deployment

LayerImplementationCurrent status
FrontendStatic HTML/CSS/JS in portfolio-web/Works locally and on GitHub Pages
BackendPython CLI and file-based pipeline stagesNo persistent service; demo-safe CLI works
DatabaseNoneParquet/CSV/JSON files are the data contracts
APINone for public demoStatic manifest is fetched by the browser
Real ML runtimeLocal GPU workstation workflowRequires model/data warehouse and compatible CUDA stack
Demo runtimeCPU-safe manifest and synthetic assetsWorks without private data, GPU, external APIs, or model weights
DeploymentGitHub Pages via gh-pages, plus Docker/NginxVerified public demo and media URLs

Demo Scenarios

ScenarioWhat to showTime
Fast portfolio reviewOpen portfolio page, play demo video, inspect gallery1-2 minutes
Product demoOpen public demo site, scroll Results -> Pipeline -> Media3-5 minutes
Engineering reviewRun demo manifest generator and tests locally5 minutes
Architecture reviewWalk through README diagrams and stage matrix5-10 minutes
Deployment reviewShow GitHub Pages workflow and Docker image build3 minutes

Local Demo Commands

Install only the demo-safe dependencies:

pip install -r requirements/core.txt -r requirements/dev.txt

Refresh the manifest from existing CPU-safe outputs:

python scripts/demo/run_demo_pipeline.py --skip-pipeline

Run the complete stub pipeline first, then refresh the manifest:

bash bash/run_full_pipeline_stub.sh
python scripts/demo/run_demo_pipeline.py --skip-pipeline

Serve the static demo:

python -m http.server 8080 -d portfolio-web

Build and serve through Docker:

docker build -f docker/portfolio.Dockerfile -t 3d-animation-lora-pipeline-demo .
docker run --rm -p 8080:80 3d-animation-lora-pipeline-demo

Testing and Verification

CheckCommandExpected result
Demo manifest unit testspython -m pytest tests/demo -q3 passing tests
Demo-safe smoke suite./tests/run_tests.shFocused CPU-safe suite passes
Static Docker builddocker build -f docker/portfolio.Dockerfile -t 3d-animation-lora-pipeline-demo .Nginx image builds
Static asset HTTP checkcurl -I http://localhost:8080/assets/video/demo-walkthrough.mp4200 OK
Pipeline config/status smokepython -m scripts.core.pipeline status --project <project_id>Stage list renders without crash

Full python -m pytest tests/ can require GPU/model dependency alignment because several tests touch heavy diffusers, inpainting, training, or local model paths.

Real Model Workflow

Real model runs are workstation jobs, not hosted website jobs.

pip install -r requirements/all.txt
bash scripts/setup/install_pipeline_dependencies.sh
python -m scripts.core.pipeline validate --project <project_id>
python -m scripts.core.pipeline run --project <project_id> --device cuda

Common requirements:

RequirementWhy it is needed
CUDA-capable GPUReal segmentation, embeddings, diffusion inference, training
Local model warehouseAvoids committing model weights
Dataset warehouseStores raw media and generated artifacts outside git
Optional OPENAI_API_KEY or LLM_VENDOR_API_KEYOnly for API-based captioning/refinement workflows
Optional ComfyUIVisual workflow comparison and generation tests

Deployment Architecture

flowchart LR
    Dev[Local repo] --> Commit[Commit demo assets]
    Commit --> Main[origin/main]
    Main --> Action[Deploy Demo Site workflow]
    Action --> GH[gh-pages branch]
    GH --> Pages[GitHub Pages]
    Pages --> Public[Public demo URL]

    Commit --> Docker[Docker/Nginx image]
    Docker --> Local[Local static preview]

The ML pipeline does not run on GitHub Pages. Pages only hosts portfolio-web/, static screenshots, demo video, and the generated manifest.

Current Status

AreaStatusEvidence
Demo-safe pipelineWorkingpython scripts/demo/run_demo_pipeline.py --skip-pipeline
Demo testsWorkingpython -m pytest tests/demo -q
Smoke suiteWorking./tests/run_tests.sh
Static siteWorkingportfolio-web/ local server and public Pages URL
Docker static packageWorkingdocker/portfolio.Dockerfile builds
Portfolio integrationWorkingMain portfolio page links to demo, screenshots, video, GitHub, README
Real GPU trainingEnvironment-dependentRequires local model/data warehouse and CUDA stack
Hosted backend/APINot applicableThe public demo is intentionally static

Known Risks and Limits

RiskImpactMitigation
Real model workflows depend on local GPU and model pathsCannot be fully reproduced on CPU-only machinesPublic demo uses deterministic mock-safe artifacts
Large generated media/checkpoints are intentionally untrackedReviewers cannot inspect private raw training dataREADME and demo show anonymized stage outputs
Some scripts are research/batch orientedFull repo contains more tools than the interview path needsDemo-safe commands and docs define the stable path
Optional API captioning needs secretsPublic CI cannot call external captioning APIsAPI paths are not required for demo-safe tests
GitHub Pages is static onlyCannot run real ML inference in browserReal pipeline remains CLI/workstation; static demo shows results

Interview Highlights

Reviewers should focus on:

  • File-based ML data contracts through parquet/JSON manifests.
  • Config-driven orchestration rather than hard-coded one-off scripts.
  • Clear separation between real GPU workflows and CPU-safe public demo mode.
  • Public demo assets that are safe to screenshot, record, and publish.
  • Docker and GitHub Pages deployment packaging for a static technical showcase.
  • Main portfolio integration with cover, gallery, video, demo link, GitHub, and README.

Privacy and Data Notes

Raw media, model weights, generated datasets, checkpoints, logs, and secrets are intentionally excluded from git. Public assets in portfolio-web/ are synthetic/anonymized demonstration files intended for portfolio review.

Contributors

Justin21523

31 commits

Justin21523/3d-animation-lora-pipeline

A full pipeline for 3D character processing — including video-to-frames extraction, layered segmentation, CLIP/HDBSCAN clustering, pose/view sub-clustering, schema-guided VLM captioning, dataset assembly, LoRA training, automated checkpoint evaluation, and batch video synthesis.

1

stars

31

commits

Python

primary language

Jun 28, 2026

updated

README

3D Animation LoRA Pipeline

Portfolio-ready data engineering and training pipeline for animation LoRA datasets. The project turns animation footage into staged artifacts: frames, detections, foreground/background cutouts, pose records, embeddings, LoRA-ready datasets, inference samples, animation frames, and a public demo layer that is safe to show without private media or model weights.

The repository is intentionally file-driven. There is no production database or hosted API server. YAML/TOML configs, parquet metadata, image artifacts, and static JSON manifests are the contracts between stages.

AssetURL / PathPurpose
Public demo sitehttps://justin21523.github.io/3d-animation-lora-pipeline/Static product-style demo for interview review
Portfolio case studyhttps://justin21523.github.io/zh-TW/projects/3d-animation-lora-pipeline/Main portfolio page with card, screenshots, recording, and links
Local static siteportfolio-web/index.htmlSource for the published demo
Demo manifestportfolio-web/demo-data/manifest.jsonFrontend data contract for stages, scenarios, metrics, and media
Demo screenshotsportfolio-web/assets/screenshots/Review-ready screenshots
Demo videoportfolio-web/assets/video/demo-walkthrough.mp4Short walkthrough recording

Quick Reviewer Path

pip install -r requirements/core.txt -r requirements/dev.txt
python scripts/demo/run_demo_pipeline.py --skip-pipeline
python -m pytest tests/demo -q
python -m http.server 8080 -d portfolio-web

Open http://localhost:8080.

If you want the full demo-safe smoke suite:

./tests/run_tests.sh

What This Project Demonstrates

AreaWhat is shownWhy it matters in an interview
ML data engineeringStage outputs are tracked as files and metadataShows reproducible data contracts, not only model prompting
Computer vision pipelineExtraction, tracking, segmentation, pose, embeddingsShows multi-stage CV orchestration and artifact handling
Training readinessLoRA/ControlNet dataset assembly and training adaptersShows awareness of real training constraints
Demo safetyCPU-safe stub mode and synthetic public assetsLets reviewers run it without private media, GPUs, or secrets
Product presentationStatic site, screenshots, video, portfolio integrationMakes the work understandable in a few minutes
DeploymentGitHub Pages, Docker/Nginx static hostingShows practical release packaging

Product Demo Flow

flowchart LR
    A[Reviewer opens portfolio card] --> B[Project detail page]
    B --> C[Demo video]
    B --> D[Screenshot gallery]
    B --> E[GitHub README]
    B --> F[Live demo site]
    F --> G[Pipeline status]
    F --> H[Product-style results]
    F --> I[Runbook commands]

The first screen of the demo shows the product itself: stage readiness, metrics, and result artifacts. It is not just a marketing landing page.

System Architecture

flowchart TB
    subgraph Inputs
        V[Animation footage]
        C[Project YAML/TOML configs]
        M[Local model warehouse]
    end

    subgraph Pipeline_Core[Python pipeline core]
        O[Stage orchestrator]
        R[Resource monitor]
        S[Stage manager]
        IO[Metadata IO]
    end

    subgraph CV_Stages[Computer vision stages]
        F[Frame extraction]
        D[Detection and tracking]
        G[Segmentation and matting]
        P[Pose extraction]
        E[Embedding index]
    end

    subgraph Training_Stages[Training and evaluation stages]
        LD[LoRA dataset builder]
        CD[ControlNet dataset builder]
        T[LoRA / ControlNet training adapters]
        Q[Evaluation and sample generation]
    end

    subgraph Demo_Layer[Public demo layer]
        Stub[CPU-safe stub artifacts]
        Manifest[Static manifest JSON]
        Site[portfolio-web static site]
        Media[Screenshots and video]
    end

    V --> F
    C --> O
    M --> D
    O --> R
    O --> S
    S --> IO
    F --> D --> G --> P --> E --> LD --> CD --> T --> Q
    IO --> Manifest
    Stub --> Manifest
    Manifest --> Site
    Media --> Site

Data Contracts

flowchart LR
    Frames[frames.parquet] --> Detections[detections.parquet]
    Detections --> Foreground[fg.parquet + RGBA cutouts]
    Foreground --> Pose[poses.parquet + pose images]
    Foreground --> Embeddings[embeddings.parquet]
    Pose --> ControlNet[controlnet metadata]
    Embeddings --> LoRA[LoRA dataset metadata]
    LoRA --> Samples[inference sample metadata]
    ControlNet --> Samples
    Samples --> Animation[animation frames metadata]
    Animation --> Manifest[portfolio manifest JSON]

Each stage produces inspectable metadata. The public demo reads the final manifest and renders the same stage model in the browser.

Pipeline Stage Matrix

OrderStageOutput contractDemo-safe?Real workflow dependency
1Frame extractionmetadata/frames.parquet, frame imagesYesVideo decoder, scene detection
2Perceptual dedupemetadata/frames_dedupe.parquetYesCPU image hashing
3Detection and trackingmetadata/detections.parquetYesYOLO/ByteTrack or stub detections
4Foreground/background splitmetadata/fg.parquet, RGBA cutoutsYesSAM/ToonOut/LaMa-style components
5Pose conditioningmetadata/poses.parquet, pose previewsYesMediaPipe/DWPose for real data
6Embedding indexmetadata/embeddings.parquetYesCLIP embeddings for real data
7LoRA datasetlora_datasets/.../metadata.parquetYesCaptioning and dataset assembly
8ControlNet datasetcontrolnet_datasets/.../metadata.parquetYesPose-conditioned dataset assembly
9Inference samplesoutputs/inference/metadata.parquetYesDiffusers/ComfyUI for real samples
10Animation exportoutputs/animation/.../metadata.parquetYesUpscale/interpolation stack for real video

Repository Map

.
|-- anime_pipeline/          Packaged 2D/3D pipeline library
|-- scripts/
|   |-- core/pipeline/       Main staged orchestrator CLI
|   |-- demo/                Demo manifest and public asset generator
|   |-- generic/             Reusable video, segmentation, clustering, quality, training tools
|   |-- batch/               Long-running generation/training automation
|   |-- training/            Training launchers, monitoring, checkpoint evaluation
|   `-- monitoring/          Progress and health scripts
|-- configs/                 Global, stage, project, batch, training, and evaluation configs
|-- portfolio-web/           Public static demo website
|-- docker/                  Nginx static hosting package
|-- tests/                   Demo-safe smoke tests and focused pytest suites
|-- requirements/            Modular dependency sets
`-- .github/workflows/       GitHub Pages demo deployment workflow

Frontend, Backend, Data, API, Deployment

LayerImplementationCurrent status
FrontendStatic HTML/CSS/JS in portfolio-web/Works locally and on GitHub Pages
BackendPython CLI and file-based pipeline stagesNo persistent service; demo-safe CLI works
DatabaseNoneParquet/CSV/JSON files are the data contracts
APINone for public demoStatic manifest is fetched by the browser
Real ML runtimeLocal GPU workstation workflowRequires model/data warehouse and compatible CUDA stack
Demo runtimeCPU-safe manifest and synthetic assetsWorks without private data, GPU, external APIs, or model weights
DeploymentGitHub Pages via gh-pages, plus Docker/NginxVerified public demo and media URLs

Demo Scenarios

ScenarioWhat to showTime
Fast portfolio reviewOpen portfolio page, play demo video, inspect gallery1-2 minutes
Product demoOpen public demo site, scroll Results -> Pipeline -> Media3-5 minutes
Engineering reviewRun demo manifest generator and tests locally5 minutes
Architecture reviewWalk through README diagrams and stage matrix5-10 minutes
Deployment reviewShow GitHub Pages workflow and Docker image build3 minutes

Local Demo Commands

Install only the demo-safe dependencies:

pip install -r requirements/core.txt -r requirements/dev.txt

Refresh the manifest from existing CPU-safe outputs:

python scripts/demo/run_demo_pipeline.py --skip-pipeline

Run the complete stub pipeline first, then refresh the manifest:

bash bash/run_full_pipeline_stub.sh
python scripts/demo/run_demo_pipeline.py --skip-pipeline

Serve the static demo:

python -m http.server 8080 -d portfolio-web

Build and serve through Docker:

docker build -f docker/portfolio.Dockerfile -t 3d-animation-lora-pipeline-demo .
docker run --rm -p 8080:80 3d-animation-lora-pipeline-demo

Testing and Verification

CheckCommandExpected result
Demo manifest unit testspython -m pytest tests/demo -q3 passing tests
Demo-safe smoke suite./tests/run_tests.shFocused CPU-safe suite passes
Static Docker builddocker build -f docker/portfolio.Dockerfile -t 3d-animation-lora-pipeline-demo .Nginx image builds
Static asset HTTP checkcurl -I http://localhost:8080/assets/video/demo-walkthrough.mp4200 OK
Pipeline config/status smokepython -m scripts.core.pipeline status --project <project_id>Stage list renders without crash

Full python -m pytest tests/ can require GPU/model dependency alignment because several tests touch heavy diffusers, inpainting, training, or local model paths.

Real Model Workflow

Real model runs are workstation jobs, not hosted website jobs.

pip install -r requirements/all.txt
bash scripts/setup/install_pipeline_dependencies.sh
python -m scripts.core.pipeline validate --project <project_id>
python -m scripts.core.pipeline run --project <project_id> --device cuda

Common requirements:

RequirementWhy it is needed
CUDA-capable GPUReal segmentation, embeddings, diffusion inference, training
Local model warehouseAvoids committing model weights
Dataset warehouseStores raw media and generated artifacts outside git
Optional OPENAI_API_KEY or LLM_VENDOR_API_KEYOnly for API-based captioning/refinement workflows
Optional ComfyUIVisual workflow comparison and generation tests

Deployment Architecture

flowchart LR
    Dev[Local repo] --> Commit[Commit demo assets]
    Commit --> Main[origin/main]
    Main --> Action[Deploy Demo Site workflow]
    Action --> GH[gh-pages branch]
    GH --> Pages[GitHub Pages]
    Pages --> Public[Public demo URL]

    Commit --> Docker[Docker/Nginx image]
    Docker --> Local[Local static preview]

The ML pipeline does not run on GitHub Pages. Pages only hosts portfolio-web/, static screenshots, demo video, and the generated manifest.

Current Status

AreaStatusEvidence
Demo-safe pipelineWorkingpython scripts/demo/run_demo_pipeline.py --skip-pipeline
Demo testsWorkingpython -m pytest tests/demo -q
Smoke suiteWorking./tests/run_tests.sh
Static siteWorkingportfolio-web/ local server and public Pages URL
Docker static packageWorkingdocker/portfolio.Dockerfile builds
Portfolio integrationWorkingMain portfolio page links to demo, screenshots, video, GitHub, README
Real GPU trainingEnvironment-dependentRequires local model/data warehouse and CUDA stack
Hosted backend/APINot applicableThe public demo is intentionally static

Known Risks and Limits

RiskImpactMitigation
Real model workflows depend on local GPU and model pathsCannot be fully reproduced on CPU-only machinesPublic demo uses deterministic mock-safe artifacts
Large generated media/checkpoints are intentionally untrackedReviewers cannot inspect private raw training dataREADME and demo show anonymized stage outputs
Some scripts are research/batch orientedFull repo contains more tools than the interview path needsDemo-safe commands and docs define the stable path
Optional API captioning needs secretsPublic CI cannot call external captioning APIsAPI paths are not required for demo-safe tests
GitHub Pages is static onlyCannot run real ML inference in browserReal pipeline remains CLI/workstation; static demo shows results

Interview Highlights

Reviewers should focus on:

  • File-based ML data contracts through parquet/JSON manifests.
  • Config-driven orchestration rather than hard-coded one-off scripts.
  • Clear separation between real GPU workflows and CPU-safe public demo mode.
  • Public demo assets that are safe to screenshot, record, and publish.
  • Docker and GitHub Pages deployment packaging for a static technical showcase.
  • Main portfolio integration with cover, gallery, video, demo link, GitHub, and README.

Privacy and Data Notes

Raw media, model weights, generated datasets, checkpoints, logs, and secrets are intentionally excluded from git. Public assets in portfolio-web/ are synthetic/anonymized demonstration files intended for portfolio review.

Contributors

Justin21523

31 commits

Languages

Python

85.3%

Shell

14.1%