Skip to content

Commit

Permalink
[major changes] data transformed on the server
Browse files Browse the repository at this point in the history
* Server now stores all the data and sends only the data necessary to view. Should allow for a much more responsive client.
* Communication now through sockets in preparation for eventual move to electron
* viz completely broken
* sidebar UI
* config can be changed through the GUI (buggy)
  • Loading branch information
jameshadfield committed May 4, 2019
1 parent 14933e6 commit efff550
Show file tree
Hide file tree
Showing 28 changed files with 7,590 additions and 4,048 deletions.
6 changes: 5 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"extends": "react-app"
"extends": "react-app",
"plugins": ["import"],
"rules": {
"import/no-unresolved": 2
}
}
4 changes: 2 additions & 2 deletions examples/EBOV/ZEBOV_3Samples_NB_config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "EBOV Validation Run (MinIT)",
"referenceConfigPath": "./examples/EBOV/EBOV_v1.0.json",
"referencePanelPath": "./examples/EBOV/reference-genomes.fasta",
"referenceConfigPath": "./EBOV_v1.0.json",
"referencePanelPath": "./reference-genomes.fasta",
"samples": [
{
"name": "Mayinga",
Expand Down
22 changes: 19 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
"d3-scale-chromatic": "^1.3.3",
"d3-selection": "^1.3.0",
"d3-shape": "^1.2.0",
"eslint-plugin-import": "^2.17.2",
"express": "^4.16.3",
"glamor": "^2.20.40",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-scripts": "1.1.1"
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.0",
"socket.io": "2.2.0",
"socket.io-client": "^2.2.0"
},
"scripts": {
"start": "react-scripts start",
Expand All @@ -33,5 +37,17 @@
},
"devDependencies": {
"electron": "^2.0.6"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
43 changes: 10 additions & 33 deletions rampart.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
const server = require("./server/server");
const { parser } = require("./server/args");
const { parseConfig } = require("./server/config");
const Deque = require("collections/deque");
const { mapper } = require("./server/mapper");
const { demuxer } = require("./server/demuxer");
const getInitialConfig = require("./server/config").getInitialConfig;
const { startUp } = require("./server/startUp");
const { startGuppyWatcher } = require("./server/guppyWatcher");
const { sleep } = require("./server/utils");

/* make some globals available everywhere */
global.args = parser.parseArgs();
global.config = parseConfig(global.args);
const args = parser.parseArgs();
if (args.verbose) global.VERBOSE = true;
if (args.mockFailures) global.MOCK_FAILURES = true;
global.io = undefined;
global.config = getInitialConfig(args)
global.datastore = {};
global.barcodesSeen = new Set();
global.haveBeenSeen = new Set();
global.demuxQueue = new Deque();
global.mappingQueue = new Deque();
global.mappingResults = new Deque();
global.timeMap = new Map();
global.epochMap = new Map();


const startWatchers = () => {
/* as things get pushed onto the deques, we want to spawn the
appropriate processes (e.g. guppy, porechop).
As things are processed, they are shifted off one deque and pushed
onto another! */
global.demuxQueue.addRangeChangeListener(() => demuxer());
global.mappingQueue.addRangeChangeListener(() => mapper());

// start watchers
demuxer();
mapper();
startGuppyWatcher();
}


const main = async () => {
await startUp(); /* block until we've read the appropriate files */
/* Listen on localhost and process requests from the client */
const app = server.run({}); // eslint-disable-line
await sleep(200);
startWatchers();
await startUp({emptyDemuxed: args.emptyDemuxed}); /* block until we've read the appropriate files */
const app = await server.run({devClient: args.devClient}); // eslint-disable-line
}

main();
32 changes: 25 additions & 7 deletions server/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,31 @@ const parser = new argparse.ArgumentParser({
RAMPART is curently under development!
`
});
parser.addArgument('--config', {required: true, help: "path to JSON configuration file"});
parser.addArgument('--subsetFastqs', {action: "storeTrue", help: "Development flag -- only considers subset of FASTQs for speed reasons"});
parser.addArgument('--mockFailures', {action: "storeTrue", help: "Development flag -- stochastically fail to run guppy / porechop / mapping"});
parser.addArgument('--startWithDemuxedReads', {action: "storeTrue", help: "Development flag."});
parser.addArgument('--relaxedDemuxing', {action: "storeTrue", help: "Development flag -- don't require matching barcodes to demux."});
parser.addArgument('--emptyDemuxed', {action: "storeTrue", help: "Development flag -- remove any demuxed files present when rampart starts up"});
parser.addArgument('--basecalledDir', {help: "Overwrite the config provided basecalled directory"});
parser.addArgument('--verbose', {action: "storeTrue", help: "verbose output"});

/* ----------------- CONFIG OPTIONS -------------------- */
const config = parser.addArgumentGroup({title: 'Config commands', description: "These options can all be specified in the GUI"});
config.addArgument('--basecalledDir', {help: "basecalled directory"});
config.addArgument('--demuxedDir', {help: "demuxed directory"});
config.addArgument('--title', {help: "experiment title"});
config.addArgument('--referencePanelPath', {help: "FASTA reference panel"});
config.addArgument('--referenceConfigPath', {help: "JSON reference config"});
config.addArgument('--barcodeNames', {nargs: '+', help: "barcode=name, e.g. BC01=kikwit. Can have more than one."})

/* ----------------- DEVELOPMENT -------------------- */
const development = parser.addArgumentGroup({title: 'Development commands'});
development.addArgument('--emptyDemuxed', {action: "storeTrue", help: "remove any demuxed files present when rampart starts up"});
development.addArgument('--devClient', {action: "storeTrue", help: "Don't serve build (client)"})
development.addArgument('--mockFailures', {action: "storeTrue", help: "stochastic failures (mapping / demuxing / basecalling)"});


/* ----------------- DEPRECATED -------------------- */
const deprecated = parser.addArgumentGroup({title: 'Deprecated commands'});
deprecated.addArgument('--config', {required: true, help: "path to JSON configuration file"});
deprecated.addArgument('--subsetFastqs', {action: "storeTrue", help: "Development flag -- only considers subset of FASTQs for speed reasons"});
deprecated.addArgument('--startWithDemuxedReads', {action: "storeTrue", help: "Development flag."});
deprecated.addArgument('--relaxedDemuxing', {action: "storeTrue", help: "Development flag -- don't require matching barcodes to demux."});


module.exports = {
parser
Expand Down
153 changes: 105 additions & 48 deletions server/config.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,128 @@
const fs = require('fs')
const path = require('path')
const chalk = require('chalk');
const { getAbsolutePath } = require("./utils");
const { getAbsolutePath, verbose, log, warn } = require("./utils");
const { mapper } = require("./mapper");

const ensurePathExists = (p, {make=false}={}) => {
if (!fs.existsSync(p)) {
if (make) {
console.log(chalk.yellowBright("Path", p, "created"));
log(`Creating path ${p}`);
fs.mkdirSync(p, {recursive: true})
} else {
console.log("ERROR. Path", p, "doesn't exist.");
process.exit(1);
throw new Error(`ERROR. Path ${p} doesn't exist.`);
}
}
}

const parseConfig = (args) => {
const configDir = path.dirname(getAbsolutePath(args.config));
let config = JSON.parse(fs.readFileSync(getAbsolutePath(args.config)));
const getReferenceNames = (referencePanelPath) => {
return fs.readFileSync(referencePanelPath, "utf8")
.split("\n")
.filter((l) => l.startsWith(">"))
.map((n) => {
if (n.indexOf(" ") > 0) {
return {
"name": n.substring(1, n.indexOf(" ")), // fasta name is up until the first space
"description": n.substring(n.indexOf(" ")) // fasta description is the rest
};
} else {
return {
"name": n.substring(1),
"description": ""
};
}
});
}

/* check config file has the appropriate fields... */
/**
* Create initial config file from command line arguments
*/
const getInitialConfig = (args) => {
const barcodes = [
"BC01", "BC02", "BC03", "BC04", "BC05", "BC06", "BC07", "BC08", "BC09", "BC10", "BC11", "BC12"
];

/* sort out paths */
config.referenceConfigPath = getAbsolutePath(config.referenceConfigPath, {relativeTo: configDir});
config.referencePanelPath = getAbsolutePath(config.referencePanelPath, {relativeTo: configDir});
const config = {
title: args.title ? args.title : "",
barcodeToName: {},
barcodes,
basecalledPath: "",
demuxedPath: "",
referenceConfigPath: "",
referencePanelPath: "",
referencePanel: [],
reference: undefined,
relaxedDemuxing: args.relaxedDemuxing,
};

if (args.basecalledDir) {
config.basecalledPath = getAbsolutePath(args.basecalledDir, {relativeTo: process.cwd()});
console.log("BC:", config.basecalledPath)
} else {
config.basecalledPath = getAbsolutePath(config.basecalledPath, {relativeTo: configDir});
}
config.demuxedPath = getAbsolutePath(config.demuxedPath, {relativeTo: configDir});
/* most options _can_ be specified on the command line, but may also be specified in the client */
barcodes.forEach((bc) => {
config.barcodeToName[bc] = undefined;
})
if (args.barcodeNames) {
args.barcodeNames.forEach((raw) => {
const [bc, name] = raw.split('=');
if (!barcodes.includes(bc)) {
throw new Error(`Invalid barcode ${bc}`)
}
config.barcodeToName[bc] = name;
});
}

/* check if paths exist (perhaps we could make them if they don't) */
ensurePathExists(config.referenceConfigPath);
ensurePathExists(config.referencePanelPath);
if (args.basecalledDir !== "") {
config.basecalledPath = getAbsolutePath(args.basecalledDir, {relativeTo: process.cwd()});
}
if (args.demuxedDir !== "") {
config.demuxedPath = getAbsolutePath(args.demuxedDir, {relativeTo: process.cwd()});
ensurePathExists(config.demuxedPath, {make: true});
}

if (args.referencePanelPath) {
ensurePathExists(args.referencePanelPath);
config.referencePanelPath = getAbsolutePath(args.referencePanelPath, {relativeTo: process.cwd()});
config.referencePanel = getReferenceNames(config.referencePanelPath);
}

if (args.referenceConfigPath) {
ensurePathExists(args.referenceConfigPath);
config.referenceConfigPath = getAbsolutePath(args.referenceConfigPath, {relativeTo: process.cwd()});

/* parse the "main reference" configuration file (e.g. primers, genes, ref seq etc) */
const secondConfig = JSON.parse(fs.readFileSync(config.referenceConfigPath));
config = {...config, ...secondConfig};

/* get the names of the sequences in the reference panel */
config.referencePanel = fs.readFileSync(config.referencePanelPath, "utf8")
.split("\n")
.filter((l) => l.startsWith(">"))
.map((n) => {
if (n.indexOf(" ") > 0) {
return {
"name": n.substring(1, n.indexOf(" ")), // fasta name is up until the first space
"description": n.substring(n.indexOf(" ")) // fasta description is the rest
};
} else {
return {
"name": n.substring(1),
"description": ""
};
}
}); // remove the > character

/* things that may be put into the config JSON in the future */
config.maxMappingFilesPerRequest = 100;

return config;
const reference = JSON.parse(fs.readFileSync(config.referenceConfigPath)).reference;
config.reference = reference;

}


return config;
};

/**
* update the config file via GUI provided data
*/
const modifyConfig = (newConfig) => {

global.config.barcodeToName = newConfig.barcodeToName;

if (!global.config.referencePanelPath && newConfig.referencePanelPath) {
try {
ensurePathExists(newConfig.referencePanelPath);
// take approprieate action?
} catch (err) {
warn(err.message);
newConfig.referencePanelPath = "";
}
}

global.config = Object.assign({}, global.config, newConfig);

/* try to start the mapper, which may not be running due to insufficent
config information. It will exit gracefully if required */
mapper();

}


module.exports = {
parseConfig
getInitialConfig,
modifyConfig
};
Loading

0 comments on commit efff550

Please sign in to comment.