Update on merging VMAT into dev - #377
Conversation
New: Siochi leaf sequencing with more fluid number of kept apertures. Still respect constraints of min/max gantry angle spacing.
Changed the first gantry angle from 0 to non-zero. This makes things much easier.
Figured out problem with DAO: the leaf speed was defined as the difference between positions, not absolute difference. Then speed was constrained to be between 0 and 6 cm/s, which forced unidirectional motion, increasing the objective function. Temporary fix: allow negative values. Eventually, change definition of function so that it considers the absolute difference. Will also have to change the Jacobian.
Fixed leaf speed constraints and Jacobian. Siochi sequencing ready to be pilled to dev.
# Conflicts: # optimization/matRad_constFuncWrapper.m # optimization/matRad_jacobFuncWrapper.m
Testing Revert
This reverts commit 3fe0406.
Weight to MU changed from 1 to 100.
pln.bioOptimization put into options structure
This reverts commit 79e5632.
Added leaf speed constraints back in
Changed leaf speed from mm/s -> cm/s, greatly reducing treatment time. Added function helping to test planning capabilities. Fixed bug in Dij sampling, when no voxels are outside of the core.
Shifted time variable to be the time over the optimized beam's arc, rather than the time between optimized beam angles. This makes more sense when calculating e.g. dose rate. Also, this will make more sense when doing dynamic fluence calculation.
Split the daoVec2ApertureInfo into 3 functions so that we didn't have to keep running if statements on VMAT, dynamic vs. static fluence calculation, etc.
Added option for dynamic fluence optimization for VMAT DAO, including support for interpolated apertures.
Support for Jacobian scaling
Added support for 4D dose calculation, including deformed CT cubes and deformation vectors.
This reverts commit de9682f.
Everything else matching matRad_calcPhotonDose*.m at the doseCalc top level is a deprecation shim that warns and redirects to the engine architecture; the VMC wrapper is a real implementation serving as the backend of DoseEngines.matRad_PhotonVmcEngine and does not warn. Placing it in doseCalc/vmc++/ with the helpers it calls makes that distinction visible; the engine keeps calling it by name. Touching the file brings it up to the MISS_HIT style rules, with a vmc++-local .miss_hit grandfathering the legacy wrapper's metrics like the MC engines in +DoseEngines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- apertureInfo.newIteration was written once (with a comment asking whether it is needed) and read nowhere - the answer is no. - optDelivery recomputes the per-segment maximum leaf speeds as its first step, so the explicit maxLeafSpeed call before it was redundant; the magic second argument 1 is optDelivery's default and is dropped. - The wDao local duplicated w for no reason, and matRad_sequencing assigned a visMode fallback value that was never read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The files revived from the old VMAT fork still referenced the fork-era LICENSES.txt; the repo standard header references LICENSE.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tems - matRad_recalcApertureInfo and matRad_recalcApertureBixelWeights are implementation details of matRad_refineApertureArc (its only caller); they move to matRad/optimization/private/ so the public API surface is just matRad_refineApertureArc. - matRad_bixWeightAndGrad becomes matRad_calcBixelWeightAndGradient - "bixel" is spelled out everywhere else in the codebase - and its pre-2018 personal GPL header is replaced by the standard license block. It stays public because the @matRad_OptimizationProblemVMAT methods call it. - matRad_sequencing gets the same local dose-cube merge helper as the DAO wrapper instead of an inline fieldnames loop. - Example 22 no longer spells out matRad_backProjection internals in its commented dij-reuse recipe; it points at the documented 'reuseDij' option instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pln.propOpt.runDAO and pln.propSeq.runSequencing are workflow gates read only by the matRad.m script and the GUI buttons - no function the VMAT example calls reads them, so carrying them in the example suggested a plan dependency that does not exist. The example keeps only pln.propOpt.runVMAT, the actual mode flag consumed by fluence optimization, sequencing and DAO. The stale commented-out runDAO hijack block in matRad_WorkflowWidget (the GUI gates on its buttons, not on pln.propOpt.runDAO) is removed, with small hook fallout fixes in that file and the gui .miss_hit covering double-underscore GUIDE callbacks. The numLevels property now documents its VMAT semantics in the example and the sequencer base class: it is the starting stratification level count, increased automatically until every FMO beam yields at least as many apertures as it has DAO child angles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR is an updated merge of the VMAT development branch into dev, aligning VMAT/DAO with the refactored optimization/sequencing interfaces while adding VMAT-focused utilities (delivery metrics, arc refinement) and new regression/consistency tests.
Changes:
- Introduces a dedicated
matRad_OptimizationProblemVMAT(subclassing DAO) plus VMAT-specific constraint plumbing and post-processing (leaf-touching cleanup, max leaf speed, delivery-time optimization). - Adds VMAT analysis tooling: delivery metrics (
matRad_calcDeliveryMetrics) and fine-angle resampling/recalculation (matRad_refineApertureArc+ private helpers). - Updates sequencing/DAO integration (including deprecation bridging), GUI wiring, and expands test coverage for VMAT sequencing and DAO/VMAT derivatives.
Reviewed changes
Copilot reviewed 76 out of 77 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/sequencing/test_xiaLeafSequencing.m | Spelling fix in test comment. |
| test/sequencing/test_siochiLeafSequencingVMAT.m | New VMAT sequencing + delivery metrics + arc refinement regression tests. |
| test/sequencing/test_siochiLeafSequencing.m | Fixes incorrect test suite name and typo. |
| test/sequencing/test_sequencerBase.m | Updates tests for renamed sequencing property (numLevels) and adds deprecated-alias test. |
| test/sequencing/test_engelLeafSequencing.m | Spelling fix in test comment. |
| test/sequencing/test_directApertureOptimization.m | Renames test function and adds prescription scaling regression test. |
| test/optimization/test_daoGradientConsistency.m | New finite-difference checks for DAO and VMAT objective gradients / constraint Jacobians. |
| test/doseCalc/test_TopasMCEngine.m | Updates sequencing configuration key (numLevels) and minor formatting. |
| matRad/util/matRad_upgradeApertureInfo.m | New migration helper for legacy apertureInfo field names. |
| matRad/util/matRad_SpotRemovalDij.m | Refactors logging/formatting and cleans up property definitions/spacing. |
| matRad/util/matRad_getFieldOrDefault.m | New utility for safe struct-field access with optional warning/deprecation warning. |
| matRad/sequencing/matRad_xiaLeafSequencing.m | Removes deprecated functional wrapper (Xia). |
| matRad/sequencing/matRad_siochiLeafSequencing.m | Removes deprecated functional wrapper (Siochi). |
| matRad/sequencing/matRad_SequencingPhotonsXiaLeaf.m | Renames sequencingLevel usage to numLevels and minor formatting fixes. |
| matRad/sequencing/matRad_SequencingPhotonsEngelLeaf.m | Renames sequencingLevel usage to numLevels, adds miss_hit pragma, fixes typos/formatting. |
| matRad/sequencing/matRad_sequencing2ApertureInfo.m | Removes legacy sequencing-to-apertureInfo converter. |
| matRad/sequencing/matRad_engelLeafSequencing.m | Removes deprecated functional wrapper (Engel). |
| matRad/planAnalysis/matRad_calcDeliveryMetrics.m | New VMAT delivery metrics computation (MU/time/leaf speeds/constraint fractions). |
| matRad/optimization/projections/matRad_DoseProjection.m | Adds optional dij.scaleFactor support in forward projection + gradient backprojection. |
| matRad/optimization/private/matRad_recalcApertureInfo.m | New helper to rebuild/interpolate apertureInfo for a new steering-angle grid. |
| matRad/optimization/private/matRad_recalcApertureBixelWeights.m | New helper to recompute bixel weights/shape maps after aperture arc resampling. |
| matRad/optimization/matRad_refineApertureArc.m | New public API to resample VMAT apertures onto a finer angular grid. |
| matRad/optimization/matRad_preconditionFactors.m | New Jacobi preconditioner factor computation for DAO/VMAT apertures. |
| matRad/optimization/@matRad_OptimizationProblemVMAT/optDelivery.m | New VMAT delivery-time optimization (rate/speed constraints), with re-interpolation trigger. |
| matRad/optimization/@matRad_OptimizationProblemVMAT/maxLeafSpeed.m | New VMAT max leaf speed computation. |
| matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_OptimizationProblemVMAT.m | New optimization-problem subclass wiring VMAT overrides and helpers. |
| matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_getConstraintBounds.m | New VMAT constraint bounds (dosimetric + leaf-speed + MU-rate). |
| matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_daoApertureInfo2Vec.m | VMAT-specific apertureInfo→vector conversion including gantry time entries and bounds. |
| matRad/optimization/@matRad_OptimizationProblemVMAT/matRad_constraintFunctions.m | VMAT constraint evaluation (adds leaf speed + MU-rate constraints to DAO). |
| matRad/optimization/@matRad_OptimizationProblemVMAT/leafTouching.m | New VMAT leaf-touching cleanup post-processing. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_OptimizationProblemDAO.m | Accepts legacy apertureInfo via upgrade chokepoint and formatting cleanup. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_objectiveGradient.m | Adds analytic Jacobian path (bixelJApVec) and adjusts gradient scaling for jacobiScale. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getJacobianStructure.m | Refactors dosimetric Jacobian structure assembly and constraint ordering. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_getConstraintBounds.m | Reorders bounds to match new constraint concatenation order. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_daoApertureInfo2Vec.m | Updates vector layout to incorporate jacobiScale into weight variables and field renames. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintJacobian.m | Reorders Jacobian rows and adds analytic Jacobian shortcut when available. |
| matRad/optimization/@matRad_OptimizationProblemDAO/matRad_constraintFunctions.m | Reorders constraint vector to match new convention (dosimetric first). |
| matRad/matRad_sequencing.m | Refactors sequencing wrapper: photon vs particle paths, VMAT bridging, and safe dose-cube merging. |
| matRad/matRad_directApertureOptimization.m | Adds VMAT/DAO polymorphic dispatch, preconditioning/prescription scaling flow, and result merging. |
| matRad/MatRad_Config.m | Adds default propSeq.numLevels, fixes loadobj merge recursion, and minor platform checks/formatting. |
| matRad/gui/widgets/matRad_WorkflowWidget.m | GUI spelling cleanups and minor refactors. |
| matRad/gui/widgets/matRad_ViewingWidget.m | Uses existing stf (if present) to determine number of beams; adds fallback logic. |
| matRad/gui/widgets/matRad_PlanWidget.m | Updates GUI plan serialization to propSeq.numLevels, spelling fixes, and stores propStf.numOfBeams. |
| matRad/gui/.miss_hit | Adds relaxed miss_hit rules for legacy GUI code. |
| matRad/doseCalc/vmc++/matRad_vmcOptions.m | New VMC++ option assembly helper. |
| matRad/doseCalc/vmc++/matRad_readDoseVmc.m | New VMC++ binary dose reader. |
| matRad/doseCalc/vmc++/matRad_matRad2vmcSourceAngles.m | New gantry/couch→VMC++ angle conversion helper. |
| matRad/doseCalc/vmc++/matRad_exportCtVmc.m | New CT export utility for VMC++. |
| matRad/doseCalc/vmc++/matRad_createVmcInput.m | New VMC++ input file writer. |
| matRad/doseCalc/vmc++/matRad_createVmcBatchFile.m | New batch launcher script generator for VMC++. |
| matRad/doseCalc/vmc++/matRad_bixelPhspVmc.m | New phase-space splitting helper for bixelized sources. |
| matRad/doseCalc/vmc++/.miss_hit | Adds relaxed miss_hit rules for legacy VMC++ wrapper code. |
| matRad/doseCalc/+DoseEngines/matRad_PhotonVmcEngine.m | New DoseEngines wrapper integrating VMC++ as an optional Monte Carlo engine. |
| matRad/doseCalc/+DoseEngines/matRad_PhotonPencilBeamSVDEngine.m | Adds weightToMU initialization fallback in photon PB-SVD engine. |
| matRad/doseCalc/+DoseEngines/matRad_ParticleMCsquareEngine.m | Fixes typos, improves warnings, minor refactors, and Windows binary invocation hardening. |
| matRad/doseCalc/+DoseEngines/@matRad_ParticleFREDEngine/calcDose.m | Fixes LICENSE link typo. |
| matRad/doseCalc/+DoseEngines/.miss_hit | Adds relaxed miss_hit rules for dose engine package. |
| matRad/dicom/@matRad_DicomImporter/matRad_importDicomSteeringPhotons.m | Improves isocenter handling (single vs per-beam) and formatting. |
| examples/matRad_example3_photonsDAO.m | Clarifies that this example is static delivery; points to VMAT example. |
| examples/matRad_example22_photonsVMAT.m | New end-to-end VMAT example script (FMO → sequencing → DAO → analysis → refinement). |
| CHANGELOG.md | Updates changelog entry to reflect removal of legacy sequencing wrappers and VMAT OO integration. |
| .miss_hit | Updates miss_hit naming rules for methods/classes/attributes. |
| .codespellrc | Adds assignin to ignore list. |
Comments suppressed due to low confidence (1)
matRad/optimization/@matRad_OptimizationProblemVMAT/leafTouching.m:76
- Same issue as above:
doseAngleBordersbelongs toapertureInfo.arc.beam, notapertureInfo.beam. Using the wrong struct path breaks gantry-angle sampling for the final border.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The max*GantryAngleSpacing properties are upper bounds, but the arc subdivision derived the dose and DAO counts with ceil while deriving the FMO:DAO ratio with floor, then rounded even ratios down to satisfy the odd-centring requirement. Both steps paid for the bounds with apertures, the one quantity that must not be sacrificed: example 22's 15/30/45 collapsed to a single aperture per fluence map, stripping all modulation out of the sequenced plan and leaving DAO nothing to recover. Any maxFMO below 3x the realized DAO spacing behaved the same way. Derive the nesting top-down instead (FMO -> DAO -> dose), with every count from ceil and every odd correction rounding up. Going finer can never violate a maximum, so the aperture budget is now the invariant and the spacings give way. Add minAperturesPerFMOBeam (default 3), optional explicit aperturesPerFMOBeam/doseAnglesPerDAOBeam factors, and maxNumOfDAOAngles as an explosion guard. Unify both delivery modes on centre sampling: every beam sits at the centre of its own sector. This makes the three angle sets nest exactly, keeps each beam strictly inside the arc (a closed 360 deg arc previously placed two beams on the same physical gantry position in step-and-shoot), and lets the DAO/FMO sets be selected by index so membership tests compare bit-identical values. It also means dose beams now exist outside the DAO-bracketed span, which broke three assumptions that beam 1 is always a DAO beam: prepareArcs carried lastDAOBeamIx/nextDAOBeamIx forward from the previous iteration, leafTouching probed beam(1).shape(1) to pick its sampling mode, and the first DAO beam's leading leaf-sweep segment got index 0. Other fixes found while reviewing the branch: - Time interpolation fractions took abs() of the numerators, which defeated the [0,1] clamp when the DAO border fell outside the dose sector: both fractions clamped to 1 instead of to 0 and 1, so they no longer summed to 1 and the interpolated gantry speed came out ~1.5x off. Use a signed denominator so the arc direction cancels and the clamp still discriminates. - matRad_recalcApertureInfo anchored the beam-centre trajectory only at the old beam centres, which do not bracket the outermost new centres, so refinement returned NaN leaf positions on the first and last beam. - leafTouching built its interpolation support as setdiff(1:numBeams, ...), covering only half the columns in its twice-per-beam mode. Behaviour is unchanged on the live path; that mode has never been reachable from the sole call site, which is now recorded in the file. - discardApertures leaked input class and orientation into its output and silently zeroed a beam whose kept apertures had no dose-area product. - matRad_getWorldAxes reduced a 1x3 isfield result with an implicit all(), so a partially populated grid struct errored instead of being completed. - VMC++: portable batch invocation, clearer doseGrid override naming, and a single identified error for the scenario/grid contract. Add dispInfo/dispDebug coverage across the VMAT pipeline: realized arc geometry and aperture budget, per-map stratification and shape distribution, apertureInfo problem size, delivery-limit checks, DAO variable breakdown and machine limits, and the refinement resampling step. None of the above was visible before, which is why the aperture collapse went unnoticed. Move leafTouching, maxLeafSpeed and optDelivery out of @matRad_OptimizationProblemVMAT into sequencing as matRad_leafTouching, matRad_calcMaxLeafSpeed and matRad_enforceDeliveryConstraints. They are arc-sequencing helpers that the optimization problem never used as methods. The differentiable leaf-speed constraint stays in the optimization problem. Tests: new test/steering/test_stfGeneratorPhotonVMAT.m covers the subdivision invariants across configurations, arc directions and both delivery modes, pins the formerly degenerate 15/30/45, and checks the validation paths. The sequencing suite gains a fixture with interpolated beams, since the reworked geometry makes 15/30/45 resolve to one dose angle per DAO sector and would otherwise have lost that coverage entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The private helpers introduced by the previous split are single-use: the views and the player share the aperture geometry, weight colouring and panel bookkeeping, so splitting them only moved that code into files nothing else calls. Inline them again and exempt the file instead: - an in-file MISS_HIT pragma justifies the file_length metric, placed at the end of the file so it does not become the function's help text - matRad/util/.miss_hit turns off mh_style's duplicate of that check, which has no per-file justification mechanism; the metric itself stays enabled, so other files still have to stay under the limit Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fluence objective work touches a number of files that predate the current pre-commit configuration and do not satisfy it, so no commit including them could pass the hooks. This is that mechanical pass, kept separate so that the commits following it show behaviour changes only: - mh_style formatting: indentation, comma and comment spacing, statement layout. No functional edits. - 9 codespell findings, all in comments. - 24 lines wrapped to the 150 character limit. The 64 row jet colormap literal in matRad_OptimizationWidget is split over its matrix rows; the numeric sequence was compared against the original and is identical. - mh_metric justifications for the pre-existing cyclomatic complexity and control nesting deviations of matRad_objectiveFunction, matRad_objectiveGradient and matRad_selectVoxelsFromCst, following the precedent already set in matRad_fluenceOptimization. - convertOldOptimizationStruct's parameter renamed old_s -> oldStruct, which the parameter naming rule requires and mh_style cannot fix by itself. Reviewing this commit with --ignore-all-space reduces it to the wrapped lines, the pragmas and the rename. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Smoothing the fluence during FMO needs objectives that act on w itself instead
of on a dose quantity, so they take no part in the backprojection and their
gradient already is a gradient w.r.t. the weights.
Extract the quantity independent part of matRad_DoseOptimizationFunction into a
new common ancestor matRad_OptimizationFunction (parameters, struct,
createInstanceFromStruct) and put matRad_FluenceOptimizationFunction beside it.
matRad_DoseOptimizationFunction keeps its name, its API, and the robustness and
dose parameter accessors.
FluenceObjectives.matRad_FluenceVariance penalizes the fluence variance within
each beam, normalized either per beam ('relative', invariant under a rescaling
of the fluence so penalties transfer between plans) or not at all ('absolute',
the plain quadratic form w'Sw). The per-beam denominator is deliberate: pooling
the mean square over all beams would put the differences between the beam
levels into the denominator, so the objective could be lowered by driving the
beams apart in intensity rather than by smoothing, which on a VMAT arc
concentrates the plan on a few control points.
It needs only the bixel-to-beam mapping the dij carries, so it works for
photons and particles alike. Smoothing that couples neighbouring bixels needs
the beamlet geometry from the stf and has to wait for an optimization entry
point that receives it.
Fluence objectives are accepted both in pln.propOpt.fluenceObjectives and next
to the dose objectives in cst{i,6}; matRad_getFluenceObjectives collects and
initializes them. Direct aperture optimization warns and ignores them, since
smoothing a sum of deliverable apertures has no well defined meaning -
dedicated aperture objectives would be needed there.
Also extract the cst objective promotion and per-fraction dose rescaling that
matRad_fluenceOptimization and matRad_directApertureOptimization duplicated
into matRad_fractionateCstFunctions; the DAO copy had lost the compatibility
branch for the old struct array format.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The VMAT branch of the siochi sequencer applied a hard coded Gaussian blur to
every FMO fluence map before stratifying it. Turn that into the sequencer
property arcFluenceSmoothing ('gaussian' | 'none'), settable from pln.propSeq.
It has to stay on by default. A fluence that is flat over its field decomposes
into exactly one aperture at any number of stratification levels (measured:
1 1 1 1 1 1 1 1 1 1 for levels 1 to 10, against 1 2 3 4 4 4 5 5 5 7 with the
blur), so the loop that raises numLevels until every FMO beam yields one
aperture per DAO control point depends on the blurred field rim to converge at
all. Selecting 'none' used to spin there forever, so that loop is now bounded
and reports which setting to change instead of hanging.
Add apertureSelection ('doseAreaProduct' | 'leastSquares') for reducing the
apertures the stratification generates to the number the arc can deliver. The
existing heuristic keeps the ones with the highest dose-area product - which,
because the stratification gives every segment the same weight, means the
widest ones - and rescales them by a common factor. 'leastSquares' instead
keeps the apertures that best reconstruct the fluence, by forward stepwise
selection with a non-negative least squares refit at every candidate, and then
rescales to the original dose-area product. Refitting before choosing matters:
plain matching pursuit commits to an aperture before the other weights can
adapt and ends up worse than the heuristic, and without the final rescale the
fit loses 12 to 15% of the monitor units and underdoses.
Measured on TG119 with three apertures per beam, 'leastSquares' moves the
sequenced plan from 0.97 to 1.01 of the fluence optimization target D95 and its
D5 overshoot from 1.29 to 1.13. The default is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same mechanical pass as the earlier one, for the example test harness, so that the change adding the VMAT example to it stays readable: - mh_style formatting, and the matRad_unitTestTextManipulation calls wrapped to the 150 character limit - codespell: supressing -> suppressing - the local function renamed runSingleExampleTest -> helper_runSingleExampleTest, which the function naming rule requires for local functions in test files No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each fluence map is optimized at a single gantry angle but delivered as minAperturesPerFMOBeam separate apertures spread over the FMO sector. The sum reproduces the optimized fluence, yet every individual control point receives only one fragment of it, so the wider that sector, the more the delivered dose clusters into a few entrance directions. Measured on this case as the peak-to-mean of the out-of-target dose binned by gantry angle, the excess that sequencing introduces is +38% at 72 deg, +30% at 45 deg and +2% at 24 deg. Direct aperture optimization then amplifies whatever it inherits rather than repairing it - a nearly closed aperture has almost no gradient, so its weight drifts to zero while the already dominant control points grow: the angular dynamic range goes 2.5 -> 5.7 at 72 deg but only 1.36 -> 1.65 at 24 deg. Target coverage and maximum dose look healthy in every case (after DAO the coarse plan even has a lower maximum than the fluence optimization result), so this is invisible to the usual metrics and shows up only in the spatial dose distribution. Drop maxFMOGantryAngleSpacing to 24 deg accordingly. Add the example to the example tests. Arc plans are expensive in every stage, so preprocess the gantry angle spacings the same way the bixel width and the dose grid resolution already are: collapse the arc to three angles that are each their own DAO control point and FMO angle, one aperture per beam, with a coarse fine-arc recalculation and a static aperture view instead of the real time playback. minAperturesPerFMOBeam has to be overridden as well, or the stf generator subdivides the DAO angles again and undoes the coarsening. Runs in about 46 s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the entries this line of work was missing: VMAT planning and the fluence objectives as one Added item each, the VMC++ dose engine and the new aperture visualization views individually, plus the individual DAO, sequencing and API changes and the correctness fixes made along the way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
I did quite a substantial overhaul (thanks to the help of Claude Code) to fit the branch more to matRad's current philosophy. What is new since my last comment
Other refactors
Roughly a dozen correctness bugs in the pre-existing DAO/VMAT code were fixed Notes
|
The old scheme (matRad_SequencingModalityTypeCollimator) put the distinguishing part of the name last and read as an action rather than an object, so the classes neither sorted together in the folder nor matched the dose engine naming they mirror: matRad_PhotonSequencerAbstract -> matRad_PhotonLeafSequencerAbstract matRad_PhotonSequencerVMATAbstract -> matRad_PhotonLeafSequencerVMATAbstract matRad_SequencingPhotonsSiochiLeaf -> matRad_PhotonLeafSequencerSiochi matRad_SequencingPhotonsXiaLeaf -> matRad_PhotonLeafSequencerXia matRad_SequencingPhotonsEngelLeaf -> matRad_PhotonLeafSequencerEngel matRad_ParticleSequencer -> matRad_ParticleScanningSequencerSpill matRad_SequencerBase keeps its name, carrying neither modality nor collimator. The particle sequencer gains a type, since its timing model (spill recharge, spill size and intensity) is specific to a spilled beam and leaves room for a continuous-beam variant; its display name now ends in "Sequencer" like the other three. shortName is untouched throughout, so pln.propSeq.sequencer is unaffected, and sequencer discovery goes through matRad_findSubclasses rather than a name pattern. Since the whole hierarchy is unreleased, the changelog entry that introduces it simply names the final classes instead of recording a rename. Also brings the touched files up to the pre-commit standard, which had never seen them: three typos, trailing whitespace, one 202-character line and five test functions whose underscore names violate regex_function_name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sphinx-build reported 24 warnings, 9 of them ERROR and 5 CRITICAL. All of them were malformed reStructuredText in docstrings or .rst sources, so the affected pages rendered wrongly rather than not at all: - Parameter tables written as a description ending in ':' followed by an aligned block are not RST. Where the block was only as deep as the description text, docutils read the wrapped continuation lines as unexpected indentation and ran the whole table together into prose. These are now literal blocks, indented one level past their description. - matRad_ImageRegistrationBase had a bullet list with no blank line before it, so the first item was absorbed into the preceding paragraph. - Five functions opened their help text with the '%%%%' banner instead of a summary line. docutils read it as a section-title overline (missing underline -> CRITICAL), and MATLAB's own H1 mechanism, lookfor included, saw a row of percent signs as the summary. The banner is dropped and the license block merged into the help text, which is what the other 368 files in matRad/ do. - The docs referenced :ref:`basedata_photons` as '_basedata_photons', underlined a title too short, and indented a bullet list one level too deep inside an admonition. - test_TopasMCEngine.m declares function test_topasMCEngine, which the MATLAB domain flagged as a name mismatch. The file is renamed rather than the function, since 'test_TopasMCEngine' does not satisfy regex_function_name. Touching a .rst file for the first time also exposed that the rstcheck hook cannot pass on documentation that sphinx builds cleanly: it checks one file at a time, so cross-file :ref: labels look like unreferenced targets, and it does not load third-party extensions, so the MATLAB domain's :scpt: role is an unknown role. Its report level is raised to warning and scpt is declared. codespell likewise flagged a German thesis title and a hostname, both now declared in .codespellrc. sphinx-build -E -W --keep-going now exits 0 with an empty warning log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dard Both files predate the current pre-commit configuration, so no commit touching them could pass the hooks. Separating the mechanical part keeps the following Octave fix reviewable - it is three lines across these two files, against roughly 190 lines of reformatting here. Beyond what mh_style fixes on its own (comma and operator whitespace, trailing whitespace, missing final newline) this wraps six lines over the 150 character limit, renames the local functions cutAtArgmin and parseFromDicom to satisfy regex_function_name, fixes two typos codespell flags, and justifies the report generator's cyclomatic complexity of 54 following the precedent of matRad_fluenceOptimization. No behaviour change: the two-argument round() calls that motivated touching these files are deliberately left as they are. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Octave's round takes the value to be rounded only; passing a digit count fails with "Invalid call to round". Since arguments are evaluated before the call, this errored even where the result was only going to be discarded, as in the VMAT steering generator's dispDebug calls at a log level that does not print them. The new matRad_roundCompat joins the other util/octaveCompat helpers and implements Matlab's round(x,n) semantics for both environments, using the native call where it exists. Eleven call sites were affected: three in VMAT steering information generation, six in the sampling report, one in the DVH band plot and one in a VMAT steering test. Five of them passed a digit count of zero, which is plain round(x), so only six actually need the helper. Five hot paths already carry a local copy of the scaling idiom (matRad_calcBixelWeightAndGradient, matRad_daoVec2ApertureInfo, matRad_ParticlePencilBeamEngineAbstract, matRad_postprocessing and matRad_StfGeneratorParticleSingleBeamlet). Those are left untouched: they are Octave-safe as written, and routing them through this function would change what they compute, since scaling by 10^n and rounding is not bit-identical to round(x,n) - which matters at the 10 and 15 digits the DAO leaf position bookkeeping asks for. The docstring says so, so that they do not get unified later by mistake. test_roundCompat covers the semantics against Matlab's native round and scans matRad/, test/ and examples/ for reintroduced two-argument calls. Verified with Octave 8.4.0: test_roundCompat and the VMAT steering tests pass there, 14 passed and 1 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
VMAT had no user facing documentation at all, and the technical guide only mentioned sequencing in passing, as a parenthesis on the fluence optimization step. Since sequencing and DAO are workflow steps that users invoke rather than cross cutting topics, this adds them as a step of their own between fluence optimization and visualization, rather than under "Additional information". The new page covers the three photon delivery techniques and how they are switched on, the available leaf sequencers and how to add one, what DAO optimizes and why it reuses the fluence optimization machinery, and VMAT: the nested FMO / DAO / dose angle hierarchy, the machine limits that constrain the arc with their fallback values, the arc specific sequencing options, and how to check a finished plan. It also records that maxFMOGantryAngleSpacing drives how faithfully an arc can be sequenced, which is not obvious from the parameter name. The three line DAO stub in planopt.rst is replaced by a pointer, and the quickstart no longer tells users to call matRad_engelLeafSequencing, which the sequencer refactor removed. Two gaps in the API documentation are fixed along the way. The FluenceObjectives package was absent from both the module index and the API pages, because nothing referenced it - only packages named by an automodule directive are indexed. Classes in @ folders were missing for the same kind of reason: automodule does not descend into them, so matRad_OptimizationProblem with its DAO and VMAT subclasses, matRad_DoseEngineBase and matRad_ParticleFREDEngine were documented nowhere, and the references to them in planopt.rst silently rendered as plain text. They are now pulled in with explicit autoclass directives. Documenting those files for the first time exposed malformed docstrings in matRad_FluenceVariance, matRad_ParticleFREDEngine and matRad_daoApertureInfo2Vec, of the same kind fixed in 55fd43e, so sphinx-build -E -W --keep-going still exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
I think the time has come that this is (finally) ready to be merged 🎉 Due to a bunch of API changes this will also mean that next matRad after this merge will be a new major release, into which I will fold an optimization overhaul as well, which will likely change the fluence and aperture optimization call structure a bit. Thanks to everybody involved for the hard work on this. |
Dear @markbangert @eric11210 ,
this is my updated attempt at merging dev_VMAT with dev especially concerning the new optimization interface, but also dose calculation.
What I did here, is merge dev_VMAT into a copy of the current dev branch. I think that this is better than what I did before (#376 ), because know you have a better acces to the changed files within this pull request (and see the changes i did within the merge commit).
Also @markangert should be able to close (#308 ).
It seems like it runs smoothly now with the old settings, i.e. fluence Opt & DAO opt without preconditioning. It doesn't work with VMAT yet (dose calculation works, but fluence optimization is weird and DAO/VMAT doesn't work)
But anyways, I hope you now have a better basis to continue with the merge.
Some comments:
Let me know if you have questions. I will let you know if I forgot something in the list.
P.S.: Does someone of you have a very light test script for the VMAT?