Skip to content

Conversation

@jrobinso
Copy link
Contributor

@jrobinso jrobinso commented Jan 9, 2026

No description provided.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a dedicated HicInteractionTrack subclass to separate HiC-specific behavior from the generic InteractionTrack implementation. The refactoring replaces conditional logic based on the isHIC flag with proper object-oriented inheritance.

Key changes:

  • New HicInteractionTrack subclass handles HiC-specific features (normalization options, contact maps, specialized menus)
  • ContactRecord now stores both raw counts and normalized counts
  • Reservoir sampling added to WrappedInteractionSource for feature count limiting
  • Contact map view UI improvements including throttled slider updates, bin size display, and tooltips

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
TrackLoader.java Routes HiC format files to HicInteractionTrack, other interaction files to InteractionTrack
IGVSessionReader.java Adds HicInteractionTrack to session restoration logic
HicFile.java Updates to use new ContactRecord with normCounts field
ContactRecord.java Extends record to include both counts and normCounts fields with convenience constructor
WrappedInteractionSource.java Implements reservoir sampling for maxFeatureCount limiting
NestedArcRenderer.java Simplifies color handling with default track color and consistent transparency
InteractionTrack.java Removes isHIC conditional logic, adds extensibility hooks for subclasses
HicSource.java Applies normalization at query time, uses normCounts throughout
HicInteractionTrack.java New subclass implementing HiC-specific behavior (zoom filtering, normalization menu, contact map)
ContactMapView.java Adds tooltip display, throttled slider updates, bin size label, and improved color scale management
Comments suppressed due to low confidence (1)

src/main/java/org/igv/bedpe/HicSource.java:175

  • The adjacent region queries on lines 170 and 175 are using "NONE" for normalization, while the main region query on line 164 uses the requested normalization. This creates inconsistency where diagonal contacts will be normalized but adjacent region contacts will not be normalized. All three queries should use the same normalization parameter to ensure consistent data processing.
            List<ContactRecord> adjacentRecords = hicFile.getContactRecords(region1, adjacent, "BP", binSize, "NONE", false);
            records.addAll(adjacentRecords);
        }

        Region adjacent2 = new Region(chr, end, end + (end - start));
        List<ContactRecord> adjacentRecords2 = hicFile.getContactRecords(region1, adjacent2, "BP", binSize, "NONE", false);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 113 to 117
ContactRecord found = cachedRecords.stream()
.filter(r -> (r.bin1() == searchBin1 && r.bin2() == searchBin2) ||
(r.bin1() == searchBin2 && r.bin2() == searchBin1))
.findFirst()
.orElse(null);
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

This tooltip lookup performs a linear search through cachedRecords on every mouse movement, which could be inefficient for large datasets. Consider indexing cachedRecords by bin pairs in a HashMap for O(1) lookup performance instead of streaming through the entire list on each mouseMoved event.

Copilot uses AI. Check for mistakes.

