I'm trying to use soundtouch.js to preserve the pitch of audio in the browser, but am having some trouble with an instance property not being recognized by the audio context during an event.
Here's my basic React UI:
App.js
These are the functions in my React component handling the input ranges and getting a Playback instance:
const playbackEngine = new PlaybackEngine({
emitter: emitter,
pitch: pitch,
tempo: tempo,
});
const handlePitchChange = (event) => {
pitch = event.target.value;
document.getElementById('pitch-value').innerHTML = `Pitch (${pitch}x)`;
emitter.emit('pitch');
playbackEngine.pitch = event.target.value;
}
const handleTempoChange = (event) => {
tempo = event.target.value;
document.getElementById('tempo-value').innerHTML = `Tempo (${tempo}x)`;
emitter.emit('tempo');
playbackEngine.tempo = event.target.value;
}
const handleSeek = (event) => {
percent = parseFloat(event.target.value);
document.getElementById('seek-value').value = percent;
playbackEngine.seekPercent(percent);
playbackEngine.play();
}
PlaybackEngine.js
const {SimpleFilter, SoundTouch} = require('./soundtouch');
const BUFFER_SIZE = 4096;
export default class PlaybackEngine {
constructor({emitter, pitch, tempo}) {
this.emitter = emitter;
this.context = new AudioContext();
this.scriptProcessor = this.context.createScriptProcessor(BUFFER_SIZE, 2, 2);
this.scriptProcessor.onaudioprocess = e => {
const l = e.outputBuffer.getChannelData(0);
const r = e.outputBuffer.getChannelData(1);
const framesExtracted = this.simpleFilter.extract(this.samples, BUFFER_SIZE);
if (framesExtracted === 0) {
this.emitter.emit('stop');
}
for (let i = 0; i < framesExtracted; i++) {
l[i] = this.samples[i * 2];
r[i] = this.samples[i * 2 + 1];
}
};
this.soundTouch = new SoundTouch();
this.soundTouch.pitch = pitch;
this.soundTouch.tempo = tempo;
this.duration = undefined;
}
get pitch() {
return this.soundTouch.pitch;
}
set pitch(pitch) {
this.soundTouch.pitch = pitch;
}
get tempo() {
return this.soundTouch.tempo;
}
set tempo(tempo) {
this.soundTouch.tempo = tempo;
}
decodeAudioData(data) {
return this.context.decodeAudioData(data);
}
setBuffer(buffer) {
const bufferSource = this.context.createBufferSource();
bufferSource.buffer = buffer;
this.samples = new Float32Array(BUFFER_SIZE * 2);
this.source = {
extract: (target, numFrames, position) => {
this.emitter.emit('time', (position / this.context.sampleRate));
const l = buffer.getChannelData(0);
const r = buffer.getChannelData(1);
for (let i = 0; i < numFrames; i++) {
target[i * 2] = l[i + position];
target[i * 2 + 1] = r[i + position];
}
return Math.min(numFrames, l.length - position);
},
};
this.simpleFilter = new SimpleFilter(this.source, this.soundTouch);
this.emitter.emit('buffered', this.simpleFilter);
this.duration = buffer.duration;
this.emitter.emit('duration', buffer.duration);
}
play() {
this.scriptProcessor.connect(this.context.destination);
}
pause() {
this.scriptProcessor.disconnect(this.context.destination);
}
seekPercent(percent) {
if (this.simpleFilter !== undefined) {
this.simpleFilter.sourcePosition = Math.round(
percent / 100 * this.duration * this.context.sampleRate
);
}
}
}
App.js calls playbackEngine.setBuffer() once the audio file is ready, which adds this.simpleFilter as an instance property. The audio plays correctly, but when I call seekPercent() in my handleSeek function, it is undefined. Consequently, the onaudioprocess crashes because of this, with the following error:
Uncaught TypeError: Cannot read property 'extract' of undefined
at ScriptProcessorNode.PitchEngine.scriptProcessor.onaudioprocess
Thanks in advance for the help.
Solved it.
The problem was that the audio's arrayBuffer was being placed in setBuffer() during the component's useEffect with an empty dependency array (componentDidMount-style). So when events occurred on the instantiated playback engine, they were as if only the instance properties before the useEffect was called existed. Fixed it by defining event listeners within the useEffect on my input controls.
Related
This Game is a multiplication game where player gets 4 options to choose from for example 4*5 and user has to choose between 20 and 3 other wrong answers but once user selects the right option it doesn't increment the score although correct answer === users choice, i am new to JavaScript so please give full explanation to why the logic isn't working .
This is the HTML
"use strict";
const resetBtn = document.querySelector(".reset");
const Navscore = document.querySelector(".ScoreNumber");
const scoreToReset = document.querySelector(".score");
const question = document.querySelector(".question");
const highScoreNumber = document.querySelector(".highScoreNumber");
const choices1 = document.getElementById("1");
const choices2 = document.getElementById("2");
const choices3 = document.getElementById("3");
const choices4 = document.getElementById("4");
const answer1 = document.querySelector(".answer1");
const answer2 = document.querySelector(".answer2");
const answer3 = document.querySelector(".answer3");
const answer4 = document.querySelector(".answer4");
const choices = document.querySelector(".choices");
let score = 0;
let Highscore = 0;
const numberForNumberGen = function() {
return Math.trunc(Math.random() * 12 + 1);
};
const choicesAssinger = function(usersChoice) {
// document.getElementById(correct).innerHTML = questionGen();
console.log(`user choice ${usersChoice}`);
const answerText = questionGen();
const correct = numberGen(4);
console.log(`correct choice ${correct}`);
if (correct === 1) {
answer1.innerHTML = answerText;
answer2.innerHTML = answerText + numberForNumberGen();
answer3.innerHTML = answerText - numberForNumberGen();
answer4.innerHTML =
answerText + numberForNumberGen() - numberForNumberGen();
} else if (correct === 2) {
answer2.innerHTML = answerText;
answer1.innerHTML = answerText - numberForNumberGen();
answer4.innerHTML = answerText - numberForNumberGen();
answer3.innerHTML =
answerText + numberForNumberGen() - numberForNumberGen();
} else if (correct === 3) {
answer3.innerHTML = answerText;
answer4.innerHTML = answerText + numberForNumberGen();
answer2.innerHTML = answerText - numberForNumberGen();
answer1.innerHTML =
answerText + numberForNumberGen() - numberForNumberGen();
} else if (correct === 4) {
answer4.innerHTML = answerText;
answer3.innerHTML = answerText + numberForNumberGen();
answer1.innerHTML = answerText - numberForNumberGen();
answer2.innerHTML =
answerText + numberForNumberGen() - numberForNumberGen();
}
// return correct;
console.error(correct);
console.error(usersChoice);
console.log(correct == usersChoice);
if (correct == usersChoice) {
console.log("correct");
document.querySelector("body").style.background = "green";
score++;
questionGen();
choicesAssinger();
Navscore.innerHTML = score;
if (score > Highscore) {
Highscore = score;
highScoreNumber.innerHTML = Highscore;
}
} else {
if (score !== 0) {
document.querySelector("body").style.background = " #925e36";
Navscore.innerHTML = "Please click Reset";
choices1.removeEventListener("click", handler);
choices2.removeEventListener("click", handler);
choices3.removeEventListener("click", handler);
choices4.removeEventListener("click", handler);
}
}
// return rightChoiceNumber;
};
const start = () => {
// choicesAssinger();
choices1.addEventListener("click", handler);
choices2.addEventListener("click", handler);
choices3.addEventListener("click", handler);
choices4.addEventListener("click", handler);
};
const numberGen = function(n) {
const number = Math.trunc(Math.random() * n + 1);
return number;
};
const questionGen = function() {
const num1 = numberGen(numberForNumberGen());
const num2 = numberGen(numberForNumberGen());
const answer = num1 * num2;
console.log(answer);
const questionWriting = `${num1} x ${num2}`;
question.innerHTML = questionWriting;
return answer;
};
function handler(event) {
const usersChoice = event.target.id;
choicesAssinger(usersChoice);
}
resetBtn.addEventListener("click", () => {
start();
document.querySelector("body").style.background = " #925e36";
score = 0;
Navscore.innerHTML = 0;
questionGen();
});
questionGen();
// choicesAssinger();
start();
<nav>
<div class="reset utilites">Reset</div>
<div class="score utilites">
Score: <span class="ScoreNumber"> 0</span>
</div>
<div class="highScore utilites">
High Score: <span class="highScoreNumber"> 0</span>
</div>
</nav>
<section>
<div class="choices" id="1">
🚪
<div class="answer answer1">answer</div>
</div>
<div class="choices" id="2">
🚪
<div class="answer answer2">answer</div>
</div>
<div class="choices" id="3">
🚪
<div class="answer answer3">answer</div>
</div>
<div class="choices" id="4">
🚪
<div class="answer answer4">answer</div>
</div>
</section>
<div class="question">---</div>
I was having difficulty understanding what the specific issue is with the code you provided because I did see my score change to 1 when I got a correct answer. However, upon getting a correct answer, the background of the HTML body turned to brown (hex: #925e36) whereas I think you intend for it to turn green.
The truth is, the body actually does turn green, but it quickly (imperceptibly to the eye because it is so fast) turns brown. This is because within the same code block that sets the body to green, you are calling choicesAssinger(); with no argument. This resets the options and, because there is no argument, sets the usersChoice to undefined - meaning the user will never have the correct option and so the body will switch to brown because score !== 0.
I am not sure that there is a small change that can correct your logic. Overall, I find the code very hard to read and reason about because it is all so deeply connected (coupled).
I really want to help you to simplify this solution, but I don't think I can do so without significantly re-writing the code.
I would approach this by:
Encapsulating the state (the values that change) into a single object.
Having a single render function that will render the HTML according to the state object.
Have event handlers whose job is to update the state according to the user's action and then call the render function.
Following these guides, I have re-written your code. I don't think I will be able to explain everything I would like to, but I will try my best to explain my decisions in comments:
// utility function to shuffle the options
// copied from: https://stackoverflow.com/a/33760558/3397771
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
// same number generator as in original post, just renamed
const generateNumber = function() {
return Math.trunc(Math.random() * 12 + 1);
};
// function to generate a new set of options
// it ensures that the correct option is in the set
// it also ensure no duplicate options
const generateOptions = (state) => {
const product = state.numbersToMultiply[0] * state.numbersToMultiply[1];
const options = (new Array(state.numOptions - 1).fill(null)).reduce((acc) => {
let nextTry = Math.round(Math.random() * 100);
// ensure no duplicates in options
while (acc.includes(nextTry)) {
nextTry = Math.round(Math.random() * 100);
}
acc.push(nextTry);
return acc;
}, [product]); // start with the correct answer
return shuffle(options);
}
// state object contains all of the values that change
// over the life of the app
// arguably `numOptions` could go in a config since it does not change
const state = {
highScore: 0,
isShowOptions: false,
numbersToMultiply: [generateNumber(), generateNumber()],
numOptions: 4,
options: [],
selectedOption: null,
score: 0
};
// set the options for the first question
state.options = generateOptions(state);
// DOM elements that we will interact with or set the contents of
const choices = document.getElementById("Choices");
const highScore = document.getElementById("HighScore");
const next = document.getElementById("Next");
const question = document.getElementById("Question");
const reset = document.getElementById("Reset");
const score = document.getElementById('Score');
// single render function
// it will get all of the values it needs from the state object
const render = (state) => {
const product = state.numbersToMultiply[0] * state.numbersToMultiply[1];
const optionElements = state.options.map(option => {
let className = '';
// determine the class for this option
// the definition for these classes must be defined in CSS
if (state.isShowOptions) {
if (state.selectedOption === option && state.selectedOption === product) {
className = 'correct';
} else if (state.selectedOption === option) {
className = 'incorrect';
} else if (product === option) {
className = 'actual';
}
}
// map options to <div> elements to be injected into DOM
return `<div class="${className}" data-option="${option}">🚪 ${state.isShowOptions ? option : ''}</div>`;
});
choices.innerHTML = optionElements.join('');
highScore.textContent = String(state.highScore);
question.innerHTML = `${state.numbersToMultiply[0]} x ${state.numbersToMultiply[1]}`;
score.textContent = String(state.score);
};
choices.addEventListener('click', (event) => {
// if we are showing the options,
// we don't want the state to change when options are clicked
// so we return early
if (state.isShowOptions) { return; }
const selectedOption = Number(event.target.dataset.option);
const correctOption = state.numbersToMultiply[0] * state.numbersToMultiply[1];
const isCorrect = correctOption === selectedOption;
state.isShowOptions = true;
state.selectedOption = selectedOption;
state.score += isCorrect ? 1 : 0;
state.highScore = Math.max(state.score, state.highScore);
render(state);
});
next.addEventListener('click', () => {
state.isShowOptions = false;
state.numbersToMultiply = [generateNumber(), generateNumber()];
state.options = generateOptions(state);
render(state);
});
reset.addEventListener('click', () => {
state.highScore = state.score;
state.isShowOptions = false;
state.numbersToMultiply = [generateNumber(), generateNumber()];
state.options = generateOptions(state);
state.score = 0;
render(state);
})
// on app start, render the first question
render(state);
I have created a fiddle for reference.
This question already has answers here:
Shallow-clone a Map or Set
(6 answers)
Closed 2 years ago.
How can I deep copy a map object to another map ? I'm trying to use ES6 method but this return Map(0){} empty object. My first try was send a map object to function and assign this to a new value in this class. But on next() method I'm removing one property from this map and this change map in unit test file
creatureTurnQueue.js
export default class CreatureTurnQueue {
constructor() {
this.creatureMap = new Map();
this.creatureArray = [];
this.observersArray = [];
}
initQueue(list = {}) {
this.creatureMap = Object.assign({}, list)
this.creatureMap = list //<=old method
list.forEach(val => this.creatureArray.push(val));
}
getActiveCreature() {
let [first] = this.creatureArray.filter(el => el);
return first;
}
next(list = {}) {
this.creatureMap.delete(this.creatureMap.keys().next().value);
if (this.creatureMap.size == 0) {
this.notifyObserver();
this.initQueue(list);
return true;
}
}
addObserver(_observer) {
this.observersArray.push(_observer)
}
removeObserver(_observer) {
this.observersArray.pull(_observer)
}
notifyObserver() {
this.observersArray.forEach(item => item.resetCounterAttack())
}
}
creatureTurnQueueTest.js
import Creature from "../creature.js";
import CreatureTurnQueue from "../creatureTurnQueue.js";
import Point from './../point';
export default class CreatureTurnQueueTest {
queueShoulChangeActiveCreature() {
let creatureTurnQueue = new CreatureTurnQueue();
let creture1 = new Creature("aaaa", 1, 1, 1, 1);
let creture2 = new Creature("bbbb", 1, 1, 1, 1);
let creture3 = new Creature("cccc", 1, 1, 1, 1);
let point1 = new Point(1, 0)
let point2 = new Point(2, 0)
let point3 = new Point(3, 0)
let creatureMap = new Map();
creatureMap.set(point1, creture1);
creatureMap.set(point2, creture2);
creatureMap.set(point3, creture3);
creatureTurnQueue.initQueue(creatureMap);
console.log("~ creatureMap", creatureMap) <= map have 2 elements
creatureMap.forEach(item => { <= creatureMap return 2 elements becouse this one value is removed
if (item !== creatureTurnQueue.getActiveCreature()) {
console.log("~ item", item)
console.log("~ creatureTurnQueue.getActiveCreature()", creatureTurnQueue.getActiveCreature())
throw `Exception: => Kolejka nie dziala poprawnie zwracana aktywna creatura jest inna`;
}
if (creatureTurnQueue.next(creatureMap)) {
throw `Exception: => Kolejka nie dziala poprawnie w momecie wywolania funkcji next()`;
}
});
}
}
The rest of a classes
point.js
export default class Point {
constructor(_x, _y) {
this.x = _x;
this.y = _y;
}
}
creature.js
import CreatureStatistics from "./creatureStatistics.js";
export default class Creature {
constructor(_name, _attack, _armor, _maxHp, _moveRange) {
this.stats = this.createCreature(_name, _attack, _armor, _maxHp, _moveRange);
this.stats.currentHp = this.stats.maxHp;
this.stats.wasCounterAttack = false;
}
createCreature(_name, _attack, _armor, _maxHp, _moveRange) {
return new CreatureStatistics(
_name || "Smok",
_attack || 1,
_armor || 1,
_maxHp || 100,
_moveRange || 10
);
}
setDefaultStats() {
// this.stats.wasCounterAttack == true ? this.stats.wasCounterAttack = false : this.stats.wasCounterAttack = true
this.stats.currentHp = this.stats.currentHp != undefined ? this.stats.currentHp : this.stats.maxHp;
}
// popraw counter atack
attack(_defender) {
_defender.setDefaultStats();
this.setDefaultStats();
if (_defender.isAlive()) {
_defender.stats.currentHp = this.calculateDamage(_defender);
if (_defender.isAlive() && !_defender.stats.wasCounterAttack) {
_defender.stats.wasCounterAttack = true;
this.stats.currentHp = _defender.calculateDamage(this);
}
}
}
calculateDamage(attackedCreature) {
return attackedCreature.stats.currentHp - this.stats.getAttack() + attackedCreature.stats.getArmor() > attackedCreature.stats.getMaxHp()
? attackedCreature.stats.currentHp
: attackedCreature.stats.currentHp - this.stats.getAttack() + attackedCreature.stats.getArmor();
}
isAlive() {
if (this.stats.currentHp > 0) {
return true;
}
}
getCurrentHp() {
return this.stats.currentHp;
}
resetCounterAttack() {
this.stats.wasCounterAttack = false;
}
canCounterAttack() {
return !this.stats.wasCounterAttack
}
}
creatureStatistics.js
export default class CreatureStatistics {
constructor(_name, _attack, _armor, _maxHp, _moveRange) {
this.name = _name;
this.attack = _attack;
this.armor = _armor;
this.maxHp = _maxHp;
this.moveRange = _moveRange;
}
getName() {
return this.name;
}
getAttack() {
return this.attack;
}
getArmor() {
return this.armor;
}
getMaxHp() {
return this.maxHp;
}
getMoveRange() {
return this.moveRange;
}
}
The map was in fact a reference of the creatureMap from unit test script. Modifying the map inside your class will modify the creatureMap in unit test. You can try to create a new Map and copy over all values:
// Start class CreateTurnQueue
initQueue(list = {}) {
const newMap = new Map();
// Both Object and Map has entries method although the order is different
const iterator = list.entries();
for(const item of iterator) {
const [point, creature] = item;
newMap.set(point, creature);
this.creatureArray.push(creature);
}
this.creatureMap = newMap;
}
// End class CreateTurnQueue
Now you don't have a reference to the creatureMap from the unit test script and modify this.creatureMap will not affect the one you passed to the initQueue method as argument.
This is the code. It is an assignment from a random stranger that asked me to solve it for him since he saw a post of mine on IG.
// Player class
class Player {
constructor(name, strength = 2, weapons) {
this.name = name;
this.health = 10;
this.strength = strength;
this.weapons = [...weapons];
}
applyDamage(int) {
this.health -= int;
}
isAlive() {
return this.health > 0;
}
attackWith() {
let randomNum = Math.floor(Math.random() * 8);
return this.weapons[randomNum];
}
}
// Weapon class
class Weapon {
constructor(name) {
this.name = name;
this.damage = Math.ceil(Math.random() * 5);
}
attack(player, enemy) {
if (player.isAlive() && player.isAlive()) {
let dmg = player.strength * this.damage;
enemy.applyDamage(dmg);
if (!enemy.isAlive()) {
return;
} else {
enemy.attack(player);
}
}
}
}
// Enemy class
class Enemy {
constructor(name = "Enemy", health = 5, strength = 2) {
this.name = name;
this.health = health;
this.strength = strength;
}
applyDamage(int) {
this.health -= int;
}
isAlive() {
return this.health > 0;
}
attack(player) {
player.applyDamage(this.strength);
}
}
// BattleSimulation class
class BattleSimulation {
constructor() {
this.players = [];
this.enemies = [];
}
createEnemies() {
for (let i = 0; i < 20; i += 1) {
this.enemies[i] = new Enemy();
}
}
createPlayers() {
// Weapons
let pencil = new Weapon("Pencil");
let book = new Weapon("Book");
let screwdriver = new Weapon("Screwdriver");
let theOneRing = new Weapon("Sauron's Ring");
let mustardGass = new Weapon("Mustard Gass");
let bigBoy = new Weapon("A Nuke");
let love = new Weapon("Power of Love");
let theForce = new Weapon("The Force");
let weaponsCache = [
pencil,
book,
screwdriver,
theOneRing,
mustardGass,
bigBoy,
love,
theForce
];
// Players
let luke = new Player("Luke", 5, weaponsCache);
let baldingCoder = new Player("DraciVik", 10, weaponsCache);
let trump = new Player("Trump", 1, weaponsCache);
let kikiMakarena = new Player("Kiki Makarena", 5, weaponsCache);
let johnWick = new Player("John Wick", 2, weaponsCache);
this.players = [luke, baldingCoder, trump, kikiMakarena, johnWick];
}
run() {
console.log("Simulating Battle");
this.createEnemies();
this.createPlayers();
while (this.players.length !== 0 || this.enemies.length !== 0) {
let randomPlayerIndex = Math.floor(Math.random() * this.players.length);
let randomPlayer = this.players[randomPlayerIndex];
let randomEnemyIndex = Math.floor(Math.random() * this.enemies.length);
let randomEnemy = this.enemies[randomEnemyIndex];
let weapon = randomPlayer.attackWith();
weapon.attack(randomPlayer, randomEnemy);
if (!randomPlayer.isAlive()) {
this.players.splice(randomPlayerIndex, 1);
}
if (!randomEnemy.isAlive()) {
this.enemies.splice(randomEnemyIndex, 1);
}
}
console.log(this.players);
if (this.players.length > 0) {
return "Congratulations, you have defeated Scarlet Byle";
}
return "Sorry, Scarlet Byle has defeated you and conquered the free world";
}
}
let battle = new BattleSimulation();
battle.run();
Anyone can see where the error is? I get a return error 'enemy.applyDamage(dmg)' is undefined.
What is this error that I need more writing than just code? Should I spam some letters?
The bug here is actually just in the condition of your while loop:
while(this.players.length !== 0 || this.enemies.length !== 0)
Your condition says to loop while there is at least one player OR there is at least one enemy. So as long as ONE of the arrays is not empty, it will continue to loop.
But when you first create this.players and this.enemies, they start at different sizes. Then when you remove one entry from each array, eventually one of the arrays is empty before the other.
Then your code has var randomEnemyIndex = Math.floor(Math.random() * this.enemies.length); which will evaluate to 0 when the array is empty. And when you do this.enemies[0], it returns undefined. When undefined is passed into weapon.attack like weapon.attack(randomPlayer, undefined), then it tries to call applyDamage(dmg) on the undefined, which throws your exception.
If you modify your code to have the following console logs, you will see the issue:
while (this.players.length !== 0 || this.enemies.length !== 0) {
console.log("PLAYERS: " + this.players.length.toString());
console.log("ENEMIES: " + this.enemies.length.toString());
...
You will see:
'Simulating Battle'
'PLAYERS: 5'
'ENEMIES: 20'
'PLAYERS: 5'
'ENEMIES: 20'
'PLAYERS: 5'
[...]
'PLAYERS: 4'
'ENEMIES: 1'
'PLAYERS: 4'
'ENEMIES: 0'
error: Uncaught TypeError: Cannot read property 'applyDamage' of undefined
So to fix this, you'll need to either change your conditional to this:
while (this.players.length !== 0 && this.enemies.length !== 0) {
Or you'll need to start both arrays off at the same size.
I'm doing a coding challenge from the coding train, and I'm trying to improve on his code. The idea is that the cars are driving around a race track. When I went back to check something, I noticed that I misspelled "activation: sigmoid", as in activation function. When I fixed it, the cars seemed to be driving in circles.
I'm a very new coder (as I am 12 years old), so many things in my code are broken, hard to understand, or just not finished. I'm also pretty new to stack overflow, so I might be breaking a lot of rules.
The link to download my project is here: https://1drv.ms/u/s!ApmY_SAko19ChzCKe5uNT7I9EZAX?e=YUg2ff
The misspelled words are at lines 29 and 34 in the nn.js file.
car.js
function pldistance(p1, p2, x, y) {
const num = abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x);
const den = p5.Vector.dist(p1, p2);
return num / den;
}
class Car {
constructor(brain, color = [random(255), random(255), random(255)]) {
this.colorGene = color;
this.dead = false;
this.finished = false;
this.fitness = 0;
this.rays = [];
this.wallRays = [];
this.degreeOfSight = degreeOfSight;
this.degreeOfRays = degreeOfSight / (numOfRays - 1);
if (this.degreeOfSight == 360) {
this.degreeOfRays = degreeOfSight / numOfRays;
}
this.pos = createVector(start.x, start.y);
this.vel = createVector();
this.acc = createVector();
this.sight = sight;
this.maxspeed = maxspeed;
this.maxforce = maxTurningSpeed;
this.currentGoal = 0;
this.timeTillDeadC = timeTillDead;
this.timeTillDead = this.timeTillDeadC;
this.goal;
this.rate = mutationRate;
if (degreeOfSight != 360) {
for (let a = -(this.degreeOfSight / 2); a <= this.degreeOfSight / 2; a += this.degreeOfRays) {
this.rays.push(new Ray(this.pos, radians(a)));
}
} else {
for (let a = -(this.degreeOfSight / 2); a < this.degreeOfSight / 2; a += this.degreeOfRays) {
this.rays.push(new Ray(this.pos, radians(a)));
}
}
for (let a = 0; a < 360; a += 45) {
this.wallRays.push(new Ray(this.pos, radians(a)));
}
if (brain) {
this.brain = brain.copy();
} else {
this.brain = new NeuralNetwork(this.rays.length + 2, 16, 2);
}
}
applyForce(force) {
this.acc.add(force);
}
update(x, y) {
this.timeTillDead--;
if (this.timeTillDead <= 0) {
this.dead = true;
}
if (!this.dead || this.finished) {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.vel.limit(this.maxspeed);
this.acc.set(0, 0);
}
for (let i = 0; i < this.rays.length; i++) {
this.rays[i].rotate(this.vel.heading());
}
for (let i = 0; i < this.wallRays.length; i++) {
this.wallRays[i].rotate(this.vel.heading());
}
}
show(walls) {
push();
translate(this.pos.x, this.pos.y);
if (visualization) {
fill(this.colorGene[0], this.colorGene[1], this.colorGene[1]);
} else {
fill(0);
}
stroke(255);
const heading = this.vel.heading();
rotate(heading);
rectMode(CENTER);
rect(0, 0, 10, 5);
pop();
if (!this.dead) {
checkpoints[this.currentGoal].show();
}
for (let i = 0; i < this.rays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.rays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record && d < this.sight) {
record = d;
closest = pt;
}
}
}
if (closest) {
if (showLines) {
ellipse(closest.x, closest.y, 4)
stroke(255, 100)
line(this.pos.x, this.pos.y, closest.x, closest.y);
}
}
}
}
check(checkpoints, walls) {
if (!this.dead) {
this.goal = checkpoints[this.currentGoal];
const d = pldistance(this.goal.a, this.goal.b, this.pos.x, this.pos.y);
if (d < 5) {
this.fitness++;
this.currentGoal++;
this.timeTillDead = this.timeTillDeadC;
if (this.currentGoal == checkpoints.length) {
this.finished = true;
this.fitness = this.fitness * 1.5;
if (endBarrier) {
this.dead = true;
} else {
this.currentGoal = 0;
}
}
}
}
for (let i = 0; i < this.wallRays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.wallRays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record) {
record = d;
closest = pt;
}
}
}
if (record < 4) {
this.dead = true;
}
}
}
look(walls) {
const inputs = [];
for (let i = 0; i < this.wallRays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.rays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record && d < this.sight) {
record = d;
closest = pt;
}
}
}
inputs[i] = map(record, 0, 50, 1, 0);
}
inputs.push(end.x);
inputs.push(end.y);
const output = this.brain.predict(inputs);
let angle = map(output[0], 0, 1, -PI, PI);
let speed = map(output[1], 0, 1, -this.maxspeed, this.maxspeed);
angle += this.vel.heading();
const steering = p5.Vector.fromAngle(angle);
steering.setMag(speed);
steering.limit(this.maxforce);
this.applyForce(steering);
}
mutateDemBabies() {
if (this.finished) {
this.rate = finishingMutationRate;
}
this.brain.mutate(this.rate);
let changeColor = this.brain.mutated();
if (changeColor) {
for (let color of this.colorGene) {
let r = map(random(20), 0, 20, -25, 25);
color += r;
}
}
this.rate = mutationRate;
}
dispose() {
this.brain.dispose();
}
}
nn.js
//<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#1.1.0/dist/tf.min.js"></script>
class NeuralNetwork {
//this how many inputs, hidden, and output nodes there are. modelC is the brain that we want to copy to give to the new bird
constructor(inputNumber, hiddenNumber, outputNumber, modelC) {
if (modelC instanceof tf.Sequential) {
//this is the making a copy of the neural network
this.input_nodes = inputNumber;
this.hidden_nodes = hiddenNumber;
this.output_nodes = outputNumber;
this.model = modelC;
} else {
//this is the creating a random brain
this.input_nodes = inputNumber;
this.hidden_nodes = hiddenNumber;
this.output_nodes = outputNumber;
this.model = this.createBrain();
}
this.changeColor = false;
}
createBrain() {
//the model is the neural network
const model = tf.sequential();
//configuring the hidden layer
const hiddenLayer = tf.layers.dense({
units: this.hidden_nodes,
inputShape: [this.input_nodes],
activaation: "sigmoid"
});
//configuring the output layer
const outputLayer = tf.layers.dense({
units: this.output_nodes,
activaation: "sigmoid"
});
//adding the hidden layer to the model
model.add(hiddenLayer);
//adding the output layer to the model
model.add(outputLayer);
//returning the model
return model;
}
predict(inputs) {
//clearing the tensors after using them
//then returning the output
return tf.tidy(() => {
//creating a tensor with the inputs
const xs = tf.tensor2d([inputs]);
//running the inputs through the neural network
const ys = this.model.predict(xs);
//getting the raw numbers from the tensor object
const outputs = ys.dataSync();
//returning the outputs
return outputs;
});
}
copy() {
//clearing the tensors after using them
//then returning the output
return tf.tidy(() => {
//creating a new neural network
const modelCopy = this.createBrain();
//getting the weights from the old neural network
const weights = this.model.getWeights();
//setting the new weights
modelCopy.setWeights(weights);
//making a new network but this time with all the weights then returning it
return new NeuralNetwork(
this.input_nodes,
this.hidden_nodes,
this.output_nodes,
modelCopy
);
});
}
mutate(rate, colorGene) {
//clearing the tensors after using them
tf.tidy(() => {
this.changeColor = false;
//getting the weights so that we can change them later
const weights = this.model.getWeights();
//the variable that will be holding the mutated weights
const mutatedWeights = [];
for (let i = 0; i < weights.length; i++) {
//getting the shape of the current weights
let shape = weights[i].shape;
//making a copy of the raw numbers from the object tensor
//dataSync gets the numbers, but doesn't make a copy, so slice will make the copy
let values = weights[i].dataSync().slice();
for (let j = 0; j < values.length; j++) {
//if the random number is less than mutation rate the it runs the code
if (random(1) < rate) {
this.changeColor = true;
//mutating the value
//randomGaussianis returns a float from a series of numbers with a mean of 0
values[j] = values[j] + randomGaussian();
}
}
//holding the new value of each weight
mutatedWeights[i] = tf.tensor(values, shape);
}
//setting the mutated weights as the new weights
this.model.setWeights(mutatedWeights);
});
}
mutated() {
if (this.changeColor) {
this.changeColor = false;
return true;
} else {
this.changeColor = false;
return false;
}
}
dispose() {
//disposing the brain so that memory doesn't leak
this.model.dispose();
}
}
I am referring this example. It is on vanilla javascript.
Imported everything to as an angular provider service in angular 7.3.8 with AMI version 0.32.0 (three 0.99.0).
Using the same test examples as in the link above.
Original scan with segmentation map is getting loaded but the class color is not loaded.
Please refer the code below to understand the changes:
loadAMIFile()
Not loading files using loader.load() as in original version but only parsing as loader.load() is not handling non-Dicom files properly. Hence, getting the required data to pass to loader.parse() from files using the below script.
loadAMIFile(files) {
const ext = files[0].name.split('.').pop();
this.readAsDataURL(files[0]).then((dataUrl) => {
this.readAsArrayBuffer(files[0]).then((arrayBuffer) => {
const resp = {
buffer: arrayBuffer,
extension: ext,
filename: files[0].name,
gzcompressed: null,
pathname: "",
query: "",
url: dataUrl + '?filename=' + files[0].name
};
that.amiProvider.toParse(resp);
}).catch(error => {
console.log('oops... something went wrong...');
console.log(error);
});
}).catch(error => {
console.log('oops... something went wrong...');
console.log(error);
});
}
amiProvider.toParse()
Here the data gets parsed and loaded by calling handleSeries().
For simplicity, I am loading the same file twice, whereas in reality, first, the original scan will get loaded and on user request, it's segmentation map should get loaded.
In this case, I am loading the labelmap file but it does not show respective class colors. Everything else is as similar as in the jsfiddle link.
toParse(toBeParsedDict) {
this.loader = new this.LoadersVolume(this.threeD);
const toBeParsedDictArr = [toBeParsedDict, toBeParsedDict];
const promises = [];
const that = this;
toBeParsedDictArr.forEach(toBeParsedDict_ => {
// To avoid shallow copy.
const copied = {...toBeParsedDict_};
promises.push(that.loader.parse(copied));
});
Promise.all(promises).then(data => {
console.log(data);
this.handleSeries(data);
}).catch(err => console.log(err));
I have also tried with vanilla js (copying entire project from the link above to local system) and the same issue occurred there as well.
Does anyone face such issue before?
NOTE: Original fiddle version has used AMI 0.0.17 ( ThreeJS 86) which is not working at all locally i.e. not loading test files at all. So both versions have been modified. Is there any problem with the version of the modules I am using.
Okay, So after 1 month of headache and testing all AMI versions, got to know that in the angular environment only AMI 0.0.17 is giving a color map.
Though It does not support MGZ/MGH files, I angularised and integrated MGH parser from the latest AMI (AMI 0.32.0) into my project to have support for MGZ/MGH file parsing.
Now it works like a charm.
ami.component.ts
async loadAMIFile(files, hasSegmentationMap = false) {
const ext0 = files[0].name.split('.').pop();
const arrayBuffer0 = await this.readAsArrayBuffer(files[0]);
const resp0 = {
buffer: arrayBuffer0,
extension: ext0,
filename: files[0].name,
gzcompressed: null,
pathname: files[0].name,
query: 'filename=' + files[0].name,
url: files[0].name
};
if (hasSegmentationMap) {
const ext1 = files[1].name.split('.').pop();
const arrayBuffer1 = await this.readAsArrayBuffer(files[1]);
const resp1 = {
buffer: arrayBuffer1,
extension: ext1,
filename: files[1].name,
gzcompressed: null,
pathname: files[1].name,
query: 'filename=' + files[1].name,
url: files[1].name
};
this.amiProvider.toParse([resp0, resp1], hasSegmentationMap);
} else {
this.amiProvider.toParse([resp0], hasSegmentationMap);
}
}
ami.provider.ts
toParse(toBeParsedDictArr, hasSegmentationMap = false) {
this.loader = new this.LoadersVolume(this.threeD);
const promises = [];
toBeParsedDictArr.forEach(toBeParsedDict_ => {
const copied = {...toBeParsedDict_};
if (['mgz', 'mgh'].includes(copied.extension)) {
const data = this._parseMGH(copied);
promises.push(this.loader.parse(data));
}
promises.push(this.loader.parse(copied));
});
Promise.all(promises).then(data => {
this.handleSeries(data, hasSegmentationMap);
}).catch(err => console.log(err));
}
_parseMGH(data) {
// unzip if extension is '.mgz'
if (data.extension === 'mgz') {
data.gzcompressed = false; // true
data.extension = 'mgh';
data.filename = data.filename.split('.')[0] + '.' + data.extension;
const decompressedData = PAKO.inflate(data.buffer);
data.buffer = decompressedData.buffer;
} else {
data.gzcompressed = false;
}
const mghVolumeParser = new ParsersMgh(data, 0, this.THREE);
data.volumeParser = mghVolumeParser;
return data;
}
}
mghParser.helper.ts
import {Inject} from '#angular/core';
import {VolumeParser} from './volumeParser.helper';
/**
* #module parsers/mgh
*/
export class ParsersMgh extends VolumeParser {
// https://github.com/freesurfer/freesurfer/
// See include/mri.h
MRI_UCHAR = 0;
MRI_INT = 1;
MRI_LONG = 2;
MRI_FLOAT = 3;
MRI_SHORT = 4;
MRI_BITMAP = 5;
MRI_TENSOR = 6;
MRI_FLOAT_COMPLEX = 7;
MRI_DOUBLE_COMPLEX = 8;
MRI_RGB = 9;
// https://github.com/freesurfer/freesurfer/
// See include/tags.h
TAG_OLD_COLORTABLE = 1;
TAG_OLD_USEREALRAS = 2;
TAG_CMDLINE = 3;
TAG_USEREALRAS = 4;
TAG_COLORTABLE = 5;
TAG_GCAMORPH_GEOM = 10;
TAG_GCAMORPH_TYPE = 11;
TAG_GCAMORPH_LABELS = 12;
TAG_OLD_SURF_GEOM = 20;
TAG_SURF_GEOM = 21;
TAG_OLD_MGH_XFORM = 30;
TAG_MGH_XFORM = 31;
TAG_GROUP_AVG_SURFACE_AREA = 32;
TAG_AUTO_ALIGN = 33;
TAG_SCALAR_DOUBLE = 40;
TAG_PEDIR = 41;
TAG_MRI_FRAME = 42;
TAG_FIELDSTRENGTH = 43;
public _id;
public _url;
public _buffer;
public _bufferPos;
public _dataPos;
public _pixelData;
// Default MGH Header as described at:
// https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat
// Image "header" with default values
public _version;
public _width;
public _height;
public _depth;
public _nframes;
public _type; // 0-UCHAR, 4-SHORT, 1-INT, 3-FLOAT
public _dof;
public _goodRASFlag; // True: Use directional cosines, false assume CORONAL
public _spacingXYZ;
public _Xras;
public _Yras;
public _Zras;
public _Cras;
// Image "footer"
public _tr; // ms
public _flipAngle; // radians
public _te; // ms
public _ti; // ms
public _fov; // from doc: IGNORE THIS FIELD (data is inconsistent)
public _tags; // Will then contain variable length char strings
// Other misc
public _origin;
public _imageOrient;
// Read header
// ArrayBuffer in data.buffer may need endian swap
// public _buffer = data.buffer;
// public _version;
public _swapEndian;
// public _width;
// public _height;
// public _depth; // AMI calls this frames
// public _nframes;
// public _type;
// public _dof;
// public _goodRASFlag;
// public _spacingXYZ;
// public _Xras;
// public _Yras;
// public _Zras;
// public _Cras;
// #Inject('THREE') public THREE;
public dataSize;
public vSize;
constructor(data, id, #Inject('THREE') public THREE) {
super();
/**
* #member
* #type {arraybuffer}
*/
this._id = id;
this._url = data.url;
this._buffer = null;
this._bufferPos = 0;
this._dataPos = 0;
this._pixelData = null;
// Default MGH Header as described at:
// https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat
// Image "header" with default values
this._version = 1;
this._width = 0;
this._height = 0;
this._depth = 0;
this._nframes = 0;
this._type = this.MRI_UCHAR; // 0-UCHAR, 4-SHORT, 1-INT, 3-FLOAT
this._dof = 0;
this._goodRASFlag = 0; // True: Use directional cosines, false assume CORONAL
this._spacingXYZ = [1, 1, 1];
this._Xras = [-1, 0, 0];
this._Yras = [0, 0, -1];
this._Zras = [0, 1, 0];
this._Cras = [0, 0, 0];
// Image "footer"
this._tr = 0; // ms
this._flipAngle = 0; // radians
this._te = 0; // ms
this._ti = 0; // ms
this._fov = 0; // from doc: IGNORE THIS FIELD (data is inconsistent)
this._tags = []; // Will then contain variable length char strings
// Other misc
this._origin = [0, 0, 0];
this._imageOrient = [0, 0, 0, 0, 0, 0];
// Read header
// ArrayBuffer in data.buffer may need endian swap
this._buffer = data.buffer;
this._version = this._readInt();
this._swapEndian = false;
if (this._version === 1) {
// Life is good
} else if (this._version === 16777216) {
this._swapEndian = true;
this._version = this._swap32(this._version);
} else {
const error = new Error('MGH/MGZ parser: Unknown Endian. Version reports: ' + this._version);
throw error;
}
this._width = this._readInt();
this._height = this._readInt();
this._depth = this._readInt(); // AMI calls this frames
this._nframes = this._readInt();
this._type = this._readInt();
this._dof = this._readInt();
this._goodRASFlag = this._readShort();
this._spacingXYZ = this._readFloat(3);
this._Xras = this._readFloat(3);
this._Yras = this._readFloat(3);
this._Zras = this._readFloat(3);
this._Cras = this._readFloat(3);
this._bufferPos = 284;
const dataSize = this._width * this._height * this._depth * this._nframes;
const vSize = this._width * this._height * this._depth;
switch (this._type) {
case this.MRI_UCHAR:
this._pixelData = this._readUChar(dataSize);
break;
case this.MRI_INT:
this._pixelData = this._readInt(dataSize);
break;
case this.MRI_FLOAT:
this._pixelData = this._readFloat(dataSize);
break;
case this.MRI_SHORT:
this._pixelData = this._readShort(dataSize);
break;
default:
throw Error('MGH/MGZ parser: Unknown _type. _type reports: ' + this._type);
}
this._tr = this._readFloat(1);
this._flipAngle = this._readFloat(1);
this._te = this._readFloat(1);
this._ti = this._readFloat(1);
this._fov = this._readFloat(1);
const enc = new TextDecoder();
let t = this._tagReadStart();
while (t[0] !== undefined) {
const tagType = t[0];
const tagLen = t[1];
let tagValue;
switch (tagType) {
case this.TAG_OLD_MGH_XFORM:
case this.TAG_MGH_XFORM:
tagValue = this._readChar(tagLen);
break;
default:
tagValue = this._readChar(tagLen);
}
tagValue = enc.decode(tagValue);
this._tags.push({tagType: tagType, tagValue: tagValue});
// read for next loop
t = this._tagReadStart();
}
// detect if we are in a right handed coordinate system
const first = new this.THREE.Vector3().fromArray(this._Xras);
const second = new this.THREE.Vector3().fromArray(this._Yras);
const crossFirstSecond = new this.THREE.Vector3().crossVectors(first, second);
const third = new this.THREE.Vector3().fromArray(this._Zras);
if (crossFirstSecond.angleTo(third) > Math.PI / 2) {
this._rightHanded = false;
}
// - sign to move to LPS space
this._imageOrient = [
-this._Xras[0],
-this._Xras[1],
this._Xras[2],
-this._Yras[0],
-this._Yras[1],
this._Yras[2],
];
// Calculate origin
const fcx = this._width / 2.0;
const fcy = this._height / 2.0;
const fcz = this._depth / 2.0;
for (let ui = 0; ui < 3; ++ui) {
this._origin[ui] =
this._Cras[ui] -
(this._Xras[ui] * this._spacingXYZ[0] * fcx +
this._Yras[ui] * this._spacingXYZ[1] * fcy +
this._Zras[ui] * this._spacingXYZ[2] * fcz);
}
// - sign to move to LPS space
this._origin = [-this._origin[0], -this._origin[1], this._origin[2]];
}
seriesInstanceUID() {
// use filename + timestamp..?
return this._url;
}
numberOfFrames() {
// AMI calls Z component frames, not T (_nframes)
return this._depth;
}
sopInstanceUID(frameIndex = 0) {
return frameIndex;
}
rows(frameIndex = 0) {
return this._width;
}
columns(frameIndex = 0) {
return this._height;
}
pixelType(frameIndex = 0) {
// Return: 0 integer, 1 float
switch (this._type) {
case this.MRI_UCHAR:
case this.MRI_INT:
case this.MRI_SHORT:
return 0;
case this.MRI_FLOAT:
return 1;
default:
throw Error('MGH/MGZ parser: Unknown _type. _type reports: ' + this._type);
}
}
bitsAllocated(frameIndex = 0) {
switch (this._type) {
case this.MRI_UCHAR:
return 8;
case this.MRI_SHORT:
return 16;
case this.MRI_INT:
case this.MRI_FLOAT:
return 32;
default:
throw Error('MGH/MGZ parser: Unknown _type. _type reports: ' + this._type);
}
}
pixelSpacing(frameIndex = 0) {
return this._spacingXYZ;
}
imageOrientation(frameIndex = 0) {
return this._imageOrient;
}
imagePosition(frameIndex = 0) {
return this._origin;
}
extractPixelData(frameIndex = 0) {
const sliceSize = this._width * this._height;
return this._pixelData.slice(frameIndex * sliceSize, (frameIndex + 1) * sliceSize);
}
// signed int32
_readInt(len = 1) {
const tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 4));
this._bufferPos += len * 4;
let v;
if (len === 1) {
v = tempBuff.getInt32(0, this._swapEndian);
} else {
v = new Int32Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getInt32(i * 4, this._swapEndian);
}
}
return v;
}
// signed int16
_readShort(len = 1) {
const tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 2));
this._bufferPos += len * 2;
let v;
if (len === 1) {
v = tempBuff.getInt16(0, this._swapEndian);
} else {
v = new Int16Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getInt16(i * 2, this._swapEndian);
}
}
return v;
}
// signed int64
_readLong(len = 1) {
const tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 8));
this._bufferPos += len * 8;
const v = new Uint16Array(len);
for (let i = 0; i < len; i++) {
/* DataView doesn't have Int64.
* This work around based off Scalajs
* (https://github.com/scala-js/scala-js/blob/master/library/src/main/scala/scala/scalajs/js/typedarray/DataViewExt.scala)
* v[i]=tempBuff.getInt64(i*8,this._swapEndian);
*/
let shiftHigh = 0;
let shiftLow = 0;
if (this._swapEndian) {
shiftHigh = 4;
} else {
shiftLow = 4;
}
const high = tempBuff.getInt32(i * 8 + shiftHigh, this._swapEndian);
let low = tempBuff.getInt32(i * 8 + shiftLow, this._swapEndian);
if (high !== 0) {
console.log('Unable to read Int64 with high word: ' + high + 'low word: ' + low);
low = undefined;
}
v[i] = low;
}
if (len === 0) {
return undefined;
} else if (len === 1) {
return v[0];
} else {
return v;
}
}
// signed int8
_readChar(len = 1) {
const tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len));
this._bufferPos += len;
let v;
if (len === 1) {
v = tempBuff.getInt8(0); // , this._swapEndian
} else {
v = new Int8Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getInt8(i); // , this._swapEndian
}
}
return v;
}
// unsigned int8
_readUChar(len = 1) {
const tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len));
this._bufferPos += len;
let v;
if (len === 1) {
v = tempBuff.getUint8(0); // , this._swapEndian
} else {
v = new Uint8Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getUint8(i); // , this._swapEndian
}
}
return v;
}
// float32
_readFloat(len = 1) {
const tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 4));
this._bufferPos += len * 4;
let v;
if (len === 1) {
v = tempBuff.getFloat32(0, this._swapEndian);
} else {
v = new Float32Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getFloat32(i * 4, this._swapEndian);
}
}
return v;
}
_tagReadStart() {
if (this._bufferPos >= this._buffer.byteLength) {
return [undefined, undefined];
}
let tagType = this._readInt();
let tagLen;
switch (tagType) {
case this.TAG_OLD_MGH_XFORM:
tagLen = this._readInt();
tagLen -= 1;
break;
case this.TAG_OLD_SURF_GEOM:
case this.TAG_OLD_USEREALRAS:
case this.TAG_OLD_COLORTABLE:
tagLen = 0;
break;
default:
tagLen = this._readLong();
}
if (tagLen === undefined) {
tagType = undefined;
}
return [tagType, tagLen];
}
}
volumeParser.helper.ts
/** * Imports ***/
// import ParsersVolume from './parsers.volume';
// import * as THREE from 'three';
/**
* #module parsers/volume
*/
export class VolumeParser {
public _rightHanded;
constructor() {
this._rightHanded = true;
}
pixelRepresentation() {
return 0;
}
pixelPaddingValue(frameIndex = 0) {
return null;
}
modality() {
return 'unknown';
}
segmentationType() {
return 'unknown';
}
segmentationSegments() {
return [];
}
referencedSegmentNumber(frameIndex) {
return -1;
}
rightHanded() {
return this._rightHanded;
}
spacingBetweenSlices() {
return null;
}
numberOfChannels() {
return 1;
}
sliceThickness() {
return null;
}
dimensionIndexValues(frameIndex = 0) {
return null;
}
instanceNumber(frameIndex = 0) {
return frameIndex;
}
windowCenter(frameIndex = 0) {
return null;
}
windowWidth(frameIndex = 0) {
return null;
}
rescaleSlope(frameIndex = 0) {
return 1;
}
rescaleIntercept(frameIndex = 0) {
return 0;
}
ultrasoundRegions(frameIndex = 0) {
return [];
}
frameTime(frameIndex = 0) {
return null;
}
_decompressUncompressed() {
}
// http://stackoverflow.com/questions/5320439/how-do-i-swap-endian-ness-byte-order-of-a-variable-in-javascript
_swap16(val) {
return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
}
_swap32(val) {
return (
((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff)
);
}
invert() {
return false;
}
/**
* Get the transfer syntax UID.
* #return {*}
*/
transferSyntaxUID() {
return 'no value provided';
}
/**
* Get the study date.
* #return {*}
*/
studyDate() {
return 'no value provided';
}
/**
* Get the study desciption.
* #return {*}
*/
studyDescription() {
return 'no value provided';
}
/**
* Get the series date.
* #return {*}
*/
seriesDate() {
return 'no value provided';
}
/**
* Get the series desciption.
* #return {*}
*/
seriesDescription() {
return 'no value provided';
}
/**
* Get the patient ID.
* #return {*}
*/
patientID() {
return 'no value provided';
}
/**
* Get the patient name.
* #return {*}
*/
patientName() {
return 'no value provided';
}
/**
* Get the patient age.
* #return {*}
*/
patientAge() {
return 'no value provided';
}
/**
* Get the patient birthdate.
* #return {*}
*/
patientBirthdate() {
return 'no value provided';
}
/**
* Get the patient sex.
* #return {*}
*/
patientSex() {
return 'no value provided';
}
/**
* Get min/max values in array
*
* #param {*} pixelData
*
* #return {*}
*/
minMaxPixelData(pixelData = []) {
const minMax = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY];
const numPixels = pixelData.length;
for (let index = 0; index < numPixels; index++) {
const spv = pixelData[index];
minMax[0] = Math.min(minMax[0], spv);
minMax[1] = Math.max(minMax[1], spv);
}
return minMax;
}
}
And a little bit change in parse() of AMI 0.0.17 library to accommodate MGH parser.
In the future, with the support of the latest AMI with color map integrated properly, the code will work without the need for any change in the library again.
var volumeParser = null;
try {
if (['mgh', 'mgz'].includes(response.extension)) {
volumeParser = response.volumeParser;
} else {
var Parser = _this2._parser(data.extension);
if (!Parser) {
// emit 'parse-error' event
_this2.emit('parse-error', {
file: response.url,
time: new Date(),
error: data.filename + 'can not be parsed.'
});
reject(data.filename + ' can not be parsed.');
}
volumeParser = new Parser(data, 0);
}