正在載入留言。
正在確認登入狀態。
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗
MCX-CL is the OpenCL version of Monte Carlo eXtreme (MCX), a GPU-accelerated Monte Carlo simulator of light transport in 3D turbid media such as biological tissues. Where MCX needs an NVIDIA (CUDA) or AMD (HIP) GPU, MCX-CL runs on almost any processor with an OpenCL driver: NVIDIA, AMD and Intel GPUs, integrated graphics, Apple silicon, and multi-core CPUs. It reads the same input files, accepts the same MATLAB/Python inputs and writes the same outputs as MCX, and for a given seed it traces the same photon paths.
MCX-CL can be used in four ways:
| Mode | What it is | Best for |
|---|---|---|
mcxcl command line | a standalone executable reading a JSON input file | batch jobs, clusters, scripting, reproducible inputs |
| MCXLAB-CL (MATLAB/Octave) | mcxlabcl(cfg), a mex function with in-memory inputs/outputs | interactive analysis, image reconstruction, plotting |
| PMCXCL (Python) | pmcxcl.run(cfg), a Python module installed with pip | Python/NumPy workflows, notebooks, macOS |
| MCX Studio (GUI) | a graphical editor and viewer that runs mcx, mcxcl and mmc | learning MC, designing and inspecting simulations |
MCX-CL v2026 (3.0) is a milestone release that brings MCX-CL to near feature parity with CUDA-based MCX, and makes its ray-tracing results verifiable step-by-step against MCX. Notable features ported to MCX-CL in this release include
cfg.outputtype='adjoint', including
the adjoint_dcoeff/mus/musp and dual Jacobian outputs, saving both forward
and Jacobian in the adjoint mode--lang).jdt/.jdb detected photon and trajectory files
with separate srcid recordsmake fixed accumulates the output as 64-bit fixed-point
integers instead of a float with a second float buffer (MCX v2026); the sums
do not depend on the order of the additions, so a run with -A 0 is
bit-reproducible. Each sum is a pair of 32-bit words, low and high words in
separate planes, so only 32-bit atomics are needed and a deposit touches
4 bytes per element, as the float buffers do. The speed is about that of the
default build: 1.06x on an RTX 4090, 1.01x on an A100, 0.99x on a TITAN V and
0.97x on a CPU (POCL), from 0.89x to 1.44x over 13 built-in examplesIn addition, this release contains the following fixes and improvements
-cl-unsafe-math-optimizations; -A 0
keeps the static photon split and strict math for reproducible trajectories-U 2, time-gate clamping,
SVMC mus division, ns time-of-flight accumulation, double-buffering)-O r, -O s) output; RF and rfmus replay now
agree with MCX (relative L2 4e-5 and 2e-3)-O A/D/U/V/W/Q) on the command line and a
segfault reading JSON detectors with a Dir field; adjoint detector sources
are now launched as disks so that detdir(4) shapes the detector beammcx_clearfluencetest/testtraj.sh) compare MCX-CL
against the MCX golden trajectories, and run, together with testmcx.sh,
on a CPU OpenCL device (POCL) in the CI with -A 0 -t <photons> -T 1Optode.Source.IQUV) is read from JSON
input, and Stokes trajectories (--trajstokes 1) are sized and saved correctly--maxvoidstep and --trajstokes are accepted on the command line as in MCXrand(), which differs
between Linux, macOS and Windows, so a fixed seed simulates the same photons on every
OS (the random streams for a given seed change, replay of saved seeds is unaffected)We would like to thank @hanno-gmg (#60) and @ptrhbt (#67) for reporting issues.
The most important changes since v2025.10 are summarized below; ChangeLog.txt
lists all of them, with their commit hashes.
Features ported from MCX
asgn_float) media format--lang).jdt/.jdb, with separate srcid recordsmake fixed: exact, order-independent 64-bit fixed-point output sums, as in MCX--maxvoidstep and --trajstokes; [c06cdf0] issave2pt, flog and lang in
mcxlabcl/pmcxcl, as in mcxlab/pmcxPerformance
Bug fixes
-O r, -O s) output-O A/D/U/V/W/Q) on the command line; detector JSON with Dir-U 2 skips only the detected-weight normalization in replay; [b5d3b0a] double
preprocessing with -jcl_ext_float_atomics kernels built as OpenCL C 3.0, fixing Intel Arc GPUs-r repeats or splits the photons as documented; [25ea487] a time step of 0 is one gate.jnii/.bnii filesSession.OutputType names read in full, not just their first letter--dumpjson, mcx2json and json2mcx round-trip all settingsunitinmm twice when cfg.unitinmm was set before
cfg.vol, and labelplus scaled mus/g instead of mua/musgpuid=int32(1) failed), logical volumes and column
vectors; pmcxcl accepts debuglevel='T'cfg.flog also controls the echo of the cfg fields: 'none' silences everythingcfg.flog='none' and -l none silence the log on every OS ('NUL' and '/dev/null' are accepted too; 'NUL' wrote a file on Linux, #280)Testing and robustness
test/fuzz/dfuzz.py and the built-in inputs of MCX in test/builtin; six more rounds
of fuzzing found five bugs shared with MCX, fixed in [7c1fcba]Packaging
pmcxcl.mcxlab runs with pmcxclMonte Carlo eXtreme simulates how light propagates in 3D heterogeneous, scattering media by tracing a large number of photon packets through a voxelated domain. Each packet is attenuated by absorption, randomly redirected by scattering, and reflected or refracted at interfaces between media; accumulating their weights gives a statistically exact solution of the radiative transfer equation (RTE), the model behind diffuse optics, fluorescence imaging, photobiomodulation dosimetry and many other biophotonics applications. Tracing the photons in parallel on a GPU makes this hundreds to thousands of times faster than a single-threaded CPU code.
MCX-CL implements the MCX algorithm in OpenCL [Yu2018], so the same simulation runs on GPUs and CPUs from any vendor. Since v2026, MCX-CL supports nearly all MCX features and traces the same photon paths as MCX for the same seed; the differences are listed in MCX-CL and MCX: differences.
What MCX-CL can do
The MCX family
| Software | Description |
|---|---|
| MCX | voxel-based MC in CUDA (NVIDIA) or HIP (AMD); MCXLAB, PMCX and MCX Studio |
| MCX-CL | this package: the OpenCL version of MCX; MCXLAB-CL (mcxlabcl) and PMCXCL (pmcxcl) |
| MMC | mesh-based MC on tetrahedral meshes, on CPUs, OpenCL, CUDA and OptiX; MMCLAB and PMMC |
| MCX Cloud | run MCX and MMC in a web browser on cloud GPUs |
| Redbird / Redbirdpy | diffuse optical tomography solvers that can use MCX/MMC as the forward model |
Scripts move between MCX and MCX-CL by changing mcxlab to mcxlabcl, or pmcx to
pmcxcl; in MATLAB, mcxlab(cfg, 'opencl') also runs MCX-CL.
MCX-CL needs an OpenCL runtime, the part of a graphics or CPU driver that runs
OpenCL programs (OpenCL.dll on Windows, libOpenCL.so on Linux, built into macOS).
No SDK or compiler is needed: MCX-CL compiles its kernel for your device when it
starts.
| Processor | OpenCL runtime |
|---|---|
| NVIDIA GPU | included in the NVIDIA driver; CUDA is not needed |
| AMD GPU | included in the AMD Adrenalin driver (Windows); the ROCm OpenCL runtime (Linux) |
| Intel GPU (integrated or Arc) | included in the Intel graphics driver (Windows); intel-opencl-icd (Linux) |
| Apple silicon / Intel Mac | built into macOS |
| CPU | POCL (pocl-opencl-icd on Debian/Ubuntu, works on any x86_64 or ARM CPU), or Intel's CPU runtime |
On Linux, an ICD loader (ocl-icd-libopencl1 on Debian/Ubuntu) dispatches OpenCL
calls to the installed runtimes, and clinfo lists the devices they provide. Several
runtimes can be installed together, for example a GPU driver and POCL.
As a rough guide, high-end dedicated GPUs are 20-60x faster than a multi-core CPU, and integrated GPUs 3-4x; see https://mcx.space/computebench/ for measured speeds.
| Package | How to get it |
|---|---|
| MCX Studio all-in-one package (mcx, mcxcl, mmc, the GUI and the MATLAB/Octave toolboxes) | https://mcx.space/wiki/?Get or the MCX releases; on Windows also the installer or winget install coti.mcxstudio |
| mcxcl command line or MCXLAB-CL alone | https://mcx.space/wiki/?Get or https://github.com/fangq/mcxcl/releases |
| PMCXCL (Python) | python3 -m pip install pmcxcl pmcx |
| nightly builds | https://mcx.space/nightly/ |
| source code | git clone https://github.com/fangq/mcxcl.git |
PMCXCL wheels are built for Linux (x86_64 and aarch64), macOS (arm64 and x86_64) and
Windows; pmcx provides the utility functions PMCXCL uses. The released Linux binaries
and mex files run on glibc 2.17 or newer; the Octave mex files for Windows and macOS
need Octave 10 or newer.
The all-in-one package unzips into one folder:
MCXStudio/
├── mcxstudio the GUI (MCXStudio.app on macOS)
├── mcxsuite_addpath.m adds all the MATLAB/Octave toolboxes to the path
├── MATLAB/ mcxlab, mcxlabcl, mmclab, iso2mesh and redbird
└── MCXSuite/ mcx, mcxcl and mmc, each with its bin/ folder and helper scripts
Add MCXSuite/mcxcl/bin (and mcx/bin, mmc/bin) to your PATH. In MATLAB or
Octave, cd into the MCXStudio folder, run mcxsuite_addpath, and savepath to keep
the paths for later sessions. On macOS, unzip the package under your home folder.
Unzip the package, add its bin/ folder to your PATH, and run
mcxcl -L # list the OpenCL devices
mcxcl -Q cube60 # run the "cube60" benchmark on the first device
mcxcl -L prints each platform (runtime) and its devices, numbered from 1 across all
platforms:
Platform [0] Name NVIDIA CUDA
============ GPU device ID 0 [1 of 2]: NVIDIA TITAN V ============
Device 1 of 2: NVIDIA TITAN V
Compute units : 80 core(s)
Global memory : 12625903616 B
...
Auto-thread : 327680
Auto-block : 64
Use the device number with -G: -G 2 runs on the 2nd device, and -G 0110 on the
2nd and 3rd together. If no device is listed, the OpenCL runtime is missing or broken:
install or update the driver (or POCL for a CPU) and check with clinfo. Without
clinfo, you can check that the runtime library exists:
OpenCL.dll in C:\Windows\System32;/System/Library/Frameworks/OpenCL.framework exists;ldconfig -p | grep libOpenCL lists libOpenCL.so.1; if the library is in
a non-standard folder, add that folder to LD_LIBRARY_PATH, and check that the
vendor's .icd file is in /etc/OpenCL/vendors.Intel CPUs older than Ivy Bridge (3rd generation Core, 2012) are not well supported by Intel's OpenCL runtimes; POCL is an alternative for them.
addpath('/path/to/mcxlabcl'); % mcxcl.mex*, mcxlabcl.m
addpath('/path/to/mcxcl/utils'); % helper functions
info = mcxlabcl('gpuinfo') % list the devices
python3 -m pip install pmcxcl pmcx jdata bjdata matplotlib
python3 -c "import pmcxcl; print(pmcxcl.gpuinfo())"
The MATLAB helpers in mcxcl/utils are a subset of those of MCX; for the full set
(mcxpreview, mcxplotshapes, mcxsvmc, ...), also add MCX's utils folder, which
works with MCXLAB-CL's outputs.
Windows: Windows resets a GPU that is busy for more than a few seconds. The MCX
Studio installer raises this limit; with the zip packages, right-click
setup/win64/apply_timeout_registry_fix.bat, choose Run as administrator, and
reboot.
Linux laptops with hybrid NVIDIA graphics: if the screen freezes when MCX-CL runs,
use the NVIDIA GPU as the primary GPU (sudo prime-select nvidia). If simulations fail
after the laptop wakes up from suspension, close MATLAB/Python and run
sudo rmmod nvidia-uvm; sudo modprobe nvidia-uvm.
macOS: Apple's OpenCL runs MCX-CL on Apple silicon and Intel Macs; PMCXCL is the
way to run simulations from Python on a Mac. MCX Studio is provided as a universal
MCXStudio.app.
git clone https://github.com/fangq/mcxcl.git
cd mcxcl/src
make # the mcxcl binary in ../bin
make mex # MCXLAB-CL for MATLAB: ../mcxlabcl/mcxcl.mex*
make oct # MCXLAB-CL for Octave: ../mcxlabcl/mcxcl.mex
make unittest # host unit tests
make fixed # exact 64-bit fixed-point output sums (-DUSE_FIXEDPOINT)
cd ../pmcxcl && python3 -m pip install . # PMCXCL
Building needs a C/C++ compiler and the OpenCL headers and loader (opencl-headers
and ocl-icd-opencl-dev on Debian/Ubuntu); make oct needs liboctave-dev, and the
Python module needs cmake. The kernel source, src/mcx_core.cl, is embedded in the
binary.
The same simulation - a pencil beam entering a 60x60x60 mm homogeneous cube with four detectors - in each mode.
Command line. Save this as qtest.json:
{
"Domain": {
"Dim": [60, 60, 60],
"OriginType": 1,
"LengthUnit": 1,
"Media": [
{"mua": 0.00, "mus": 0.0, "g": 1.00, "n": 1.0},
{"mua": 0.005, "mus": 1.0, "g": 0.01, "n": 1.37}
]
},
"Shapes": [
{"Grid": {"Tag": 1, "Size": [60, 60, 60]}}
],
"Session": {
"Photons": 1000000,
"RNGSeed": 29012392,
"ID": "qtest"
},
"Forward": {
"T0": 0.0e+00,
"T1": 5.0e-09,
"Dt": 5.0e-10
},
"Optode": {
"Source": {
"Type": "pencil",
"Pos": [29.0, 29.0, 0.0],
"Dir": [0.0, 0.0, 1.0]
},
"Detector": [
{"Pos": [29.0, 19.0, 0.0], "R": 1.0},
{"Pos": [29.0, 39.0, 0.0], "R": 1.0},
{"Pos": [19.0, 29.0, 0.0], "R": 1.0},
{"Pos": [39.0, 29.0, 0.0], "R": 1.0}
]
}
}
and run
mcxcl -f qtest.json -G 1
MCX-CL writes qtest.jnii (the fluence rate, 60x60x60 voxels x 10 time gates) and
qtest_detp.jdt (the detected photons).
MATLAB/Octave:
cfg.nphoton = 1e6;
cfg.vol = uint8(ones(60, 60, 60));
cfg.prop = [0 0 1 1; 0.005 1 0.01 1.37]; % [mua(1/mm), mus(1/mm), g, n] per label
cfg.issrcfrom0 = 1;
cfg.srcpos = [29 29 0];
cfg.srcdir = [0 0 1];
cfg.detpos = [29 19 0 1; 29 39 0 1; 19 29 0 1; 39 29 0 1];
cfg.tstart = 0; cfg.tend = 5e-9; cfg.tstep = 5e-10;
cfg.gpuid = 1; % device number from mcxlabcl('gpuinfo')
[flux, detp] = mcxlabcl(cfg);
imagesc(log10(squeeze(sum(flux.data(:, 30, :, :), 4)))); % CW fluence rate at y=30
Python:
import numpy as np, pmcxcl, matplotlib.pyplot as plt
cfg = {
'nphoton': 1000000,
'vol': np.ones([60, 60, 60], dtype='uint8'),
'prop': [[0, 0, 1, 1], [0.005, 1, 0.01, 1.37]],
'issrcfrom0': 1,
'srcpos': [29, 29, 0], 'srcdir': [0, 0, 1],
'detpos': [[29, 19, 0, 1], [29, 39, 0, 1], [19, 29, 0, 1], [39, 29, 0, 1]],
'tstart': 0, 'tend': 5e-9, 'tstep': 5e-10,
'gpuid': 1,
}
res = pmcxcl.mcxlab(cfg)
plt.imshow(np.log10(res['flux'][:, 29, :, :].sum(axis=2)))
plt.show()
GUI: start mcxstudio, open qtest.json, choose the intel, amd or cpu
processor (which run mcxcl), and press Run.
Grid and units. The domain is a grid of Nx*Ny*Nz cubic voxels of edge length
LengthUnit (JSON), -u (command line) or cfg.unitinmm (MATLAB/Python), in mm.
Positions of sources and detectors are given in voxel units. With
OriginType: 1 (-z 1, cfg.issrcfrom0=1), the corner of the first voxel is at
[0,0,0]; otherwise (the default in the command line and MCXLAB-CL) it is at [1,1,1],
matching MATLAB's 1-based indices. Absorption (mua) and scattering (mus)
coefficients are in 1/mm regardless of the voxel size.
Media. Each voxel holds a medium label. The optical properties of label i are
the (i+1)-th row of Media (cfg.prop): [mua, mus, g, n], with mus the scattering
(not reduced scattering) coefficient, mus=mus'/(1-g). Label 0 is the background
(outside of the domain): a photon moving from a non-zero voxel into a 0 voxel
leaves the domain. Instead of labels, a voxel can also store its own optical
properties, see Volume and media formats.
Time gates. A simulation covers the time window [T0, T1], divided into gates of width Dt; the output has one volume per gate. Photons are tracked until T1, so for a CW solution choose T1 long enough for the light to decay (a few ns in tissue), and sum the gates.
Photons and statistics. Results are averages over the simulated photon
packets; their noise falls as 1/sqrt(number of photons). 10^6-10^8 photons are
typical. Russian roulette (-e) terminates packets of low weight without biasing
the result.
Normalization. By default the output is normalized to a unit source, so MCX
produces the Green's function of the fluence rate (1/(mm^2 s), or W/mm^2 per watt
of source power), directly comparable with analytical solutions. -U 0
(cfg.isnormalized=0) returns the raw accumulated weights.
Boundaries. At the domain boundary, photons are reflected according to the
Fresnel equations when the refractive indices differ (-b 1, the default), or
leave the domain (-b 0). -B/cfg.bc sets each face separately, including
mirror and cyclic boundaries.
A simulation needs a JSON input file (-f) or a built-in benchmark (-Q).
Command-line options override the corresponding settings in the file:
mcxcl -f input.json [options]
mcxcl -Q cube60 -n 1e7 -G 1
mcxcl (or mcxcl -h) prints all options. They are the same as those of mcx,
except for the device options; the most useful ones are below.
Input and simulation control
| Option | Meaning |
|---|---|
-f file, --input | read a JSON input file; a string starting with { is read as inline JSON, -f - reads stdin |
-Q name, --bench | run a built-in benchmark (-Q alone lists them): cube60, cube60b, cube60planar, cubesph60b, onecube, twocube, skinvessel, sphshells, spherebox, colin27 |
-N name, --net | download and run a simulation shared on NeuroJSON.io |
-j '{...}', --json | modify any setting of the input with a JSON fragment |
-P '{...}', --shapes | add shapes to the domain, see Shape constructs |
-n num, --photon | number of photons |
-r num, --repeat | repeat the simulation num times; negative: split the photons into |num| runs |
-E seed, --seed | RNG seed; a _detp.jdt file name replays its detected photons |
-u mm, --unitinmm | voxel edge length in mm |
-z 0/1, --srcfrom0 | 1: the first voxel's corner is [0,0,0]; 0: [1,1,1] |
-K fmt, --mediabyte | volume data format, see Volume and media formats |
--gscatter n | after n scattering events, scatter isotropically (faster) |
Physics, boundaries and output
| Option | Meaning |
|---|---|
-b 1/0, --reflect | Fresnel reflection at the domain boundary (1) or none (0) |
-B '______', --bc | per-face boundary conditions: r Fresnel, a absorbing, m mirror, c cyclic, _ use -b; 6 more digits mark faces as detectors |
-V 0/1, --specular | include the specular reflection when the source is outside the domain |
--voidtime 1/0 | for a source outside the domain, start the clock at launch (1) or at entry (0) |
-e w, --minenergy | Russian roulette threshold |
-U 1/0/2, --normalize | normalize the output (1), raw (0), or in replay skip the detected-weight normalization (2) |
-s name, --session | name of the output files |
-O X, --outputtype | the output quantity, see Output quantities |
-F fmt, --outputformat | jnii (default), bnii, nii, hdr, mc2, tx3 |
-Z zlib, --zip | compression for jnii/bnii: zlib, gzip, base64, lzip, lzma, lz4, lz4hc |
-d 1/0/3, --savedet | save detected photons (1), not (0), or stop when -H photons are detected (3) |
-w DP, --savedetflag | detected photon data to save, see Detectors |
Devices and the OpenCL kernel
| Option | Meaning |
|---|---|
-L, --listgpu | list the OpenCL devices and exit |
-l [1|0|none], --log | 1 (or -l alone): write the messages to session.log; 0: print them without the banner; none: print nothing, not even the progress bar |
-G n or -G '0110', --gpu | use device n (from 1, as numbered by -L), or several devices given as a 0/1 mask |
-W '50,30,20', --workload | relative share of photons for each selected device |
-A 1/0, --autopilot | choose the thread count, dispatch photons dynamically and use fast math (1, default), or use -t/-T with strict math (0) |
-t num, --thread / -T num, --blocksize | total threads and threads per work group when -A 0 |
-o 3, --optlevel | kernel optimization level, 0 (none) to 3 |
-J '-D...', --compileropt | extra OpenCL compiler options, e.g. -DMCX_GPU_DEBUG (print each step) or -cl-nv-verbose (register counts on NVIDIA) |
-k file.cl, --kernel | run your own copy of the OpenCL kernel instead of the built-in one |
--showkernel 1 | print the built-in (or -k) kernel source |
--internalsrc 1 | the source is inside the domain: skip the search for the entry point |
-I, --printgpu | print the device information, then run |
Recipes
mcxcl -L # which devices do I have?
mcxcl -Q cube60b -n 1e8 -G 0111 -W 40,40,20 # three devices, unequal shares
mcxcl -f input.json -s run1 -F bnii -Z lz4 # compressed binary output
mcxcl -Q cube60 -A 0 -t 4096 -T 64 -E 1234 # bit-wise reproducible run
mcxcl -Q cube60 --showkernel 1 > mykernel.cl # get the kernel, edit it, then
mcxcl -Q cube60 -k mykernel.cl # ... run the edited kernel
mcxcl -f input.json -E run1_detp.jdt -O J # replay run1's detected photons for a Jacobian
An MCX/MCX-CL input file is a JSON object with four required sections, Domain,
Session, Forward and Optode, and an optional Shapes section. Field names are
case sensitive; the order of fields does not matter, and unknown sections (such as
Help) are ignored. A field can also be written in the root object with a dotted
name, e.g. "Session.Photons": 1e7. Command-line options take precedence over the
file, and -j edits any field without touching the file.
The fields MCX-CL reads:
Domain - the grid and its media
| Field | Meaning |
|---|---|
VolumeFile | a binary volume file (format set by MediaFormat or -K), a JSON/JNIfTI (.json, .jnii, .bnii) volume, or a shape file; omit it when Shapes builds the volume |
Dim | [Nx, Ny, Nz] |
MediaFormat | voxel format of the volume, "byte" by default (see below) |
Media | array of {"mua":..., "mus":..., "g":..., "n":...}, the first for label 0 |
LengthUnit | voxel size in mm (-u) |
OriginType | 1: first voxel's corner at [0,0,0]; 0: at [1,1,1] (-z) |
MieScatter | polarized MC: array of {"mua", "radius" (micron), "rho" (1/micron^3), "nsph", "nmed"}, one per label from 1 |
InverseCDF | a user-defined scattering phase function, the inverse CDF of cos(theta) |
AngleInverseCDF | a user-defined launch-angle distribution (also accepted under Optode.Source) |
Session - what to compute and save
| Field | Meaning |
|---|---|
ID | name of the output files (-s) |
Photons | number of photons (-n) |
RNGSeed | RNG seed (-E) |
OutputType | output quantity: "flux", "fluence", "energy", "jacobian", ... or the -O letter |
OutputFormat | "jnii", "bnii", "nii", "hdr", "mc2", "tx3" (-F) |
DoMismatch | Fresnel reflection at the boundary (-b) |
BCFlags | per-face boundary conditions (-B) |
DoNormalize | normalize the output (-U) |
DoSaveVolume | save the volume output (-S) |
DoPartialPath | save detected photons (-d) |
SaveDataMask | detected photon data to save, e.g. "DPX" (-w) |
DoSaveExit, DoSaveRef, DoSaveSeed, DoDCS | as -x, -X, -q and -m |
DoSpecular | specular reflection at entry (-V) |
DoAutoThread | autopilot (-A) |
MaxDetPhoton | the most detected photons kept (-H) |
MinEnergy | Russian roulette threshold (-e) |
DebugFlag | debug flags such as "P" (-D) |
RootPath | folder of the input files () |
Forward - time window: T0 (start), T1 (end) and Dt (gate width), in seconds.
Optode - sources and detectors
| Field | Meaning |
|---|---|
Source.Type | source type, see Sources |
Source.Pos | [x, y, z] in voxel units |
Source.Dir | [vx, vy, vz] unit direction, optionally a 4th value: focal length (nan isotropic, -inf Lambertian) |
Source.Param1, Source.Param2 | 4 numbers each, depending on Type |
Source.Pattern | the pattern of pattern/pattern3d sources: {"Nx", "Ny", "Nz", "Data"}, or a JData array |
Source.SrcNum | number of patterns simulated together (photon sharing) |
Source.Weight | initial photon weight |
Source.Frequency | modulation frequency (Hz) for frequency-domain (RF) outputs |
Source.IQUV, Source.WaveLength | Stokes vector and wavelength (nm) for polarized MC |
Detector | array of {"Pos": [x,y,z], "R": radius}, and "Dir" for the adjoint outputs |
SDMap | channels of the adjoint Jacobians: an array of [srcid, detid] arrays (1-based; extra values ignored); default: all source-detector pairs |
Optode.Source can also be an array of sources, each with its own Type, Pos,
Dir and parameters. Several examples are in the example/ folder, for example
example/quicktest, example/skinvessel and example/multisrc. mcxcl --dumpjson
prints the complete input of any simulation, including the defaults, which is a
good template.
Legacy files: mcx2json.m and json2mcx.m (MATLAB) or pmcxcl.mcx2json /
pmcxcl.json2mcx convert between an MCXLAB-CL/PMCXCL cfg and a JSON input file, so a
simulation set up in MATLAB or Python can be run with the command line, and vice
versa.
Instead of supplying a volume file, the domain can be built from simple shapes. The
Shapes array is processed in order, each shape overwriting the voxels it covers,
and can be placed in the input file, in a separate .json file named by
Domain.VolumeFile, or on the command line with -P:
"Shapes": [
{"Name": "head model"},
{"Origin": [0, 0, 0]},
{"Grid": {"Tag": 1, "Size": [60, 60, 60]}},
{"ZLayers": [[1, 5, 1], [6, 8, 2], [9, 60, 3]]},
{"Sphere": {"Tag": 4, "O": [30, 30, 30], "R": 10}},
{"Box": {"Tag": 0, "O": [10, 10, 10], "Size": [10, 10, 10]}},
{"Cylinder": {"Tag": 2, "C0": [0, 30, 20], "C1": [60, 30, 20], "R": 3}}
]
| Shape | Parameters | Result |
|---|---|---|
Grid | Tag, Size: [Nx,Ny,Nz] | (re)creates the grid, filled with Tag |
Origin | [x,y,z] | origin of the coordinates of the shapes that follow |
Sphere | Tag, O (center), R | a sphere |
Box | Tag, O (lower corner), Size | a box, floating-point coordinates |
Subgrid | Tag, O, Size | a box of whole voxels, integer indices, both ends included |
Cylinder | Tag, C0, C1 (axis ends), R | a finite cylinder |
XLayers, YLayers, ZLayers | [[start, end, tag], ...] | layers normal to an axis, 1-based voxel indices, ends included |
XSlabs, YSlabs, ZSlabs | Tag, Bound: [[start, end], ...] | slabs normal to an axis, floating-point bounds |
UpperSpace | Tag, Coef: [A,B,C,D] | the half space A*x+B*y+C*z>D |
Name | a string | a comment |
Tag is the medium label, and must be smaller than the number of media. Coordinates
start from the corner of the first voxel, at [0,0,0] or [1,1,1] depending on
OriginType, unless an Origin is given. In MATLAB/Python, the same JSON string can
be passed as cfg.shapes; mcxplotshapes.m (MCX's utils) and pmcxcl.plotshapes draw it.
MCX's example/shapetest has more examples.
The domain is usually a label volume, but MCX-CL accepts several voxel formats, set by
Domain.MediaFormat in JSON or -K on the command line. In MCXLAB-CL and PMCXCL, the
format follows from the type and shape of cfg.vol:
-K | MediaFormat | MCXLAB-CL/PMCXCL cfg.vol | Each voxel stores |
|---|---|---|---|
| 1 | byte | 3D uint8 | a label, 0-255 |
| 2 | short | 3D uint16 | a label, 0-65535 |
| 4 | integer | 3D uint32 (or single/double) | a label |
| 101 | mua_float | 1xNxxNyxNz single | mua; mus, g, n from label 1 |
| 100 | muamus_float | - | mua and mus; g, n from label 1 |
| 102 | muamus_half | 2xNxxNyxNz single | mua and mus, stored as half floats |
| 104 | muamus_short | 2xNxxNyxNz uint16 | mua and mus as 0-65535 levels between labels 1 and 2 |
| 103 | asgn_byte | 4xNxxNyxNz uint8 | mua, mus, g, n as 0-255 levels between labels 1 and 2 |
| 96 | asgn_float | 4xNxxNyxNz single | mua, mus, g, n |
| 99 | labelplus | 3xNxxNyxNz single | a label, plus one property of it replaced by a value |
| 98 | mixlabel | 3xNxxNyxNz uint16 | two labels and the fraction of the first |
| 97 | svmc | 8xNxxNyxNz uint8 | split-voxel MC: two labels and the interface plane in the voxel |
Volumes with per-voxel properties (all but the label and svmc formats) carry no
per-medium partial path data. Binary volume files are read in column-major (MATLAB)
order unless -a 1 is given. Examples: demo_continuous_mua_mus.m and the
demo_svmc_*.m scripts.
The source is set by its type, position (Pos/srcpos), direction (Dir/srcdir)
and two 4-element parameter vectors (Param1/srcparam1, Param2/srcparam2).
The 4th element of the direction is a focal length for the sources marked [*]:
0 for a collimated beam, positive to converge to a focus that far along the
direction, negative to diverge from a virtual point behind the source, nan for an
isotropic launch and -inf for a Lambertian (cosine) launch (3D only).
| Type | Description and parameters |
|---|---|
pencil | a pencil beam (default) |
isotropic | an isotropic point source |
arcsine | like isotropic, with a uniformly distributed zenith angle |
cone | a uniform cone; Param1[0]: half angle (rad) |
zgaussian | a Gaussian distribution of the zenith angle; Param1[0]: variance |
gaussian [*] | a collimated Gaussian beam; Param1[0]: waist radius |
hyperboloid [*] | a hyperboloid Gaussian beam; Param1: [waist radius, distance to focus, Rayleigh range] |
disk [*] | a uniform disk normal to Dir; Param1[0]: radius, Param1[1]: inner radius of an annulus |
ring [*] | a ring or ring sector; Param1: [outer radius, inner radius, start angle, end angle] |
planar [*] | a uniform quadrilateral with corners Pos, Pos+Param1[0:3], Pos+Param2[0:3] |
pattern [*] | the planar area with intensities from Pattern (Param1[3] x Param2[3] pixels, values 0-1) |
pattern3d [*] | a 3D intensity volume of size Param1[0:3] starting at Pos |
fourier [*] | a spatial-frequency (SFDI) pattern 0.5*[1+M*cos(2*pi*(fx*x+fy*y)+phi0)] over the planar area; fx, fy and phi0, M from Param1[3], Param2[3] |
fourierx [*] | a general Fourier source; Param1: [v1, |v2|], Param2: [kx, ky, phi0, M] |
fourierx2d [*] | a general 2D Fourier basis; Param1: [v1, |v2|], Param2: [kx, ky, phix, phiy] |
pencilarray | an array of pencil beams over the planar area; Param1[3] x Param2[3] beams |
line | a line source from Pos to Pos+Param1[0:3], emitting normal to the line; Param2[0]: half angle of a wedge (fan) beam |
slit [*] | a collimated slit beam along Dir from the same segment; Param2[0:1]: Gaussian broadening |
Several sources are given as an array of Optode.Source objects, or as one row
per source in srcpos/srcdir/srcparam1/srcparam2. By default they are
simulated together into one output; --srcid -1 (cfg.srcid=-1) gives each source
its own output, and --srcid N simulates only the N-th source. Example:
demo_multisrc.m.
Initial weight. A 4th element of srcpos (or Source.Weight) sets the initial
photon weight, which may be negative; the output scales linearly with it.
Photon sharing. A pattern source with SrcNum/cfg.srcnum > 1 simulates
many illumination patterns in a single run, each photon carrying a weight for
every pattern; the output gains a pattern dimension. Example:
demo_photon_sharing.m [Yan2020-OL].
Custom angular distributions. Source.AngleInverseCDF (cfg.angleinvcdf)
gives the launch angle distribution as the inverse CDF of theta/pi.
Example: demo_mcxlab_launchangle.m.
mcxpreview(cfg) (MATLAB, from MCX's utils) and pmcxcl.preview(cfg) (Python) draw the domain,
sources and detectors, which is the quickest way to check a source setup;
demo_mcxlab_srctype.m shows every source type.
Detectors are disks given by a center and a radius (Optode.Detector,
cfg.detpos = [x y z r]); a photon leaving the domain within a detector is
recorded. Faces of the bounding box can also act as detectors, through the 7th-12th
characters of -B/cfg.bc (see demo_bc_det.m).
For each detected photon, MCX-CL can save the fields selected by -w
(Session.SaveDataMask, cfg.savedetflag), default DP:
| Letter | Field | Columns |
|---|---|---|
D | detector ID | 1 |
S | scattering events in each medium | number of media |
P | partial path length in each medium (voxel units) | number of media |
M | momentum transfer in each medium (for DCS) | number of media |
X | exit position | 3 |
V | exit direction | 3 |
W | initial weight | 1 |
I | Stokes vector (polarized MC) | 4 |
These data make it possible to compute detector readings for different optical
properties without rerunning the simulation (perturbation Monte Carlo): the
weight of a detected photon is w0*exp(-sum(mua_i*L_i)). The helper functions do
this for you: mcxdetweight/pmcxcl.detweight (weights), mcxmeanpath/
pmcxcl.meanpath (mean partial path lengths), mcxdettpsf/pmcxcl.dettpsf (temporal
point spread function), mcxcwdref/pmcxcl.cwdref (CW reflectance) and
mcxdcsg1/pmcxcl.dcsg1 (DCS autocorrelation g1).
Saving the RNG seed of each detected photon (-q 1, or requesting the 4th output in
MCXLAB-CL) enables photon replay, see Advanced features.
-O (Session.OutputType, cfg.outputtype) selects what the volume output holds:
-O | Name | Quantity |
|---|---|---|
X | flux | fluence rate, 1/(mm^2 s) per unit source (default) |
F | fluence | fluence, 1/mm^2, integrated over each time gate |
E | energy | energy deposited per voxel |
L | length | total path length per voxel |
J | jacobian (wl) | replay: mua Jacobian |
P | nscat (wp) | replay: weighted scattering counts, for the mus Jacobian |
M | wm | replay: weighted momentum transfer |
R | rf | replay: frequency-domain mua Jacobian; without replay: complex fluence rate |
S | rfmus | replay: frequency-domain mus Jacobian |
T | wltof | replay: time of flight x path length |
B | wptof | replay: time of flight x scattering count |
A | adjoint | adjoint mua Jacobian |
D | adjoint_dcoeff | adjoint diffusion coefficient Jacobian |
U | adjoint_mus | adjoint mus Jacobian |
V | adjoint_musp | adjoint mus' Jacobian |
W | adjoint_mua_d | adjoint mua and D Jacobians together |
Q | adjoint_mua_musp | adjoint mua and mus' Jacobians together |
The adjoint Jacobians have one channel per source-detector pair, [Nx,Ny,Nz,Ns*Nd]
with the detector index varying the fastest; Optode.SDMap (cfg.sdmap), a list of
1-based [srcid, detid] rows, computes only those channels, in that order.
The volume output is an Nx*Ny*Nz*Ng array of the chosen quantity (with an extra dimension for photon-sharing patterns, separate sources or replayed detectors). The default fluence rate is the Green's function for a unit-energy impulse at t=0: the fraction of the source energy (or photons) passing through a unit area per unit time. Each gate stores the average over its time window, i.e. approximately the value at the center of the window, t0+(k-1/2)*Dt. To obtain
-O F);-O E, or multiply the fluence by mua
(mcxfluence2energy.m).The diffuse reflectance can be obtained from the detected photons (the detected
weight divided by the detector area gives the current density), or with -X 1
(cfg.issaveref=1), which records the escaping weight in the background voxels
next to the domain (stored as negative values in the raw volume; MCXLAB-CL/PMCXCL
return it separately as dref). Pad the volume with a layer of 0 voxels where you
want the reflectance. The volume output keeps the voxel order of the input volume
(column-major, or row-major with -a 1). example/validation compares MCX-CL with
analytical solutions (cwdiffusion.m, tddiffusion.m, pmcxcl.cwdiffusion, ...).
With the default normalization (-U 1), the output is the response to a source that
emits one unit of light (one joule, or one photon) in an impulse at t=0, so each
quantity can be read in energy or in photon units:
| Output | Energy units | Photon units |
|---|---|---|
fluence rate (flux, default) | W/mm^2 per J emitted | 1/(mm^2 s) per photon emitted |
fluence (fluence) | J/mm^2 per J emitted | 1/mm^2 per photon emitted |
energy (energy) | J absorbed in the voxel per J emitted | the fraction of the photons absorbed there |
To obtain absolute values, multiply by the energy (or number of photons) your source
delivers; for a continuous source of power P, the CW fluence rate is P times the
time-integrated (summed over gates and multiplied by Dt) fluence rate. Divide the
energy output by the voxel volume (LengthUnit^3) for the absorbed energy density;
mcxfluence2energy.m converts a fluence output into energy deposition. See [Fang2009] and Lihong Wang's Biomedical Optics: Principles
and Imaging, chapter 5, for the definitions.
Fluence rate and current density. The fluence rate is a scalar: the light passing through a small sphere around a point from all directions. The current density J is a vector: the net light flowing through a surface in a given direction; it has the same units. What a detector or the diffuse reflectance measures is the current density flowing out of the domain: the total weight of the detected photons divided by the detector area gives J at the detector, as a function of time when the photons are binned by their time of flight.
| File | Content |
|---|---|
session.jnii (-F jnii, default) | the volume output, JSON-based JNIfTI with compressed binary data |
session.bnii (-F bnii) | the same in binary JSON (BJData), smaller and faster to read |
session.nii / .hdr+.img (-F nii/hdr) | NIfTI-1 / Analyze 7.5 |
session.mc2 (-F mc2) | raw float32 array, column-major, no header (legacy) |
session_detp.jdt / .jdb | detected photons (and seeds with -q 1), with jnii/bnii |
session.mch | detected photons in the legacy binary format, with mc2/nii/hdr |
session_traj.jdt / .jdb (or .mct) | photon trajectories, with -D M |
session.log | the messages, with -l |
JNIfTI (.jnii, .bnii) and JData (.jdt, .jdb) files follow the
NeuroJSON specifications: they are plain JSON or binary
JSON, readable in any language, with the array size, type and compression stored
alongside the data. Detected photon files have the structure
{"MCXData": {
"Info": {"Version", "MediaNum", "DetNum", "Media", ...},
"PhotonData": {"detid", "srcid", "nscat", "ppath", "mom", "p", "v", "w0"},
"Seed": [...]
}}
with only the fields selected by -w (srcid only with several sources, Seed
only with -q 1). Trajectory files hold a Trajectory object with photonid,
p, w0, srcid and, with --trajstokes 1, iquv. Files with the older .jdat
suffix are still accepted for replay.
The legacy .mch/.mct files have a 256-byte header (MCXHistoryHeader: magic
MCXH, version, number of media, detectors, columns, photons simulated, detected
and saved, voxel size, seed bytes, normalizer, respin, srcnum, savedetflag, total
sources) followed by a float32 array of saved photons x columns, and with -q 1
the seeds; loadmch.m reads them.
MCX-CL prints its settings, the device(s) used, the time spent in each phase (ms) and,
at the end, the simulation speed (photons/ms) and the absorbed fraction. -D P
(cfg.debuglevel='P') adds a progress bar.
MATLAB/Octave - install JSONLab (or
octave-jsonlab on Debian/Ubuntu/Fedora) for JNIfTI/JData files; the other loaders
are in MCX's utils folder:
nii = loadjd('qtest.jnii'); % JSONLab: .jnii, .bnii, .jdt, .jdb, ...
flux = nii.NIFTIData; % Nx x Ny x Nz x Ng
detp = loadjd('qtest_detp.jdt');
ppath = detp.MCXData.PhotonData.ppath; % one row per detected photon
traj = loadjd('qtest_traj.jdt');
flux = loadmc2('qtest.mc2', [60 60 60 10]); % utils: .mc2 needs the dimensions
[data, header] = loadmch('qtest.mch'); % utils: legacy detected photons
nii = mcxloadnii('qtest.nii'); % utils (MATLAB), or niftiread
Python - install jdata (pip install jdata bjdata, or python3-jdata on
Debian/Ubuntu):
import jdata as jd
nii = jd.load('qtest.jnii') # also .bnii, .jdt, .jdb (needs bjdata)
flux = nii['NIFTIData'] # numpy array, Nx x Ny x Nz x Ng x 1
detp = jd.load('qtest_detp.jdt')['MCXData']['PhotonData'] # dict of arrays
import pmcxcl
flux = pmcxcl.loadmc2('qtest.mc2', [60, 60, 60, 10])
data, header = pmcxcl.loadmch('qtest.mch')[:2] # a 3rd value holds the seeds, if saved
.nii files can be read with nibabel.
MCXLAB-CL runs MCX-CL inside MATLAB or GNU Octave. It takes the same cfg struct as
MCXLAB and returns the same outputs:
[flux, detp, vol, seeds, traj] = mcxlabcl(cfg);
Request only the outputs you need. help mcxlabcl documents every cfg field, its
default and the outputs; the OpenCL-specific fields are cfg.gpuid (a device number,
or a 0/1 string selecting several), cfg.workload, cfg.optlevel,
cfg.compileropt, cfg.kernelfile and cfg.internalsrc (cfg.isatomic is still
accepted but ignored: the output is always accumulated with atomic operations).
| Output | Content |
|---|---|
flux.data | the volume output, Nx x Ny x Nz x Ng (plus extra dimensions for patterns, sources, detectors or RF parts) |
flux.dref | diffuse reflectance, with cfg.issaveref=1 |
flux.stat | runtime, photons, launched and absorbed energy, normalizer |
flux.jmua, .jd, .jmus, .jmusp | adjoint Jacobians |
detp | detected photons: detid, ppath, nscat, mom, p, v, w0, ... |
vol | the preprocessed volume |
seeds | RNG seeds of the detected photons, for replay |
traj | photon trajectories: id, pos, data |
Other calls: mcxlabcl('gpuinfo') lists the devices, mcxlabcl('version') returns the
version, mcxlabcl(cfg, 'preview') draws the domain, and mcxlabcl(cfg, 'cuda') runs
the simulation with MCX (mcx.mex) instead. A variable USE_MCXCL in the base
workspace switches both mcxlab and mcxlabcl: 1 uses MCX-CL, 0 uses MCX, and an
integer N>1 (or a device string such as '0101') also selects that device.
Helper functions in utils: mcxdetphoton, mcxdetweight, mcxmeanpath,
mcxmeanscat, mcxdettpsf, mcxdettime, mcxdcsg1 (detected photons);
cwdiffusion, tddiffusion (analytical solutions); mcxrfreplay; mcxplotphotons,
mcxplotvol, islicer, slice3i (plotting); mcx2json, json2mcx, loadmc2,
loadmch, mcxloadnii, mcxfluence2energy (files). MCX's utils folder adds
mcxpreview, mcxplotshapes, mcxsvmc, mcxcwdref, mcxcreate and more.
Examples: about 40 scripts in mcxlabcl/examples, from demo_mcxlabcl_basic.m to
atlas head models, SFDI, replay and adjoint Jacobians, polarized light and SVMC; they
are the MCXLAB examples running with MCX-CL.
PMCXCL brings MCX-CL to Python 3.6+, with the same interface as PMCX:
import pmcxcl
res = pmcxcl.run(cfg) # cfg: a dict with the MCXLAB field names
res = pmcxcl.run(nphoton=1e6, vol=vol, ...) # or keyword arguments
res = pmcxcl.mcxlab(cfg) # run(), then decode detp/traj into dicts
print(pmcxcl.gpuinfo()) # the OpenCL devices
res is a dict with flux, stat and, when produced, detp, seeds, traj,
dref, vol and the adjoint Jacobians. The utility functions - detweight,
meanpath, dettpsf, cwdref, dcsg1, cwdiffusion, tddiffusion, rfreplay,
loadmc2, loadmch, mcx2json, json2mcx, preview, plotshapes, plotphotons,
plotvol, svmc and more - come from pmcx and are available as pmcxcl.<name>.
All MCXLAB examples have Python versions in MCX's
pmcx/examples; they run with
MCX-CL after replacing import pmcx by import pmcxcl as pmcx. The
PMCX tutorial notebook
applies to PMCXCL as well. See pmcxcl/README.md for building from source.
MCX Studio 2, included in the MCX Studio packages, is the graphical front end for
MCX, MCX-CL and MMC (source code in MCX's mcxstudio2/ folder). A simulation is the
same .json file mcxcl reads; the settings are grouped in the order a simulation is
described, with F1 help for each; a 3-D view shows the domain, sources, detectors,
fluence and photon paths. Choosing the intel, amd or cpu processor runs mcxcl:
mcxstudio qtest.json # edit and run
mcxstudio --run --processor intel qtest.json # run with mcxcl
See the MCX README and mcxstudio2/README.md in MCX for details.
MCX-CL provides the same advanced features as MCX; the MCX README describes them in detail, and the MCXLAB-CL examples show them in use.
-q 1, or the 4th output of mcxlabcl), then replay them (-E file_detp.jdt, or
cfg.seed=seeds.data with cfg.detphotons) with -O J (mua), P (mus), R
(frequency domain), S, T or B, for one detector (-Y n) or all of them.
Examples: demo_mcxlab_replay.m, demo_replay_timedomain.m,
demo_replay_frequencydomain.m; example/rfreplay.Optode.Detector.Dir,
cfg.detdir) and use output type adjoint, adjoint_dcoeff, adjoint_mus,
adjoint_musp, adjoint_mua_d or adjoint_mua_musp, in CW or the frequency
domain. Examples: demo_validate_mcx_adjoint_*.m.Optode.Source.Frequency in Hz, cfg.omega in rad/s) with output type rf to get
the complex fluence rate. Example: demo_validate_mcx_rf_redbird.m.pattern source with SrcNum/cfg.srcnum > 1
simulates many illumination patterns at once. Example: demo_photon_sharing.m.Domain.MieScatter/cfg.polprop. Example:
demo_polarized_photon.m.demo_svmc_*.m, demo_continuous_mua_mus.m.cfg.invcdf, cfg.angleinvcdf.
Examples: demo_mcxlab_phasefun.m, demo_mcxlab_launchangle.m.-G 0111 -W 40,40,20 splits one simulation across devices,
which may come from different vendors; give a faster device a larger share.--showkernel 1 prints the kernel source; edit it and run it
with -k mykernel.cl to experiment with the algorithm without recompiling mcxcl.MCX-CL and MCX share their input format, cfg fields, outputs and helper functions,
and are tested against each other step by step. The differences are:
| MCX | MCX-CL | |
|---|---|---|
| hardware | NVIDIA GPUs (CUDA), AMD GPUs (HIP) | any OpenCL device: NVIDIA, AMD, Intel GPUs, Apple silicon, CPUs |
| kernel | compiled ahead of time | compiled for your device at startup (-o, -J, -k) |
-k option | --voidtime | --kernel (use --voidtime for the void timer) |
| 2D disk sources, Lambertian launch in 2D | supported | not yet (Lambertian launch is 3D only) |
| MATLAB | mcxlab | mcxlabcl, or mcxlab(cfg, 'opencl') |
| Python | pmcx | pmcxcl (utility functions from pmcx) |
Known device issues in this release:
-A 0
(static split) for such runs. GPUs are not affected.mcxcl -L and choose a dedicated GPU with -G; the
first device listed is not always the fastest. A discrete GPU is typically tens of
times faster than a CPU, and several devices can work together (-G, -W).-A 1, the default): it sizes the thread count for the
device, hands out photons dynamically, and compiles the kernel with fast math
(-cl-unsafe-math-optimizations).-A 0 to reproduce a run exactly. With dynamic dispatch and fast math, two
runs with the same seed agree statistically but not bit by bit. For identical
results, use -A 0 and keep the seed, -t and -T unchanged; saving or replaying
seeds always uses strict math and static dispatch.-r -N.| Test | What it checks | How to run |
|---|---|---|
| host unit tests | parsing, JData, shapes, Mie and output writers, without a device | make unittest in src |
| regression tests | the command-line options and outputs | test/testmcx.sh |
| built-in examples | MCX's 141 example cases, against golden values | test/testbuiltin.sh |
| trajectory tests | step-by-step photon trajectories against MCX's golden data | test/testtraj.sh |
| PMCXCL tests | the Python module | python3 -m unittest discover -s test in pmcxcl/ |
| fuzzing | mutated built-in inputs and options: crashes, hangs, sanitizer and OpenCL kernel errors | test/fuzz/dfuzz.py |
The continuous integration runs all but the fuzzer on a CPU OpenCL device (POCL) with -A 0.
help mcxlabcl, pmcxcl/README.md, and the MCX README for features shared with MCXMCX-CL is open-source software under the GPLv3 license: you may use, modify and share it, as long as derived work is also released under the GPLv3. Bug fixes and new features are welcome:
git diff to the mailing list or a GitHub issue.Please format C/C++ code with make pretty (astyle) in src, MATLAB code with
mh_style --fix (miss_hit) and Python code with black before submitting, and add a
test when you can. Contributions are acknowledged in the What's New section and the
ChangeLog.
If you use MCX-CL in your research, please cite [Yu2018], which describes MCX-CL, and [Fang2009], the original MCX paper, as well as the papers of the specific features you use. The complete list is maintained at https://mcx.space/#publications.
Core papers
Features
Mesh-based Monte Carlo (MMC), for the related mesh-based simulator
Applications: MCX has been used to model light in the brain (fNIRS and photobiomodulation dosimetry), neonatal heads, bone, and many other tissues; see https://mcx.space/#publications for examples, and Google Scholar for the many works citing MCX.
The MCX project, including MCX-CL, is funded by the US National Institutes of Health (NIH) / National Institute of General Medical Sciences (NIGMS) under grant R01-GM114365. We thank the users and contributors who reported bugs, suggested features and contributed code; they are credited in the What's New section and the ChangeLog.
MCX-CL contains modified versions of the below source codes from other open-source projects (with a compatible license).
arm64 or x86_64) instead of universal2, which
made pip install an x86_64 binary on Apple silicon running macOS 15+ (#60);
pmcxcl.mcxlab() runs the simulation with pmcxcl, and a failed import reports the
actual error (#60)liboctmex)make unittest, 609 checks) raise the
line coverage of the host code to about 95%, and the 141 built-in example tests of MCX
run with MCX-CL against golden values; the bugs these tests found were fixedtest/fuzz/dfuzz.py, seeded by MCX's built-in inputs, now in test/builtin), and six
more rounds of 300 cases on the GPU found the five bugs listed below-u), the roulette threshold (-e), device workloads (-W), media
formats (-K) and compression methods (-Z) are validated; invalid input now stops
with an error message instead of crashing or hanging--gscatter is reached), which hung the
kernel; -e of 1 or more no longer relaunches photons forever; RF forward output now
keeps its imaginary part in .jnii/.bnii files; a number given for a string field no
longer crashes the reader; a source pattern smaller than Param1/Param2 is no longer read
past its end, and one with no non-zero pixel in its sampled area no longer hangs the
kernel; svmc labels beyond the media are rejected instead of reading past the property
table; a run that detects no photon no longer frees the detected-photon bufferSession.OutputType in JSON inputs accepts the names mcxlabcl and pmcxcl use ("flux",
...); only the first letter used to be read, so "flux" gave fluence-r now repeats or splits the photons as documented; a time step of 0 is treated as
a single time gate; --dumpjson, mcx2json and json2mcx round-trip all settingscl_ext_float_atomics kernel build on Intel Arc GPUs-H num, --maxdetphoton | the most detected photons kept |
-x 1 / -X 1 / -m 1 / -q 1 | save exit positions and directions / diffuse reflectance / momentum transfer / RNG seeds |
-S 1/0, --save2pt | save the volume output (1) or skip it (0) |
--dumpjson [file] | print (or save) the complete input, volume included, as one JSON file |
-D P, --debug | P progress bar, M save trajectories, T trajectories only, R RNG test |
-y zh_CN, --lang | print messages in another language |
--root