if (x >= 0 && y >= 0) {
Color color = colorScale.getColor(record.counts());
Color color = colorScale.getColor(record.normCounts());
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

Confusing name: method renderMapFromCache also refers to field color (without qualifying it with 'this').

Copilot uses AI. Check for mistakes.
Disable map tooltext -- very costly with current design
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 14 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


for (ContactRecord rec : block.records) {
if (allRecords || (rec.bin1() >= x1 && rec.bin1() < x2 && rec.bin2() >= y1 && rec.bin2() < y2) && rec.counts() > 1) {
if ((allRecords || (rec.bin1() >= x1 && rec.bin1() < x2 && rec.bin2() >= y1 && rec.bin2() < y2)) && rec.counts() > countsTreshold) {
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

There's a spelling error in the parameter name. It should be "countsThreshold" not "countsTreshold" (missing 'h' in 'threshold').

Copilot uses AI. Check for mistakes.

/**
* Hook method for subclasses to filter features based on zoom level.
* Default implementation returns features unchanged.
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The documentation for the hook method doesn't include @param or @return tags. For consistency with Java documentation standards, consider adding "@param features The features to filter", "@param interval The loaded interval", "@param referenceFrame The reference frame", and "@return The filtered list of features" to make the method's contract clearer.

Suggested change
* Default implementation returns features unchanged.
* Default implementation returns features unchanged.
*
* @param features The features to filter
* @param interval The loaded interval
* @param referenceFrame The reference frame
* @return The filtered list of features

Copilot uses AI. Check for mistakes.
* Also updates the slider range based on the min/max of filtered counts.
* Does NOT update the slider position - caller should do that after updating the color scale.
*
* @return the percentile value, or -1 if contactRecords is empty
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The JavaDoc @return tag says "the percentile value, or -1 if contactRecords is empty" but the implementation now returns 10 when contactRecords is empty (line 839). Update the documentation to match the actual return value.

Suggested change
* @return the percentile value, or -1 if contactRecords is empty
* @return the percentile value, or 10 if contactRecords is empty

Copilot uses AI. Check for mistakes.

for (ContactRecord rec : block.records) {
if (allRecords || (rec.bin1() >= x1 && rec.bin1() < x2 && rec.bin2() >= y1 && rec.bin2() < y2) && rec.counts() > 1) {
if ((allRecords || (rec.bin1() >= x1 && rec.bin1() < x2 && rec.bin2() >= y1 && rec.bin2() < y2)) && rec.counts() > countsTreshold) {
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The threshold comparison uses strict greater than (>) which means values equal to the threshold are excluded. However, the parameter name "countsTreshold" (which should be "countsThreshold") suggests this is a minimum threshold. Consider whether this should be >= instead of > for more intuitive threshold behavior, where a threshold of 1 would include records with counts of 1 or more.

Suggested change
if ((allRecords || (rec.bin1() >= x1 && rec.bin1() < x2 && rec.bin2() >= y1 && rec.bin2() < y2)) && rec.counts() > countsTreshold) {
if ((allRecords || (rec.bin1() >= x1 && rec.bin1() < x2 && rec.bin2() >= y1 && rec.bin2() < y2)) && rec.counts() >= countsTreshold) {

Copilot uses AI. Check for mistakes.
Comment on lines 20 to 55
@Override
public List<BedPE> getFeatures(String chr, int start, int end, double bpPerPixel, String normalization, int maxFeatureCount) throws IOException {
Iterator<BedPE> featureIterator = featureSource.getFeatures(chr, start, end);
List<BedPE> list = new ArrayList<>();
while (featureIterator != null && featureIterator.hasNext()) {
list.add(featureIterator.next());
Iterator<BedPE> featureIterator = featureSource.getFeatures(chr, start, end);
List<BedPE> reservoir = new ArrayList<>();

if (featureIterator == null) {
return reservoir;
}

// If maxFeatureCount <= 0 treat as "no limit" and return all features
if (maxFeatureCount <= 0) {
while (featureIterator.hasNext()) {
reservoir.add(featureIterator.next());
}
return reservoir;
}
return list;

int i = 0; // number of items seen so far

while (featureIterator.hasNext()) {
BedPE item = featureIterator.next();
if (i < maxFeatureCount) {
// fill the reservoir
reservoir.add(item);
} else {
// replace elements with decreasing probability
int r = ThreadLocalRandom.current().nextInt(i + 1); // 0..i
if (r < maxFeatureCount) {
reservoir.set(r, item);
}
}
i++;
}

return reservoir;
}
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The reservoir sampling implementation in WrappedInteractionSource adds significant new logic for limiting feature counts. While there's an existing test for HicSource.getFeatures() that verifies the maxFeatureCount parameter works, there are no tests specifically for the reservoir sampling algorithm in WrappedInteractionSource. Consider adding tests to verify: 1) that when maxFeatureCount <= 0, all features are returned, 2) that when there are fewer features than maxFeatureCount, all are returned, 3) that when there are more features than maxFeatureCount, exactly maxFeatureCount features are returned, and 4) that the reservoir sampling produces a statistically uniform distribution over multiple runs.

Copilot uses AI. Check for mistakes.
int binSize,
String normalization,
boolean allRecords,
int countsTreshold) throws IOException {
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

There's a spelling error in the parameter name. It should be "countsThreshold" not "countsTreshold" (missing 'h' in 'threshold').

Copilot uses AI. Check for mistakes.
Comment on lines +793 to +795
int actualBinSize = binSize;
if (frame.getChrName().equalsIgnoreCase("all")) {
actualBinSize *= 1000;
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

When frame.getChrName() is "all", the actualBinSize is multiplied by 1000 (line 795). If binSize is already close to Integer.MAX_VALUE / 1000 (approximately 2.1 million), this could cause integer overflow. Consider using a long for actualBinSize to avoid potential overflow issues, or document the valid range for binSize values.

Copilot uses AI. Check for mistakes.
@jrobinso jrobinso merged commit 8ae555e into main Jan 9, 2026
2 checks passed
@jrobinso jrobinso deleted the hic_track branch January 9, 2026 06:48
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