Connected to E2E pipeline (#85)

* added RetrieveSentences.py

* removed index

* connet to E2E pipeline

* updated code

* Some modification

* clean up

* config

* documentation

* added documentation

* update documentation

* update doc

* update doc

* add js

* updated documentation

* changed doc

* changed requirements.txt
This commit is contained in:
MeowFei
2017-11-25 14:39:39 -05:00
committed by rosequ
parent 61c8c0e622
commit ad69d3abc2
15 changed files with 871 additions and 18 deletions
+86 -4
View File
@@ -1,15 +1,20 @@
## Retrieve Sentences
## Setup Retrieve Sentences and end2end QA pipeline
#### 1. Clone [Anserini](https://github.com/castorini/Anserini.git) and [Castor](https://github.com/castorini/Castor.git)
#### 1. Clone [Anserini](https://github.com/castorini/Anserini.git), [Castor](https://github.com/castorini/Castor.git), [data](https://github.com/castorini/data.git), and [models](https://github.com/castorini/models.git):
```bash
git clone https://github.com/castorini/Anserini.git
git clone https://github.com/castorini/Castor.git
git clone https://github.com/castorini/data.git
git clone https://github.com/castorini/models.git
```
Your directory structure should look like
```
.
├── Anserini
── Castor
── Castor
├── data
└── models
```
#### 2. Compile Anserini
@@ -17,15 +22,35 @@ Your directory structure should look like
```bash
cd Anserini
mvn package
cd ..
```
This creates `anserini-0.0.1-SNAPSHOT.jar` at `Anserini/target`
We highly recommend the use of [virtualenv](https://virtualenv.pypa.io/en/stable/) as the dependencies
are subjected to frequent changes.
Install the dependency packages:
```
cd Castor
pip3 install -r requirements.txt
```
Make sure that you have PyTorch installed. For more help, follow [these](https://github.com/castorini/Castor) steps.
#### 3. Download Dependencies
- Download the TrecQA lucene index
- Download the Google word2vec file from [here](https://drive.google.com/drive/folders/0B2u_nClt6NbzNWJkWExmaklYNTA?usp=sharing)
#### 4. Run the following command
#### 4. Additional files for pipeline:
As some of the files are too large to be uploaded onto GitHub, please download the following files from
[here](https://drive.google.com/drive/folders/0B2u_nClt6NbzNm1LdjlwUFdzQVE?usp=sharing) and place them
in the appropriate locations:
- copy the contents of `word2vec` directory to `data/word2vec`
- copy `word2dfs.p` to `data/TrecQA/`
### To run RetrieveSentences:
```bash
python ./anserini_dependency/RetrieveSentences.py
@@ -44,3 +69,60 @@ Possible parameters are:
| `-k` | [1, inf) | 1 | top-k passages to be retrieved |
Note: Either a query or a topic must be passed in as an argument; they can't be both empty.
__NB:__ The speech UI cannot be run in Ubuntu. To test the pipeline in Ubuntu, make the following changes:
- Comment out the JavaScript part and run the Bash script
- Make a REST API query to the endpoint using Postman, Curl etc.
### To setup the demo
#### 1. Installing libraries for demo
```sh
cd anserini_dependency/js
npm install
cd ../..
```
#### 2. Flask
- Flask is used as the server for the API
- Copy `config.cfg.example` to `config.cfg` and make necessary changes, such as setting the index path and API keys.
#### 3. Run the Demo
```sh
./run_ui.sh
```
### Additional Notes
- This is the documentation for the API call to send a question to the model and get back the predicted answer.
- The request body fields are: question(required )num_hits(optional) and k(optional).
```
# REQUEST:
HTTP Method: POST
Endpoint: [host]:[port]/answer
Content-Type: application/json
text of body in raw format:
{
"question": "What is the birthdate of Einstein?",
"num_hits": 50,
"k": 30
}
```
- The response body contains answers which is a list of objects with two fields - passage, score.
```
# RESPONSE:
Content-Type: application/json
text of body in raw format:
{
"answers": [
{"passage": "Einstein was born in the 1800s", 'score': 0.976},
{"passage": "Einstein was a physicist", 'score': 0.524}
]
}
```
+35 -14
View File
@@ -5,7 +5,7 @@ jnius_config.set_classpath("../Anserini/target/anserini-0.0.1-SNAPSHOT.jar")
from jnius import autoclass
class CallRetrieveSentences:
class RetrieveSentences:
"""Python class built to call RetrieveSentences
Attributes
----------
@@ -27,28 +27,51 @@ class CallRetrieveSentences:
"""
RetrieveSentences = autoclass("io.anserini.qa.RetrieveSentences")
Args = autoclass("io.anserini.qa.RetrieveSentences$Args")
String = autoclass("java.lang.String")
self.String = autoclass("java.lang.String")
self.args = Args()
index = String(args.index)
index = self.String(args.index)
self.args.index = index
embeddings = String(args.embeddings)
embeddings = self.String(args.embeddings)
self.args.embeddings = embeddings
topics = String(args.topics)
topics = self.String(args.topics)
self.args.topics = topics
query = String(args.query)
query = self.String(args.query)
self.args.query = query
self.args.hits = int(args.hits)
scorer = String(args.scorer)
scorer = self.String(args.scorer)
self.args.scorer = scorer
self.args.k = int(args.k)
self.rs = RetrieveSentences(self.args)
def getRankedPassages(self):
def getRankedPassages(self, query, index, hits, k):
"""
Call RetrieveSentneces.getRankedPassages
Calls RetrieveSentences.getRankedPassages
Parameters
----------
query : str
The query to be searched in the index
index: str
The index
hits: str
The number of document IDs to be returned
k: str
The number of passages to be returned
"""
self.rs.getRankedPassages(self.args)
scorer = self.rs.getRankedPassagesList(query, index, int(hits), int(k))
candidate_passages_scores = []
for i in range(0, scorer.size()):
candidate_passages_scores.append(scorer.get(i))
return candidate_passages_scores
def getTermIdfJSON(self):
"""
Calls RetrieveSentences.getTermIdfJSON
"""
return self.rs.getTermIdfJSON()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Retrieve Sentences')
@@ -61,9 +84,7 @@ if __name__ == "__main__":
parser.add_argument("-k", help="top-k passages to be retrieved", default=1)
args_raw = parser.parse_args()
rs = CallRetrieveSentences(args_raw)
rs.getRankedPassages()
rs = RetrieveSentences(args_raw)
sc = rs.getRankedPassages(args_raw.query, args_raw.index, args_raw.hits, args_raw.k)
+122
View File
@@ -0,0 +1,122 @@
import argparse
import configparser
import os
import sys
from flask import Flask, jsonify, request
# FIXME: separate this out to a classifier class where we can switch out the models
from RetrieveSentences import RetrieveSentences
from sm_cnn.bridge import SMModelBridge
app = Flask(__name__)
rs = None
@app.route("/", methods=['GET'])
def hello():
return "Hello! The server is working properly... :)"
@app.route('/answer', methods=['POST'])
def answer():
try:
req = request.get_json(force=True)
question = req["question"]
num_hits = req.get('num_hits', 30)
k = req.get('k', 20)
print("Question: {}".format(question))
# FIXME: get the answer from the PyTorch model here
answers = get_answers(question, num_hits, k)
answer_dict = {"answers": answers}
return jsonify(answer_dict)
except Exception as e:
print(e)
error_dict = {"error": "ERROR - could not parse the question or get answer. "}
return jsonify(error_dict)
@app.route('/wit_ai_config', methods=['GET'])
def wit_ai_config():
return jsonify({'WITAI_API_SECRET': app.config['Frontend']['witai_api_secret']})
# FIXME: separate this out to a classifier class where we can switch out the models
def get_answers(question, num_hits, k):
parser = argparse.ArgumentParser(description='Retrieve Sentences')
parser.add_argument("-index", help="Lucene index", required=True)
parser.add_argument("-embeddings", help="Path of the word2vec index", default="")
parser.add_argument("-topics", help="topics file", default="")
parser.add_argument("-query", help="a single query", default="")
parser.add_argument("-hits", help="max number of hits to return", default=100)
parser.add_argument("-scorer", help="passage scores", default="Idf")
parser.add_argument("-k", help="top-k passages to be retrieved", default=1)
args_raw = parser.parse_args(["-query", question, "-hits", str(num_hits), "-scorer",
"Idf", "-k", str(k), "-index", app.config['Flask']['index']])
global rs
if rs == None:
rs = RetrieveSentences(args_raw)
candidate_passages_scores = rs.getRankedPassages(question, app.config['Flask']['index'], num_hits, k)
candidate_sent_scores = []
candidate_passages_sm = []
for ps in candidate_passages_scores:
ps_split = ps.split('\t')
candidate_passages_sm.append(ps_split[0])
candidate_sent_scores.append((float(ps_split[1]), ps_split[0]))
if app.config['Flask']['model'] == "sm":
path_to_castorini = os.getcwd() + "/.."
model = SMModelBridge(path_to_castorini + '/models/sm_model/sm_model.fixed_ext_feats_paper.puncts_stay',
path_to_castorini + '/data/word2vec/aquaint+wiki.txt.gz.ndim=50.cache',
app.config['Flask']['index'])
idf_json = rs.getTermIdfJSON()
flags = {
"punctuation": "", # ignoring for now you can {keep|remove} punctuation
"dash_words": "" # ignoring for now. you can {keep|split} words-with-hyphens
}
answers_list = model.rerank_candidate_answers(question, candidate_passages_sm, idf_json, flags)
sorted_answers = sorted(answers_list, key=lambda x: x[0], reverse=True)
else:
# the re-ranking model chosen is idf
sorted_answers = list(candidate_sent_scores)
print("in idf:{}".format(sorted_answers))
answers = []
for score, sent in sorted_answers:
answers.append({'passage': sent, 'score': score})
return answers
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Start the Flask API at the specified host, port')
parser.add_argument('--config', help='config to use', required=False, type=str, default='config.cfg')
parser.add_argument("--debug", help="print debug info", action="store_true")
parser.add_argument("--model", help="[idf|sm]", default="idf")
args = parser.parse_args()
if not os.path.isfile(args.config):
print("The configuration file ({}) does not exist!".format(args.config))
sys.exit(1)
config = configparser.ConfigParser()
config.read(args.config)
for name, section in config.items():
if name == 'DEFAULT':
continue
app.config[name] = {}
for key, value in config.items(name):
app.config[name][key] = value
app.config['Flask']['model'] = args.model
print("Config: {}".format(args.config))
print("Index: {}".format(app.config['Flask']['index']))
print("Host: {}".format(app.config['Flask']['host']))
print("Port: {}".format(app.config['Flask']['port']))
print("Re-ranking Model: {}".format(app.config['Flask']['model']))
print("Debug info: {}".format(args.debug))
app.run(debug=args.debug, host=app.config['Flask']['host'], port=int(app.config['Flask']['port']))
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+71
View File
@@ -0,0 +1,71 @@
<html>
<head>
<title>Anserini Speech Demo</title>
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://use.fontawesome.com/b2b2989db9.js"></script>
<script src="recorder.js"></script>
<script src="speech.js"></script>
<script>if (window.module) module = window.module;</script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style>
html {
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
margin: 10px 0;
height: 100%;
}
#analyser {
background: #f9f9f9;
flex: 0 1 auto;
max-height: 70px;
}
#viz {
flex: 1 1 auto;
display: flex;
flex-direction: column;
align-items: center;
}
#controls {
flex: 0 1 50px;
margin: 5px 5px 10px 5px;
}
#controls > span {
display: table;
margin: 0 auto;
}
#record.recording {
text-shadow: 0px 0px 10px red;
}
#question {
font-weight: bold;
font-size: larger;
flex: 1 0 auto;
margin: 5px;
text-align: center;
}
#answer {
flex: 1 1 auto;
overflow-y: auto;
margin: 5px;
}
</style>
</head>
<body>
<canvas id="analyser"></canvas>
<div id="viz">
<p class="lead" id="question">What can I help you with?</p>
<p id="answer"></p>
</div>
<div id="controls">
<span id="record" class="fa-stack fa-lg fa-2x" onclick="toggleRecording(this);">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-microphone fa-stack-1x fa-inverse"></i>
</span>
</div>
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
var menubar = require('menubar');
var mb = menubar();
mb.on('ready', function ready () {
console.log('Speech to text loaded..');
});
+17
View File
@@ -0,0 +1,17 @@
{
"name": "speech-qa-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "electron-packager . SpeechDemo --electron-version 1.6.2 --icon=Icon.icns",
"start": "electron ."
},
"dependencies": {
"menubar": "^5.2.3"
},
"devDependencies": {
"electron-packager": "^8.5.2",
"electron": "^1.6.2"
}
}
+154
View File
@@ -0,0 +1,154 @@
/*License (MIT)
Copyright © 2013 Matt Diamond
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
function run() {
var WORKER_PATH = 'recorderWorker.js';
var Recorder = function(source, cfg) {
var config = cfg || {};
var bufferLen = config.bufferLen || 4096;
this.context = source.context;
if(!this.context.createScriptProcessor){
this.node = this.context.createJavaScriptNode(bufferLen, 2, 2);
} else {
this.node = this.context.createScriptProcessor(bufferLen, 2, 2);
}
var worker = new Worker(config.workerPath || WORKER_PATH);
worker.postMessage({
command: 'init',
config: {
sampleRate: this.context.sampleRate
}
});
var recording = false,
currCallback;
this.node.onaudioprocess = function(e) {
if (!recording) return;
worker.postMessage({
command: 'record',
buffer: [
e.inputBuffer.getChannelData(0),
e.inputBuffer.getChannelData(1)
]
});
};
this.configure = function(cfg) {
for (var prop in cfg){
if (cfg.hasOwnProperty(prop)) {
config[prop] = cfg[prop];
}
}
};
this.record = function() {
recording = true;
};
this.stop = function() {
recording = false;
};
this.clear = function() {
worker.postMessage({ command: 'clear' });
};
this.getBuffers = function(cb) {
currCallback = cb || config.callback;
worker.postMessage({ command: 'getBuffers' })
};
this.exportWAV = function(cb, type) {
currCallback = cb || config.callback;
type = type || config.type || 'audio/wav';
if (!currCallback) throw new Error('Callback not set');
worker.postMessage({
command: 'exportWAV',
type: type
});
};
this.exportMonoWAV = function(cb, type) {
currCallback = cb || config.callback;
type = type || config.type || 'audio/wav';
if (!currCallback) throw new Error('Callback not set');
worker.postMessage({
command: 'exportMonoWAV',
type: type
});
};
worker.onmessage = function(e) {
var blob = e.data;
currCallback(blob);
};
source.connect(this.node);
this.node.connect(this.context.destination); // if the script node is not connected to an output the "onaudioprocess" event is not triggered in chrome.
};
$.ajax({
type: 'GET',
url: 'http://0.0.0.0:5546/wit_ai_config'
}).done(function(data) {
window.WITAI_API_SECRET = data.WITAI_API_SECRET;
}).fail(function(req, textStatus, e) {
console.log(e);
});
Recorder.speechToText = function(blob) {
$.ajax({
type: 'POST',
url: 'https://api.wit.ai/speech?v=20170308',
data: blob,
processData: false,
contentType: 'audio/wav',
headers: {
Authorization: 'Bearer ' + window.WITAI_API_SECRET
}
}).done(function(data) {
$('#question').text(data._text);
window.setTimeout(function () {
$('#answer').text('Asking Anserini for answer...');
}, 500);
$.ajax({
type: 'POST',
url: 'http://0.0.0.0:5546/answer',
data: JSON.stringify({question: data._text, k: 5}),
contentType : 'application/json'
}).done(function(data) {
var answers = data.answers.map(function(a) {
return '<li>' + a.passage + ' (' + Number((a.score).toFixed(4)) + ')</li>';
});
var formattedAnswers = '<ol>' + answers.join('\n') + '</ol>';
$('#answer').html(formattedAnswers);
}).fail(function(req, textStatus, e) {
$('#answer').text(e);
});
}).fail(function(req, textStatus, e) {
$('#question').text(e);
});
};
window.Recorder = Recorder;
}
window.addEventListener('load', run);
+161
View File
@@ -0,0 +1,161 @@
/*License (MIT)
Copyright © 2013 Matt Diamond
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
var recLength = 0,
recBuffersL = [],
recBuffersR = [],
sampleRate;
this.onmessage = function(e) {
switch(e.data.command) {
case 'init':
init(e.data.config);
break;
case 'record':
record(e.data.buffer);
break;
case 'exportWAV':
exportWAV(e.data.type);
break;
case 'exportMonoWAV':
exportMonoWAV(e.data.type);
break;
case 'getBuffers':
getBuffers();
break;
case 'clear':
clear();
break;
}
};
function init(config) {
sampleRate = config.sampleRate;
}
function record(inputBuffer) {
recBuffersL.push(inputBuffer[0]);
recBuffersR.push(inputBuffer[1]);
recLength += inputBuffer[0].length;
}
function exportWAV(type) {
var bufferL = mergeBuffers(recBuffersL, recLength);
var bufferR = mergeBuffers(recBuffersR, recLength);
var interleaved = interleave(bufferL, bufferR);
var dataview = encodeWAV(interleaved);
var audioBlob = new Blob([dataview], { type: type });
this.postMessage(audioBlob);
}
function exportMonoWAV(type) {
var bufferL = mergeBuffers(recBuffersL, recLength);
var dataview = encodeWAV(bufferL, true);
var audioBlob = new Blob([dataview], { type: type });
this.postMessage(audioBlob);
}
function getBuffers() {
var buffers = [];
buffers.push( mergeBuffers(recBuffersL, recLength) );
buffers.push( mergeBuffers(recBuffersR, recLength) );
this.postMessage(buffers);
}
function clear() {
recLength = 0;
recBuffersL = [];
recBuffersR = [];
}
function mergeBuffers(recBuffers, recLength) {
var result = new Float32Array(recLength);
var offset = 0;
for (var i = 0; i < recBuffers.length; i++) {
result.set(recBuffers[i], offset);
offset += recBuffers[i].length;
}
return result;
}
function interleave(inputL, inputR) {
var length = inputL.length + inputR.length;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length){
result[index++] = inputL[inputIndex];
result[index++] = inputR[inputIndex];
inputIndex++;
}
return result;
}
function floatTo16BitPCM(output, offset, input) {
for (var i = 0; i < input.length; i++, offset+=2){
var s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
function writeString(view, offset, string) {
for (var i = 0; i < string.length; i++){
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function encodeWAV(samples, mono) {
var buffer = new ArrayBuffer(44 + samples.length * 2);
var view = new DataView(buffer);
/* RIFF identifier */
writeString(view, 0, 'RIFF');
/* file length */
view.setUint32(4, 32 + samples.length * 2, true);
/* RIFF type */
writeString(view, 8, 'WAVE');
/* format chunk identifier */
writeString(view, 12, 'fmt ');
/* format chunk length */
view.setUint32(16, 16, true);
/* sample format (raw) */
view.setUint16(20, 1, true);
/* channel count */
view.setUint16(22, mono?1:2, true);
/* sample rate */
view.setUint32(24, sampleRate, true);
/* byte rate (sample rate * block align) */
view.setUint32(28, sampleRate * 4, true);
/* block align (channel count * bytes per sample) */
view.setUint16(32, 4, true);
/* bits per sample */
view.setUint16(34, 16, true);
/* data chunk identifier */
writeString(view, 36, 'data');
/* data chunk length */
view.setUint32(40, samples.length * 2, true);
floatTo16BitPCM(view, 44, samples);
return view;
}
+182
View File
@@ -0,0 +1,182 @@
/*
Modifications Copyright 2017 Anserini
The audio recording and analyzer visualization code was originally developed by Chris Wilson
and modified for use in Anserini.
Copyright 2013 Chris Wilson
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext = new AudioContext();
var audioInput = null,
realAudioInput = null,
inputPoint = null,
audioRecorder = null;
var rafID = null;
var analyserContext = null;
var canvasWidth, canvasHeight;
var recIndex = 0;
function saveAudio() {
//audioRecorder.exportWAV( doneEncoding );
// could get mono instead by saying
audioRecorder.exportMonoWAV( doneEncoding );
}
function gotBuffers(buffers) {
// the ONLY time gotBuffers is called is right after a new recording is completed.
audioRecorder.exportMonoWAV( doneEncoding );
}
function doneEncoding(blob) {
Recorder.speechToText(blob);
recIndex++;
}
function toggleRecording(e) {
if (e.classList.contains("recording")) {
// stop recording
audioRecorder.stop();
e.classList.remove("recording");
$('#question').text("Trying to understand your query...");
audioRecorder.getBuffers( gotBuffers );
} else {
// start recording
if (!audioRecorder)
return;
e.classList.add("recording");
$('#question').text("Listening...");
$('#answer').html("");
audioRecorder.clear();
audioRecorder.record();
}
}
function convertToMono(input) {
var splitter = audioContext.createChannelSplitter(2);
var merger = audioContext.createChannelMerger(2);
input.connect(splitter);
splitter.connect(merger, 0, 0);
splitter.connect(merger, 0, 1);
return merger;
}
function cancelAnalyserUpdates() {
window.cancelAnimationFrame(rafID);
rafID = null;
}
function updateAnalysers(time) {
if (!analyserContext) {
var canvas = document.getElementById("analyser");
canvasWidth = canvas.width;
canvasHeight = canvas.height;
analyserContext = canvas.getContext('2d');
}
// analyzer draw code here
{
var SPACING = 5;
var BAR_WIDTH = 3;
var numBars = Math.round(canvasWidth / SPACING);
var freqByteData = new Uint8Array(analyserNode.frequencyBinCount);
analyserNode.getByteFrequencyData(freqByteData);
analyserContext.clearRect(0, 0, canvasWidth, canvasHeight);
analyserContext.fillStyle = '#F6D565';
analyserContext.lineCap = 'round';
var multiplier = analyserNode.frequencyBinCount / numBars;
// Draw rectangle for each frequency bin.
for (var i = 0; i < numBars; ++i) {
var magnitude = 0;
var offset = Math.floor( i * multiplier );
// gotta sum/average the block, or we miss narrow-bandwidth spikes
for (var j = 0; j< multiplier; j++)
magnitude += freqByteData[offset + j];
magnitude = magnitude / multiplier;
var magnitude2 = freqByteData[i * multiplier];
analyserContext.fillStyle = "hsl( " + Math.round((i*360)/numBars) + ", 100%, 50%)";
analyserContext.fillRect(i * SPACING, canvasHeight, BAR_WIDTH, -magnitude);
}
}
rafID = window.requestAnimationFrame(updateAnalysers);
}
function toggleMono() {
if (audioInput != realAudioInput) {
audioInput.disconnect();
realAudioInput.disconnect();
audioInput = realAudioInput;
} else {
realAudioInput.disconnect();
audioInput = convertToMono( realAudioInput );
}
audioInput.connect(inputPoint);
}
function gotStream(stream) {
inputPoint = audioContext.createGain();
// Create an AudioNode from the stream.
realAudioInput = audioContext.createMediaStreamSource(stream);
audioInput = realAudioInput;
audioInput.connect(inputPoint);
analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 2048;
inputPoint.connect( analyserNode );
audioRecorder = new Recorder( inputPoint );
zeroGain = audioContext.createGain();
zeroGain.gain.value = 0.0;
inputPoint.connect( zeroGain );
zeroGain.connect( audioContext.destination );
updateAnalysers();
}
function initAudio() {
if (!navigator.getUserMedia)
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!navigator.cancelAnimationFrame)
navigator.cancelAnimationFrame = navigator.webkitCancelAnimationFrame || navigator.mozCancelAnimationFrame;
if (!navigator.requestAnimationFrame)
navigator.requestAnimationFrame = navigator.webkitRequestAnimationFrame || navigator.mozRequestAnimationFrame;
navigator.getUserMedia(
{
"audio": {
"mandatory": {
"googEchoCancellation": "false",
"googAutoGainControl": "false",
"googNoiseSuppression": "false",
"googHighpassFilter": "false"
},
"optional": []
}
}, gotStream, function(e) {
alert('Error getting audio');
console.log(e);
});
}
window.addEventListener('load', initAudio);
+7
View File
@@ -0,0 +1,7 @@
[Flask]
host = 0.0.0.0
port = 5546
index = /path/to/index/here
[Frontend]
witai_api_secret = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+7
View File
@@ -0,0 +1,7 @@
gensim==1.0.1
numpy==1.12.1
pandas==0.19.2
Flask==0.12.1
nltk==3.2.2
pyjnius==1.1.1
-e git+https://github.com/castorini/Castor.git#egg=sm-cnn-1.0.0
Executable
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
echo "Start the Flask server..."
python3 anserini_dependency/api.py --model idf &
PID_2=$!
echo "Start the JavaScript UI..."
pushd anserini_dependency/js
npm start &
PID_3=$!
popd
# clean up before exiting
function clean_up {
kill $PID_3
kill $PID_2
exit
}
trap clean_up SIGHUP SIGINT SIGTERM SIGKILL
wait