XPCS Viewer Dependency Diagram

Legend

┌─────────┐
│ Module  │  Regular module
└─────────┘

┏━━━━━━━━━┓
┃ Module  ┃  High fan-in (integration hotspot)
┗━━━━━━━━━┛

╔═════════╗
║ Module  ║  High fan-out (brittle dependencies)
╚═════════╝

───────>    Dependency (A depends on B)
========>   Critical I/O boundary
- - - ->    Signal-based coupling (loose)

Layer 1: Backend Abstraction (Foundation)

                    ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓
                    ┃ backends._conversions   ┃  (FAN-IN: 9)
                    ┃ - ensure_numpy()        ┃
                    ┃ - ensure_backend_array()┃
                    ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛
                              ▲
                              │
                    ┏━━━━━━━━━┻━━━━━━━━━┓
                    ┃ backends          ┃  (FAN-IN: 8)
                    ┃ - get_backend()   ┃
                    ┃ - set_backend()   ┃
                    ┗━━━━━━━━━━━━━━━━━━━┛
                        ▲         ▲
            ┌───────────┴─────────┴────────────┐
            │                                  │
  ┌──────────────────┐              ┌──────────────────┐
  │ _numpy_backend   │              │ _jax_backend     │
  │ - NumPy impl     │              │ - JAX impl       │
  │                  │              │ - JIT support    │
  └──────────────────┘              └──────────────────┘
            │                                  │
            └──────────────┬───────────────────┘
                           │
                  ┌────────┴─────────┐
                  │ io_adapter.py    │
                  │ - PyQtGraph      │
                  │ - HDF5           │
                  │ - Matplotlib     │
                  └───────────────────┘

Layer 2: Data Access & I/O

┏━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ utils.logging_config   ┃  (FAN-IN: 24) - Used everywhere
┗━━━━━━━━━━━━━━━━━━━━━━━━┛

┌─────────────────────────────────────────────────────────┐
│                    HDF5 I/O Layer                       │
└─────────────────────────────────────────────────────────┘

       ┌──────────────────┐
       │ fileIO.aps_8idi  │  Beamline key mapping
       │ - key["g2"]      │
       │ - key["saxs1d"]  │
       └────────┬─────────┘
                │
                ▼
   ┏━━━━━━━━━━━━━━━━━━━━━┓
   ┃ fileIO.hdf_reader   ┃  (FAN-IN: 2)
   ┃ - HDF5ConnectionPool┃  Connection pooling
   ┃ - get(), put()      ┃
   ┗━━━━━━━━━┳━━━━━━━━━━━┛
             │
             │ Uses
             ▼
   ┌──────────────────────┐
   │ fileIO.qmap_utils    │
   │ - QMap class         │
   │ - get_qmap()         │
   └──────────────────────┘

Layer 3: Core Data Model

                    ╔═════════════════════╗
                    ║ xpcs_file.py        ║  (FAN-IN: 2, FAN-OUT: 3)
                    ║ - XpcsFile class    ║  ** GOD OBJECT **
                    ║ - 39 public methods ║
                    ╚═══════════╤═════════╝
                                │
                ┌───────────────┼───────────────┐
                │               │               │
                ▼               ▼               ▼
   ┌─────────────────┐ ┌────────────────┐ ┌─────────────────┐
   │ xpcs_file/cache │ │ xpcs_file/     │ │ xpcs_file/      │
   │ - DataCache     │ │ memory         │ │ fitting         │
   │ - CacheItem     │ │ - MemoryMonitor│ │ - legacy funcs  │
   └─────────────────┘ └────────────────┘ └─────────────────┘
                │               │
                └───────┬───────┘
                        │ Used by
                        ▼
            ┌─────────────────────┐
            │ viewer_kernel.py    │  (FAN-IN: 1)
            │ - ViewerKernel      │  Coordinates analysis
            │ - Lazy module load  │
            └──────────┬──────────┘
                       │
                       │ Loads analysis modules on demand
                       ▼

Layer 4: Analysis Modules (Consumer Layer)

