Skip to content

3D Matrix Analysis and Web Viewer

github-actions[bot] edited this page Oct 9, 2026 · 7 revisions

3D Matrix Analysis & Web Viewer

The cmat3d_webviewer.py server and companion cmat3d_webviewer.html client provide an ultra-responsive, browser-based spectroscopy environment for navigating 3D matrix cubes in the .cmat format (e.g. $\gamma$-$\gamma$-$\text{Rings}$, $\gamma$-$\gamma$-$\Delta t$, or 3-fold coincidence volumes).

The viewer couples pure Python/NumPy memory-mapped slice decompression with a high-performance HTML5 Canvas frontend featuring real-time multi-plane orthoslicing, 3D multi-gate coincidence cuts, 2D graphical Banana ROI gates, 1D/2D photopeak fitting, and publication-quality vector PDF export.

Live ROOT TH3 support: The 3D viewer can also display live TH3 histograms from ROOT THttpServer / c4Root (or saved JSON snapshots), with orthogonal-plane gating, double gates, polygon cuts, and time-spectra half-life fits. See ROOT Files & Live Histograms.

Development status: The large symmetric 3D .cmat viewing workflow is preliminary and under active development. Sparse slicing, synchronized navigation, fitting workflows, and performance should be treated as experimental while validation and optimization continue.


Launching the 3D Web Viewer

Run the 3D viewer from your terminal, pointing to a 3D .cmat file:

# Open a 3D matrix cube
python cmat3d_webviewer.py /path/to/matrix3d.cmat

# Open multiple 3D matrices for rapid cycling
python cmat3d_webviewer.py cube1.cmat cube2.cmat

The server binds to http://0.0.0.0:8080 (or the configured host/port) and automatically opens your default web browser.


Architectural Overview

+-----------------------------------------------------------------------------------+
|                              cmat3d_webviewer.py                                  |
|                                                                                   |
|  +---------------------------+   +---------------------------------------------+  |
|  | CMAT3DReader (cmat3d.py)  |   | ThreadingHTTPServer                         |  |
|  | - IVF Slice Decompressor  |   | - Multi-threaded concurrent request handler |  |
|  | - .cmat3d_cache/ (memmap) |   | - /api/tile (Subregion Slicing: ~1.6 ms)    |  |
|  | - 3D Coincidence Slicing  |   | - /api/projection_region (~2.1 ms)          |  |
|  | - 2D Banana Polygon ROI   |   | - /api/gate_1d (Coincidence Cuts)           |  |
|  +---------------------------+   +---------------------------------------------+  |
+------------------------------------------^----------------------------------------+
                                           | HTTP / REST (JSON + Binary ArrayBuffers)
+------------------------------------------v----------------------------------------+
|                             cmat3d_webviewer.html                                 |
|                                                                                   |
|  +---------------------------+   +---------------------------------------------+  |
|  | 2D Density Heatmap Canvas |   | Triple 1D Spectroscopic Displays            |  |
|  | - Turbo/Viridis LUT Engine|   | - Det 1 (Axis 0) Stepped Histogram          |  |
|  | - Multi-Plane: 0-1, 0-2, 1-2 | - Det 2 (Axis 1) Stepped Histogram          |  |
|  | - 2D Banana ROI (a/b) |   | - Rings / dt (Axis 2) Stepped Histogram     |  |
|  | - AbortController Sync   |   | - 60 FPS Client-Side Instant Channel Zoom   |  |
|  +---------------------------+   +---------------------------------------------+  |
+-----------------------------------------------------------------------------------+

Key Features & Capabilities

1. Dual 3D Storage Engines: Memory-Mapped & Sparse On-Demand (.cmat3d_cache/)

