Commit 09d54f89 authored by Dorian Goepp's avatar Dorian Goepp

Make all modules configurable & strip configs of extras

parent e6134a89
......@@ -54,10 +54,11 @@ class InteractionTrace extends Component {
.ratioCell(0.5) // Row for the valences (takes a ratio of the avalable space)
.fixedCell(75); // Row for the actions (temporary value, updated later in function of this.em)
const config = this.props.node.getConfig();
// Value for the boredom at which we plot a red line (showing max value)
this.maxBoredom = 2;
this.maxBoredom = config.maxBoredom;
// How many primitive actions (at least) we want to display
this.nbPrimitiveActions = 3;
this.nbPrimitiveActions = config.nbPrimitiveActions;
// Will store the conversion from 1em in px for the LabelColumn.
// This is a tricky trick to set the size of the LabelColumn to match with the longest chain of actions that
......@@ -65,7 +66,7 @@ class InteractionTrace extends Component {
this.em = undefined;
// In debug mode, we add bounding boxes for the different areas of this figure
this.debug = false;
this.debug = config.debug;
// Other parameter: how many em the minimal column width has to be (function minXWidth).
}
......@@ -291,25 +292,6 @@ class InteractionTrace extends Component {
const viewbox = "0 0 " + (width + this.margin.left + this.margin.right)
+" "+ (height + this.margin.top + this.margin.bottom);
const config = this.props.node.getConfig()
if (config && 'displayMode' in config) {
if (config.displayMode === "readme") {
return <div className="about">
<h1>Trace of the reasoning process performed by the agent</h1>
<p>The <b>mood</b> represents the effect of the <b>valence</b> on the mood of the agent.</p>
<h2>Boredom</h2>
<p>
Observe the evolution of the <b>interactions</b> performed by the agent. <b>Intended</b> interactions are the interactions the agent plans to perform whereas <b>enacted</b> interactions are the interactions actually performed in the physical world of the agent. An interaction is composed of an <b>action</b> and a <b>valence</b>, the valence being dependent of the response from the environment.
</p>
</div>;
} else if (config.displayMode === "settings") {
return <div>
<p>config panel</p>
</div>;
}
}
return (
<svg viewBox={viewbox} width="100%" height="99%" className="interaction-trace">
{/* Compute how many pixels an em takes */}
......
import React, {Component} from 'react'
import {Modal} from '../utils/modeHandler'
import {observable, decorate} from 'mobx'
import {observer} from 'mobx-react'
import sizeMe from 'react-sizeme'
......@@ -12,42 +13,88 @@ class RosInteractionTrace extends Component {
super(props)
this.data = [];
this.lowerIdResets = true;
this.trace = {
topic: "/algorithm/trace",
type: "april_messages/trace"
}
this.boredom = {
topic: "/algorithm/boredom",
type: "april_messages/boredom"
}
this.mood = {
topic: "/algorithm/mood",
type: "april_messages/mood"
}
}
static get modes() {
return {
readme: (
<div className="about">
<h1>Trace of the reasoning process performed by the agent</h1>
<p>The <b>mood</b> represents the effect of the <b>valence</b> on the mood of the agent.</p>
<h2>Boredom</h2>
<p>
Observe the evolution of the <b>interactions</b> performed by the agent. <b>Intended</b> interactions are the
interactions the agent plans to perform whereas <b>enacted</b> interactions are the interactions actually
performed in the physical world of the agent. An interaction is composed of an <b>action</b> and a
<b>valence</b>, the valence being dependent of the response from the environment.
</p>
</div>),
settingsSchema: {
title: "Interaction trace",
type: "object",
required: ["trace", "boredom", "mood"],
properties: {
trace: {
title: "Topic for the trace",
type: "string",
default: "/algorithm/trace"
},
boredom: {
title: "Topic for the boredom",
type: "string",
default: "/algorithm/boredom"
},
mood: {
title: "Topic for the mood",
type: "string",
default: "/algorithm/mood"
},
lowerIdResets: {
title: "When a new interaction arrives with an ID lower than the latest one, reset the display ?",
type: "boolean",
default: true,
},
maxBoredom: {
title: "Maximal expected value for the boredom (used for scaling)",
type: "integer",
default: 2,
},
nbPrimitiveActions: {
title: "How many primitive actions are displayed",
type: "integer",
default: 3,
},
debug: {
title: "Debug mode",
type: "boolean",
default: false
}
}
}
};
}
componentDidMount() {
if ('ros' in this.props && this.props.ros) {
const config = this.props.node.getConfig();
this.trace_listener = new RosLib.Topic({
ros : this.props.ros,
name: this.trace.topic,
messageType: this.trace.type
name: config.trace,
messageType: "april_messages/trace"
});
this.trace_listener.subscribe(this.interaction_to_data.bind(this));
this.boredom_listener = new RosLib.Topic({
ros : this.props.ros,
name: this.boredom.topic,
messageType: this.boredom.type
name: config.boredom,
messageType: "april_messages/boredom"
});
this.boredom_listener.subscribe(this.boredom_to_data.bind(this));
this.mood_listener = new RosLib.Topic({
ros : this.props.ros,
name: this.mood.topic,
messageType: this.mood.type
name: config.mood,
messageType: "april_messages/mood"
});
this.mood_listener.subscribe(this.mood_to_data.bind(this));
} else {
......@@ -109,7 +156,8 @@ class RosInteractionTrace extends Component {
*/
mergeDataInState(newDatum) {
// Empty the data and get the first interaction of an experiment
if (this.lowerIdResets && newDatum.id === 0 && this.data.length > 1) {
const config = this.props.node.getConfig();
if (config.lowerIdResets && this.data.length > 0 && newDatum.id < this.data[this.data.length-1].id) {
this.data = [];
}
......@@ -125,7 +173,11 @@ class RosInteractionTrace extends Component {
)
decorate(RosInteractionTrace, {
data: observable,
lowerIdResets: observable,
// lowerIdResets: observable,
})
export default sizeMe({monitorHeight: true})(RosInteractionTrace);
\ No newline at end of file
const ModalRosInteractionTrace = observer(function(props) {
return <Modal component={RosInteractionTrace} {...props}/>
});
export default sizeMe({monitorHeight: true})(ModalRosInteractionTrace);
\ No newline at end of file
import React, {Component} from 'react'
import RosLib from 'roslib'
import {Modal} from '../utils/modeHandler'
import {LatestInteraction} from './index'
class RosLatestInteraction extends Component {
......@@ -20,6 +21,33 @@ class RosLatestInteraction extends Component {
}
}
static get modes() {
return {
readme: (
<div className="about">
<p>
<b>Last action</b> done by the agent and its <b>valence</b>.
</p>
<p>
If you place your mouse pointer over an <b>action</b> in the interaction trace, this widget will display
the <b>full interaction</b>.
</p>
</div>),
settingsSchema: {
title: "Interaction trace",
type: "object",
required: ["trace"],
properties: {
trace: {
title: "Topic for the trace",
type: "string",
default: "/algorithm/trace"
},
}
}
};
}
componentDidMount() {
if ('ros' in this.props && this.props.ros) {
this.trace_listener = new RosLib.Topic({
......@@ -46,4 +74,8 @@ class RosLatestInteraction extends Component {
}
}
export { RosLatestInteraction };
const ModalLatestInteraction = function(props) {
return <Modal component={RosLatestInteraction} {...props}/>
};
export { ModalLatestInteraction as RosLatestInteraction };
......@@ -46,20 +46,6 @@ function LatestInteraction(props) {
);
}
const config = this.props.node.getConfig()
if (config && 'displayMode' in config) {
if (config.displayMode === "readme") {
return <div className="about">
<p><b>Last action</b> done by the agent and its <b>valence</b>.</p>
<p>If you place your mouse pointer over an <b>action</b> in the interaction trace, this widget will display the <b>full interaction</b>.</p>
</div>;
} else if (config.displayMode === "settings") {
return <div>
<p>config panel</p>
</div>;
}
}
return (
<div className="latest-interaction">
{iteration}
......
......@@ -2,82 +2,138 @@ import React, {Component} from 'react';
import property from 'lodash/property';
import RosLib from 'roslib';
import {Modal} from '../utils/modeHandler'
import {SizedLineChart} from './LineChart'
class RosLineChart extends Component {
constructor(props) {
super(props);
this.listeners = [];
this.state = {
data: {"" : []},
topic: {
name: "/predictions/interaction_involvement",
messageType: "april_messages/head_orientation_prediction",
fields: [
"prediction_of_head_orientation.list[0].values[1]",
]
},
// xLabel: "Count",
yLabel: "Involvement",
data: {"" : []}
};
}
static get modes() {
return {
readme: (
<div className="about">
<p>
This linechart represents a measure evolving over time. It is possible to choose
any viable ROS topic to be plotted.
</p>
<p>
For readibility's sake, we chose to limit the number of curves to ten on a single plot. Hopefully
you would never reach such an unreasonable number.
</p>
</div>
),
settingsSchema: {
title: "Generic plot",
type: "object",
required: ["curves", "maxPoints"],
properties: {
maxPoints: {
title: "How many data points are plotted",
type: "integer",
minimum: 0,
maximum: 200,
default: 50,
},
curves: {
title: "Fields",
type: "array",
minItems: 1,
maxItems: 10,
default: [{
topic: "/predictions/interaction_involvement",
field: "prediction_of_head_orientation.list[0].values[1]"
}],
items: {
type: "object",
properties: {
topic: {
title: "topic",
type: "string",
default: "/std_msgs/int64"
},
field: {
title: "field",
type: "string",
default: "data[0]"
}
}
},
},
xLabel: {
title: "Label of abscissa",
type: "string",
},
yLabel: {
title: "Label of ordinate",
type: "string",
default: "Involvement",
},
}
}
};
}
componentDidMount() {
if ('ros' in this.props && this.props.ros) {
this.listener = new RosLib.Topic(Object.assign({ros : this.props.ros}, this.state.topic));
this.listener.subscribe(function(message) {
/**
* Extract a field from a ros message and add it to the state, for plotting.
*
* If there are already maxPoints in storage, we remove the oldest data point.
* @param {string} field path to the field to be extracted from incoming messages
* @param {integer} maxPoints mupper limit on the number of data entries for the plot
* @param {Object} message messages from a ROS topic
*/
function process_message(field, maxPoints, message) {
this.setState((state) => {
let fields;
if (!('fields' in this.state.topic) && ('field' in this.state.topic)) {
fields = [this.state.topic.field];
} else {
fields = this.state.topic.fields;
}
let new_data = this.state.data;
fields.forEach((field) => {
if (!(field in new_data)) {
new_data[field] = []
}
console.debug("Raw message: ", message);
console.debug("Selected field: ", property(field)(message));
new_data[field] = this.state.data[field].concat([{"y": property(field)(message)}])
})
if (!(field in new_data)) {
new_data[field] = []
}
if (new_data[field].length >= maxPoints) {
new_data[field].shift();
}
new_data[field] = this.state.data[field].concat([{"y": property(field)(message)}])
return {
data: new_data,
}
});
}.bind(this));
}
// Create a ROS topic listener for each curve (topic + field) in the configuration
const config = this.props.node.getConfig();
if ('curves' in config) {
config.curves.forEach((curve) => {
const listener = new RosLib.Topic({ros : this.props.ros, name: curve.topic});
listener.subscribe(process_message.bind(this, curve.field, config.maxPoints));
this.listeners.push(listener);
});
}
} else {
console.warn('RosLineChart expects to be passed a valid Ros object as property, got ', this.props.ros);
}
}
componentWillUnmount() {
if ('listener' in this) {
this.listener.unsubscribe();
if ('listeners' in this) {
this.listeners.forEach((listener) => {listener.unsubscribe();});
}
}
render () {
const config = this.props.node.getConfig()
if (config && 'displayMode' in config) {
if (config.displayMode === "readme") {
return <p className="about">This <b>linechart</b> represents a <b>measure</b> evolving over time.</p>;
}
if (config.displayMode === "settings") {
return <div>
<p>config panel</p>
</div>;
}
}
return <SizedLineChart data={this.state.data} xLabel={this.state.xLabel} yLabel={this.state.yLabel} {...this.props}/>
return <SizedLineChart data={this.state.data} xLabel={config.xLabel} yLabel={config.yLabel} {...this.props}/>
}
}
export {RosLineChart}
const ModalLineChart = function(props) {
return <Modal component={RosLineChart} {...props}/>
};
export {ModalLineChart as RosLineChart}
import React, {Component} from 'react';
import sizeMe from 'react-sizeme';
import {property} from 'lodash'
import './MemoryRanked.css';
import LinearLayout from '../utils/LinearLayout'
......@@ -10,6 +11,8 @@ import { NumberColumn } from './NumberColumn';
class MemoryRanked extends Component {
constructor(props) {
super(props);
const config = this.props.node.getConfig();
// Use the margin convention practice
this.margin = {top: 10, right: 20, bottom: 20, left: 20}
......@@ -26,15 +29,11 @@ class MemoryRanked extends Component {
this.vStep = 30;
// How frequent the horizontal guide lines should be put
this.guideLineCount = 3; // If set to 4, one guide line will be displayed every four line of data
// If set to 4, one guide line will be displayed every four line of data
this.guideLinesStep = config.guideLinesStep;
// The highest number of actions displayed in one memory entry.
this.maxActionLength = 15;
this.sorter = function (interaction) {
// return interaction.action_names.length
return interaction.valence
}
this.maxActionLength = config.maxActionLength;
}
/** Get the font size of a node in the DOM. It tells us how big one em is in px.
......@@ -63,32 +62,45 @@ class MemoryRanked extends Component {
guideLines() {
const data = this.props.data;
// Pre-computation for the guide lines, inserted below, in 6.
let NbGuideLines = Math.floor(data.length / this.guideLineCount);
if (data.length % this.guideLineCount === 0) { // no line at the bottom
let NbGuideLines = Math.floor(data.length / this.guideLinesStep);
if (data.length % this.guideLinesStep === 0) { // no line at the bottom
--NbGuideLines;
}
// Build a set of ordinates, one for each line
let y = []
for (let i = 1; i <= NbGuideLines; i++) {
// Here, 50 is the top margin used in all the columns of this component
y.push(this.margin.top + 50 + this.yScale(this.guideLineCount*i) + this.vStep / 2)
y.push(this.margin.top + 50 + this.yScale(this.guideLinesStep*i) + this.vStep / 2)
}
return y;
}
sort(data, accessor = (value => 0), revert = false) {
return data.sort((a, b) => {
if (!revert) {
return accessor(a) - accessor(b)
} else {
return accessor(b) - accessor(a)
sort(data) {
let sorter;
// Make a sorting function based on the configuration or, by default, sort
// by descending valence.
const config = this.props.node.getConfig();
if ('sorting' in config) {
const getter = property(config.sorting.value);
sorter = function(a, b) {
if(config.sorting.order === "ascending") {
return getter(a) - getter(b);
} else {
return getter(b) - getter(a);
}
}
});
} else {
sorter = function(a, b) {
return b.valence - a.valence;
}
}
return data.sort(sorter);
}
render() {
const data = this.sort(this.props.data, this.sorter, true);
const data = this.sort(this.props.data);
// Minimal required height for this figure
......
import React, {Component} from 'react';
import RosLib from 'roslib';
import {Modal} from '../utils/modeHandler'
import MemoryRanked from './MemoryRanked'
class RosMemoryRanked extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
topic: {
name: "/algorithm/memory",
messageType: "april_messages/memory"
this.state = {data: []};
}
static get modes() {
return {
readme: (
<div>
<h1>Display the sequences of the interactions learnt by the agent</h1>
<p>
Each time the agent performs a <b>sequence of interactions</b> in the environment, it records the
corresponding <b>valence</b> and stores it in the memory. Each record is called an <b>occurence</b>
of the sequence.
</p>
</div>),
settingsSchema: {
title: "Robot's memory",
type: "object",
required: ["guideLinesStep", "maxActionLength", "topic", "sorting"],
properties: {
guideLinesStep: {
title: "How many entries between two horizontal rules",
type: "integer",
minimum: 2,
default: 3,
},
maxActionLength: {
title: "The highest number of actions displayed in one memory entry",
type: "integer",
minimum: 2,
default: 10,
},
topic: {
title: "Topic for the memory",
type: "string",
default: "/algorithm/memory"
},
sorting: {
title: "How to sort the entries of the memory",
type: "object",
required: ["value", "order"],
default: {
value: "valence",
order: "descending"
},
properties: {
value: {
title: "Value used for sorting",
type: "string",
default: "valence",
anyOf: [
{
title: "valence",
type: "string",
enum: ["valence"]
},
{
title: "action length",
type: "string",
enum: ["action_names.length"]
},
{
title: "number of occurences",
type: "string",
enum: ["occurence"]
},
],
},
order: {
title: "Order of sorting",
type: "string",