Commit 6b297a5b authored by Matus Talcik's avatar Matus Talcik
Browse files

fully working template

parent 43b1f7d6
Pipeline #137636 passed with stage
in 2 minutes and 12 seconds
export * from "./shared"; export * from "./shared";
export * from "./bounding_volume_hierarchy"; export * from "./bounding_volume_hierarchy";
export * from "./binned_sah_builder"; export * from "./binned_sah_builder.worker";
\ No newline at end of file \ No newline at end of file
...@@ -5,7 +5,7 @@ import { createRenderPipelines } from "./pipelines/index"; ...@@ -5,7 +5,7 @@ import { createRenderPipelines } from "./pipelines/index";
import { BindGroupLayouts, ComputePipelines, PipelineLayouts, RenderPipelines } from "./pipelines/shared"; import { BindGroupLayouts, ComputePipelines, PipelineLayouts, RenderPipelines } from "./pipelines/shared";
import { Scene } from "./scene"; import { Scene } from "./scene";
import { createShaderModules, ShaderModules } from "./shaders/index"; import { createShaderModules, ShaderModules } from "./shaders/index";
import { ChromatinViewport, DistanceViewport } from "./viewports/index"; import { ChromatinViewport, DistanceViewport, Viewport3D } from "./viewports/index";
export * from "./cameras/index"; export * from "./cameras/index";
export * from "./allocators/index"; export * from "./allocators/index";
...@@ -72,6 +72,10 @@ export class GraphicsLibrary { ...@@ -72,6 +72,10 @@ export class GraphicsLibrary {
}); });
} }
public create3DViewport(canvas: HTMLCanvasElement | null, scene: Scene | null = null, camera: OrbitCamera | SmoothCamera | null = null): Viewport3D {
return new Viewport3D(this, canvas, scene, camera);
}
public createChromatinViewport(canvas: HTMLCanvasElement | null, scene: Scene | null = null, camera: OrbitCamera | SmoothCamera | null = null): ChromatinViewport { public createChromatinViewport(canvas: HTMLCanvasElement | null, scene: Scene | null = null, camera: OrbitCamera | SmoothCamera | null = null): ChromatinViewport {
return new ChromatinViewport(this, canvas, scene, camera); return new ChromatinViewport(this, canvas, scene, camera);
} }
......
...@@ -222,7 +222,7 @@ export class Scene { ...@@ -222,7 +222,7 @@ export class Scene {
const copyOfObjectsBuffer = this._buffer.cpuBuffer().slice(this._buffer.data.byteOffset, this._buffer.data.byteOffset + this._buffer.data.byteLength); const copyOfObjectsBuffer = this._buffer.cpuBuffer().slice(this._buffer.data.byteOffset, this._buffer.data.byteOffset + this._buffer.data.byteLength);
// console.timeEnd('scene::buildBVH::bboxes'); // console.timeEnd('scene::buildBVH::bboxes');
this.bvhWorker = new Worker(new URL('./bvh/binned_sah_builder.ts', import.meta.url)); this.bvhWorker = new Worker(new URL('./bvh/binned_sah_builder.worker.ts', import.meta.url));
this.bvhWorker.onmessage = ({ data: { result } }) => { this.bvhWorker.onmessage = ({ data: { result } }) => {
// console.time('scene::buildBVH::Finish'); // console.time('scene::buildBVH::Finish');
if (result == null) { if (result == null) {
......
export * from './shared';
\ No newline at end of file
import { vec3 } from "gl-matrix";
import { BoundingBoxEmpty, BoundingBoxExtendByPoint } from "../graphics/shared";
const ATOM_NAME = 'ATOM ';
const RESIDUE_NAME = 'SEQRES';
export class Atom {
serial: number | null = null;
name: string | null = null;
altLoc: string | null = null;
resName: string | null = null;
chainID: string | null = null;
resSeq: number | null = null; // Also CHROMOSOME
iCode: string | null = null;
x: number | null = null;
y: number | null = null;
z: number | null = null;
occupancy: number | null = null;
tempFactor: number | null = null;
element: string | null = null;
charge: string | null = null;
}
export class SeqResEntry {
serNum: number | null = null;
chainID: string | null = null;
numRes: number | null = null;
resNames: Array<string> = [];
}
export class Residue {
id: number | null = null;
serNum: number | null = null;
chainID: string | null = null;
resName: string | null = null;
atoms: Array<Atom> = [];
}
export type ChromatinModel = {
atoms: Array<{x: number, y: number, z: number}>;
ranges: Array<{ name: string, from: number, to: number }>;
normalizeCenter: vec3;
normalizeScale: number;
};
/**
* Parses the given PDB string into json
* @param {String} pdb
* @returns {Object}
*/
export function parsePdb(pdb: string): Array<ChromatinModel> {
const pdbLines = pdb.split('\n');
let atoms: Array<{x: number, y: number, z: number}> = [];
// Connectivity
let connectivityBitset: Array<0 | 1> = new Array(pdbLines.length).fill(0);
let names: Array<string> = [];
// Iterate each line looking for atoms
let stop = false;
pdbLines.forEach((pdbLine) => {
if (pdbLine.length < 6) {
return;
}
const identification = pdbLine.substring(0, 6);
if (identification === ATOM_NAME || identification === 'HETATM') {
if (!stop) {
atoms.push({
x: parseFloat(pdbLine.substring(30, 38)),
y: parseFloat(pdbLine.substring(38, 46)),
z: parseFloat(pdbLine.substring(46, 54))
});
}
names.push(`chr${pdbLine.substring(17,21).trim()}`)
// http://www.wwpdb.org/documentation/file-format-content/format33/sect9.html#ATOM
// if (!stop) {
// atoms.push({
// serial: parseInt(pdbLine.substring(6, 11)),
// name: pdbLine.substring(12, 16).trim(),
// altLoc: pdbLine.substring(16, 17).trim(),
// resName: pdbLine.substring(17, 20).trim(),
// chainID: pdbLine.substring(21, 22).trim(),
// resSeq: parseInt(pdbLine.substring(22, 26)),
// iCode: pdbLine.substring(26, 27).trim(),
// x: parseFloat(pdbLine.substring(30, 38)),
// y: parseFloat(pdbLine.substring(38, 46)),
// z: parseFloat(pdbLine.substring(46, 54)),
// occupancy: parseFloat(pdbLine.substring(54, 60)),
// tempFactor: parseFloat(pdbLine.substring(60, 66)),
// element: pdbLine.substring(76, 78).trim(),
// charge: pdbLine.substring(78, 80).trim(),
// });
// }
} else if (identification === 'CONECT') {
const from = parseInt(pdbLine.substring(6, 11)) - 1;
const to = parseInt(pdbLine.substring(11, 16)) - 1;
if (to - from == 1) {
connectivityBitset[from] = 1;
}
} else if (identification === 'ENDMDL') {
stop = true;
}
});
const ranges: Array<{ name: string, from: number, to: number }> = [];
connectivityBitset = connectivityBitset.slice(0, atoms.length);
let expandingRange = false;
for (let i = 0; i < connectivityBitset.length; i++) {
const currentValue = connectivityBitset[i];
if (expandingRange && i == connectivityBitset.length - 1 && currentValue === 1) {
ranges[ranges.length - 1].to = connectivityBitset.length;
break;
}
if (currentValue === 0 && !expandingRange) continue;
if (currentValue === 1 && expandingRange) continue;
if (currentValue === 1 && !expandingRange) {
// Start new range
ranges.push({
name: names[i],
from: i,
to: i + 1
});
expandingRange = true;
}
if (currentValue === 0 && expandingRange) {
// End the range
ranges[ranges.length - 1].to = i;
expandingRange = false;
}
}
// Normalize to [-1.0, 1.0] and center
// Build bounding box
const bb = BoundingBoxEmpty();
let points = atoms.map(v => vec3.fromValues(v.x, v.y, v.z));
for (const point of points) {
BoundingBoxExtendByPoint(bb, point);
}
points = points.map(v => vec3.sub(vec3.create(), v, bb.center));
const bbSizeLengthsVec3 = vec3.sub(vec3.create(), bb.max, bb.min);
const bbSizeLengths = [Math.abs(bbSizeLengthsVec3[0]), Math.abs(bbSizeLengthsVec3[1]), Math.abs(bbSizeLengthsVec3[2])];
const maxLength = Math.max(...bbSizeLengths);
const scale = 1.0 / maxLength;
points = points.map(v => vec3.scale(vec3.create(), v, scale));
atoms = atoms.map((a, i) => {
return {
...a,
x: points[i][0],
y: points[i][1],
z: points[i][2]
};
});
return [{
// Raw data from pdb
atoms,
// Connectivity
ranges,
// Normalize
normalizeCenter: bb.center,
normalizeScale: scale
}];
}
import Papa, { parse } from "papaparse";
import { ChromatinModel, parsePdb } from "./parsePDB";
// import gff from "@gmod/gff";
import { toNumber } from "lodash";
import { vec3 } from "gl-matrix";
export const enum FileType {
PDB,
CSV,
}
export const enum CSVDelimiter {
Comma = ',',
Tabulator = '\t',
Space = ' ',
}
export const enum FileState {
NoFile,
ParseError,
Downloading,
Parsing,
Done,
}
export type ParsePDBConfiguration = { type: FileType.PDB };
export type ParseCSVConfiguration = {
type: FileType.CSV, delimiter: CSVDelimiter,
hasHeader: boolean,
};
export type ParseConfiguration = ParsePDBConfiguration | ParseCSVConfiguration;
export type ParseResultType = 'CSV' | 'PDB';
export type ParseResult = ParseResultCSV | ParseResultPDB;
export interface IParseResult {
type: ParseResultType;
}
export interface ParseResultCSV extends IParseResult {
type: 'CSV';
columns: Array<string | number>;
rows: Array<Record<string, string>>;
}
export interface ParseResultPDB extends IParseResult {
type: 'PDB';
atoms: Array<{ x: number, y: number, z: number }>;
normalizeCenter: vec3;
normalizeScale: number;
ranges: Array<{ name: string, from: number, to: number }>;
}
export function fileStateToText(state: FileState): string {
switch (state) {
case FileState.NoFile: return "No file is selected";
case FileState.ParseError: return "There was an unknown error during parsing";
case FileState.Downloading: return "Downloading file";
case FileState.Parsing: return "File is being parsed";
case FileState.Done: return "Parsing has completed";
}
}
export function parseToRows(content: string, config: ParseConfiguration): Array<ParseResult> {
switch (config.type) {
case FileType.PDB: return parsePDBToObjects(content, config as ParsePDBConfiguration);
case FileType.CSV: return [parseCSVToObjects(content, config as ParseCSVConfiguration)];
}
}
function parsePDBToObjects(content: string, config: ParsePDBConfiguration): Array<ParseResultPDB> {
const parsed = parsePdb(content);
return parsed.map((p: ChromatinModel) => {
return {
type: 'PDB',
atoms: p.atoms,
normalizeCenter: p.normalizeCenter,
normalizeScale: p.normalizeScale,
ranges: p.ranges,
}
});
}
function parseCSVToObjects(content: string, config: ParseCSVConfiguration): ParseResultCSV {
const result: ParseResultCSV = {
type: 'CSV',
columns: [],
rows: [],
};
let delimiter;
const header = config.hasHeader;
switch (config.delimiter) {
case CSVDelimiter.Comma: delimiter = ','; break;
case CSVDelimiter.Space: delimiter = ' '; break;
case CSVDelimiter.Tabulator: delimiter = '\t'; break;
}
const papaResults = Papa.parse(content, {
delimiter,
header,
});
if (header && papaResults.meta['fields']) {
result.columns = papaResults.meta['fields'];
} else {
const row = papaResults.data[0] as Array<unknown>;
result.columns = Array.from({ length: row.length }, (_, i) => i + 1);
}
if (header) {
for (let i = 0; i < papaResults.data.length; i++) {
result.rows.push(papaResults.data[i] as unknown as Record<string, string>);
}
} else {
for (let i = 0; i < papaResults.data.length; i++) {
const row = papaResults.data[i] as Array<unknown>;
const entries = new Map<string, string>();
for (let i = 0; i < row.length; i++) {
const value = row[i];
entries.set(String(i + 1), String(value));
}
result.rows.push(Object.fromEntries(entries));
}
}
return result;
}
export function parseResultToXYZ(parseResult: ParseResultCSV, columns: Array<string | number>): Array<{ x: number, y: number, z: number }> {
const positions: Array<{ x: number, y: number, z: number }> = [];
for (let i = 0; i < parseResult.rows.length; i++) {
const row = parseResult.rows[i];
positions.push({
x: parseFloat(row[columns[0]]),
y: parseFloat(row[columns[1]]),
z: parseFloat(row[columns[2]])
});
}
return positions;
}
export function parseResultToSparse1D(parseResult: ParseResultCSV, columns: Array<string | number>): Array<{ from: number, to: number, value: number }> {
const data: Array<{ from: number, to: number, value: number }> = [];
for (let i = 0; i < parseResult.rows.length; i++) {
const row = parseResult.rows[i];
data.push({
from: parseFloat(row[columns[0]]),
to: parseFloat(row[columns[1]]),
value: parseFloat(row[columns[2]])
});
}
return data;
}
export function parseResultToSparseDistanceMatrix(parseResult: ParseResultCSV, columns: Array<string | number>): Array<{ from: number, to: number, distance: number }> {
const data: Array<{ from: number, to: number, distance: number }> = [];
for (let i = 0; i < parseResult.rows.length; i++) {
const row = parseResult.rows[i];
data.push({
from: parseFloat(row[columns[0]]),
to: parseFloat(row[columns[1]]),
distance: parseFloat(row[columns[2]])
});
}
return data;
}
//todo: screw the lib and rewrite
// export function parseGFF(content: string): Array<{
// seqId: string | null,
// from?: number | null, to?: number | null,
// score?: number | null,
// strand?: "+" | "-" | "?" | "." | null
// attributes: Record<string, string[] | undefined>
// }> {
// const annotations = gff.parseStringSync(content);
// const parsedAnnotations = [];
// for (const annotation of annotations) {
// if (!(Symbol.iterator in Object.values(annotation))) {
// continue;
// }
// for (const feature of annotation) {
// parsedAnnotations.push({
// seqId: feature.seq_id,
// from: feature.start,
// to: feature.end,
// score: feature.score,
// strand: feature.strand as "+" | "-" | "?" | "." | null,
// attributes: feature.attributes ?? {}
// })
// }
// }
// return parsedAnnotations;
// }
export type ParseResultBED = Array<{
chromosome: string,
from: number, to: number,
attributes: Record<number, string>
}>
export function parseBED(content: string, delimiter: CSVDelimiter.Space | CSVDelimiter.Tabulator): ParseResultBED {
const parseResults = parseCSVToObjects(content, {
type: FileType.CSV,
hasHeader: false,
delimiter: delimiter
}) as ParseResultCSV;
const annotations = parseResults.rows.filter(r => r[1] != "browser" && r[1] != "track" && Object.keys(r).length != 1)
return annotations.map(
a => {
return {
chromosome: a[1],
from: toNumber(a[2]),
to: toNumber(a[3]),
attributes: a,
...a
}
}
);
}
\ No newline at end of file
...@@ -18,8 +18,10 @@ ...@@ -18,8 +18,10 @@
"react-dom": "^18.1.0" "react-dom": "^18.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/lodash": "^4.14.182",
"@types/react": "^18.0.9", "@types/react": "^18.0.9",
"@types/react-dom": "^18.0.3", "@types/react-dom": "^18.0.3",
"esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.6.4" "typescript": "^4.6.4"
} }
}, },
...@@ -42,6 +44,12 @@ ...@@ -42,6 +44,12 @@
"@babel/runtime": "^7.10.3" "@babel/runtime": "^7.10.3"
} }
}, },
"node_modules/@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"node_modules/@types/pako": { "node_modules/@types/pako": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.3.tgz",
...@@ -94,6 +102,12 @@ ...@@ -94,6 +102,12 @@
"resolved": "https://registry.npmjs.org/canny-edge-detector/-/canny-edge-detector-1.0.0.tgz", "resolved": "https://registry.npmjs.org/canny-edge-detector/-/canny-edge-detector-1.0.0.tgz",
"integrity": "sha512-SpewmkHDE1PbJ1/AVAcpvZKOufYpUXT0euMvhb5C4Q83Q9XEOmSXC+yR7jl3F4Ae1Ev6OtQKbFgdcPrOdHjzQg==" "integrity": "sha512-SpewmkHDE1PbJ1/AVAcpvZKOufYpUXT0euMvhb5C4Q83Q9XEOmSXC+yR7jl3F4Ae1Ev6OtQKbFgdcPrOdHjzQg=="
}, },
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.0.11", "version": "3.0.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
...@@ -374,6 +388,16 @@ ...@@ -374,6 +388,16 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/esbuild-plugin-inline-worker": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/esbuild-plugin-inline-worker/-/esbuild-plugin-inline-worker-0.1.1.tgz",
"integrity": "sha512-VmFqsQKxUlbM51C1y5bRiMeyc1x2yTdMXhKB6S//++g9aCBg8TfGsbKxl5ZDkCGquqLY+RmEk93TBNd0i35dPA==",
"dev": true,
"dependencies": {
"esbuild": "latest",
"find-cache-dir": "^3.3.1"
}
},
"node_modules/esbuild-sunos-64": { "node_modules/esbuild-sunos-64": {
"version": "0.14.38", "version": "0.14.38",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz", "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz",
...@@ -497,6 +521,36 @@ ...@@ -497,6 +521,36 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/find-cache-dir": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
"dev": true,
"dependencies": {
"commondir": "^1.0.1",
"make-dir": "^3.0.2",
"pkg-dir": "^4.1.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
}
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/gl-matrix": { "node_modules/gl-matrix": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
...@@ -610,6 +664,18 @@ ...@@ -610,6 +664,18 @@
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"dependencies": {