-
Notifications
You must be signed in to change notification settings - Fork 403
Hic track #1806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hic track #1806
Changes from all commits
6e2bd72
30eb974
8ed0b66
b9f22ac
9d81ce2
e6d0baf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| package org.igv.bedpe; | ||
|
|
||
| import org.igv.hic.HicFile; | ||
| import org.igv.renderer.ContinuousColorScale; | ||
| import org.igv.track.RenderContext; | ||
| import org.igv.track.TrackClickEvent; | ||
| import org.igv.ui.panel.FrameManager; | ||
| import org.igv.ui.panel.IGVPopupMenu; | ||
| import org.igv.ui.panel.ReferenceFrame; | ||
| import org.igv.util.ResourceLocator; | ||
| import org.w3c.dom.Document; | ||
| import org.w3c.dom.Element; | ||
|
|
||
| import javax.swing.*; | ||
| import java.awt.*; | ||
| import java.util.List; | ||
|
|
||
| /** | ||
| * Subclass of InteractionTrack for HiC format files. | ||
| * Handles HiC-specific behavior like normalization, contact map views, and zoom-based filtering. | ||
| */ | ||
| public class HicInteractionTrack extends InteractionTrack { | ||
|
|
||
| public HicInteractionTrack() { | ||
| super(); | ||
| } | ||
|
|
||
| public HicInteractionTrack(ResourceLocator locator, HicSource source) { | ||
| super(locator, source); | ||
| // HiC-specific defaults | ||
| maxFeatureCount = 5000; | ||
| graphType = GraphType.NESTED_ARC; | ||
| useScore = true; | ||
| setColor(Color.red); | ||
| } | ||
|
|
||
| @Override | ||
| protected List<BedPE> filterFeaturesForZoom(List<BedPE> features, LoadedInterval interval, ReferenceFrame referenceFrame) { | ||
| // In HiC mode we limit interactions to those in view plus a margin of one screen width to either side. | ||
| // If zooming in this means we have to filter the features from the previous zoom level that are outside | ||
| // of this range. Not doing so leads to inconsistent rendering when loading for the current zoom | ||
| // completes and repaints. | ||
| if (interval.zoom() < referenceFrame.getZoom()) { | ||
| int start = (int) referenceFrame.getOrigin(); | ||
| int end = (int) referenceFrame.getEnd(); | ||
| int w = (end - start); | ||
| int finalStart = start - w; | ||
| int finalEnd = end + w; | ||
| return features.stream() | ||
| .takeWhile(f -> f.getStart() <= finalEnd) | ||
| .filter(f -> f.getEnd() >= finalStart) | ||
| .toList(); | ||
| } | ||
| return features; | ||
| } | ||
|
|
||
| @Override | ||
| protected void addFormatSpecificMenuItems(IGVPopupMenu menu, TrackClickEvent te) { | ||
| // Add normalization options for HiC tracks | ||
| List<String> normalizationTypes = featureSource.getNormalizationTypes(); | ||
| if (normalizationTypes != null && normalizationTypes.size() > 1) { | ||
| menu.addSeparator(); | ||
| menu.add(new JLabel("<html><b>Normalization</b>")); | ||
| ButtonGroup normGroup = new ButtonGroup(); | ||
| for (String type : normalizationTypes) { | ||
| String label = normalizationLabels.getOrDefault(type, type); | ||
| JRadioButtonMenuItem normItem = new JRadioButtonMenuItem(label); | ||
| normItem.setSelected(type.equals(normalization)); | ||
| normItem.addActionListener(e -> { | ||
| this.normalization = type; | ||
| if (contactMapView != null) { | ||
| contactMapView.setNormalization(type); | ||
| } | ||
| this.repaint(); | ||
| }); | ||
| normGroup.add(normItem); | ||
| menu.add(normItem); | ||
| } | ||
| } | ||
|
|
||
| menu.addSeparator(); | ||
| JMenuItem mapItem = new JMenuItem("Contact Map View..."); | ||
| mapItem.setEnabled(contactMapView == null && !FrameManager.isGeneListMode()); | ||
| mapItem.addActionListener(e -> { | ||
| ReferenceFrame frame = te.getFrame() != null ? te.getFrame() : FrameManager.getDefaultFrame(); | ||
| if (contactMapView == null) { | ||
| ContinuousColorScale colorScale = this.getColorScale(); | ||
| HicFile hicFile = ((HicSource) featureSource).getHicFile(); | ||
| ContactMapView.showPopup(this, hicFile, normalization, frame, colorScale.getMaxColor()); | ||
| } | ||
| }); | ||
| menu.add(mapItem); | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean supportsGraphTypeSelection() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean supportsCircularView() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean supportsAutoscaleMenu() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| protected boolean supportsFeatureWindowMenu() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public void marshalXML(Document document, Element element) { | ||
| super.marshalXML(document, element); | ||
|
|
||
| String nviString = ((HicSource) featureSource).getNVIString(); | ||
| if (nviString != null) { | ||
| element.setAttribute("nvi", nviString); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void unmarshalXML(Element element, Integer version) { | ||
| super.unmarshalXML(element, version); | ||
|
|
||
| if (element.hasAttribute("nvi")) { | ||
| String nviString = element.getAttribute("nvi"); | ||
| ((HicSource) featureSource).setNVIString(nviString); | ||
| } | ||
| } | ||
| } | ||
|
|
||
jrobinso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,10 @@ | |
| import org.igv.hic.Region; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.*; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.Comparator; | ||
| import java.util.List; | ||
| import java.util.concurrent.ThreadLocalRandom; | ||
|
|
||
| /** | ||
|
|
@@ -36,7 +39,7 @@ public List<BedPE> getFeatures(String chr, int start, int end, double bpPerPixel | |
|
|
||
| final int binSize = hicFile.getBinSize(chr, bpPerPixel); | ||
|
|
||
| List<ContactRecord> records = getRecords(chr, start, end, binSize); | ||
| List<ContactRecord> records = getRecords(chr, start, end, binSize, normalization); | ||
|
|
||
| if (records.isEmpty()) { | ||
| return Collections.emptyList(); | ||
|
|
@@ -52,15 +55,15 @@ public List<BedPE> getFeatures(String chr, int start, int end, double bpPerPixel | |
| int b1 = rec.bin1(); | ||
| int b2 = rec.bin2(); | ||
| if (Math.abs(b1 - b2) > this.binThreshold) { | ||
| values.add(rec.counts()); | ||
| values.add(rec.normCounts()); | ||
| } | ||
| if (b1 < binMin) binMin = b1; | ||
| if (b2 < binMin) binMin = b2; | ||
| if (b1 > binMax) binMax = b1; | ||
| if (b2 > binMax) binMax = b2; | ||
| } | ||
|
|
||
| if(values.isEmpty()) { | ||
| if (values.isEmpty()) { | ||
| return Collections.emptyList(); | ||
| } | ||
|
|
||
|
|
@@ -78,7 +81,7 @@ public List<BedPE> getFeatures(String chr, int start, int end, double bpPerPixel | |
| int bin2 = rec.bin2(); | ||
| if (Math.abs(bin1 - bin2) <= this.binThreshold) continue; | ||
|
|
||
| float counts = rec.counts(); | ||
| float counts = rec.normCounts(); | ||
| if (counts > threshold) { | ||
| significantRecords.add(rec); | ||
| } else if (counts == threshold) { | ||
|
|
@@ -107,48 +110,17 @@ public List<BedPE> getFeatures(String chr, int start, int end, double bpPerPixel | |
| } | ||
| } | ||
|
|
||
| double[] normVector = null; | ||
| boolean useNormalization = normalization != null && !"NONE".equals(normalization); | ||
| if (useNormalization) { | ||
| NormalizationVector nv = hicFile.getNormalizationVector(normalization, chr, "BP", binSize); | ||
| if (nv == null) { | ||
| useNormalization = false; | ||
| } else { | ||
| normVector = nv.getValues(binMin, binMax); | ||
| if (normVector == null) { | ||
| useNormalization = false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Convert contact records to features | ||
| List<BedPE> features = new ArrayList<>(); | ||
| String c = getChromosomeNameFromGenome(chr); | ||
| for (ContactRecord rec : significantRecords) { | ||
|
|
||
| int bin1 = rec.bin1(); | ||
| int bin2 = rec.bin2(); | ||
| float value = rec.counts(); | ||
| if (useNormalization) { | ||
| double nvnv = 1; | ||
| try { | ||
| nvnv = normVector[bin1 - binMin] * normVector[bin2 - binMin]; | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| if (!Double.isNaN(nvnv)) { | ||
| value /= nvnv; | ||
| } else { | ||
| continue; | ||
| } | ||
| } | ||
|
|
||
| int start1 = bin1 * binSize; | ||
| int start1 = rec.bin1() * binSize; | ||
| int end1 = start1 + binSize; | ||
| int start2 = bin2 * binSize; | ||
| int start2 = rec.bin2() * binSize; | ||
| int end2 = start2 + binSize; | ||
|
|
||
| HicFeature f = new HicFeature(c, start1, end1, c, start2, end2, rec.counts(), value); | ||
| HicFeature f = new HicFeature(c, start1, end1, c, start2, end2, rec.counts(), rec.normCounts()); | ||
| int score = (max > min) ? (int) Math.round(200 + Math.min(Math.max((f.getValue() - min) / (max - min), 0), 1) * 600) : 800; | ||
| f.setScore(score); | ||
| features.add(f); | ||
|
|
@@ -181,19 +153,19 @@ public boolean hasNormalizationVector(String type, String chr, double bpPerPixel | |
| * @return | ||
| * @throws IOException | ||
| */ | ||
| private List<ContactRecord> getRecords(String chr, int start, int end, int binSize) throws IOException { | ||
| private List<ContactRecord> getRecords(String chr, int start, int end, int binSize, String normalization) throws IOException { | ||
|
|
||
| final Region region1 = new Region(chr, start, end); | ||
| List<ContactRecord> records = hicFile.getContactRecords( | ||
| region1, | ||
| region1, | ||
| "BP", | ||
| binSize, | ||
| "NONE", | ||
| normalization, | ||
| false | ||
| ); | ||
|
|
||
| if(start > 0) { | ||
| if (start > 0) { | ||
| Region adjacent = new Region(chr, Math.max(0, start - (end - start)), start); | ||
| List<ContactRecord> adjacentRecords = hicFile.getContactRecords(region1, adjacent, "BP", binSize, "NONE", false); | ||
|
||
| records.addAll(adjacentRecords); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The menu item text says "Contact Map View..." with an ellipsis, but the old code on line 482 in the InteractionTrack.java said "Open Contact Map View" without ellipsis. The ellipsis typically indicates that clicking the menu item will open a dialog or additional window for more input. In this case, it directly opens the contact map view without additional prompts, so the ellipsis may be misleading. Consider using "Open Contact Map View" or "Contact Map View" without the ellipsis.