Architecture Review: VKIST Ultrasound Codebase

Identified deepening opportunities to improve testability and AI-navigability.

Candidate: Refactor app.py into modular services

Priority: Strong

Files Affected: app.py Proposed: inference_pipeline.py, model_loader.py, preprocessor.py, postprocessor.py

The Problem

The main application file (app.py) is acting as a "god object". It manages web framework setup, model loading, inference pipelines, preprocessing, postprocessing, utilities, and API endpoints simultaneously. This creates **low architectural depth** (the interface is nearly as complex as its implementation), **poor testability** (isolated pipeline units cannot be targeted), and **low locality** (unrelated logic must be parsed to fix individual features).

Proposed Solution

Split operational concerns into highly focused, cleanly encapsulated modules:

  • ModelLoader: Handles asynchronous loading and persistent caching of PyTorch weights (angle, inflammation, segmentation models).
  • Preprocessor: Isolates structural image transformations and CLAHE enhancement parameters.
  • InferencePipeline: Orchestrates execution flows: angle classification inflammation detection conditional segmentation sizing/severity metrics.
  • Postprocessor: Houses structural thickness evaluations, severity metrics calculations, and visual overlay generators.
  • APIHandler: A thin, declarative FastAPI endpoint wrapper delegating exclusively to the primary InferencePipeline.

Expected Architecture Benefits

  • Architectural Depth High complexity is hidden beneath simple interfaces (e.g., analyze_image(img) -> Result).
  • High Code Locality Bugs in sizing algorithms map cleanly to the postprocessor without risking model caching logic.
  • Simplified Testing Individual pipeline states and weights can be unit-tested seamlessly using mock behaviors.
  • AI and Dev Navigation Highly categorized file trees accelerate context gathering for language models and onboarding engineers.

Before (Shallow Architecture)

graph TD A[FastAPI Endpoint] --> B[load_angle_model] A --> C[load_inflammation_model] A --> D[load_segmentation_model_sup] A --> E[load_segmentation_model_post] A --> F[predict_angle] A --> G[predict_inflammation] A --> H[segment_image] A --> I[measure_thickness_new] A --> J[analyze_inflammation_severity] A --> K[create_segmentation_overlay] A --> L[apply_clahe] A --> M[save_patient_data] style A fill:#f87171,stroke:#ef4444,stroke-width:2px,color:#fff style B fill:#fee2e2,stroke:#f87171 style C fill:#fee2e2,stroke:#f87171 style D fill:#fee2e2,stroke:#f87171 style E fill:#fee2e2,stroke:#f87171 style F fill:#fee2e2,stroke:#f87171 style G fill:#fee2e2,stroke:#f87171 style H fill:#fee2e2,stroke:#f87171 style I fill:#fee2e2,stroke:#f87171 style J fill:#fee2e2,stroke:#f87171 style K fill:#fee2e2,stroke:#f87171 style L fill:#fee2e2,stroke:#f87171 style M fill:#fee2e2,stroke:#f87171

After (Deepened Architecture)

graph TD subgraph API [API Gateway Router] A[FastAPI Endpoint] --> B[InferencePipeline.analyze] end subgraph InferencePipeline [Core Workflow Orchestrator] B --> C[ModelLoader.get_angle_model] B --> D[Preprocess] B --> E[predict_angle] E --> F{Inflammation?} F -->|Yes| G[ModelLoader.get_inflammation_model] G --> H[predict_inflammation] H --> I{Inflammation Detected?} I -->|Yes| J[ModelLoader.get_seg_model_sup/post] J --> K[segment_image] K --> L[measure_thickness] K --> M[analyze_severity] L --> N[Result: Measurement] M --> O[Result: Severity] K --> P[Result: Segmentation Masks] E -->|No| Q[Result: Angle Only] F -->|No| Q end subgraph ModelLoader [Model Assets Lifecycle] C --> C1[Load Angle Model] G --> G1[Load Inflammation Model] J --> J1[Load Segmentation Model] end subgraph Preprocessor [Image Engineering] D --> D1[Transforms] D --> D2[CLAHE] end subgraph Postprocessor [Metrics & Artifact Generation] L --> L1[Thickness Calc] M --> M1[Severity Scoring] end style A fill:#3b82f6,stroke:#2563eb,stroke-width:2px,color:#fff style B fill:#dbeafe,stroke:#60a5fa style C fill:#dbeafe,stroke:#60a5fa style D fill:#dbeafe,stroke:#60a5fa style E fill:#dbeafe,stroke:#60a5fa style F fill:#dbeafe,stroke:#60a5fa style G fill:#dbeafe,stroke:#60a5fa style H fill:#dbeafe,stroke:#60a5fa style I fill:#dbeafe,stroke:#60a5fa style J fill:#dbeafe,stroke:#60a5fa style K fill:#dbeafe,stroke:#60a5fa style L fill:#dbeafe,stroke:#60a5fa style M fill:#dbeafe,stroke:#60a5fa style N fill:#e0f2fe,stroke:#0284c7 style O fill:#e0f2fe,stroke:#0284c7 style P fill:#e0f2fe,stroke:#0284c7 style Q fill:#e0f2fe,stroke:#0284c7 style C1 fill:#f3e8ff,stroke:#a855f7 style G1 fill:#f3e8ff,stroke:#a855f7 style J1 fill:#f3e8ff,stroke:#a855f7 style D1 fill:#fef3c7,stroke:#d97706 style D2 fill:#fef3c7,stroke:#d97706 style L1 fill:#ecfccb,stroke:#65a30d style M1 fill:#ecfccb,stroke:#65a30d