┌────────────────────────────────────────────────────────────┐
│                     Analysis Modules                        │
│  (All depend on: utils.logging_config, backends/_conversions)│
└────────────────────────────────────────────────────────────┘

        ┌──────────────┐        ╔══════════════════╗
        │ module.g2mod │        ║ module.twotime   ║  (FAN-OUT: 4)
        │ - G2 plots   │        ║ - Two-time corr  ║  ** BRITTLE **
        └──────────────┘        ╚══════════════════╝
                │                       │
                │                       ├───> backends
                │                       ├───> backends._conversions
                │                       ├───> backends._conversions
                │                       └───> utils.logging_config
                │
        ┌───────┴────────┐
        │                │
        ▼                ▼
┌──────────────┐  ┌──────────────┐
│ module.      │  │ module.      │
│ saxs1d       │  │ saxs2d       │
│ - 1D SAXS    │  │ - 2D SAXS    │
└──────────────┘  └──────────────┘
        │                │
        └────────┬───────┘
                 │ Both use
                 ▼
        ┌──────────────────┐
        │ backends.        │
        │ _conversions     │
        │ - ensure_numpy() │  ** I/O BOUNDARY **
        └────────┬─────────┘
                 │
                 ▼
        [ PyQtGraph Plots ]

Layer 5: SimpleMask Subsystem (Loosely Coupled)

┌─────────────────────────────────────────────────────────┐
│              SimpleMask (Self-Contained)                 │
└─────────────────────────────────────────────────────────┘

    ┌─────────────────────────┐
    │ simplemask_window.py    │  QMainWindow
    │ - Signals:              │
    │   * mask_exported       │ ─ ─ ─ ─> [ XPCS Viewer ]
    │   * qmap_exported       │ ─ ─ ─ ─> [ XPCS Viewer ]
    └────────────┬────────────┘
                 │ Owns
                 ▼
    ╔═══════════════════════════╗
    ║ simplemask_kernel.py      ║  (FAN-IN: 2, FAN-OUT: 4)
    ║ - SimpleMaskKernel        ║
    ║ - compute_qmap()          ║
    ║ - compute_partition()     ║
    ╚═══════════╤═══════════════╝
                │
        ┌───────┼────────┬───────────┐
        │       │        │           │
        ▼       ▼        ▼           ▼
  ┌──────┐ ┏━━━━━┓ ┏━━━━━━━┓ ┌───────────┐
  │area_ │ ┃qmap ┃ ┃utils  ┃ │pyqtgraph_ │
  │mask  │ ┃     ┃ ┃       ┃ │mod        │
  └──┬───┘ ┗━━┳━━┛ ┗━━━┳━━━┛ └─────┬─────┘
     │        │        │           │
     │        │        │           │ (FAN-IN: 3)
     └────────┴────────┴───────────┘
              │ All use
              ▼
     ┏━━━━━━━━━━━━━━━━━┓
     ┃ backends        ┃
     ┃ backends.       ┃
     ┃ _conversions    ┃
     ┗━━━━━━━━━━━━━━━━━┛

Layer 6: Fitting Module (JAX-Dependent)

┌─────────────────────────────────────────────────────────┐
│                  Fitting Subsystem                       │
│  (Requires JAX backend for NumPyro)                     │
└─────────────────────────────────────────────────────────┘

    ┌──────────────────────┐
    │ fitting/__init__.py  │
    │ - fit_single_exp()   │
    │ - fit_double_exp()   │
    │ - nlsq_fit()         │
    └──────────┬───────────┘
               │
       ┌───────┼────────┐
       │       │        │
       ▼       ▼        ▼
  ┌────────┐ ┌─────┐ ┌────────┐
  │models  │ │nlsq │ │sampler │
  │- NumPyro│ │- JAX│ │- NUTS  │
  └────────┘ └──┬──┘ └────┬───┘
                │         │
                └────┬────┘
                     │ Requires
                     ▼
            ┏━━━━━━━━━━━━━┓
            ┃ backends    ┃  MUST be JAX backend
            ┃ - value_and_┃  (raises error otherwise)
            ┃   grad()    ┃
            ┗━━━━━━━━━━━━━┛
                     │
                     │ Cross-dependency
                     ▼
       ┌─────────────────────────┐
       │ simplemask.calibration  │
       │ - minimize_with_grad()  │
       │ - beam center fitting   │
       └─────────────────────────┘

Critical I/O Boundaries (Conversion Points)

