Commit 052c99a1 authored by Matus Talcik's avatar Matus Talcik
Browse files

3d sasa + better 2d viewport

parent 85af6f4c
Loading
Loading
Loading
Loading
+1640 −256

File changed.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@
    "react-sigma-v2": "^1.3.0",
    "react-tooltip": "^4.2.21",
    "react-use": "^17.3.1",
    "reaviz": "^13.1.8",
    "rooks": "^5.7.3",
    "sigma": "^2.2.0",
    "simple-statistics": "^7.7.0",
+3 −3
Original line number Diff line number Diff line
@@ -274,7 +274,7 @@ export function ChromatinViewportConfigurationPanel(props: {
        const high = quantiles[1] + 1.5 * iqr;

        const radius = low;
        const radiusRange = { min: low, max: high };
        const radiusRange = { min: 0.0, max: high };
        //#region Calculate radius range

        newData.push({
@@ -742,8 +742,8 @@ export function ChromatinViewportConfigurationPanel(props: {
            />
            <Slider
                label="Probe Size"
                min={0}
                max={1}
                min={0.01}
                max={1.0}
                step={0.01}
                value={configuration.sasa.probeSize}
                showValue={true}
+30 −228
Original line number Diff line number Diff line
@@ -226,19 +226,33 @@ export function ChromatinViewport(props: {
            return;
        }

        const mapScaleToChromatin = (chromatinPart: GraphicsModule.ChromatinPart, values: Array<number>, scale: chroma.Scale): Array<vec4> => {
            const ratio = Math.max(...values);
            const valuesNormalized = values.map(v => v / ratio);

            const colors: Array<vec4> = valuesNormalized.map(v => {
                return scale(v).gl();
            });

            return colors;
        }

        const newColors = new Array(configuration.data.length);
        for (const [configurationDatumIndex, configurationDatum] of configuration.data.entries()) {
            const data3D = viewport.getChromatinPartByDataId(configurationDatumIndex);
            const datum = data.data.find(d => d.id === configurationDatum.id)?.values as Positions3D;
            const chromatinPart = viewport.getChromatinPartByDataId(configurationDatumIndex);
            let dataMarkers = null;

            if (!data3D) {
            if (!datum || !chromatinPart) {
                continue;
            }

            const binsAmount = data3D.getBinsPositions().length;
            const positions = datum.positions;

            const binsAmount = chromatinPart.getBinsPositions().length;

            if (configurationDatum.colorMappingMode == "single-color") {
                newColors[configurationDatumIndex] = data3D.cacheColorArray(new Array(binsAmount).fill(vec4.fromValues(
                newColors[configurationDatumIndex] = chromatinPart.cacheColorArray(new Array(binsAmount).fill(vec4.fromValues(
                    configurationDatum.color.r / 255.0,
                    configurationDatum.color.g / 255.0,
                    configurationDatum.color.b / 255.0,
@@ -269,239 +283,27 @@ export function ChromatinViewport(props: {
                for (let i = 0; i < binsAmount; i++) {
                    finalColors[i] = colors[finalColorIndices[i]];
                }
                newColors[configurationDatumIndex] = data3D.cacheColorArray(finalColors);
            }
        }

        /*
        const mapScaleToChromatin = (values: Array<number>, scale: chroma.Scale): Array<Array<vec4>> => {
            const ratio = Math.max(...values);
            const valuesNormalized = values.map(v => v / ratio);

            const allColors: Array<Array<vec4>> = new Array(data3D.chromosomes.length);

            for (let chromosomeIndex = 0; chromosomeIndex < configuration.chromosomes.length; chromosomeIndex++) {
                const chromatinPart = viewport.getChromatinPartByChromosomeIndex(chromosomeIndex);
                if (!chromatinPart) {
                    continue;
                }

                const chromosomeBinOffset = chromatineSlices[chromosomeIndex].from;

                const colors: Array<vec4> = valuesNormalized.slice(chromosomeBinOffset, chromosomeBinOffset + chromatinPart.getBinsPositions().length).map(v => {
                    return scale(v).gl();
                });

                allColors[chromosomeIndex] = chromatinPart.cacheColorArray(colors);
            }

            return allColors;
        }

        if (configuration.colorMappingMode == '1d-density') {

            const data1d: Array<{ chromosome: string, from: number, to: number }> | null = data.data.find(d => d.id == isoDataID.wrap(configuration.mapValues.id))?.values as Sparse1DTextData | Sparse1DNumericData | null;
            if (!data1d) {
                return;
            }

            const scale = chroma.scale(['white', 'blue']);
            const countPerBin: Array<number> = Array(data3D.values.length);
            _.fill(countPerBin, 0)

            for (let chromosomeIndex = 0; chromosomeIndex < configuration.chromosomes.length; chromosomeIndex++) {
                const partInfo = chromatineSlices[chromosomeIndex];
                const chromosomeData1d = data1d.filter(d => d.chromosome == partInfo.name);
                const res = data3D.basePairsResolution;
                for (let binIndex = 0; binIndex < partInfo.to - partInfo.from; binIndex++) {
                    for (const datum of chromosomeData1d) {
                        if (datum.from <= (binIndex + 1) * res && datum.to >= binIndex * res) {
                            countPerBin[binIndex + partInfo.from] += 1;
                        }
                    }
                }
            }

            const logCountPerBin = countPerBin.map(v => Math.log(v) + 1);

            setInnerColors(() => mapScaleToChromatin(logCountPerBin, scale));
        }

        if (configuration.colorMappingMode == '1d-numerical') {
            const data1d: Sparse1DNumericData | null = data.data.find(d => d.id == isoDataID.wrap(configuration.mapValues.id))?.values as Sparse1DNumericData | null;
            if (!data1d) {
                return;
            }

            const scale = chroma.scale(['white', 'blue']);
            const valuesPerBin: Array<Array<number>> = Array.from(Array(data3D.values.length), () => [])

            for (let chromosomeIndex = 0; chromosomeIndex < configuration.chromosomes.length; chromosomeIndex++) {
                const partInfo = chromatineSlices[chromosomeIndex];
                const chromosomeData1d = data1d.filter(d => d.chromosome == partInfo.name);
                const res = data3D.basePairsResolution;
                for (let binIndex = 0; binIndex < partInfo.to - partInfo.from; binIndex++) {
                    for (const datum of chromosomeData1d) {
                        if (datum.from <= (binIndex + 1) * res && datum.to >= binIndex * res) {
                            valuesPerBin[binIndex + partInfo.from].push(datum.value);
                        }
                    }
                }
            }



            const aggregationFunction: (n: Array<number>) => number | undefined = {
                "min": _.min,
                "max": _.max,
                "mean": _.mean,
                "median": median,
                "sum": _.sum
            }[configuration.mapValues.aggregationFunction]
            const aggregatedValuesPerBin = valuesPerBin.map((vs) => {
                const result = aggregationFunction(vs)
                if (result == null || isNaN(result)) {
                    return 0;
                }
                return result;
            })


            setInnerColors(() => mapScaleToChromatin(aggregatedValuesPerBin, scale));
        }

        if (configuration.colorMappingMode == "centromers") {
            const mapData1D: Positions3D | null = data.data.find(d => d.id == isoDataID.wrap(configuration.mapValues.id))?.values as Positions3D | null;
            if (!mapData1D) {
                return;
            }
            const centromereBins = new Array(mapData1D.length);

            // Normalize centromeres to current bounding box
            const normalizeCenter = data3D.normalizeCenter;
            const normalizeScale = data3D.normalizeScale;
            const centromeres: Array<vec3> = [];
            for (const c of mapData1D) {
                let centromere = vec3.fromValues(c.x, c.y, c.z);

                centromere = vec3.sub(vec3.create(), centromere, normalizeCenter);
                centromere = vec3.scale(vec3.create(), centromere, normalizeScale);

                centromeres.push(centromere);
            }

            // Map centromere 3D position to 1D bin index
            for (let centromereIndex = 0; centromereIndex < centromeres.length; centromereIndex++) {
                let minDistance = 1.0;
                let minIndex = -1;

                for (let valueIndex = 0; valueIndex < data3D.values.length; valueIndex++) {
                    const value = vec3.fromValues(data3D.values[valueIndex].x, data3D.values[valueIndex].y, data3D.values[valueIndex].z);

                    const diff = vec3.sub(vec3.create(), centromeres[centromereIndex], value);
                    const distance = vec3.dot(diff, diff);

                    if (distance < minDistance) {
                        minDistance = distance;
                        minIndex = valueIndex;
                    }
                }

                centromereBins[centromereIndex] = minIndex;
            }

            // Map bin to distance
            const distances: Array<number> = [];
            for (let valueIndex = 0; valueIndex < data3D.values.length; valueIndex++) {
                const distance = Math.min(...centromereBins.map((v) => Math.abs(v - valueIndex)));
                distances.push(distance);
            }

            // Color inside with mapping
            // const colorScale = chroma.scale('YlGnBu');
            const colorScale = chroma.scale(['white', 'blue']);

            setInnerColors(() => mapScaleToChromatin(distances, colorScale));
        }

        if (configuration.colorMappingMode == "linear-order") {
            const order: Array<number> = [];
            for (let chromosomeIndex = 0; chromosomeIndex < configuration.chromosomes.length; chromosomeIndex++) {
                const partInfo = chromatineSlices[chromosomeIndex];
                for (let o = 0; o < partInfo.to - partInfo.from + 1; o++) {
                    order.push(o);
                }
            }
            const colorScale = chroma.scale('YlGnBu'); //pick better color scale
            setInnerColors(() => mapScaleToChromatin(order, colorScale));
        }

        if (configuration.colorMappingMode == 'sasa') {
                newColors[configurationDatumIndex] = chromatinPart.cacheColorArray(finalColors);
            } else if (configurationDatum.colorMappingMode == "sasa") {
                if (configuration.sasa.method == 'generated') {
                throw "Not implemented"
                    throw "Not implemented";
                }
            //TODO: per chromosome or whole chromosome
            const globalSasaValues: Array<number> = [];

            if (!configuration.sasa.individual) {
                globalSasaValues.push(...sasa(data3D.values, {
                    method: configuration.sasa.method,
                    probe_size: configuration.sasa.probeSize,
                }, configuration.sasa.accuracy))

            } else {
                for (let chromosomeIndex = 0; chromosomeIndex < configuration.chromosomes.length; chromosomeIndex++) {
                    const partInfo = chromatineSlices[chromosomeIndex];
                    const chromosomePositions = data3D.values.slice(partInfo.from, partInfo.to + 1);
                    globalSasaValues.push(...sasa(chromosomePositions, {
                //TODO: per chromosome or whole chromosome
                const globalSasaValues = sasa(positions, {
                    method: configuration.sasa.method,
                    probe_size: configuration.sasa.probeSize,
                    }, configuration.sasa.accuracy));
                }
                }, configuration.sasa.accuracy);

                //TODO: fix underlying bug where the something sometimes don't contain last bin of the chromosome
                if (globalSasaValues.length < data3D.values.length) {
                    globalSasaValues.push(globalSasaValues.reduce((a, b) => a + b, 0) / globalSasaValues.length);
                }

            }


            const colorScale = chroma.scale(['white', 'green']);

            setInnerColors(() => mapScaleToChromatin(globalSasaValues, colorScale));
        }

        if (configuration.colorMappingMode == '3d-density') {
            //TODO: per chromosome or whole chromosome
            const densities: Array<number> = [];

            if (!configuration.density.individual) {
                densities.push(...density(data3D.values, configuration.density.probeSize))
            } else {
                // if (globalSasaValues.length < chromatinPart.values.length) {
                // globalSasaValues.push(globalSasaValues.reduce((a, b) => a + b, 0) / globalSasaValues.length);
                // }

                for (let chromosomeIndex = 0; chromosomeIndex < configuration.chromosomes.length; chromosomeIndex++) {
                    const partInfo = chromatineSlices[chromosomeIndex];
                    const chromosomePositions = data3D.values.slice(partInfo.from, partInfo.to + 1);
                    densities.push(...density(
                        chromosomePositions,
                        configuration.density.probeSize // TODO: user setting
                    ));
                }
                const colorScale = chroma.scale(['#fcfdfd', '#010a4e']);
                newColors[configurationDatumIndex] = chromatinPart.cacheColorArray(mapScaleToChromatin(chromatinPart, globalSasaValues, colorScale));
            }
            //TODO: fix underlying bug where the data3d.values sometimes don't contain last bin of the chromosome
            if (densities.length < data3D.values.length) {
                console.warn("Fixing bullshit")
                console.log(densities)
                console.log(data3D.values)
                densities.push(densities.reduce((a, b) => a + b, 0) / densities.length);
            }

            const colorScale = chroma.scale(['white', 'red']);

            setInnerColors(() => mapScaleToChromatin(densities, colorScale));
        }
        */

        setColors(() => newColors);
    }, [viewport, globalSelections.selections, configuration.data, configuration.sasa, data.data, configuration.chromosomes, configuration.density]);
+60 −4
Original line number Diff line number Diff line
@@ -9,7 +9,8 @@ import { BinPosition, squareDiameter } from "../../modules/graphics";
import { SelectionAction, SelectionActionKind, SelectionState } from "../../modules/storage/models/selections";
import { DataAction, DataState, Positions3D } from "../../modules/storage/models/data";
import { useConfiguration, useSelections } from "../hooks";

import { sasa } from "../../modules/sasa";
import { ChartShallowDataShape, LineChart, LineSeries, ScatterPlot, SparklineChart } from 'reaviz';

function groupSubsequentNumbers(array: Array<number>): Array<Array<number>> {
    return array.reduce<Array<Array<number>>>((grouped, next) => {
@@ -72,6 +73,8 @@ export function TADViewport(props: {
    const [binGroups, setBinGroups] = useState<Array<Array<number>>>([]);
    const [svgNumbers, setSvgNumbers] = useState<Array<JSX.Element>>([]);

    const [sasaValues, setSasaValues] = useState<Array<Array<number>>>([]);

    const updatePositions = () => {
        if (!viewport || !viewport.canvas) {
            return;
@@ -111,7 +114,39 @@ export function TADViewport(props: {
                        positions.push(vec4.fromValues(values[i].x, values[i].y, values[i].z, 1.0));
                    }

                    const globalSasaValues = [sasa(values, {
                        method: 'constant',
                        probe_size: 0.02,
                    }, 100)];

                    viewport.setPositions(positions);

                    for(let lod = 1; lod < 32; lod++) {
                        const size = viewport.globals.sizes[lod];
                        if (size === 0) {
                            break;
                        }
                        
                        const offset = viewport.globals.offsets[lod-1];

                        const values = [];
                        for(let i = 0; i < size; i++) {
                            const j = i;

                            if (i == size - 1 && viewport.globals.sizes[lod - 1] % 2 !== 0) {
                                values.push((globalSasaValues[lod-1][j] + globalSasaValues[lod-1][j + 1] + globalSasaValues[lod-1][j + 2]) * 0.33);
                                break;
                            } else {
                                values.push((globalSasaValues[lod-1][j] + globalSasaValues[lod-1][j + 1]) * 0.5);
                            }                            
                        }

                        globalSasaValues.push(values);
                        console.log(globalSasaValues);
                    }                    

                    setSasaValues(() => globalSasaValues);

                    break;
                }
            }
@@ -574,6 +609,15 @@ export function TADViewport(props: {
        setSelectionsRanges(() => selectionsRanges);
    }, [viewport, data, allSelections, selections, viewport.currentLoD]);

    let maxSasa = 0;
    let normalizedSasaValues = null;
    let xSize = 0;
    if (sasaValues[viewport.currentLoD] && tracksBlock) {
        maxSasa = Math.max(...sasaValues[viewport.currentLoD]);
        normalizedSasaValues = sasaValues[viewport.currentLoD].map(v => (v / maxSasa));
        xSize = tracksBlock.width / normalizedSasaValues.length;
    }

    return (<div style={{ width: '100%', height: '100%', overflow: 'hidden', position: 'relative' }}>
        <canvas ref={canvasElement} style={{ width: '100%', height: '100%', overflow: 'hidden', position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} onClick={onClick}></canvas>
        <svg style={{ width: '100%', height: '100%', overflow: 'hidden', position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, pointerEvents: 'none' }}>
@@ -589,7 +633,7 @@ export function TADViewport(props: {
            )}
            {/* {svgNumbers} */}
        </svg>
        {(currentBinsAmount && tracksBlock && !isNaN(tracksBlock.top)) && (<div className={'topDiv'}>
        {(currentBinsAmount && tracksBlock && normalizedSasaValues && !isNaN(tracksBlock.top)) && (<div className={'topDiv'}>
            <div style={{
                width: tracksBlock.width,
                position: 'absolute',
@@ -604,7 +648,8 @@ export function TADViewport(props: {
                            height: '8px',
                            display: 'grid',
                            marginTop: '8px',
                            gridTemplateColumns: 'repeat(' + currentBinsAmount + ', 1fr)'
                            gridTemplateColumns: 'repeat(' + currentBinsAmount + ', 1fr)',
                            position: 'absolute'
                        }}>
                            {selectionRange.map((range, index) => {
                                return <div key={index} style={{
@@ -615,6 +660,17 @@ export function TADViewport(props: {
                        </div>
                    } else { return <div key={selectionRangeIndex}></div> }
                })}
                <div style={{width: '100%', height: '40px' }}></div>
                <SparklineChart 
                width={tracksBlock.width} 
                height={80} 
                data={ normalizedSasaValues.map((v, i) => { return { key: i, data: 1.0 - v } as ChartShallowDataShape } ) }>
                </SparklineChart>
                {/* <svg style={{marginTop: '40px'}} width={tracksBlock.width.toString()} height={"80"} viewBox={"0 0 " + tracksBlock.width.toString() + " 80"}>
                    <path fill="none" stroke="blue" strokeWidth={"1"}
                        d={"" + normalizedSasaValues.map((v, i) => { return (i == 0 ? "M" : " L") + (i * xSize).toString() + "," + (80 - v).toString(); })}
                    />
                </svg> */}
            </div>
        </div>)}
    </div>