Skip to content

WIP: Add pctgradientdescent application - #99

Open
acoussat wants to merge 4 commits into
RTKConsortium:mainfrom
acoussat:pctgradientdescent
Open

acoussat wants to merge 4 commits into
RTKConsortium:mainfrom
acoussat:pctgradientdescent

Conversation

@acoussat

@acoussat acoussat commented Sep 1, 2026 •

Copy link
Copy Markdown
Collaborator

Left to do:

  • Update opengate.contrib.protonct.protonct to introduce the changes needed by GradientDescentReconstruction.py (PR opened here: Improve protonct.py for latest PCT developments OpenGATE/opengate#1132)
  • Decide on whether to use LocalTime or GlobalTime for TOF (or both?) (will be applied in a separate PR once next version of GATE is released)
  • Decide whether it is acceptable to use pytest-xdist to run tests in new processes

To be discussed.

RetriggerView in GreptileConfidence Score: 3/5

The PR needs fixes for unequal-length paths and batch-dependent updates before merging.

Findings

  1. P1 Unequal paths break batching ▶
  2. P1 Smaller batches enlarge updates ▶

Summary

Adds gradient-descent reconstruction and a stopping-power table generator.

  • Gradient descent turns paired proton measurements into an RSP map.
  • A new command creates stopping-power data for a chosen material.
  • Proton pairing uses LocalTime for time measurements.
  • Package CI runs Python tests across available CPU cores.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[List-mode files and geometry] --> B[Build proton paths]
  C[Stopping-power table] --> D[Predict time or energy]
  B --> D
  E[Current RSP image] --> D
  D --> F[Compare with measurements]
  F --> G[Update RSP image]
  G --> E
  G --> H[Save images and checkpoint]
Loading

Reviews (6) · Last reviewed commit: "ENH: Improve loading of inputs of pctgra..." · Reviewed by Greptile

@acoussat
acoussat force-pushed the pctgradientdescent branch 2 times, most recently from d6434d6 to e59b5e9 Compare September 15, 2026 14:41
@acoussat acoussat changed the title WIP: Add pctgradientdescent application Add pctgradientdescent application Sep 15, 2026
@acoussat acoussat changed the title Add pctgradientdescent application WIP: Add pctgradientdescent application Oct 15, 2026
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment thread applications/pctgradientdescent/pctgradientdescent.py
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment thread test/pct_gradientdescent_operators_test.py Outdated
Comment thread test/pct_gradientdescent_operators_test.py Outdated
Comment thread applications/pctpairprotons/pctpairprotons.py
Comment thread applications/pctgradientdescent/pctgradientdescent.py
@acoussat
acoussat force-pushed the pctgradientdescent branch 2 times, most recently from 167a324 to 3bb026e Compare October 15, 2026 13:23
Comment thread applications/pctgradientdescent/pctgradientdescent.py
Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment on lines +767 to +768
backward_start_time = time.time()
loss_n.backward()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Smaller batches enlarge updates

Each batch uses a mean loss, but loss_n.backward() adds that full gradient without dividing by batch_per_subset. With SGD, splitting the same subset into two equal batches doubles the update at the same learning rate. Normalize the accumulated gradient so --max-batch-size controls memory use rather than changing the effective learning rate.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed a problem now that batch sizes can be uneven. I changed the accumulation method of the loss function to sum, then divided the total by the size of a batch in order to normalize.

Comment thread applications/pctgradientdescent/pctgradientdescent.py Outdated
Comment on lines +738 to +742
proton_mask = (es_fm > 1.0).detach()
loss_n = (
loss(pq_fm[proton_mask], pq_m[proton_mask])
+ args_info.tv * total_variation(rsp)
) / proton_mask.sum()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stopped batches break reconstruction

If the current image makes every proton in a batch stop, forward_iter clamps their energies to 1.0 and proton_mask excludes them all. The loss then divides by zero, producing a non-finite loss that can stop reconstruction during backward. Skip batches with no valid protons before dividing, while still counting them as consumed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning if that occurs, even though it probably indicates a poor learning rate choice or a wrong initial estimate of the RSP map.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning is useful, but the stopped-batch case still is not safe. processed_protons is incremented only after computing the valid-proton loss; the continue therefore leaves it unchanged. Once the dataloader is exhausted, the outer loop keeps creating exhausted iterators and repeating forever.

Count each fetched batch as consumed before applying the mask, or increment the processed count in the zero-valid-proton branch. Also handle an entirely empty loss_tot explicitly, since torch.tensor(loss_tot).mean() then produces nan and optimizer.step() has no gradients.

Comment on lines +65 to +71
parser.add_argument(
"--hull",
"--quadric",
help="Quadric representing the hull of the object",
type=float,
nargs=10,
required=False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Python hull calls fail

Calling pct.pctgradientdescent with a list for hull fails before reconstruction starts. parse_kwargs turns the list into one comma-separated token, but --hull requires ten separate tokens. Only options with nargs="+" get comma splitting.

Support fixed-length lists in the parser so Python callers can supply a hull.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be fixed once #97 is merged.

Comment on lines +676 to +684
dataloader = torch.utils.data.DataLoader(
dataset,
sampler=RandomSampler(len(dataset)),
batch_size=batch_size,
num_workers=args_info.num_workers,
in_order=False,
persistent_workers=args_info.num_workers > 0,
pin_memory=True,
drop_last=False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unequal paths break batching

ProtonDataset builds each path with a length based on that proton’s detector positions, but this DataLoader uses default batching. When valid list-mode inputs contain different detector distances, a batch can contain unequal-length tensors. Stacking them raises an error and stops reconstruction.

Group equal-length paths, or pad them and exclude padded segments from the model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant