Skip to content

Tpc distortion correction#4246

Merged
osbornjd merged 4 commits intosPHENIX-Collaboration:masterfrom
hupereir:tpc_distortion_correction
Apr 15, 2026
Merged

Tpc distortion correction#4246
osbornjd merged 4 commits intosPHENIX-Collaboration:masterfrom
hupereir:tpc_distortion_correction

Conversation

@hupereir
Copy link
Copy Markdown
Contributor

@hupereir hupereir commented Apr 13, 2026

This PR

  • moves loading distortion correction histograms from TFile and saving to TFile to TpcDistortionCorrectionContainer, to avoid code duplication
  • puts filling guarding bins in distortion correction histogram to a dedicated method from TpcSpaceChargeReconstructionHelper so that it can be reused in distortion corrections generation macros without duplicating the code

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work for users)
  • Requiring change in macros repository (Please provide links to the macros pull request in the last section)
  • I am a member of GitHub organization of sPHENIX Collaboration, EIC, or ECCE (contact Chris Pinkenburg to join)

What kind of change does this PR introduce? (Bug fix, feature, ...)

TODOs (if applicable)

Links to other PRs in macros and calibration repositories (if applicable)

TPC Distortion Correction: Consolidate Histogram I/O and Extract Guarding Bin Logic

Motivation / Context

Refactor TPC distortion-correction code to remove duplicated ROOT file I/O and to centralize guarding-bin filling logic so macros and reconstruction code reuse the same behavior. This improves maintainability and reduces risk of divergent implementations of histogram loading/saving and guard-bin population.

Key changes

  • Added TpcDistortionCorrectionContainer::load_histograms(const std::string&) and ::save_histograms(const std::string&) to encapsulate ROOT file I/O for the distortion-correction histograms (m_hDRint, m_hDPint, m_hDZint, m_hentries) for both +z and -z sectors.
  • Updated TpcLoadDistortionCorrection::InitRun to delegate histogram loading to TpcDistortionCorrectionContainer::load_histograms(...) instead of performing ad-hoc TFile opening and per-histogram Get/assert logic.
  • Updated TpcSpaceChargeMatrixInversion::save_distortion_corrections(...) to delegate persistence to TpcDistortionCorrectionContainer::save_histograms(...) rather than manually creating a TFile and writing histograms.
  • Extracted guarding-bin population into TpcSpaceChargeReconstructionHelper::fill_guarding_bins(TH3*), and updated add_guarding_bins(...) to call it. The new method derives bin counts from the provided TH3 and implements 2π periodicity in phi and nearest-neighbor replication for r and z.
  • Build: registered TpcDistortionCorrectionContainer.cc in offline/packages/tpc/Makefile.am.

Potential risk areas

  • IO/format expectations: The new load/save methods centralize assumptions about histogram names, types and ROOT file layout. If naming conventions or file layouts differ in existing files, loads may fail or assert; verify compatibility with existing calibration/production files.
  • Error handling: load_histograms currently aborts on missing files/histograms (uses assert/exit in the implemented changes). Ensure callers and CI handle these failure modes appropriately and consider more graceful error propagation/logging.
  • Behavioral equivalence: The guarding-bin logic was refactored to compute bin counts from the TH3 object; verify that the produced guard-bin contents match previous behavior for all existing distortion-correction histograms.
  • Thread-safety & performance: Centralizing I/O and modifying when files are opened/written may change concurrency characteristics; review usage in multi-threaded workflows and any performance implications when writing/reading large histograms.

Possible future improvements

  • Add unit/integration tests that validate round-trip load/save and verify guarding-bin results against known-good histograms.
  • Replace abrupt aborts/asserts with error-return or exception-based diagnostics and clearer logging to aid CI and offline debugging.
  • Document the expected ROOT histogram names, types and dimensionality that load_histograms()/save_histograms() rely on, and provide an API for alternative naming/layouts or versioning.
  • Consider providing a small utility or header-only helper to expose fill_guarding_bins to external calibration macros and scripts (if not already intended) and add thread-safe I/O helpers if needed.

Note: AI-assisted summary — AI can make mistakes; please validate file-format, error-handling and numeric equivalence of guarding-bin logic when reviewing.

This is the correct way:
- 2pi invariance is used for phi axis
- identical content is used for r and z axis.
…DistortionCorrectionContainer, to avoid future code duplication.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Refactors TPC histogram I/O into TpcDistortionCorrectionContainer::load_histograms / save_histograms and extracts guarding-bin population into TpcSpaceChargeReconstructionHelper::fill_guarding_bins, updating call sites and adding the new implementation file to the build. Only behaviorally equivalent encapsulation and delegation changes were introduced.

Changes

Cohort / File(s) Summary
Build
offline/packages/tpc/Makefile.am
Added TpcDistortionCorrectionContainer.cc to libtpc_la_SOURCES so the new implementation is compiled into libtpc.
Distortion container I/O
offline/packages/tpc/TpcDistortionCorrectionContainer.h, offline/packages/tpc/TpcDistortionCorrectionContainer.cc
Added load_histograms(const std::string&) and save_histograms(const std::string&) implementations: load_histograms opens a ROOT file and loads expected histograms (assert on missing); save_histograms writes stored histograms to a ROOT file.
Call-site delegation
offline/packages/tpc/TpcLoadDistortionCorrection.cc, offline/packages/tpccalib/TpcSpaceChargeMatrixInversion.cc
Replaced inline ROOT file I/O in InitRun and save_distortion_corrections with delegated calls to the container's load_histograms / save_histograms, removing direct TFile open/write logic and per-histogram retrieval/writes from those functions.
Guarding-bin extraction
offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.h, offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.cc
Extracted guarding-bin population into new static void fill_guarding_bins(TH3* source); add_guarding_bins now constructs the output histogram and delegates guard filling to fill_guarding_bins, using derived bin counts and copying rules (periodic phi, boundary replication r/z).

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6fc37e2c-f9ac-4db7-8bfe-9a41ca56d523

📥 Commits

Reviewing files that changed from the base of the PR and between a1e339e and 9029c58.

📒 Files selected for processing (6)
  • offline/packages/tpc/Makefile.am
  • offline/packages/tpc/TpcDistortionCorrectionContainer.h
  • offline/packages/tpc/TpcLoadDistortionCorrection.cc
  • offline/packages/tpccalib/TpcSpaceChargeMatrixInversion.cc
  • offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.cc
  • offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.h

Comment thread offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.cc
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@sphenix-jenkins-ci
Copy link
Copy Markdown

Build & test report

Report for commit 9029c588ee68240e24e2d09b4ad132f4aa8b6c80:
Jenkins on fire


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

@sphenix-jenkins-ci
Copy link
Copy Markdown

Build & test report

Report for commit da0b57625ac101fc8b541d00fe4a249234c737d8:
Jenkins on fire


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

@hupereir
Copy link
Copy Markdown
Contributor Author

The clang-tidy error is a false positive.
The QA failures I think are due to lustre

@osbornjd
Copy link
Copy Markdown
Contributor

The QA failures aren't from lustre, sometimes for reasons unknown to me the jobs are unable to access a G4 header file. I restarted the build

@sphenix-jenkins-ci
Copy link
Copy Markdown

Build & test report

Report for commit da0b57625ac101fc8b541d00fe4a249234c737d8:
Jenkins on fire


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ef0474dd-bb5f-4621-8ba5-3bed6242c290

📥 Commits

Reviewing files that changed from the base of the PR and between 9029c58 and f879f90.

📒 Files selected for processing (2)
  • offline/packages/tpc/TpcDistortionCorrectionContainer.cc
  • offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.cc
🚧 Files skipped from review as they are similar to previous changes (1)
  • offline/packages/tpccalib/TpcSpaceChargeReconstructionHelper.cc

Comment on lines +21 to +38
auto *distortion_tfile = TFile::Open(source.c_str());
if (!distortion_tfile)
{
std::cout << "TpcDistortionCorrectionContainer::load_histograms - cannot open " << source << std::endl;
exit(1);
}

const std::array<const std::string, 2> extension = {{"_negz", "_posz"}};
for (int j = 0; j < 2; ++j)
{
m_hDPint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionP")+extension[j]).c_str()));
assert(m_hDPint[j]);
m_hDRint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionR")+extension[j]).c_str()));
assert(m_hDRint[j]);
m_hDZint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionZ")+extension[j]).c_str()));
assert(m_hDZint[j]);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the class definition and inheritance chain
fd 'TpcDistortionCorrectionContainer' --type f | head -20

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 190


🏁 Script executed:

# Check the header file to understand the class
cat offline/packages/tpc/TpcDistortionCorrectionContainer.h

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 1634


🏁 Script executed:

# Search for similar TFile::Open patterns in the TPC package
rg 'TFile::Open' offline/packages/tpc/ -A 3 -B 1 | head -100

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 1610


🏁 Script executed:

# Check if TpcDistortionCorrectionContainer inherits from SubsysReco
rg 'class TpcDistortionCorrectionContainer' offline/ -A 5

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 3145


🏁 Script executed:

# Verify TFile lifecycle in this specific context - check if histograms survive file closure
rg -A 10 'load_histograms' offline/packages/tpc/TpcDistortionCorrectionContainer.cc | head -25

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 884


🏁 Script executed:

# Check how the container is used - does it hold the file open or expect file to close?
rg 'TpcDistortionCorrectionContainer' offline/packages/ --type h -A 3 | grep -E '(load_histograms|m_hD)' | head -30

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 190


🏁 Script executed:

# Verify save_histograms implementation for comparison
sed -n '40,55p' offline/packages/tpc/TpcDistortionCorrectionContainer.cc

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 675


Fix TFile resource leak in load_histograms to match save_histograms pattern

The raw TFile* opened at line 21 is never closed. This leaks file handles, especially when load_histograms() is called repeatedly. The same class already uses std::unique_ptr<TFile> in save_histograms() (line 46), making this inconsistency a clear correctness gap.

Additionally, histogram validation relies on assert() (lines 32, 34, 36), which silently fails in release builds (NDEBUG), allowing null pointers to propagate.

Proposed fix
 void TpcDistortionCorrectionContainer::load_histograms( const std::string& source )
 {
   std::cout << "TpcDistortionCorrectionContainer::load_histograms - reading corrections from " << source << std::endl;
-  auto *distortion_tfile = TFile::Open(source.c_str());
+  std::unique_ptr<TFile> distortion_tfile(TFile::Open(source.c_str()));
   if (!distortion_tfile)
   {
     std::cout << "TpcDistortionCorrectionContainer::load_histograms - cannot open " << source << std::endl;
     exit(1);
   }

Per coding guidelines, prioritize correctness and memory safety in .cc files.

Comment on lines +31 to +36
m_hDPint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionP")+extension[j]).c_str()));
assert(m_hDPint[j]);
m_hDRint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionR")+extension[j]).c_str()));
assert(m_hDRint[j]);
m_hDZint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionZ")+extension[j]).c_str()));
assert(m_hDZint[j]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "TpcDistortionCorrectionContainer.cc" -type f

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 136


🏁 Script executed:

head -50 offline/packages/tpc/TpcDistortionCorrectionContainer.cc | cat -n

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 2309


🏁 Script executed:

sed -n '25,45p' offline/packages/tpc/TpcDistortionCorrectionContainer.cc | cat -n

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 1182


🏁 Script executed:

# Check how m_hDPint, m_hDRint, m_hDZint are used elsewhere in the file
rg "m_hDPint|m_hDRint|m_hDZint" offline/packages/tpc/TpcDistortionCorrectionContainer.cc -A 2 -B 1

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 623


🏁 Script executed:

# Check the header file for member variable definitions
find . -name "TpcDistortionCorrectionContainer.h" -type f

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 135


🏁 Script executed:

# Examine the header file to understand member variables
cat offline/packages/tpc/TpcDistortionCorrectionContainer.h | head -100

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 1634


Replace assert-only histogram checks with runtime validation

Lines 32/34/36 use assert for histogram validity, which is compiled out in release builds (-DNDEBUG); missing histograms silently flow as null pointers into the m_hDPint, m_hDRint, and m_hDZint arrays, then cause memory safety issues when dereferenced in save_histograms().

Proposed fix
+  auto get_hist_or_throw = [&](const std::string& name) -> TH1*
+  {
+    auto* h = dynamic_cast<TH1*>(distortion_tfile->Get(name.c_str()));
+    if (!h) {
+      throw std::runtime_error(
+        "TpcDistortionCorrectionContainer::load_histograms - missing histogram " + name);
+    }
+    return h;
+  };
+
   const std::array<const std::string, 2> extension = {{"_negz", "_posz"}};
   for (int j = 0; j < 2; ++j)
   {
-    m_hDPint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionP")+extension[j]).c_str()));
-    assert(m_hDPint[j]);
-    m_hDRint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionR")+extension[j]).c_str()));
-    assert(m_hDRint[j]);
-    m_hDZint[j] = dynamic_cast<TH1*>(distortion_tfile->Get((std::string("hIntDistortionZ")+extension[j]).c_str()));
-    assert(m_hDZint[j]);
+    m_hDPint[j] = get_hist_or_throw(std::string("hIntDistortionP") + extension[j]);
+    m_hDRint[j] = get_hist_or_throw(std::string("hIntDistortionR") + extension[j]);
+    m_hDZint[j] = get_hist_or_throw(std::string("hIntDistortionZ") + extension[j]);
   }

Comment on lines +45 to +47
std::unique_ptr<TFile> outputfile(TFile::Open(destination.c_str(), "RECREATE"));
outputfile->cd();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "TpcDistortionCorrectionContainer.cc" -type f

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 136


🏁 Script executed:

git ls-files | grep -i "tpcdistortion"

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 386


🏁 Script executed:

sed -n '40,55p' offline/packages/tpc/TpcDistortionCorrectionContainer.cc

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 675


🏁 Script executed:

wc -l offline/packages/tpc/TpcDistortionCorrectionContainer.cc

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 137


🏁 Script executed:

cat -n offline/packages/tpc/TpcDistortionCorrectionContainer.cc | head -61

Repository: sPHENIX-Collaboration/coresoftware

Length of output: 2506


🌐 Web query:

ROOT TFile::Open() return behavior null zombie failure

💡 Result:

TFile::Open is a static method that returns a TFile pointer (TFile*) on success or NULL (0) on complete failure (e.g., unsupported URL, no plugin handler found). If a TFile* is returned, check IsZombie to determine if the file opened successfully: true means failure (invalid ROOT file, cannot open, etc.), during which MakeZombie sets the kZombie bit. The constructor behaves similarly: creates a zombie object on failure, detectable via IsZombie.

Citations:


Guard output-file open failure before dereference

Line 46 dereferences outputfile unconditionally. If TFile::Open() returns null (complete failure) or a "zombie" object (partial failure like permission denied or write error), this causes a null dereference or silent data loss crash.

The same file demonstrates the correct pattern in load_histograms() (lines 21–26), where it checks the result before use. Add a guard:

Proposed fix
   std::unique_ptr<TFile> outputfile(TFile::Open(destination.c_str(), "RECREATE"));
+  if (!outputfile || outputfile->IsZombie())
+  {
+    throw std::runtime_error(
+      "TpcDistortionCorrectionContainer::save_histograms - cannot create " + destination);
+  }
   outputfile->cd();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
std::unique_ptr<TFile> outputfile(TFile::Open(destination.c_str(), "RECREATE"));
outputfile->cd();
std::unique_ptr<TFile> outputfile(TFile::Open(destination.c_str(), "RECREATE"));
if (!outputfile || outputfile->IsZombie())
{
throw std::runtime_error(
"TpcDistortionCorrectionContainer::save_histograms - cannot create " + destination);
}
outputfile->cd();

@sphenix-jenkins-ci
Copy link
Copy Markdown

Build & test report

Report for commit f879f900878a9eb927d176185e58d64e76e25e99:
Jenkins on fire


Automatically generated by sPHENIX Jenkins continuous integration
sPHENIX             jenkins.io

@osbornjd osbornjd merged commit 9ada433 into sPHENIX-Collaboration:master Apr 15, 2026
19 of 22 checks passed
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.

2 participants