3D .cmat files vary from moderately-sized asymmetric cubes to massive symmetric volumes:

  • Dense Memory-Mapped Volume Engine (.dat): For moderately sized cubes (e.g. $4096 \times 4096 \times 128 = 2.14 \times 10^9$ channels, $\approx 8.5\text{ GB}$ uncompressed), CMAT3DReader decompresses slices on first access into a binary cache file (.cmat3d_cache/<filename>.dat) accessed as a numpy.memmap (int32), providing instant subsequent startup ($&lt;50\text{ ms}$).
  • Sparse On-Demand Indexing Engine (.idx): For massive symmetric 3-fold cubes (e.g. $8192 \times 8192 \times 8192 = 5.5 \times 10^{11}$ channels, which would require $\approx 2.2\text{ TB}$ uncompressed), building a full dense file is impossible. python-cmat automatically enables a sparse on-demand reader:
    • Generates a compact index file (.cmat3d_cache/<filename>.idx) in $&lt;0.5\text{ seconds}$ without uncompressing unread data.
    • Slices, orthogonal planes, and coincidence cuts are extracted on-the-fly directly from the compressed IVF segment streams in $&lt;10\text{ ms}$.

2. Exact Intra-Block Tetrahedral Symmetrization

In folded 3D symmetric matrices (matmode == 1, $N_x = N_y = N_z$):

  • Block coordinates are folded into the lower tetrahedron $s_1 \le s_2 \le s_3$.
  • In addition, sub-block channels along diagonal planes are folded internally:
    • Plane $s_1 = s_2 &lt; s_3$: internal channels folded into $dx \le dy$.
    • Plane $s_1 &lt; s_2 = s_3$: internal channels folded into $dy \le dz$.
    • Main diagonal $s_1 = s_2 = s_3$: internal channels folded into $dx \le dy \le dz$ (1/6th sub-tetrahedron).
  • CMAT3DReader performs vectorized sub-tetrahedral unfolding across all 6 permutations, eliminating dark triangular artifacts along diagonal boundaries and guaranteeing exact spatial symmetry ($|M - M^T| = 0$).

3. Multi-Plane Orthogonal Projections

The viewer allows real-time orthogonal slicing across any pair of dimensions:

  • Plane 0-1 (Det 1 vs Det 2): Standard $4096 \times 4096$ $\gamma$-$\gamma$ coincidence matrix, sliced over the visible/gated 3rd axis.
  • Plane 0-2 (Det 1 vs Rings): $4096 \times 128$ matrix showing $\gamma$-ray energy on Det 1 as a function of detector ring or timing.
  • Plane 1-2 (Det 2 vs Rings): $4096 \times 128$ matrix showing $\gamma$-ray energy on Det 2 as a function of detector ring or timing.

Switch planes instantly using the header toolbar buttons (0-1, 0-2, 1-2) or dropdown menu.

4. Real-Time Subregion Slicing & Accelerated Navigation

  • Pre-Sum Slicing: When zooming into a 2D region $[x_0..x_1, y_0..y_1]$, the backend extracts only the required subvolume from the 3D memory map before summing over the 3rd axis, accelerating tile generation from $76.5\text{ ms}$ to $1.6\text{ ms}$ (~48× speedup).
  • Multi-Threaded Server: ThreadingHTTPServer processes simultaneous tile requests, 1D projections, and peak fits on separate background threads without blocking.
  • Debounced 2D Synchronization: 1D wheel zooming and channel panning update the stepped histograms client-side at 60 FPS ($&lt;1\text{ ms}$), while heavy 2D tile rendering is debounced (45 ms) with AbortController cancellation to prevent network congestion.

3D Coincidence Gating & Banana ROIs

Multi-Gate Coincidence Slicing ($W$ & $X$)

Set coincidence gates directly on any 1D spectrum:

  1. Hover cursor over a 1D spectrum panel (Det 1, Det 2, or Rings).
  2. Press W at the lower gate limit, move cursor to the upper limit, and press W again to confirm.
  3. (Optional) Press X or B twice to define background subtraction windows.
  4. The remaining spectra and 2D projections immediately update to display the background-subtracted coincidence cut:

$$\text{Net Spec}(i) = \text{Raw Gate}(i) - \frac{\sum \text{Peak Gate Widths}}{\sum \text{BG Gate Widths}} \times \text{BG Spec}(i)$$

  • Press Z or click Clear Gate to reset active coincidence gates.

2D Banana Gate Polygon Engine (a / b)

For particle-gamma identification, ring discrimination, or kinematic curve gating:

  1. Draw Peak Banana (W): Press a with the cursor over the active 2D plane to start drawing and add the first Peak ROI vertex (rendered in gold #ffd600); each further a press adds a vertex at the cursor position. The polygon auto-closes (the last vertex connects back to the first) — press Enter to apply the gate. Mouse click-drag zoom/pan keeps working while drawing.

  2. Draw Background Banana (B): Press b with the cursor over the 2D plane to add Background ROI vertices (rendered in magenta #ff4081) the same way; press Enter to apply.

  3. Area-Normalized Subtraction: The viewer computes the continuous geometric surface area (in $\text{ch}^2$ via the Shoelace algorithm) and discrete rasterized pixel count ($\text{px}$) for both polygons. It normalizes background subtraction by the ratio of their surface areas:

    $$\text{scale} = \frac{\text{Area}{\text{peak}}}{\text{Area}{\text{bg}}}$$ $$\text{Net Spec}(i) = \text{Spec}{\text{peak}}(i) - \text{scale} \times \text{Spec}{\text{bg}}(i)$$

  4. Terminal Reporting: Applying a banana gate prints a detailed diagnostic report in the terminal listing surface areas (in pixels and $\text{ch}^2$), count totals inside each banana, the scale factor, and net counts:

    [2D Banana Gate Cut] matrix.cmat -> Plane 0-1 => Axis 3 (Z):
      • Peak Banana (W): 400 px (area: 400.0 ch², 4 vertices) | Counts: 228,000 cts
      • Bg Banana (B):   200 px (area: 200.0 ch², 4 vertices) | Counts: 64,000 cts (Scale factor: 2.0000)
      • Net Area Counts: 100,000.0 counts
    
  5. Clear Gate: Click Clear Bananas or press Z to remove active banana gates.

  6. Banana Save Files (.ban): Banana gates are saved to the server working directory only on demand: click Save Banana in the sidebar or press w with the cursor over the 2D plane. Files are named p<E1>-<E2>.ban (peak) / b<E1>-<E2>.ban (background), where E1/E2 are the calibrated centroid coordinates of the polygon on the plane's two axes as integers (channels if uncalibrated). Each file lists one x y vertex pair per line in channel coordinates. To keep peak/background pairs associated, a background file reuses the coordinates of the most recently saved peak: the first background saved after a peak becomes b<E1>-<E2>.ban, and each additional one gets an incrementing index (b<E1>-<E2>_2.ban, _3.ban, …) until a new peak is saved. If no peak has been saved yet, the background file uses its own centroid coordinates.

  7. Result Logging: When fit results logging is enabled (fit_log on [filename]), the gross peak area and the net background-subtracted area (± error) of every applied banana gate are appended to the results .txt file, tagged with the active plane.

  8. Matrix Cycling: Banana gates are re-applied automatically when the active matrix is switched ([ / ]) with unchanged dimensions: the diagnostic report is re-printed for the new matrix and the 1D gated spectrum is refreshed. If the new matrix has different dimensions, the bananas are cleared with a notification.

  9. Per-Plane Memory: Each plane (0-1, 0-2, 1-2) remembers its own banana gates and in-progress drawings. Switching planes (Tab or the plane buttons) restores that plane's banana and re-applies it automatically; Z clears only the active plane's banana.

  10. Manual Save / Load: The sidebar Save Banana button saves the drawn polygon(s) as p<E1>-<E2>.ban / b<E1>-<E2>.ban (same format as the automatic save) in the server working directory. The Load Banana button opens a server-side file browser (starting in the server working directory) to pick a .ban file from the server filesystem: the type is auto-recognized from the header (Peak / Background) and the polygon is placed on the active plane and applied immediately.


1D & 2D Peak Fitting and Gamba 3D Coincidence Slicing

The 3D viewer incorporates the complete spectroscopy peak fitting engine:

  • Gaussian: Standard symmetric photopeak with linear/quadratic continuum baseline.

  • RadWare / SAMPO: Photopeak with an exponential low-energy tail ($A, \eta$) for high-energy HPGe detectors.

  • Hypermet EMG: Convolved Gaussian exponential tail plus complementary error function ($\text{erfc}$) Compton step.

  • Multiplet Fits: Place multiple candidate markers with P or click fit region with R, then press V to perform multi-component non-linear least-squares fitting.

  • 2D Peak Fit & Gamba 3D Slicing (Ctrl+Click or V in 2D): Ctrl+Click on any 2D orthogonal plane fits the 2D coincidence peak and decomposes the local region into the Gamba 4-component model. In 3D matrices, this automatically computes and displays the net 3rd-axis coincidence spectrum:

    $$\text{Spec}{\text{net}}(z) = \text{Spec}{P|P}(z) - \frac{N_{P|BG}}{N_{BG|BG \text{ (1)}}} \text{Spec}{BG|P}(z) - \frac{N{BG|P}}{N_{BG|BG \text{ (2)}}} \text{Spec}{P|BG}(z) + \frac{N{BG|BG \text{ (corr)}}}{N_{BG|BG \text{ (all)}}} \text{Spec}_{BG|BG}(z)$$

    This opens an interactive modal window displaying the net 3rd-axis projection (e.g. lifetime distribution or ring spectrum) with peak areas, background breakdown, 1D fitting tools, and vector PDF export.


Configuration (python-cmat3d-config.txt)

The 3D viewer automatically reads and writes configuration files in the working directory:

[General]
port = 8080
colormap = turbo
scale_mode_2d = log
scroll_sensitivity = 0.04
active_plane = 0-1
proj_range_mode = synced

[Axes]
axis0_name = Det 1
axis1_name = Det 2
axis2_name = Rings

[Calibration]
axis0_cal = 0.0, 0.5, 0.0
axis1_cal = 0.0, 0.5, 0.0
axis2_cal = 0.0, 1.0, 0.0

[PeakFit]
fit_type = gaussian
fwhm_est = 4.0
fwhm_mult = 4.0

Python Library API (cmat3d.py)

You can also use cmat3d.py programmatically in Python scripts and Jupyter notebooks:

from cmat3d import CMAT3DReader

# 1. Open 3D .cmat file
reader = CMAT3DReader("GeE-Rings3D.cmat")
print(f"Matrix shape: {reader.shape}")  # e.g. (4096, 4096, 128)
print(f"Total counts: {reader.total_counts:,}")

# 2. Access memory-mapped 3D volume
mmap_vol = reader.to_memmap()  # Shape: (4096, 4096, 128), dtype: int32

# 3. Extract 2D orthogonal plane projection
# Plane options: '0-1' (Det 1 vs 2), '0-2' (Det 1 vs Rings), '1-2' (Det 2 vs Rings)
plane_01 = reader.get_2d_plane("0-1")
plane_gated = reader.get_2d_plane("0-1", gate_3rd=[[10, 12]]) # Gated on Ring 10-12

# 4. Extract 1D total projection
proj_det1 = reader.get_projection_1d(axis=0)

# 5. Coincidence Gating
net_spec, bg_spec = reader.get_gate_1d(
    target_axis=0,
    w_gates={2: [[11, 11]]},        # Gate on Ring 11
    b_gates={2: [[1, 2], [20, 21]]} # Background rings
)

# 6. 2D Banana Gate Polygon (Peak & Background Subtraction)
poly_peak = [[1000, 10], [1500, 12], [1480, 15], [980, 13]] # [[x, y], ...]
poly_bg   = [[900, 10], [950, 12], [940, 15], [890, 13]]
banana_res = reader.get_banana_gate(
    plane="0-2",
    polygon_peak=poly_peak,
    polygon_bg=poly_bg
)
print(f"Peak: {banana_res['pixel_count_peak']} px, Bg: {banana_res['pixel_count_bg']} px (scale: {banana_res['scale']:.4f})")
print(f"Net Gated Counts: {banana_res['total_gated_counts']:,}")

Clone this wiki locally