diff --git a/src/main/java/org/igv/bedpe/HicSource.java b/src/main/java/org/igv/bedpe/HicSource.java index 4159e5350f..1aaab47ed8 100644 --- a/src/main/java/org/igv/bedpe/HicSource.java +++ b/src/main/java/org/igv/bedpe/HicSource.java @@ -34,7 +34,7 @@ public HicSource(String path, Genome genome) throws IOException { @Override public List getFeatures(String chr, int start, int end, double bpPerPixel, String normalization, int maxFeatureCount) throws IOException { - final int binSize = getBinSize(bpPerPixel); + final int binSize = getBinSize(chr, bpPerPixel); List records = getRecords(chr, start, end, binSize); @@ -159,7 +159,13 @@ public List getFeatures(String chr, int start, int end, double bpPerPixel return features; } - private int getBinSize(double bpPerPixel) { + private int getBinSize(String chr, double bpPerPixel) { + + if("all".equalsIgnoreCase(chr)) { + // Special case, the whole-genome psuedo-chromosome all has a single resolution + return hicFile.getWGResolution(); + } + // choose resolution List resolutions = hicFile.getBpResolutions(); int index = 0; @@ -180,7 +186,7 @@ public List getNormalizationTypes() { @Override public boolean hasNormalizationVector(String type, String chr, double bpPerPixel) { - return hicFile.hasNormalizationVector(type, chr, "BP", getBinSize(bpPerPixel)); + return hicFile.hasNormalizationVector(type, chr, "BP", getBinSize(chr, bpPerPixel)); } /** diff --git a/src/main/java/org/igv/encode/EncodeTrackChooserFactory.java b/src/main/java/org/igv/encode/EncodeTrackChooserFactory.java index d84c4d5176..f733cee3a0 100644 --- a/src/main/java/org/igv/encode/EncodeTrackChooserFactory.java +++ b/src/main/java/org/igv/encode/EncodeTrackChooserFactory.java @@ -4,6 +4,17 @@ package org.igv.encode; +import org.igv.Globals; +import org.igv.logging.LogManager; +import org.igv.logging.Logger; +import org.igv.prefs.Constants; +import org.igv.prefs.PreferencesManager; +import org.igv.ui.IGV; +import org.igv.ui.action.BrowseEncodeAction; +import org.igv.util.Pair; +import org.igv.util.ParsingUtils; + +import javax.swing.text.NumberFormatter; import java.awt.*; import java.io.BufferedReader; import java.io.IOException; @@ -12,17 +23,6 @@ import java.util.*; import java.util.List; import java.util.stream.Collectors; -import javax.swing.*; -import javax.swing.text.NumberFormatter; - -import org.igv.logging.*; -import org.igv.Globals; -import org.igv.prefs.Constants; -import org.igv.prefs.PreferencesManager; -import org.igv.ui.IGV; -import org.igv.ui.action.BrowseEncodeAction; -import org.igv.util.Pair; -import org.igv.util.ParsingUtils; /** * @author Jim Robinson @@ -35,7 +35,9 @@ public class EncodeTrackChooserFactory { private static NumberFormatter numberFormatter = new NumberFormatter(); private static String ENCODE_HOST = "https://www.encodeproject.org"; - private static Set filteredColumns = new HashSet(Arrays.asList("ID", "Assembly", "HREF", "path")); + private static Set filteredColumns = new HashSet<>(Arrays.asList( + "ID", "Assembly", "HREF", "path", + "url", "Project", "name", "color", "altColor")); private static List filteredExtensions = Arrays.asList("tsv", "tsv.gz"); @@ -53,6 +55,8 @@ public class EncodeTrackChooserFactory { static HashSet ucscSupportedGenomes = new HashSet<>(Arrays.asList("hg19", "mm9")); static HashSet supportedGenomes = new HashSet<>( Arrays.asList("ce10", "ce11", "dm3", "dm6", "GRCh38", "hg19", "mm10", "mm9")); + static HashSet hicGenomes = new HashSet<>( + Arrays.asList("GRCh38", "hg19", "mm10", "mm9")); /** * Return a new or cached instance of a track chooser for the given genome and type. @@ -79,7 +83,6 @@ public synchronized static TrackChooser getInstance(String genomeId, BrowseEncod instance = new TrackChooser(parent, headings, rows, title); instanceMap.put(key, instance); } - return instance; } @@ -87,6 +90,8 @@ private static String getDialogTitle(String genomeId, BrowseEncodeAction.Type ty if (type == BrowseEncodeAction.Type.UCSC) { return "ENCODE data hosted at UCSC (2012)"; + } else if (type == BrowseEncodeAction.Type.FOUR_DN) { + return "4DN"; } else { switch (type) { case SIGNALS_CHIP: @@ -103,6 +108,10 @@ public static boolean genomeSupportedUCSC(String genomeId) { return genomeId != null && ucscSupportedGenomes.contains(getEncodeGenomeID(genomeId)); } + public static boolean hicSupportedUCSC(String genomeId) { + return genomeId != null && hicGenomes.contains(getEncodeGenomeID(genomeId)); + } + public static boolean genomeSupported(String genomeId) { return genomeId != null && supportedGenomes.contains(getEncodeGenomeID(genomeId)); } @@ -128,26 +137,16 @@ private static Pair, List> getEncodeFileRecords(String if (is == null) { return null; } - Pair, List> headingRecordPair = parseRecords(is, type, genomeId); - - if (IGV.hasInstance()) { - Set loadedPaths = IGV.getInstance().getDataResourceLocators().stream() - .map(rl -> rl.getPath()) - .collect(Collectors.toSet()); - - for (FileRecord fileRecord : headingRecordPair.getSecond()) { - if (loadedPaths.contains(fileRecord.getPath())) { - fileRecord.setSelected(true); - } - } - } - return headingRecordPair; + return parseRecords(is, type, genomeId); } } private static InputStream getStreamFor(String genomeId, BrowseEncodeAction.Type type) throws IOException { if (type == BrowseEncodeAction.Type.UCSC) { return EncodeTrackChooserFactory.class.getResourceAsStream("encode." + genomeId + ".txt"); + } else if (type == BrowseEncodeAction.Type.FOUR_DN) { + String url = PreferencesManager.getPreferences().get(Constants.FOUR_DN_FILELIST_URL) + "4dn_" + genomeId + "_tracks.txt"; + return ParsingUtils.openInputStream(url); } else { String root = PreferencesManager.getPreferences().get(Constants.ENCODE_FILELIST_URL) + genomeId + "."; String url = null; @@ -158,6 +157,9 @@ private static InputStream getStreamFor(String genomeId, BrowseEncodeAction.Type case SIGNALS_OTHER: url = root + "signals.other.txt.gz"; break; + case HIC: + url = root + "hic.txt.gz"; + break; case OTHER: url = root + "other.txt.gz"; break; @@ -175,7 +177,10 @@ private static Pair parseRecords(InputStream is, BrowseEncodeAction.Type type, S String[] headers = Globals.tabPattern.split(reader.readLine()); - int pathColumn = type == BrowseEncodeAction.Type.UCSC ? 0 : Arrays.asList(headers).indexOf("HREF"); + int pathColumn = switch (type) { + case UCSC, FOUR_DN -> 0; + default -> Arrays.asList(headers).indexOf("HREF"); + }; List records = new ArrayList<>(20000); String nextLine; @@ -183,13 +188,16 @@ private static Pair parseRecords(InputStream is, BrowseEncodeAction.Type type, S if (!nextLine.startsWith("#")) { String[] tokens = Globals.tabPattern.split(nextLine, -1); - String path = type == BrowseEncodeAction.Type.UCSC ? tokens[pathColumn] : ENCODE_HOST + tokens[pathColumn]; + String path = switch (type) { + case UCSC, FOUR_DN -> tokens[pathColumn]; + default -> ENCODE_HOST + tokens[pathColumn]; + }; if (filteredExtensions.stream().anyMatch(e -> path.endsWith(e))) { continue; } - Map attributes = new LinkedHashMap<>(); + Map attributes = new HashMap<>(); for (int i = 0; i < headers.length; i++) { String value = i < tokens.length ? tokens[i] : ""; if (value.length() > 0) { @@ -198,7 +206,6 @@ private static Pair parseRecords(InputStream is, BrowseEncodeAction.Type type, S } final FileRecord record = new FileRecord(path, attributes); records.add(record); - } } diff --git a/src/main/java/org/igv/encode/FileRecord.java b/src/main/java/org/igv/encode/FileRecord.java index 3fc3decd4d..1aff0690ca 100644 --- a/src/main/java/org/igv/encode/FileRecord.java +++ b/src/main/java/org/igv/encode/FileRecord.java @@ -42,10 +42,6 @@ public String getAttributeValue(String name) { return value; } - public Collection getAttributeNames() { - return attributes.keySet(); - } - public Map getAttributes() { return attributes; } diff --git a/src/main/java/org/igv/encode/TrackChooser.java b/src/main/java/org/igv/encode/TrackChooser.java index ba5dbaee62..0db9b50607 100644 --- a/src/main/java/org/igv/encode/TrackChooser.java +++ b/src/main/java/org/igv/encode/TrackChooser.java @@ -8,6 +8,7 @@ import org.igv.Globals; import org.igv.logging.LogManager; import org.igv.logging.Logger; +import org.igv.ui.IGV; import org.igv.util.Pair; import javax.swing.*; @@ -24,6 +25,7 @@ import java.text.ParseException; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -109,6 +111,18 @@ public List getAllRecords() { return model.getRecords(); } + public void updateRecordSelectionState() { + + Set loadedPaths = IGV.getInstance().getDataResourceLocators().stream() + .map(rl -> rl.getPath()) + .collect(Collectors.toSet()); + + for (FileRecord fileRecord : model.getRecords()) { + if (loadedPaths.contains(fileRecord.getPath())) { + fileRecord.setSelected(true); + } + } + } private class RegexFilter extends RowFilter { diff --git a/src/main/java/org/igv/encode/TrackChooserModel.java b/src/main/java/org/igv/encode/TrackChooserModel.java index c928fc610d..2472d8599e 100644 --- a/src/main/java/org/igv/encode/TrackChooserModel.java +++ b/src/main/java/org/igv/encode/TrackChooserModel.java @@ -108,10 +108,9 @@ public void updateSelections() { for (int row = 0; row < records.size(); row++) { FileRecord record = records.get(row); - if (loadedPaths.contains(record.getPath())) { - record.setSelected(true); - } + record.setSelected(loadedPaths.contains(record.getPath())); } + fireTableDataChanged(); } public List getRecords() { diff --git a/src/main/java/org/igv/hic/HicFile.java b/src/main/java/org/igv/hic/HicFile.java index dc98005799..5f82bef931 100644 --- a/src/main/java/org/igv/hic/HicFile.java +++ b/src/main/java/org/igv/hic/HicFile.java @@ -6,6 +6,7 @@ import org.igv.logging.LogManager; import org.igv.logging.Logger; import org.igv.util.CompressionUtils; +import org.igv.util.collections.CaseInsensitiveMap; import org.igv.util.collections.LRUCache; import org.igv.util.stream.IGVSeekableStreamFactory; @@ -56,7 +57,8 @@ public int getByteSize() { private Map expectedValueVectors; private Map attributes; private List chromosomes = new ArrayList<>(); - private Map chromosomeIndexMap = new HashMap<>(); + private Map chromosomeIndexMap = new CaseInsensitiveMap(); + private Integer wgResolution = null; private List bpResolutions = new ArrayList<>(); private List fragResolutions = new ArrayList<>(); private Map chrAliasTable = new HashMap<>(); @@ -90,7 +92,7 @@ public int getVersion() { } public String getNVIString() { - if(this.normVectorIndexPosition > 0 && this.normVectorIndexSize > 0) { + if (this.normVectorIndexPosition > 0 && this.normVectorIndexSize > 0) { return this.normVectorIndexPosition + "," + this.normVectorIndexSize; } else { return null; @@ -158,17 +160,19 @@ private void readHeaderAndFooter() throws IOException { // chromosomes this.chromosomes = new ArrayList<>(); - this.chromosomeIndexMap = new HashMap<>(); + this.chromosomeIndexMap = new CaseInsensitiveMap<>(); int nChrs = bodyParser.getInt(); for (int i = 0; i < nChrs; i++) { String name = getString(bodyParser); long size = this.version < 9 ? bodyParser.getInt() : bodyParser.getLong(); Chromosome chr = new Chromosome(i, name, (int) size); - if ("all".equalsIgnoreCase(name)) { - // whole genome handling omitted other fields - } this.chromosomes.add(chr); + + String canonicalName = genome == null ? name : genome.getCanonicalChrName(name); + chrAliasTable.put(canonicalName, name); + this.chromosomeIndexMap.put(name, i); + } // bp resolutions @@ -185,12 +189,6 @@ private void readHeaderAndFooter() throws IOException { this.fragResolutions.add(bodyParser.getInt()); } } - - // build alias table - for (String chrName : chromosomeIndexMap.keySet()) { - String canonicalName = genome == null ? chrName : genome.getCanonicalChrName(chrName); - chrAliasTable.put(canonicalName, chrName); - } } private void readFooter() throws IOException { @@ -280,7 +278,7 @@ public List getContactRecords(Region region1, if (block == null) continue; 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() > 1) { contactRecords.add(rec); } } @@ -289,6 +287,24 @@ public List getContactRecords(Region region1, return contactRecords; } + public int getWGResolution() { + if (wgResolution == null) { + try { + Integer idx = chromosomeIndexMap.get("all"); + if (idx == null) return -1; + Matrix matrix = getMatrix(idx, idx); + if (matrix == null) return -1; + List zdArray = matrix.getBpZoomData(); + if (zdArray.isEmpty()) return -1; + wgResolution = zdArray.get(0).getZoom().binSize(); + } catch (IOException e) { + log.error(e.getMessage()); + wgResolution = -1; + } + } + return wgResolution; + } + private List getBlocks(Region region1, Region region2, String unit, int binSize) throws IOException { init(); String chr1 = getFileChrName(region1.chr()); diff --git a/src/main/java/org/igv/prefs/Constants.java b/src/main/java/org/igv/prefs/Constants.java index 655fba6753..4e2a9a9f78 100644 --- a/src/main/java/org/igv/prefs/Constants.java +++ b/src/main/java/org/igv/prefs/Constants.java @@ -320,6 +320,7 @@ private Constants() { // Misc URLS public static final String ENCODE_FILELIST_URL = "ENCODE_FILELIST_URL"; + public static final String FOUR_DN_FILELIST_URL = "FOUR_DN_FILELIST_URL"; /** diff --git a/src/main/java/org/igv/prefs/IGVPreferences.java b/src/main/java/org/igv/prefs/IGVPreferences.java index 861db5581f..babc6d93e8 100644 --- a/src/main/java/org/igv/prefs/IGVPreferences.java +++ b/src/main/java/org/igv/prefs/IGVPreferences.java @@ -84,35 +84,6 @@ public String get(String key, String defaultValue) { return val == null ? defaultValue : val; } - - /** - * Return preference as explicitly set in prefs.properties. If no value is set return null. - * @param key - * @return - */ - public String getExplicitValue(String key) { - key = key.trim(); - String val = userPreferences.get(key); - if (val == null && parent != null) { - val = parent.userPreferences.get(key); - } - return val; - } - - /** - * Return the default preference value as set in org.igv.prefs.preferences.tab - * @param key - * @return - */ - public String getDefault(String key) { - key = key.trim(); - String val = defaults.get(key); - if (val == null && parent != null) { - val = parent.defaults.get(key); - } - return val; - } - /** * Return the preference as a boolean value. * @@ -123,13 +94,12 @@ public boolean getAsBoolean(String key) { key = key.trim(); Boolean boolValue = booleanCache.get(key); if (boolValue == null) { - String value = get(key); if (value == null) { - log.warn("No default value for: " + key); + log.warn("No value for preference key:: " + key); return false; } - boolValue = Boolean.valueOf(get(key, value)); + boolValue = Boolean.valueOf(value); booleanCache.put(key, boolValue); } return boolValue; @@ -145,22 +115,16 @@ public int getAsInt(String key) { key = key.trim(); Number value = (Number) objectCache.get(key); if (value == null) { - String defValue = get(key); - if (defValue == null) { - log.warn("No default value for: " + key); + String stringValue = get(key); + if (stringValue == null) { + log.warn("No value for preference key:: " + key); return 0; } - String userPref = get(key, defValue); try { - value = Integer.valueOf(userPref); + value = Integer.valueOf(stringValue); } catch (NumberFormatException e) { - log.warn("Invalid integer preference for key '" + key + "': '" + userPref + "'. Using default value: '" + defValue + "'."); - try { - value = Integer.valueOf(defValue); - } catch (NumberFormatException e2) { - log.warn("Invalid integer default preference for key '" + key + "': '" + defValue + "'. Falling back to 0."); - value = 0; - } + log.warn("Invalid integer preference for key '" + key + "': '" + stringValue + "'. Falling back to 0."); + value = 0; } objectCache.put(key, value); } @@ -179,7 +143,7 @@ public Color getAsColor(String key) { if (value == null) { String defValue = get(key); if (defValue == null) { - log.warn("No default value for: " + key); + log.warn("No value for preference key:: " + key); return Color.white; } value = ColorUtilities.stringToColor(defValue); @@ -198,22 +162,16 @@ public float getAsFloat(String key) { key = key.trim(); Number value = (Number) objectCache.get(key); if (value == null) { - String defValue = get(key); - if (defValue == null) { - log.warn("No default value for: " + key); + String stringValue = get(key); + if (stringValue == null) { + log.warn("No value for preference key:: " + key); return 0; } - String prefString = get(key, defValue); try { - value = Float.valueOf(prefString); + value = Float.valueOf(stringValue); } catch (NumberFormatException e) { - log.warn("Invalid float value for preference '" + key + "': '" + prefString + "'. Using default '" + defValue + "'.", e); - try { - value = Float.valueOf(defValue); - } catch (NumberFormatException e2) { - log.warn("Invalid float default value for preference '" + key + "': '" + defValue + "'. Using 0.0f.", e2); - value = Float.valueOf(0.0f); - } + log.warn("Invalid float value for preference '" + key + "': '" + stringValue + "'. Falling back to 0.0f.", e); + value = 0.0f; } objectCache.put(key, value); } diff --git a/src/main/java/org/igv/track/AbstractTrack.java b/src/main/java/org/igv/track/AbstractTrack.java index c3b576383c..ba254c2492 100644 --- a/src/main/java/org/igv/track/AbstractTrack.java +++ b/src/main/java/org/igv/track/AbstractTrack.java @@ -28,7 +28,6 @@ import java.awt.*; import java.awt.event.MouseEvent; -import java.util.List; import java.util.*; import static org.igv.prefs.Constants.*; @@ -561,35 +560,34 @@ public void setAutoScale(boolean autoScale) { * @param properties */ public void setProperties(TrackProperties properties) { - this.itemRGB = properties.isItemRGB(); - this.useScore = properties.isUseScore(); - this.viewLimitMin = properties.getMinValue(); - this.viewLimitMax = properties.getMaxValue(); - this.yLine = properties.getyLine(); + + if (properties.isItemRGB() != null) { + this.itemRGB = properties.isItemRGB(); + } + if (properties.isUseScore() != null) { + this.useScore = properties.isUseScore(); + } + if (properties.getyLine() != null) { + this.yLine = properties.getyLine(); + } this.drawYLine = properties.isDrawYLine(); this.sortable = properties.isSortable(); + // The viewLimit properties com from UCSC track lines and control "useScore" shading. They + // are somewhat redundant with dataRange, but we keep both for now to avoid breaking existing behavior. + this.viewLimitMin = properties.getMinValue() != null ? properties.getMinValue() : Float.NaN; + this.viewLimitMax = properties.getMaxValue() != null ? properties.getMaxValue() : Float.NaN; - // If view limits are explicitly set turn off autoscale - if (!Float.isNaN(viewLimitMin) && !Float.isNaN(viewLimitMax)) { - this.setAutoScale(false); - } - // Color scale properties - if (!properties.isAutoScale()) { + if (properties.getMaxValue() != null) { - float min = properties.getMinValue(); float max = properties.getMaxValue(); - float mid = properties.getMidValue(); - if (Float.isNaN(mid)) { - if (min >= 0) { - mid = Math.max(min, 0); - } else { - mid = Math.min(max, 0); - } - } + Float minVal = properties.getMinValue(); + float min = (minVal == null) ? 0f : minVal; + Float midVal = properties.getMidValue(); + float mid = (midVal == null) ? (min < 0 ? 0f : min) : midVal; DataRange dr = new DataRange(min, mid, max); setDataRange(dr); @@ -603,10 +601,10 @@ public void setProperties(TrackProperties properties) { Color minColor = properties.getAltColor(); if (maxColor != null && minColor != null) { - float tmp = properties.getNeutralFromValue(); - float neutralFrom = Float.isNaN(tmp) ? mid : tmp; + Float tmp = properties.getNeutralFromValue(); + float neutralFrom = tmp == null ? mid : tmp; tmp = properties.getNeutralToValue(); - float neutralTo = Float.isNaN(tmp) ? mid : tmp; + float neutralTo = tmp == null ? mid : tmp; Color midColor = properties.getMidColor(); if (midColor == null) { @@ -614,7 +612,6 @@ public void setProperties(TrackProperties properties) { } colorScale = new ContinuousColorScale(neutralFrom, min, neutralTo, max, minColor, midColor, maxColor); } - } if (properties.getDisplayMode() != null) { @@ -633,10 +630,10 @@ public void setProperties(TrackProperties properties) { if (properties.getMidColor() != null) { //setMidColor(trackProperties.getMidColor()); } - if (properties.getHeight() > 0) { + if (properties.getHeight() != null) { setHeight(properties.getHeight()); } - if (properties.getMinHeight() > 0) { + if (properties.getMinHeight() != null) { setMinimumHeight(properties.getMinHeight()); } if (properties.getRendererClass() != null) { @@ -659,10 +656,10 @@ public void setProperties(TrackProperties properties) { } } - // Start of Roche-Tessella modification - this.autoScale = properties.getAutoScale(); - // End of Roche-Tessella modification - + Boolean as = properties.getAutoScale(); + if (as != null) { + this.autoScale = as; + } } /** diff --git a/src/main/java/org/igv/track/DataSourceTrack.java b/src/main/java/org/igv/track/DataSourceTrack.java index 6382ba8c68..fe1b3120de 100644 --- a/src/main/java/org/igv/track/DataSourceTrack.java +++ b/src/main/java/org/igv/track/DataSourceTrack.java @@ -42,10 +42,6 @@ public void setDatasource(DataSource dataSource) { this.dataSource = dataSource; if (this.dataSource != null) { setTrackType(dataSource.getTrackType()); - // List scores = this.dataSource.getSummaryScoresForRange(Globals.CHR_ALL, -1, -1, 0); - // if (scores.size() > 0) { - // initScale(dataSource, scores); - // } } } @@ -83,7 +79,6 @@ public LoadedDataInterval> getSummaryScores(String chr, int sta return new LoadedDataInterval<>(chr, startLocation, endLocation, zoom, tmp); } - @Override public void setWindowFunction(WindowFunction statType) { clearCaches(); @@ -96,9 +91,7 @@ public boolean isLogNormalized() { return dataSource != null ? dataSource.isLogNormalized() : false; } - public WindowFunction getWindowFunction() { - return dataSource != null ? dataSource.getWindowFunction() : null; } @@ -136,7 +129,5 @@ public void unmarshalXML(Element element, Integer version) { ((CoverageDataSource) dataSource).setNormalize(Boolean.parseBoolean(element.getAttribute("normalize"))); } } - - } } diff --git a/src/main/java/org/igv/track/DataTrack.java b/src/main/java/org/igv/track/DataTrack.java index b698baf770..da9292a0c9 100644 --- a/src/main/java/org/igv/track/DataTrack.java +++ b/src/main/java/org/igv/track/DataTrack.java @@ -62,7 +62,7 @@ public DataTrack() { public void receiveEvent(IGVEvent event) { if (event instanceof FrameManager.ChangeEvent) { - + // Purge any cached data not associated with current frames Collection frames = ((FrameManager.ChangeEvent) event).frames(); Map>> newCache = Collections.synchronizedMap(new HashMap<>()); for (ReferenceFrame f : frames) { diff --git a/src/main/java/org/igv/track/TrackProperties.java b/src/main/java/org/igv/track/TrackProperties.java index 82de02bcfa..faa110200e 100644 --- a/src/main/java/org/igv/track/TrackProperties.java +++ b/src/main/java/org/igv/track/TrackProperties.java @@ -28,15 +28,20 @@ public enum BaseCoord { ZERO, ONE, UNSPECIFIED } - private Track.DisplayMode displayMode; + /** + * The original track line from a track file or track hub. This is not always set. Not sure why we need this. + */ + private String trackLine; + private Track.DisplayMode displayMode; + /** * Base coordinate system, either 0 or 1 */ private BaseCoord baseCoord = BaseCoord.UNSPECIFIED; - private String trackLine; + private String type; /** @@ -59,9 +64,9 @@ public enum BaseCoord { /** * The track height in pixels */ - private int height; + private Integer height; - private int minHeight; + private Integer minHeight; private boolean gffTags = false; @@ -81,19 +86,17 @@ public enum BaseCoord { private String genome; - private int offset; - - private boolean autoscale = false; + private Boolean autoscale; - private float minValue = Float.NaN; + private Float minValue; - private float maxValue = Float.NaN; + private Float maxValue; - private float midValue = Float.NaN; + private Float midValue; - private float neutralFromValue = Float.NaN; + private Float neutralFromValue; - private float neutralToValue = Float.NaN; + private Float neutralToValue ; private boolean drawYLine = false; @@ -103,15 +106,15 @@ public enum BaseCoord { private int smoothingWindow; - private boolean itemRGB = true; + private Boolean itemRGB = null; - private boolean useScore = false; + private Boolean useScore = null; private int featureVisibilityWindow = -1; - private boolean logScale; + private boolean logScale = false; - private float yLine; + private Float yLine; private boolean sortable = true; @@ -146,15 +149,31 @@ public TrackProperties() { * @param trackConfig */ public TrackProperties(TrackConfig trackConfig) { - this.color = parseColor(trackConfig.color); - this.altColor = parseColor(trackConfig.altColor); - this.displayMode = parseDisplayMode(trackConfig.displayMode); + if(trackConfig.color != null) { + this.color = parseColor(trackConfig.color); + } + if(trackConfig.altColor != null) { + this.altColor = parseColor(trackConfig.altColor); + } + if(trackConfig.displayMode != null) { + this.displayMode = parseDisplayMode(trackConfig.displayMode); + } setFeatureVisibilityWindow(trackConfig.visibilityWindow != null ? trackConfig.visibilityWindow : -1); - setMinValue(trackConfig.min != null ? trackConfig.min : Float.NaN); - setMaxValue(trackConfig.max != null ? trackConfig.max : Float.NaN); - setAutoScale(Boolean.TRUE.equals(trackConfig.autoscale)); - setHeight(trackConfig.height != null ? trackConfig.height : this.height); - setMinHeight(trackConfig.minHeight != null ? trackConfig.minHeight : this.minHeight); + if(trackConfig.min != null) { + setMinValue(trackConfig.min); + } + if(trackConfig.max != null) { + setMaxValue(trackConfig.max); + } + if(trackConfig.autoscale != null) { + setAutoScale(trackConfig.autoscale); + } + if(trackConfig.height != null) { + setHeight(trackConfig.height); + } + if(trackConfig.minHeight != null) { + setMinHeight(trackConfig.minHeight); + } } @@ -228,35 +247,22 @@ public void setFeatureVisibilityWindow(int featureVisibilityWindow) { this.featureVisibilityWindow = featureVisibilityWindow; } - public boolean isUseScore() { + public Boolean isUseScore() { return useScore; } - public void setUseScore(boolean useScore) { + public void setUseScore(Boolean useScore) { this.useScore = useScore; } - public boolean isItemRGB() { + public Boolean isItemRGB() { return itemRGB; } - public void setItemRGB(boolean itemRGB) { + public void setItemRGB(Boolean itemRGB) { this.itemRGB = itemRGB; } - /** - * Method description - * - * @return - */ - public int getOffset() { - return offset; - } - - public void setOffset(int offset) { - this.offset = offset; - } - public String getName() { return name; } @@ -286,12 +292,12 @@ public void setUrl(String url) { } - public int getHeight() { + public Integer getHeight() { return height; } - public void setHeight(int height) { + public void setHeight(Integer height) { this.height = height; } @@ -305,7 +311,6 @@ public void setColor(Color color) { this.color = color; } - public Color getAltColor() { return altColor; } @@ -315,44 +320,44 @@ public void setAltColor(Color altColor) { this.altColor = altColor; } - - public boolean isAutoScale() { - return autoscale || Float.isNaN(minValue) || Float.isNaN(maxValue); - } - - public boolean getAutoScale() { - return this.autoscale; - } - public String getGenome() { return genome; } - public void setGenome(String genome) { this.genome = genome; } + public Boolean getAutoScale() { + return this.autoscale; + } - public float getMinValue() { + public void setAutoScale(Boolean autoScale) { + this.autoscale = autoScale; + } + public Float getMinValue() { return minValue; } - public void setMinValue(float minValue) { this.minValue = minValue; } - - public float getMaxValue() { + public Float getMaxValue() { return maxValue; } - - public void setMaxValue(float maxValue) { + public void setMaxValue(Float maxValue) { this.maxValue = maxValue; } + public Float getMidValue() { + return midValue; + } + + public void setMidValue(Float midValue) { + this.midValue = midValue; + } public WindowFunction getWindowingFunction() { return windowingFunction; @@ -382,17 +387,6 @@ public void setRendererClass(Class rendererClass) { this.rendererClass = rendererClass; } - public void setAutoScale(boolean autoScale) { - this.autoscale = autoScale; - } - - public float getMidValue() { - return midValue; - } - - public void setMidValue(float midValue) { - this.midValue = midValue; - } public Color getMidColor() { return midColor; @@ -410,11 +404,11 @@ public void setDrawYLine(boolean drawYLine) { this.drawYLine = drawYLine; } - public int getMinHeight() { + public Integer getMinHeight() { return minHeight; } - public void setMinHeight(int minHeight) { + public void setMinHeight(Integer minHeight) { this.minHeight = minHeight; } @@ -426,27 +420,26 @@ public void setBaseCoord(BaseCoord baseCoord) { this.baseCoord = baseCoord; } - public float getNeutralFromValue() { + public Float getNeutralFromValue() { return neutralFromValue; } - - public void setNeutralFromValue(float neutralFromValue) { + public void setNeutralFromValue(Float neutralFromValue) { this.neutralFromValue = neutralFromValue; } - public float getNeutralToValue() { + public Float getNeutralToValue() { return neutralToValue; } - public void setNeutralToValue(float neutralToValue) { + public void setNeutralToValue(Float neutralToValue) { this.neutralToValue = neutralToValue; } - public float getyLine() { + public Float getyLine() { return yLine; } - public void setyLine(float yLine) { + public void setyLine(Float yLine) { this.yLine = yLine; } diff --git a/src/main/java/org/igv/ui/IGVMenuBar.java b/src/main/java/org/igv/ui/IGVMenuBar.java index a8db1cb5c8..595c8ad3e1 100644 --- a/src/main/java/org/igv/ui/IGVMenuBar.java +++ b/src/main/java/org/igv/ui/IGVMenuBar.java @@ -699,6 +699,12 @@ private void updateHubsMenu(Genome genome) { otherSignalsItem.setAction(new BrowseEncodeAction("ENCODE Other Signals ...", 0, BrowseEncodeAction.Type.SIGNALS_OTHER, igv)); hubsMenu.add(otherSignalsItem); + if (EncodeTrackChooserFactory.hicSupportedUCSC(ucscId)) { + JMenuItem hicSignalsItem = new JMenuItem(); + hicSignalsItem.setAction(new BrowseEncodeAction("ENCODE Contact Maps ...", 0, BrowseEncodeAction.Type.HIC, igv)); + hubsMenu.add(hicSignalsItem); + } + JMenuItem otherItem = new JMenuItem(); otherItem.setAction(new BrowseEncodeAction("ENCODE Other ...", 0, BrowseEncodeAction.Type.OTHER, igv)); hubsMenu.add(otherItem); @@ -710,6 +716,13 @@ private void updateHubsMenu(Genome genome) { new BrowseEncodeAction("ENCODE 2012 UCSC Repository ...", KeyEvent.VK_E, BrowseEncodeAction.Type.UCSC, igv)); hubsMenu.add(encodeUCSCMenuItem); } + + if ("hg38".equals(ucscId)) { + JMenuItem fdnItem = new JMenuItem(); + fdnItem.setAction(new BrowseEncodeAction("4DN ...", 0, BrowseEncodeAction.Type.FOUR_DN, igv)); + hubsMenu.add(fdnItem); + } + } // User selected UCSC public and user loaded hubs. Selections are stored in "hubs.txt" file. diff --git a/src/main/java/org/igv/ui/action/BrowseEncodeAction.java b/src/main/java/org/igv/ui/action/BrowseEncodeAction.java index aa66fb7359..2c008b73ba 100644 --- a/src/main/java/org/igv/ui/action/BrowseEncodeAction.java +++ b/src/main/java/org/igv/ui/action/BrowseEncodeAction.java @@ -1,17 +1,18 @@ package org.igv.ui.action; +import org.igv.encode.EncodeTrackChooserFactory; +import org.igv.encode.FileRecord; import org.igv.encode.TrackChooser; -import org.igv.feature.genome.GenomeManager; -import org.igv.logging.*; import org.igv.feature.genome.Genome; +import org.igv.feature.genome.GenomeManager; +import org.igv.logging.LogManager; +import org.igv.logging.Logger; import org.igv.track.AttributeManager; import org.igv.track.Track; import org.igv.ui.IGV; import org.igv.ui.WaitCursorManager; import org.igv.ui.util.MessageUtils; import org.igv.util.ResourceLocator; -import org.igv.encode.EncodeTrackChooserFactory; -import org.igv.encode.FileRecord; import javax.swing.*; import java.awt.*; @@ -32,7 +33,9 @@ public enum Type { UCSC, SIGNALS_CHIP, SIGNALS_OTHER, - OTHER + OTHER, + HIC, + FOUR_DN } private static Logger log = LogManager.getLogger(BrowseEncodeAction.class); @@ -40,7 +43,7 @@ public enum Type { private static Map colors; static { - colors = new HashMap(); + colors = new HashMap<>(); colors.put("H3K27AC", new Color(200, 0, 0)); colors.put("H3K27ME3", new Color(200, 0, 0)); colors.put("H3K36ME3", new Color(0, 0, 150)); @@ -51,8 +54,18 @@ public enum Type { colors.put("H3K9ME1", new Color(100, 0, 0)); } + /** Sample info attributes + * * Properties available in various sets + * * UCSC Encode: path cell dataType antibody view replicate type lab hub + * * Encode: Biosample AssayType Target BioRep TechRep OutputType + * * 4DN: Type Biosource Assay Replicate Dataset Accession Experiment name + */ static Set sampleInfoAttributes = new HashSet<>(Arrays.asList( - "dataType", "cell", "antibody", "lab", "Biosample", "AssayType", "Target")); + "dataType", "cell", "antibody", "lab", "Biosample", "AssayType", "Target", "Biosource")); + + static Set trackLineAttributes = new HashSet<>(Arrays.asList( + "name", "description", "color", "altColor", "visibility", "maxHeightPixels", + "viewLimits", "autoScale", "priority")); private final Type type; @@ -126,34 +139,81 @@ private ResourceLocator getResourceLocator(FileRecord record) { ResourceLocator rl = new ResourceLocator(record.getPath()); rl.setName(getTrackName(record)); Map attributes = record.getAttributes(); + StringBuffer trackLine = new StringBuffer(); + String antibody = attributes.containsKey("antibody") ? attributes.get("antibody") : attributes.get("Target"); if (antibody != null) { rl.setColor(colors.get(antibody.toUpperCase())); } + for (Map.Entry entry : attributes.entrySet()) { String value = entry.getValue(); - if (value != null && value.length() > 0 && sampleInfoAttributes.contains(entry.getKey())) { - AttributeManager.getInstance().addAttribute(rl.getName(), entry.getKey(), value); + String normalizedKey = normalizeAttributeName(entry.getKey()); + if (value != null && value.length() > 0 && sampleInfoAttributes.contains(normalizedKey)) { + AttributeManager.getInstance().addAttribute(rl.getName(),normalizedKey, value); + } else if (value != null && value.length() > 0 && trackLineAttributes.contains(normalizedKey)) { + trackLine.append(normalizedKey).append("=\"").append(value).append("\" "); } } rl.setMetadata(attributes); + if (trackLine.length() > 0) { + rl.setTrackLine(trackLine.toString().trim()); + } + return rl; } /** - * Return a friendly name for the track. Unfortunately it is neccessary to hardcode certain attributes. + * Normalize attribute names across different data sources (Encode, UCSC, 4DN) + * @param rawName + * @return + */ + private static String normalizeAttributeName(String rawName) { + switch (rawName) { + case "cell": + case "Biosource": + return "Biosample"; + case "antibody": + return "Target"; + default: + return rawName; + } + } + + + /** + * Return a friendly name for the track. + * + * Properties available in various sets + * UCSC Encode: path cell dataType antibody view replicate type lab hub + * Encode: Biosample AssayType Target BioRep TechRep OutputType + * 4DN: Type Biosource Assay Replicate Dataset Accession Experiment name * * @return */ public String getTrackName(FileRecord record) { Map attributes = record.getAttributes(); + if (attributes.containsKey("name")) { + return attributes.get("name"); + } + if(attributes.containsKey("Dataset")) { + return attributes.get("Dataset"); + } + StringBuffer sb = new StringBuffer(); - if (attributes.containsKey("cell")) sb.append(attributes.get("cell") + " "); - if (attributes.containsKey("antibody")) sb.append(attributes.get("antibody") + " "); - if (attributes.containsKey("dataType")) sb.append(attributes.get("dataType") + " "); - if (attributes.containsKey("view")) sb.append(attributes.get("view") + " "); - if (attributes.containsKey("replicate")) sb.append("rep " + attributes.get("replicate")); + if (attributes.containsKey("cell")) sb.append(attributes.get("cell")); + else if (attributes.containsKey("Biosample")) sb.append(attributes.get("Biosample")); + + if (attributes.containsKey("antibody")) sb.append(" ").append(attributes.get("antibody")); + else if (attributes.containsKey("Target")) sb.append(" ").append(attributes.get("Target")); + + if (attributes.containsKey("dataType")) sb.append(" ").append(attributes.get("dataType")); + else if (attributes.containsKey("AssayType")) sb.append(" ").append(attributes.get("AssayType")); + else if (attributes.containsKey("Assay")) sb.append(" ").append(attributes.get("Assay")); + + if (attributes.containsKey("view")) sb.append(" ").append(attributes.get("view")); + else if (attributes.containsKey("OutputType")) sb.append(" ").append(attributes.get("OutputType")); String trackName = sb.toString().trim(); if (sb.length() == 0) { diff --git a/src/main/java/org/igv/ui/color/ColorUtilities.java b/src/main/java/org/igv/ui/color/ColorUtilities.java index fb5edb1903..988be8f63a 100644 --- a/src/main/java/org/igv/ui/color/ColorUtilities.java +++ b/src/main/java/org/igv/ui/color/ColorUtilities.java @@ -188,9 +188,9 @@ public static Color stringToColorNoDefault(String string) throws NumberFormatExc } else { String[] rgb = string.split(","); - int red = Integer.parseInt(rgb[0]); - int green = Integer.parseInt(rgb[1]); - int blue = Integer.parseInt(rgb[2]); + int red = Integer.parseInt(rgb[0].trim()); + int green = Integer.parseInt(rgb[1].trim()); + int blue = Integer.parseInt(rgb[2].trim()); c = new Color(red, green, blue); } } else if (string.startsWith("#")) { diff --git a/src/main/java/org/igv/util/collections/CaseInsensitiveMap.java b/src/main/java/org/igv/util/collections/CaseInsensitiveMap.java new file mode 100644 index 0000000000..a38ea47fa7 --- /dev/null +++ b/src/main/java/org/igv/util/collections/CaseInsensitiveMap.java @@ -0,0 +1,94 @@ +package org.igv.util.collections; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * A Map implementation that treats String keys as case-insensitive. + * Keys are converted to lowercase internally. + * + * @param the type of mapped values + */ +public class CaseInsensitiveMap implements Map { + + private final Map map = new HashMap<>(); + private final Map originalKeys = new HashMap<>(); + + private static String lowerKey(Object key) { + return key == null ? null : key.toString().toLowerCase(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(lowerKey(key)); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(lowerKey(key)); + } + + @Override + public V put(String key, V value) { + originalKeys.put(lowerKey(key), key); + return map.put(lowerKey(key), value); + } + + @Override + public V remove(Object key) { + originalKeys.remove(lowerKey(key)); + return map.remove(lowerKey(key)); + } + + @Override + public void putAll(Map m) { + for (Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + map.clear(); + originalKeys.clear(); + } + + /** + * Get the set of keys in the map. + * @return a Set containing the keys in their original case (snapshot) + */ + @Override + public Set keySet() { + // Return the original-case keys (snapshot) instead of the internal lower-cased keys + return new LinkedHashSet<>(originalKeys.values()); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet().stream() + .map(e -> new AbstractMap.SimpleEntry<>(originalKeys.get(e.getKey()), e.getValue())) + .collect(Collectors.toSet()); + } +} + + diff --git a/src/main/resources/preferences.tab b/src/main/resources/preferences.tab index e6f3311df8..01ac2ac535 100644 --- a/src/main/resources/preferences.tab +++ b/src/main/resources/preferences.tab @@ -316,6 +316,7 @@ UCSC_HOST hgdownload.soe.ucsc.edu UCSC_BACKUP_HOST genome-browser.s3.us-east-1.amazonaws.com ENCODE_FILELIST_URL https://raw.githubusercontent.com/igvteam/igv-data/refs/heads/main/data/encode/ +FOUR_DN_FILELIST_URL https://raw.githubusercontent.com/igvteam/igv-data/refs/heads/main/data/4dn/ PROVISIONING_URL_DEFAULT https://igv.org/services/desktop_google SAM.SHOW_JUNCTION_FLANKINGREGIONS FALSE diff --git a/src/test/java/org/igv/util/ParsingUtilsTest.java b/src/test/java/org/igv/util/ParsingUtilsTest.java index 91a847f0c6..f01fbf3762 100644 --- a/src/test/java/org/igv/util/ParsingUtilsTest.java +++ b/src/test/java/org/igv/util/ParsingUtilsTest.java @@ -144,7 +144,7 @@ public void testParseTrackLine() { assertEquals(0, props.getMinValue(), 1.0e-9); assertEquals(18, props.getMaxValue(), 1.0e-9); assertEquals(WindowFunction.mean, props.getWindowingFunction()); - assertEquals(false, props.isAutoScale()); + assertEquals(false, props.getAutoScale()); assertEquals(new Color(255, 0, 0), props.getColor()); assertEquals("http://www.broadinstitute.org/epigenomics/dataportal/track_00196.portal.bw", props.getDataURL()); }