Skip to content

Commit

Permalink
Make TileLoader's responsible determining available contigs and autom…
Browse files Browse the repository at this point in the history
…atically create first panel using this
  • Loading branch information
haxiomic committed Apr 20, 2019
1 parent 6d7e951 commit ff79614
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 131 deletions.
104 changes: 2 additions & 102 deletions src/GenomeVisualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { SignalTileLoader } from "./track/signal/SignalTileLoader";
import { SignalTrack } from "./track/signal/SignalTrack";
import { BigWigReader, AxiosDataLoader } from "bigwig-reader";
import { SignalTrackModel, AnnotationTrackModel, SequenceTrackModel, VariantTrackModel } from "./track";
import { GenomicLocation } from "./model";
import { GenomicLocation, Contig } from "./model";
import { Panel } from "./ui";
import Axios from "axios";
import { Formats, GenomicFileFormat } from "./formats/Formats";
Expand All @@ -40,6 +40,7 @@ interface CustomTileLoader<ModelType> {

// produce a key differentiates models that require a separate tile loader / data cache instance
cacheKey (model: ModelType): string | null;
getAvailableContigs(model: ModelType): Promise<Array<Contig>>;
}

interface CustomTrackObject {
Expand All @@ -61,114 +62,13 @@ export class GenomeVisualizer {

if (Array.isArray(configuration)) {
if (configuration.length > 0) {

// add tracks from path list
for (let path of configuration) {
this.addTrackFromFilePath(path, undefined, false);
}

let foundContigs = false;

// we determine a GenomeVisualizerConfiguration by inspecting the files in the list
for (let path of configuration) {

// we don't know what contigs are available so we must read the first file for this
let format = Formats.determineFormat(path);

this.trackViewer.setNothingToDisplayText('Loading');

switch (format) {
case GenomicFileFormat.BigWig:
case GenomicFileFormat.BigBed:
{
foundContigs = true;

let bigwigReader = new BigWigReader(new AxiosDataLoader(path));
bigwigReader.getHeader().then((header) => {
this.trackViewer.resetNothingToDisplayText();
// create a manifest that lists the available contigs
let manifest: Manifest = {
contigs: []
}

let availableChromosomes = header.chromTree.idToChrom;
availableChromosomes.sort((a, b) => {
return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });
});

for (let contigId of availableChromosomes) {
manifest.contigs.push({
id: contigId,
startIndex: 0,
span: header.chromTree.chromSize[contigId]
});
}

if (this.getPanels().length === 0) {
this.addPanel({ contig: manifest.contigs[0].id, x0: 0, x1: manifest.contigs[0].span }, false);
this.setDataSource(new ManifestDataSource(manifest));
}
}).catch((reason) => {
this.trackViewer.setNothingToDisplayText('Error loading bigwig header (see browser console)');
console.error(`Error loading bigwig header: ${reason}`);
});
break;
}

case GenomicFileFormat.ValisDna:
case GenomicFileFormat.ValisGenes:
case GenomicFileFormat.ValisVariants:
{
foundContigs = true;

Axios.get(path + '/manifest.json')
.then((response) => {
let json = response.data;

// create a manifest that lists the available contigs
let manifest: Manifest = {
contigs: json.contigs
}

if (this.getPanels().length === 0) {
this.addPanel({ contig: manifest.contigs[0].id, x0: 0, x1: manifest.contigs[0].span }, false);
this.setDataSource(new ManifestDataSource(manifest));
}
})
.catch((reason) => {
this.trackViewer.setNothingToDisplayText('Error loading manifest (see browser console)');
console.error(`Error loading vdna-dir manifest: ${reason}`);
});
break;
}

// case 'bam': { break; }
// case 'vcf': { break; }
// case 'fasta': { break; }
// case 'gff3': { break; }

default: {
this.trackViewer.resetNothingToDisplayText();
}
}

if (foundContigs) break;
}

if (!foundContigs) {
console.error(`Could not determine contigs from supplied files`);
this.addPanel({ contig: 'chr1', x0: 0, x1: 100 }, false);
}
}
} else {
if (configuration != null) {
// default panel if none is set
if (configuration.panels == null) {
configuration.panels = [{
location: { contig: 'chr1', x0: 0, x1: 249e6 }
}];
}

this.setConfiguration(configuration);
}
}
Expand Down
28 changes: 26 additions & 2 deletions src/data-source/InternalDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GenomeVisualizer } from '../GenomeVisualizer';
import TileLoader from '../track/TileLoader';
import TrackModel from '../track/TrackModel';
import { IDataSource } from './IDataSource';
import { Contig } from '..';

