Skip to content

Commit

Permalink
Merge pull request #374 from vgteam/eliding_small_snarls
Browse files Browse the repository at this point in the history
Eliding small snarls
  • Loading branch information
adamnovak authored Dec 7, 2023
2 parents e70b93c + a147acb commit 050b962
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 21 deletions.
5 changes: 3 additions & 2 deletions public/help/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ The following procedure describes adding and updating settings of custom tracks.
* A start position and a distance (e.g. "chr1:1+100")
* A node ID anchor and a distance (e.g. "node:100+10")
![Region Input Options](helpGuideImages/img8.png)
9. Click Go to see the selected tracks render in the visualization area.
![Go Button](helpGuideImages/img9.png)
4. If simplifying the BED file chunk or graph is possible, users will see a "Simplify Off" button, which when clicked with toggle to "Simplify On". This option enables vg simplify, which would remove small snarls. This option will only appear when there aren't any reads to be displayed.
5. Click Go to see the selected tracks render in the visualization area.
![Go Button](helpGuideImages/img9.png)
11 changes: 11 additions & 0 deletions src/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ export function defaultTrackColors(trackType){
}
}

/* Function to determine if any of the tracks are reads, where the tracks parameter is an object of track types */
export function readsExist(tracks){
for (let key in tracks){
if (tracks[key].trackType === "read"){
return true;
}
}
return false;
}

// Accepts a string, returns whether or not the input is a valid http URL we can call fetch on
export function isValidURL(string) {
if (!string) {
Expand All @@ -130,3 +140,4 @@ export function isValidURL(string) {
export function isEmpty(obj) {
return Object.keys(obj).length === 0;
}

12 changes: 9 additions & 3 deletions src/components/CopyLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,15 @@ export const urlParamsToViewTarget = (url) => {
result = qs.parse(s[1]);
}

// TODO: qs can't tell the difference between false and "false", and "false"
// is truthy. So we need to go through and coerce things to real booleans at
// some point.
// Ensures that the simplify field is a boolean, as the qs module can't tell
// the difference between false and "false"
if (result != null){
if (result.simplify === "true"){
result.simplify = true;
} else if (result.simplify === "false"){
result.simplify = false;
}
}

return result;
};
26 changes: 21 additions & 5 deletions src/components/HeaderForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Container, Row, Col, Label, Alert } from "reactstrap";
import { Container, Row, Col, Label, Alert, Button } from "reactstrap";
import { dataOriginTypes } from "../enums";
import { fetchAndParse } from "../fetchAndParse";
import "../config-client.js";
Expand All @@ -10,7 +10,7 @@ import ExampleSelectButtons from "./ExampleSelectButtons";
import RegionInput from "./RegionInput";
import TrackPicker from "./TrackPicker";
import BedFileDropdown from "./BedFileDropdown";
import { parseRegion, stringifyRegion, isEmpty } from "../common.mjs";
import { parseRegion, stringifyRegion, isEmpty, readsExist } from "../common.mjs";


// See src/Types.ts
Expand Down Expand Up @@ -108,7 +108,7 @@ function tracksEqual(curr, next) {
return false;
}
}
//count falsy file names as the same
// count falsy file names as the same
if ((!curr_file && !next_file) || curr_file === next_file) {
return true;
}
Expand Down Expand Up @@ -155,11 +155,14 @@ function viewTargetsEqual(currViewTarget, nextViewTarget) {
return false;
}

if (currViewTarget.simplify !== nextViewTarget.simplify){
return false;
}

return true;

}


class HeaderForm extends Component {
state = EMPTY_STATE;
componentDidMount() {
Expand Down Expand Up @@ -208,6 +211,7 @@ class HeaderForm extends Component {
region: ds.region,
dataType: ds.dataType,
name: ds.name,
simplify: ds.simplify
};
return stateVals;
});
Expand Down Expand Up @@ -465,6 +469,7 @@ class HeaderForm extends Component {
name: this.state.name,
region: this.state.region,
dataType: this.state.dataType,
simplify: this.state.simplify && !(readsExist(this.state.tracks))
});

handleGoButton = () => {
Expand Down Expand Up @@ -713,6 +718,11 @@ class HeaderForm extends Component {
};
};

/* Function for toggling simplify button, enabling vg simplify to be turned on or off */
toggleSimplify = () => {
this.setState( { simplify: !this.state.simplify });
}

