From: gmungoc Date: Tue, 24 Oct 2017 12:45:15 +0000 (+0100) Subject: Merge branch 'develop' into features/JAL-1793VCF; lambda Function for X-Git-Tag: Release_2_11_0~188 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=8d3aefef72e993e55c0f6b5c26e3979ae7269e0f;p=jalview.git Merge branch 'develop' into features/JAL-1793VCF; lambda Function for EnsemblLookup parsing Conflicts: src/jalview/ext/ensembl/EnsemblInfo.java src/jalview/ext/ensembl/EnsemblLookup.java src/jalview/ext/ensembl/EnsemblRestClient.java --- 8d3aefef72e993e55c0f6b5c26e3979ae7269e0f diff --cc src/jalview/ext/ensembl/EnsemblGene.java index 115ecd4,50dfa90..afff4c2 --- a/src/jalview/ext/ensembl/EnsemblGene.java +++ b/src/jalview/ext/ensembl/EnsemblGene.java @@@ -166,47 -161,9 +166,48 @@@ public class EnsemblGene extends Ensemb } /** + * Parses and saves fields of an Ensembl-style description e.g. + * chromosome:GRCh38:17:45051610:45109016:1 + * + * @param alignment + */ + private void parseChromosomeLocations(AlignmentI alignment) + { + for (SequenceI seq : alignment.getSequences()) + { + String description = seq.getDescription(); + if (description == null) + { + continue; + } + String[] tokens = description.split(":"); + if (tokens.length == 6 && tokens[0].startsWith(DBRefEntry.CHROMOSOME)) + { + String ref = tokens[1]; + String chrom = tokens[2]; + try + { + int chStart = Integer.parseInt(tokens[3]); + int chEnd = Integer.parseInt(tokens[4]); + boolean forwardStrand = "1".equals(tokens[5]); + String species = ""; // dunno yet! + int[] from = new int[] { seq.getStart(), seq.getEnd() }; + int[] to = new int[] { forwardStrand ? chStart : chEnd, + forwardStrand ? chEnd : chStart }; + MapList map = new MapList(from, to, 1, 1); + seq.setGeneLoci(species, ref, chrom, map); + } catch (NumberFormatException e) + { + System.err.println("Bad integers in description " + description); + } + } + } + } + + /** - * Converts a query, which may contain one or more gene or transcript - * identifiers, into a non-redundant list of gene identifiers. + * Converts a query, which may contain one or more gene, transcript, or + * external (to Ensembl) identifiers, into a non-redundant list of gene + * identifiers. * * @param accessions * @return diff --cc src/jalview/ext/ensembl/EnsemblLookup.java index 0968663,31da9c0..f314b0a --- a/src/jalview/ext/ensembl/EnsemblLookup.java +++ b/src/jalview/ext/ensembl/EnsemblLookup.java @@@ -28,24 -28,28 +28,29 @@@ import java.net.MalformedURLException import java.net.URL; import java.util.Arrays; import java.util.List; ++import java.util.function.Function; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; /** -- * A client for the Ensembl lookup REST endpoint; used to find the Parent gene -- * identifier given a transcript identifier. ++ * A client for the Ensembl lookup REST endpoint * * @author gmcarstairs */ public class EnsemblLookup extends EnsemblRestClient { + private static final String SPECIES = "species"; - private static final String OBJECT_TYPE_TRANSLATION = "Translation"; private static final String PARENT = "Parent"; + ++ private static final String OBJECT_TYPE_TRANSLATION = "Translation"; + private static final String OBJECT_TYPE_TRANSCRIPT = "Transcript"; + private static final String ID = "id"; + private static final String OBJECT_TYPE_GENE = "Gene"; + private static final String OBJECT_TYPE = "object_type"; + /** * Default constructor (to use rest.ensembl.org) */ @@@ -125,30 -129,8 +130,31 @@@ * @param identifier * @return */ - public String getParent(String identifier) + public String getGeneId(String identifier) { - return getAttribute(identifier, PARENT); ++ return getResult(identifier, br -> parseGeneId(br)); + } + + /** + * Calls the Ensembl lookup REST endpoint and retrieves the 'species' for the + * given identifier, or null if not found + * + * @param identifier + * @return + */ + public String getSpecies(String identifier) + { - return getAttribute(identifier, SPECIES); ++ return getResult(identifier, br -> getAttribute(br, SPECIES)); + } + + /** + * @param identifier + * @param attribute + * @return + */ - protected String getAttribute(String identifier, String attribute) ++ protected String getResult(String identifier, ++ Function parser) + { List ids = Arrays.asList(new String[] { identifier }); BufferedReader br = null; @@@ -159,7 -141,7 +165,7 @@@ { br = getHttpResponse(url, ids); } - return (parseResponse(br, attribute)); - return br == null ? null : parseResponse(br); ++ return br == null ? null : parser.apply(br); } catch (IOException e) { // ignore @@@ -180,23 -162,49 +186,71 @@@ } /** - * Parses the value of 'attribute' from the JSON response and returns the - * value, or null if not found ++ * Answers the value of 'attribute' from the JSON response, or null if not ++ * found + * + * @param br + * @param attribute + * @return - * @throws IOException + */ - protected String parseResponse(BufferedReader br, String attribute) throws IOException ++ protected String getAttribute(BufferedReader br, String attribute) ++ { ++ String value = null; ++ JSONParser jp = new JSONParser(); ++ try ++ { ++ JSONObject val = (JSONObject) jp.parse(br); ++ value = val.get(attribute).toString(); ++ } catch (ParseException | NullPointerException | IOException e) ++ { ++ // ignore ++ } ++ return value; ++ } ++ ++ /** + * Parses the JSON response and returns the gene identifier, or null if not + * found. If the returned object_type is Gene, returns the id, if Transcript + * returns the Parent. If it is Translation (peptide identifier), then the + * Parent is the transcript identifier, so we redo the search with this value. + * + * @param br + * @return - * @throws IOException + */ - protected String parseResponse(BufferedReader br) throws IOException ++ protected String parseGeneId(BufferedReader br) { - String parent = null; + String geneId = null; JSONParser jp = new JSONParser(); try { JSONObject val = (JSONObject) jp.parse(br); - parent = val.get(attribute).toString(); - } catch (ParseException | NullPointerException e) + String type = val.get(OBJECT_TYPE).toString(); + if (OBJECT_TYPE_GENE.equalsIgnoreCase(type)) + { + geneId = val.get(ID).toString(); + } + else if (OBJECT_TYPE_TRANSCRIPT.equalsIgnoreCase(type)) + { + geneId = val.get(PARENT).toString(); + } + else if (OBJECT_TYPE_TRANSLATION.equalsIgnoreCase(type)) + { + String transcriptId = val.get(PARENT).toString(); + try + { + geneId = getGeneId(transcriptId); + } catch (StackOverflowError e) + { + /* + * unlikely data condition error! + */ + System.err + .println("** Ensembl lookup " + + getUrl(transcriptId).toString() + + " looping on Parent!"); + } + } - } catch (ParseException e) ++ } catch (ParseException | IOException e) { // ignore } diff --cc src/jalview/ext/ensembl/EnsemblRestClient.java index 7cabfbb,b1bc8e5..e3d1215 --- a/src/jalview/ext/ensembl/EnsemblRestClient.java +++ b/src/jalview/ext/ensembl/EnsemblRestClient.java @@@ -87,10 -85,10 +85,10 @@@ abstract class EnsemblRestClient extend static { - domainData = new HashMap(); + domainData = new HashMap<>(); domainData.put(ENSEMBL_REST, - new EnsemblInfo(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION)); - domainData.put(ENSEMBL_GENOMES_REST, new EnsemblInfo( + new EnsemblData(ENSEMBL_REST, LATEST_ENSEMBL_REST_VERSION)); + domainData.put(ENSEMBL_GENOMES_REST, new EnsemblData( ENSEMBL_GENOMES_REST, LATEST_ENSEMBLGENOMES_REST_VERSION)); } diff --cc test/jalview/gui/SeqCanvasTest.java index 0000000,a27bc3f..05b9aea mode 000000,100644..100644 --- a/test/jalview/gui/SeqCanvasTest.java +++ b/test/jalview/gui/SeqCanvasTest.java @@@ -1,0 -1,283 +1,281 @@@ + package jalview.gui; + + import static org.testng.Assert.assertEquals; + + import jalview.datamodel.AlignmentI; + import jalview.io.DataSourceType; + import jalview.io.FileLoader; + + import java.awt.Font; + import java.awt.FontMetrics; + + import junit.extensions.PA; + + import org.testng.annotations.Test; + -import sun.swing.SwingUtilities2; - + public class SeqCanvasTest + { + /** + * Test the method that computes wrapped width in residues, height of wrapped + * widths in pixels, and the number of widths visible + */ + @Test(groups = "Functional") + public void testCalculateWrappedGeometry_noAnnotations() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport av = af.getViewport(); + AlignmentI al = av.getAlignment(); + assertEquals(al.getWidth(), 157); + assertEquals(al.getHeight(), 15); + + av.setWrapAlignment(true); + av.getRanges().setStartEndSeq(0, 14); + av.setFont(new Font("SansSerif", Font.PLAIN, 14), true); + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + assertEquals(charHeight, 17); + assertEquals(charWidth, 12); + + SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; + + /* + * first with scales above, left, right + */ + av.setShowAnnotation(false); + av.setScaleAboveWrapped(true); + av.setScaleLeftWrapped(true); + av.setScaleRightWrapped(true); - FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont()); ++ FontMetrics fm = testee.getFontMetrics(av.getFont()); + int labelWidth = fm.stringWidth("000") + charWidth; + assertEquals(labelWidth, 39); // 3 x 9 + charWidth + + /* + * width 400 pixels leaves (400 - 2*labelWidth) for residue columns + * take the whole multiple of character widths + */ + int canvasWidth = 400; + int canvasHeight = 300; + int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth; + int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(wrappedWidth, residueColumns); + assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth); + assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth); + assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"), + 2 * charHeight); + int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx"); + assertEquals(repeatingHeight, charHeight * (2 + al.getHeight())); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1); + + /* + * repeat height is 17 * (2 + 15) = 289 + * make canvas height 2 * 289 + 3 * charHeight so just enough to + * draw 2 widths and the first sequence of a third + */ + canvasHeight = charHeight * (17 * 2 + 3); + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce canvas height by 1 pixel - should not be enough height + * to draw 3 widths + */ + canvasHeight -= 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * turn off scale above - can now fit in 2 and a bit widths + */ + av.setScaleAboveWrapped(false); + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce height to enough for 2 widths and not quite a third + * i.e. two repeating heights + spacer + sequence - 1 pixel + */ + canvasHeight = charHeight * (16 * 2 + 2) - 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * make canvas width enough for scales and 20 residues + */ + canvasWidth = 2 * labelWidth + 20 * charWidth; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 20); + + /* + * reduce width by 1 pixel - rounds down to 19 residues + */ + canvasWidth -= 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 19); + + /* + * turn off West scale - adds labelWidth (39) to available for residues + * which with the 11 remainder makes 50 which is 4 more charWidths rem 2 + */ + av.setScaleLeftWrapped(false); + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 23); + + /* + * add 10 pixels to width to fit in another whole residue column + */ + canvasWidth += 9; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 23); + canvasWidth += 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 24); + + /* + * turn off East scale to gain 39 more pixels (3 columns remainder 3) + */ + av.setScaleRightWrapped(false); + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 27); + + /* + * add 9 pixels to width to gain a residue column + */ + canvasWidth += 8; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 27); + canvasWidth += 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 28); + + /* + * now West but not East scale - lose 39 pixels or 4 columns + */ + av.setScaleLeftWrapped(true); + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 24); + + /* + * adding 3 pixels to width regains one column + */ + canvasWidth += 2; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 24); + canvasWidth += 1; + wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, + canvasHeight); + assertEquals(wrappedWidth, 25); + + /* + * turn off scales left and right, make width exactly 157 columns + */ + av.setScaleLeftWrapped(false); + canvasWidth = al.getWidth() * charWidth; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1); + } + + /** + * Test the method that computes wrapped width in residues, height of wrapped + * widths in pixels, and the number of widths visible + */ + @Test(groups = "Functional") + public void testCalculateWrappedGeometry_withAnnotations() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded( + "examples/uniref50.fa", DataSourceType.FILE); + AlignViewport av = af.getViewport(); + AlignmentI al = av.getAlignment(); + assertEquals(al.getWidth(), 157); + assertEquals(al.getHeight(), 15); + + av.setWrapAlignment(true); + av.getRanges().setStartEndSeq(0, 14); + av.setFont(new Font("SansSerif", Font.PLAIN, 14), true); + int charHeight = av.getCharHeight(); + int charWidth = av.getCharWidth(); + assertEquals(charHeight, 17); + assertEquals(charWidth, 12); + + SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas; + + /* + * first with scales above, left, right + */ + av.setShowAnnotation(true); + av.setScaleAboveWrapped(true); + av.setScaleLeftWrapped(true); + av.setScaleRightWrapped(true); - FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont()); ++ FontMetrics fm = testee.getFontMetrics(av.getFont()); + int labelWidth = fm.stringWidth("000") + charWidth; + assertEquals(labelWidth, 39); // 3 x 9 + charWidth + int annotationHeight = testee.getAnnotationHeight(); + + /* + * width 400 pixels leaves (400 - 2*labelWidth) for residue columns + * take the whole multiple of character widths + */ + int canvasWidth = 400; + int canvasHeight = 300; + int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth; + int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(wrappedWidth, residueColumns); + assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth); + assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth); + assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"), + 2 * charHeight); + int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx"); + assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()) + + annotationHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1); + + /* + * repeat height is 17 * (2 + 15) = 289 + annotationHeight = 507 + * make canvas height 2 * 289 + 3 * charHeight so just enough to + * draw 2 widths and the first sequence of a third + */ + canvasHeight = charHeight * (17 * 2 + 3) + 2 * annotationHeight; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce canvas height by 1 pixel - should not be enough height + * to draw 3 widths + */ + canvasHeight -= 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * turn off scale above - can now fit in 2 and a bit widths + */ + av.setScaleAboveWrapped(false); + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + + /* + * reduce height to enough for 2 widths and not quite a third + * i.e. two repeating heights + spacer + sequence - 1 pixel + */ + canvasHeight = charHeight * (16 * 2 + 2) + 2 * annotationHeight - 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2); + + /* + * add 1 pixel to height - should now get 3 widths drawn + */ + canvasHeight += 1; + testee.calculateWrappedGeometry(canvasWidth, canvasHeight); + assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3); + } + }