export class InternalDataSource {

Expand All @@ -11,11 +12,34 @@ export class InternalDataSource {
}
} = {};

protected localContigs = new Array<Contig>();

constructor(protected readonly dataSource: IDataSource) {
}

getContigs() {
return this.dataSource.getContigs();
return this.dataSource.getContigs().then(contigs => contigs.concat(this.localContigs));
}

addContig(contig: Contig) {
let existingContig = this.localContigs.find((c) => c.id === contig.id);
if (existingContig == null) {
this.localContigs.push(contig);
} else {
// expand existing contig
existingContig.startIndex = Math.min(existingContig.startIndex, contig.startIndex);
existingContig.span = Math.min(existingContig.span, contig.span);
existingContig.name = existingContig.name || contig.name;
}
}

removeContig(contig: Contig) {
let i = this.localContigs.length - 1;
while (i >= 0) {
this.localContigs[i].id === contig.id;
this.localContigs.splice(i, 1);
i--;
}
}

getTileLoader(model: TrackModel, contig: string): TileLoader<any, any> {
Expand All @@ -39,7 +63,7 @@ export class InternalDataSource {
tileCaches[key] = tileLoader = new trackTypeDescriptor.tileLoaderClass(this.dataSource, model, contig);

// set maximumX when we have access to contig info
this.dataSource.getContigs().then((contigInfoArray) => {
this.getContigs().then((contigInfoArray) => {
let matchingContigInfo = contigInfoArray.find((c) => c.id === contig);
if (matchingContigInfo != null) {
tileLoader.maximumX = matchingContigInfo.span - 1;
Expand Down
66 changes: 50 additions & 16 deletions src/track/annotation/AnnotationTileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import TrackModel from "../TrackModel";
import { UCSCBig, BigLoader } from "../../formats";
import { BigBedData, BigZoomData } from "bigwig-reader";
import { Formats, GenomicFileFormat } from "../../formats/Formats";
import { Contig, AnnotationTrackModel } from "../..";
import Axios from "axios";

// Tile payload is a list of genes extended with nesting
export type Gene = GeneInfo & {
Expand Down Expand Up @@ -35,40 +37,72 @@ export class AnnotationTileLoader extends TileLoader<TilePayload, void> {
protected readonly macroLodThresholdLow = 7;
protected readonly macroLodThresholdHigh = this.macroLodThresholdLow + this.macroLodBlendRange;

static cacheKey(model: TrackModel): string {
static cacheKey(model: AnnotationTrackModel): string {
return model.path;
}

constructor(
protected readonly dataSource: IDataSource,
protected readonly model: TrackModel,
protected readonly contig: string,
tileSize: number = 1 << 20
) {
super(tileSize, 1);

static getAnnotationFormat(model: AnnotationTrackModel) {
// determine annotation file format
if (model.path != null) {
let format = Formats.determineFormat(model.path);

switch (format) {
case GenomicFileFormat.ValisGenes:
this.annotationFileFormat = AnnotationFormat.ValisGenes;
break;
return AnnotationFormat.ValisGenes;
case GenomicFileFormat.BigBed:
this.annotationFileFormat = AnnotationFormat.BigBed;
break;
return AnnotationFormat.BigBed;
default:
// we have to guess
if (/bigbed/ig.test(model.path)) {
this.annotationFileFormat = AnnotationFormat.BigBed;
return AnnotationFormat.BigBed;
} else if (/vdna/ig.test(model.path)){
this.annotationFileFormat = AnnotationFormat.ValisGenes;
return AnnotationFormat.ValisGenes;
} else {
this.annotationFileFormat = AnnotationFormat.BigBed;
return AnnotationFormat.BigBed;
}
}
}

return null;
}

static getAvailableContigs(model: AnnotationTrackModel): Promise<Array<Contig>> {
let contigs = new Array<Contig>();

let format = this.getAnnotationFormat(model);
if (format != null) {
switch (format) {
case AnnotationFormat.ValisGenes:
if (model.path != null) {
return Axios.get(model.path + '/manifest.json')
.then((response) => {
// create a manifest that lists the available contigs
contigs = contigs.concat(response.data.contigs);
})
.catch((reason) => {
console.error(`Error loading manifest: ${reason}`);
}).then(_ => contigs);
}
break;
case AnnotationFormat.BigBed:
if (model.path != null) {
return UCSCBig.getBigLoader(model.path).then(b => UCSCBig.getContigs(b.header));
}
break;
}
}

return Promise.resolve(contigs);
}

constructor(
protected readonly dataSource: IDataSource,
protected readonly model: AnnotationTrackModel,
protected readonly contig: string,
tileSize: number = 1 << 20
) {
super(tileSize, 1);
this.annotationFileFormat = AnnotationTileLoader.getAnnotationFormat(model);
}

mapLodLevel(l: number) {
Expand Down
6 changes: 6 additions & 0 deletions src/track/interval/IntervalTileLoader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import IDataSource from "../../data-source/IDataSource";
import { Tile, TileLoader } from "../TileLoader";
import { IntervalTrackModel } from "./IntervalTrackModel";
import { Contig } from "../..";

export type IntervalTilePayload = {
intervals: Float32Array,
Expand Down Expand Up @@ -28,6 +29,11 @@ export class IntervalTileLoader extends TileLoader<IntervalTilePayload, void> {
return null;
}

static getAvailableContigs(model: IntervalTrackModel): Promise<Array<Contig>> {
let contigs = new Array<Contig>();
return Promise.resolve(contigs);
}

constructor(
protected readonly dataSource: IDataSource,
protected readonly model: IntervalTrackModel,
Expand Down
17 changes: 17 additions & 0 deletions src/track/sequence/SequenceTileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import TileLoader, { Tile } from "../TileLoader";
import { SequenceTrackModel } from "./SequenceTrackModel";
import { IDataSource } from "../../data-source/IDataSource";
import axios, { CancelToken } from 'axios';
import { Contig } from "../..";
import Axios from "axios";

type TilePayload = {
array: Uint8Array,
Expand All @@ -27,6 +29,21 @@ export class SequenceTileLoader extends TileLoader<TilePayload, BlockPayload> {
return model.path;
}

static getAvailableContigs(model: SequenceTrackModel): Promise<Array<Contig>> {
let contigs = new Array<Contig>();
if (model.path != null) {
return Axios.get(model.path + '/manifest.json')
.then((response) => {
// create a manifest that lists the available contigs
contigs = contigs.concat(response.data.contigs);
})
.catch((reason) => {
console.error(`Error loading manifest: ${reason}`);
}).then(_ => contigs);
}
return Promise.resolve(contigs);
}

constructor(
protected readonly dataSource: IDataSource,
protected readonly model: SequenceTrackModel,
Expand Down
9 changes: 9 additions & 0 deletions src/track/signal/SignalTileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SignalTrackModel } from "./SignalTrackModel";
import { BigWigReader, HeaderData } from "bigwig-reader";
import { TileLoader, Tile, TileState } from "../TileLoader";
import { IDataSource } from "../../data-source/IDataSource";
import { Contig, UCSCBig } from "../..";

export type SignalTilePayload = {
textureUnpackMultiplier: number,
Expand Down Expand Up @@ -42,6 +43,14 @@ export class SignalTileLoader extends TileLoader<SignalTilePayload, BlockPayload
return model.path;
}

static getAvailableContigs(model: SignalTrackModel): Promise<Array<Contig>> {
let contigs = new Array<Contig>();
if (model.path != null) {
return UCSCBig.getBigLoader(model.path).then(b => UCSCBig.getContigs(b.header));
}
return Promise.resolve(contigs);
}

static requestIndex = 0;

constructor(
Expand Down
16 changes: 16 additions & 0 deletions src/track/variant/VariantTileLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { IDataSource } from "../../data-source/IDataSource";
import { Tile, TileLoader } from "../TileLoader";
import { VariantTrackModel } from "./VariantTrackModel";
import Axios from "axios";
import { Contig } from "../..";

export type VariantTilePayload = Array<{
id: string,
Expand All @@ -16,6 +17,21 @@ export class VariantTileLoader extends TileLoader<VariantTilePayload, void> {
return JSON.stringify(model.query);
}

static getAvailableContigs(model: VariantTrackModel): Promise<Array<Contig>> {
let contigs = new Array<Contig>();
if (model.path != null) {
return Axios.get(model.path + '/manifest.json')
.then((response) => {
// create a manifest that lists the available contigs
contigs = contigs.concat(response.data.contigs);
})
.catch((reason) => {
console.error(`Error loading manifest: ${reason}`);
}).then(_ => contigs);
}
return Promise.resolve(contigs);
}

constructor(
protected readonly dataSource: IDataSource,
protected readonly model: VariantTrackModel,
Expand Down
Loading

0 comments on commit ff79614

Please sign in to comment.