render() {
let errorDiv = null;
if (this.state.error) {
Expand Down Expand Up @@ -752,6 +762,7 @@ class HeaderForm extends Component {
const examplesFlag = this.state.dataType === dataTypes.EXAMPLES;
const viewTargetHasChange = !viewTargetsEqual(this.getNextViewTarget(), this.props.getCurrentViewTarget());


console.log(
"Rendering header form with fileSelectOptions: ",
this.state.fileSelectOptions
Expand Down Expand Up @@ -841,7 +852,12 @@ class HeaderForm extends Component {
onChange={this.handleInputChange}
handleFileUpload={this.handleFileUpload}
></TrackPicker>
</div>
{/* Button for simplify */}
{
!(readsExist(this.state.tracks)) &&
<Button onClick={this.toggleSimplify} outline active={this.state.simplify}>{this.state.simplify ? "Simplify On" : "Simplify Off"}</Button>
}
</div>
}

<Row>
Expand Down
4 changes: 3 additions & 1 deletion src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dataPath": "default",
"region": "17:1-100",
"bedFile": "exampleData/internal/snp1kg-BRCA1.bed",
"dataType": "built-in"
"dataType": "built-in",
"simplify": false
},
{
"name": "vg \"small\" example",
Expand Down Expand Up @@ -91,4 +92,5 @@
"pickerTypeOptions": ["mounted", "upload"],
"fileExpirationTime": 86400


}
4 changes: 2 additions & 2 deletions src/end-to-end.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ describe("When we wait for it to load", () => {
it("produces correct link for view before & after go is pressed", async () => {
// First test that after pressing go, the link reflects the dat form
const expectedLinkBRCA1 =
"http://localhost?name=snp1kg-BRCA1&tracks[0][trackFile]=exampleData%2Finternal%2Fsnp1kg-BRCA1.vg.xg&tracks[0][trackType]=graph&tracks[0][trackColorSettings][mainPalette]=greys&tracks[0][trackColorSettings][auxPalette]=ygreys&tracks[1][trackFile]=exampleData%2Finternal%2FNA12878-BRCA1.sorted.gam&tracks[1][trackType]=read&dataPath=default&region=17%3A1-100&bedFile=exampleData%2Finternal%2Fsnp1kg-BRCA1.bed&dataType=built-in";
"http://localhost?name=snp1kg-BRCA1&tracks[0][trackFile]=exampleData%2Finternal%2Fsnp1kg-BRCA1.vg.xg&tracks[0][trackType]=graph&tracks[0][trackColorSettings][mainPalette]=greys&tracks[0][trackColorSettings][auxPalette]=ygreys&tracks[1][trackFile]=exampleData%2Finternal%2FNA12878-BRCA1.sorted.gam&tracks[1][trackType]=read&dataPath=default&region=17%3A1-100&bedFile=exampleData%2Finternal%2Fsnp1kg-BRCA1.bed&dataType=built-in&simplify=false";
// Set up dropdown
await act(async () => {
let dropdown = document.getElementById("dataSourceSelect");
Expand Down Expand Up @@ -378,7 +378,7 @@ it("produces correct link for view before & after go is pressed", async () => {
await clickCopyLink();

const expectedLinkCactus =
"http://localhost?tracks[0][trackFile]=exampleData%2Fcactus.vg.xg&tracks[0][trackType]=graph&tracks[1][trackFile]=exampleData%2Fcactus-NA12879.sorted.gam&tracks[1][trackType]=read&bedFile=exampleData%2Fcactus.bed&name=cactus&region=ref%3A1-100&dataType=built-in";
"http://localhost?tracks[0][trackFile]=exampleData%2Fcactus.vg.xg&tracks[0][trackType]=graph&tracks[1][trackFile]=exampleData%2Fcactus-NA12879.sorted.gam&tracks[1][trackType]=read&bedFile=exampleData%2Fcactus.bed&name=cactus&region=ref%3A1-100&dataType=built-in&simplify=false";
// Make sure link has changed after pressing go
expect(fakeClipboard).toEqual(expectedLinkCactus);
}, 20000);
Expand Down
134 changes: 126 additions & 8 deletions src/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { server as WebSocketServer } from "websocket";
import dotenv from "dotenv";
import dirname from "es-dirname";
import { readFileSync, writeFile } from 'fs';
import { parseRegion, convertRegionToRangeRegion, stringifyRangeRegion, stringifyRegion, isValidURL } from "./common.mjs";
import { parseRegion, convertRegionToRangeRegion, stringifyRangeRegion, stringifyRegion, isValidURL, readsExist } from "./common.mjs";
import { Readable } from "stream";
import { finished } from "stream/promises";
import sanitize from "sanitize-filename";
Expand Down Expand Up @@ -478,6 +478,14 @@ async function getChunkedData(req, res, next) {
req.withBed = false;
console.log("no BED file provided.");
}
// client is going to send simplify = true if they want to simplify view
req.simplify = false;
if (req.body.simplify){
if (readsExist(req.body.tracks)){
throw new BadRequestError("Simplify cannot be used on read tracks.");
}
req.simplify = true;
}

// check the bed file if this region has been pre-fetched
let chunkPath = "";
Expand Down Expand Up @@ -585,6 +593,13 @@ async function getChunkedData(req, res, next) {

console.time("vg chunk");
const vgChunkCall = spawn(`${VG_PATH}vg`, vgChunkParams);
// vg simplify for gam files
let vgSimplifyCall = null;
if (req.simplify){
vgSimplifyCall = spawn(`${VG_PATH}vg`, ["simplify", "-"]);
console.log("Spawning vg simplify call");
}

const vgViewCall = spawn(`${VG_PATH}vg`, ["view", "-j", "-"]);
let graphAsString = "";
req.error = Buffer.alloc(0);
Expand All @@ -611,12 +626,20 @@ async function getChunkedData(req, res, next) {
});

vgChunkCall.stdout.on("data", function (data) {
vgViewCall.stdin.write(data);
if (req.simplify){
vgSimplifyCall.stdin.write(data);
} else {
vgViewCall.stdin.write(data);
}
});

vgChunkCall.on("close", (code) => {
console.log(`vg chunk exited with code ${code}`);
vgViewCall.stdin.end();
if (req.simplify){
vgSimplifyCall.stdin.end();
} else {
vgViewCall.stdin.end();
}
if (code !== 0) {
console.log("Error from " + VG_PATH + "vg " + vgChunkParams.join(" "));
// Execution failed
Expand All @@ -627,6 +650,49 @@ async function getChunkedData(req, res, next) {
}
});

// vg simplify
if (req.simplify){
vgSimplifyCall.on("error", function (err) {
console.log(
"Error executing " +
VG_PATH +
"vg " +
"simplify " +
"- " +
": " +
err
);
if (!sentResponse) {
sentResponse = true;
return next(new VgExecutionError("vg simplify failed"));
}
return;
});

vgSimplifyCall.stderr.on("data", (data) => {
console.log(`vg simplify err data: ${data}`);
req.error += data;
});

vgSimplifyCall.stdout.on("data", function (data) {
vgViewCall.stdin.write(data);
});

vgSimplifyCall.on("close", (code) => {
console.log(`vg simplify exited with code ${code}`);
vgViewCall.stdin.end();
if (code !== 0) {
console.log("Error from " + VG_PATH + "vg " + "simplify - ");
// Execution failed
if (!sentResponse) {
sentResponse = true;
return next(new VgExecutionError("vg simplify failed"));
}
}
});
}

// vg view
vgViewCall.on("error", function (err) {
console.log('Error executing "vg view": ' + err);
if (!sentResponse) {
Expand Down Expand Up @@ -676,13 +742,65 @@ async function getChunkedData(req, res, next) {
// We're using a shared directory for this request, so leave it in place
// when the request finishes.
req.rmChunk = false;
const vgViewCall = spawn(`${VG_PATH}vg`, [
"view",
"-j",
`${req.chunkDir}/chunk.vg`,
]);
let filename = `${req.chunkDir}/chunk.vg`;
// vg simplify for bed files
let vgSimplifyCall = null;
let vgViewArguments = ["view", "-j"];
if (req.simplify){
vgSimplifyCall = spawn(`${VG_PATH}vg`, ["simplify", filename,]);
vgViewArguments.push("-");
console.log("Spawning vg simplify call");
} else {
vgViewArguments.push(filename);
}

let vgViewCall = spawn(`${VG_PATH}vg`, vgViewArguments);

let graphAsString = "";
req.error = Buffer.alloc(0);

// vg simplify
if (req.simplify){
vgSimplifyCall.on("error", function (err) {
console.log(
"Error executing " +
VG_PATH +
"vg " +
"simplify " +
filename +
": " +
err
);
if (!sentResponse) {
sentResponse = true;
return next(new VgExecutionError("vg simplify failed"));
}
return;
});

vgSimplifyCall.stderr.on("data", (data) => {
console.log(`vg simplify err data: ${data}`);
req.error += data;
});

vgSimplifyCall.stdout.on("data", function (data) {
vgViewCall.stdin.write(data);
});

vgSimplifyCall.on("close", (code) => {
console.log(`vg simplify exited with code ${code}`);
vgViewCall.stdin.end();
if (code !== 0) {
console.log("Error from " + VG_PATH + "vg " + "simplify " + filename);
// Execution failed
if (!sentResponse) {
sentResponse = true;
return next(new VgExecutionError("vg simplify failed"));
}
}
});
}

vgViewCall.on("error", function (err) {
console.log('Error executing "vg view": ' + err);
if (!sentResponse) {
Expand Down

0 comments on commit 050b962

Please sign in to comment.