┌──────────────────────────────────────────────────────────┐
│        I/O Boundary Conversion (ensure_numpy)             │
└──────────────────────────────────────────────────────────┘

        [ JAX/NumPy Arrays ]
                 │
                 │ backends._conversions.ensure_numpy()
                 │
        ┌────────┴────────┬────────────┬───────────┐
        │                 │            │           │
        ▼                 ▼            ▼           ▼
┌─────────────┐  ┌─────────────┐  ┌─────────┐  ┌─────────┐
│ PyQtGraph   │  │ HDF5        │  │Matplotlib│  │Signals  │
│ plot.setData│  │ h5.create_  │  │plt.plot()│  │.emit()  │
│ (np.ndarray)│  │ dataset     │  │          │  │         │
└─────────────┘  └─────────────┘  └─────────┘  └─────────┘

** 9 modules use ensure_numpy() at these boundaries **

Modules with I/O conversions:
  - module.saxs1d  (PyQtGraph)
  - module.saxs2d  (PyQtGraph)
  - module.twotime (PyQtGraph + HDF5)
  - simplemask.qmap (Signals)
  - simplemask.area_mask (HDF5)
  - simplemask.utils (Partition export)
  - fitting.visualization (Matplotlib)
  - (2 more in tests/utils)

Data Flow: XPCS Analysis Pipeline

┌──────────┐
│ HDF5 File│
│ /xpcs/   │
└────┬─────┘
     │ fileIO.hdf_reader.get()
     ▼
┌─────────────────────┐
│ XpcsFile            │ NumPy arrays (from HDF5)
│ - metadata          │
│ - qmap (dict)       │
│ - g2 data           │
└──────┬──────────────┘
       │
       │ viewer_kernel.plot_g2()
       ▼
┌─────────────────────┐
│ module.g2mod        │
│ - convert to backend│ <- ensure_backend_array()
│ - compute with JAX  │
│ - fit_g2()          │
└──────┬──────────────┘
       │
       │ fitting.fit_single_exp()
       ▼
┌─────────────────────┐
│ fitting.sampler     │ JAX arrays (computation)
│ - NLSQ warm-start   │
│ - NumPyro NUTS      │
└──────┬──────────────┘
       │
       │ FitResult.get_mean()
       ▼
┌─────────────────────┐
│ visualization       │
│ - ensure_numpy()    │ <- Convert back to NumPy
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ Matplotlib Plot     │ NumPy arrays (display)
│ - Posterior plot    │
│ - Trace plot        │
└─────────────────────┘

Data Flow: SimpleMask Integration

┌──────────────────┐
│ User Loads Image │
│ (NumPy array)    │
└────────┬─────────┘
         │
         ▼
┌─────────────────────────┐
│ SimpleMaskWindow        │
│ - User draws ROIs       │
│ - Edits mask            │
└────────┬────────────────┘
         │
         │ kernel.compute_qmap()
         ▼
┌─────────────────────────┐
│ simplemask.qmap         │
│ - Get backend           │ <- get_backend() (JAX or NumPy)
│ - JIT compile if JAX    │
│ - Compute Q-map         │
└────────┬────────────────┘
         │
         │ kernel.compute_partition()
         ▼
┌─────────────────────────┐
│ simplemask.utils        │
│ - generate_partition()  │ <- JIT compiled for JAX
│ - ensure_numpy()        │ <- Convert for export
└────────┬────────────────┘
         │
         │ window.export_to_viewer()
         ▼
┌─────────────────────────┐
│ Signal: qmap_exported   │ NumPy arrays + dict
│ {                       │
│   "partition_map": np,  │
│   "val_list": list,     │
│   "num_list": list      │
│ }                       │
└────────┬────────────────┘
         │ - - - - - (Signal, loose coupling)
         ▼
┌─────────────────────────┐
│ XPCS Viewer             │
│ - apply_qmap_result()   │
│ - Update analysis       │
└─────────────────────────┘

Circular Dependency Check (None Found ✅)

Analysis of key modules:

xpcs_file.py
  ├─> xpcs_file.cache
  ├─> xpcs_file.memory
  └─> xpcs_file.fitting
      └─> (no xpcs_file import) ✅

viewer_kernel.py
  ├─> xpcs_file
  └─> module.* (lazy loaded)
      └─> (no viewer_kernel import) ✅

backends
  └─> (no internal dependencies) ✅

fitting
  ├─> backends
  └─> simplemask.calibration
      ├─> backends
      └─> (no fitting import) ✅

module.twotime
  ├─> backends
  ├─> backends._conversions
  ├─> backends._conversions
  └─> xpcs_file (for MemoryMonitor only)
      └─> (no module.twotime import) ✅

simplemask.simplemask_kernel
  ├─> simplemask.area_mask
  ├─> simplemask.qmap
  ├─> simplemask.utils
  └─> simplemask.pyqtgraph_mod
      └─> (no simplemask_kernel import) ✅

Conclusion: Clean dependency tree, no cycles detected.

Proposed Architecture After Facade Migration

┌─────────────────────────────────────────────────────────┐
│                   NEW: Facade Layer                      │
└─────────────────────────────────────────────────────────┘

    ┌──────────────────────┐
    │ io.hdf5_facade       │  ** NEW **
    │ - read_qmap()        │  Schema validation
    │ - write_partition()  │  Versioning
    │ - get_connection()   │  Connection pooling
    └──────────┬───────────┘
               │
               │ Uses
               ▼
    ┌──────────────────────┐
    │ repositories.        │  ** NEW **
    │ xpcs_repository      │  Repository pattern
    │ - get_g2_data()      │
    │ - save_fit_result()  │
    └──────────┬───────────┘
               │
               │ Used by
               ▼
    ┌──────────────────────┐
    │ services.            │  ** NEW **
    │ g2_analysis          │  Service layer
    │ - analyze_g2()       │  (extracted from XpcsFile)
    │ - fit_correlation()  │
    └──────────────────────┘


┌─────────────────────────────────────────────────────────┐
│              NEW: Backend I/O Adapters                   │
└─────────────────────────────────────────────────────────┘

    ┌──────────────────────┐
    │ backends.io_adapter  │  ** NEW **
    │ - PyQtGraphAdapter   │  Centralized conversions
    │ - HDF5Adapter        │
    │ - MatplotlibAdapter  │
    └──────────────────────┘


BENEFITS:
  - Single source of truth for I/O patterns
  - Schema validation at boundaries
  - Easy to test with mocks
  - Performance monitoring in one place
  - Consistent error handling

Anti-Patterns Identified

❌ God Object: xpcs_file.py
   - 39 public methods
   - Knows about fitting, SAXS, G2, two-time, tau-Q
   - Violates Single Responsibility Principle

   FIX: Extract to service layer
   ✅ services/g2_analysis.py
   ✅ services/saxs_analysis.py
   ✅ services/twotime_analysis.py


❌ Scattered I/O Conversions
   - 9 modules use ensure_numpy() directly
   - No centralized adapter pattern
   - Hard to audit I/O boundaries

   FIX: Backend I/O adapters
   ✅ backends/io_adapter.py


❌ Implicit Data Contracts
   - QMapDict passed as plain dict
   - No runtime validation
   - Typos cause silent failures

   FIX: Schema validators
   ✅ schemas/validators.py
   ✅ QMapSchema dataclass with validation


✅ Good Pattern: Signal-Based Integration
   - SimpleMask uses signals for export
   - Loose coupling, testable
   - KEEP THIS PATTERN for other integrations

Complexity Metrics

Module Complexity (by Fan-In × Fan-Out):

High Risk (refactor priority):
  1. backends._conversions:  9 × 0 = 9  (high fan-in, stable)
  2. backends:               8 × 0 = 8  (high fan-in, stable)
  3. module.twotime:         0 × 4 = 4  (high fan-out, brittle)

Medium Risk:
  4. simplemask_kernel:      2 × 4 = 8  (balanced)
  5. xpcs_file:              2 × 3 = 6  (god object, needs refactor)

Low Risk:
  6. utils.logging_config:  24 × 0 = 0  (utility, stable)
  7. module.saxs1d:          0 × 2 = 2  (low coupling)
  8. module.saxs2d:          0 × 2 = 2  (low coupling)

Document Version: 1.1 Generated: 2026-01-06 (updated 2026-02-18) Maintained: Sync with dependency_analysis.md