Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2279928
WIP: add FactLog
paulsonnentag Oct 10, 2018
2ebf4d4
Add basic fact log
paulsonnentag Oct 10, 2018
fffe66b
parseWhen: add support for joined claims
paulsonnentag Oct 10, 2018
31e5f6d
Add wishes
paulsonnentag Oct 11, 2018
5715607
WIP: Single threaded execution
paulsonnentag Oct 16, 2018
c6dc505
Add basic illumination
paulsonnentag Oct 16, 2018
f2c826d
Add WithAll statementc
paulsonnentag Oct 16, 2018
bc1a3f0
Reload programs automatically
paulsonnentag Oct 16, 2018
0535821
Add canvas
paulsonnentag Oct 17, 2018
f1e2aba
added whisker ghost paper
biowaffeln Oct 18, 2018
9ea02b3
Merge branch 'claims-and-wishes' into whisker
biowaffeln Oct 18, 2018
f22c916
Add error reporting
paulsonnentag Oct 18, 2018
0485cb4
implemented ghost page with illumination
biowaffeln Oct 18, 2018
ea9cccc
formatting
biowaffeln Oct 18, 2018
d9b02ac
Display errors in editor
paulsonnentag Oct 18, 2018
07072db
WIP: illumination
biowaffeln Oct 18, 2018
e9201a0
added basic illumination methods
biowaffeln Oct 19, 2018
e8d6444
Improve errors
paulsonnentag Oct 19, 2018
f1eaeb0
Merge branch 'whisker' into claims-and-wishes
paulsonnentag Oct 19, 2018
f2aca12
linting
biowaffeln Oct 19, 2018
e00cd96
even more linting
biowaffeln Oct 19, 2018
a4176ba
added labeller
biowaffeln Oct 19, 2018
acac525
added polygon to illumination
biowaffeln Oct 19, 2018
40c8ac4
refactored illumination
biowaffeln Oct 19, 2018
17d0431
added outline ghostpaper
biowaffeln Oct 19, 2018
5edfa87
removed default stroke
biowaffeln Oct 19, 2018
88750e1
changed labeller defaults
biowaffeln Oct 19, 2018
44508d8
small changes in the illumination api
biowaffeln Oct 19, 2018
c1adcf5
whisker formatting
biowaffeln Oct 19, 2018
581f35e
Report dynamic errors as well
paulsonnentag Oct 19, 2018
c0a8567
Display matches inline
paulsonnentag Oct 19, 2018
8ddf100
refactored illumination
biowaffeln Oct 19, 2018
4cbfecb
Add log and fix codemirror bugs
paulsonnentag Oct 22, 2018
ff38ae4
Remove widgets when user starts typing
paulsonnentag Oct 22, 2018
ef273ad
added bouncy example
biowaffeln Oct 23, 2018
191fab4
Fix lint errors
paulsonnentag Oct 25, 2018
97803b5
Force to set DB URL
paulsonnentag Oct 25, 2018
48b12fa
Merge branch 'whisker' into claims-and-wishes
paulsonnentag Oct 25, 2018
a6d8c90
Remove paper
paulsonnentag Oct 25, 2018
3795131
Last fixes
paulsonnentag Oct 26, 2018
bdaf34b
Add canvas paper
paulsonnentag Oct 26, 2018
f21c84d
Allow to delete papers
paulsonnentag Oct 27, 2018
c7a354d
A whole bunch of fixes hopefully this doens't break things
paulsonnentag Oct 27, 2018
e36016d
Add lots of features
cesmoak Nov 14, 2018
b11421a
Add several example programs
cesmoak Nov 15, 2018
4518bb0
Merge pull request #4 from cesmoak/deploy
paulsonnentag Nov 21, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"GoogleMapsAPIKey": "[TODO]"
}
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
!.importjs.js
node_modules/
www/
examples/
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.babel-cache/
node_modules/
.env.*
!.env.example
48 changes: 47 additions & 1 deletion client/camera/CameraMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ export default class CameraMain extends React.Component {
);
};

_deletePaper = number => {
xhr.del(getApiUrl(this.state.spaceData.spaceName, `/programs/${number}/`), {}, error => {
if (error) {
console.error(error); // eslint-disable-line no-console
} else {
// eslint-disable-next-line no-console
console.log('deleted ', number);
}
});
};

_autoPrint = () => {
const toPrint = this.state.spaceData.programs.filter(
program => !program.printed && !this.state.autoPrintedNumbers.includes(program.number)
Expand Down Expand Up @@ -148,6 +159,10 @@ export default class CameraMain extends React.Component {
);
};

_cornersChange = (corners, cornerWidth) => {
this.props.onCornersChange(corners, cornerWidth);
};

render() {
const padding = parseInt(styles.cameraMainPadding);
const sidebarWidth = parseInt(styles.cameraMainSidebarWidth);
Expand All @@ -170,9 +185,10 @@ export default class CameraMain extends React.Component {
width={this.state.pageWidth - padding * 3 - sidebarWidth}
config={this.props.config}
onConfigChange={this.props.onConfigChange}
onProcessVideo={({ programsToRender, markers, framerate }) => {
onProcessVideo={({ programsToRender, markers, framerate, corners, cornerWidth }) => {
this.setState({ framerate });
this._programsChange(programsToRender);
this._cornersChange(corners, cornerWidth);
this.props.onMarkersChange(markers);
}}
allowSelectingDetectedPoints={this.state.selectedColorIndex !== -1}
Expand Down Expand Up @@ -279,6 +295,22 @@ export default class CameraMain extends React.Component {
{program.printed ? '[show]' : '[hide]'}
</span>
</span>
<button
onClick={evt => {
evt.stopPropagation();

if (
!confirm(`Are you sure you want to delete paper ${program.number}`)
) {
return;
}

this._deletePaper(program.number);
}}
>
delete
</button>

{this.state.debugPrograms.find(p => p.number === program.number) ===
undefined ? (
<span
Expand Down Expand Up @@ -450,6 +482,20 @@ export default class CameraMain extends React.Component {
/>{' '}
programs
</div>

<div className={styles.sidebarSubSection}>
<input
type="checkbox"
checked={this.props.config.showOverlayAlignmentHelper}
onChange={() =>
this.props.onConfigChange({
...this.props.config,
showOverlayAlignmentHelper: !this.props.config.showOverlayAlignmentHelper,
})
}
/>{' '}
alignment helper
</div>
</div>
<div className={styles.sidebarSection}>
<h3 className={styles.sidebarSubSection}>Space</h3>
Expand Down
4 changes: 2 additions & 2 deletions client/camera/CameraVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default class CameraVideo extends React.Component {
);

try {
const { programsToRender, markers, keyPoints, dataToRemember, framerate } = detectPrograms({
const { programsToRender, markers, keyPoints, corners, cornerWidth, dataToRemember, framerate } = detectPrograms({
config: this.props.config,
videoCapture: this._videoCapture,
dataToRemember: this._dataToRemember,
Expand All @@ -92,7 +92,7 @@ export default class CameraVideo extends React.Component {
});
this._dataToRemember = dataToRemember;
this.setState({ keyPoints });
this.props.onProcessVideo({ programsToRender, markers, framerate });
this.props.onProcessVideo({ programsToRender, markers, framerate, corners, cornerWidth });
} catch (error) {
console.log(error); // eslint-disable-line no-console
}
Expand Down
75 changes: 60 additions & 15 deletions client/camera/detectPrograms.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,30 @@ import { code8400 } from '../dotCodes';
import { colorNames, cornerNames } from '../constants';
import simpleBlobDetector from './simpleBlobDetector';

function keyPointToAvgColor(keyPoint, videoMat) {
const x = Math.floor(keyPoint.pt.x - keyPoint.size / 2);
const y = Math.floor(keyPoint.pt.y - keyPoint.size / 2);
function keyPointToAvgColor(keyPoint, videoMat, scaleFactor) {
const reduceSizeBy = 4;

// Reduce size to remove brighter pixels at the edge of the circle
const size = keyPoint.size - reduceSizeBy / scaleFactor;
const radius = size / 2;

// The top left corner of the ROI
const x = Math.floor(keyPoint.pt.x - radius);
const y = Math.floor(keyPoint.pt.y - radius);

// Bounding box around the key point
const circleROI = videoMat.roi({
x,
y,
width: keyPoint.size,
height: keyPoint.size,
width: size,
height: size,
});

const circleMask = cv.Mat.zeros(keyPoint.size, keyPoint.size, cv.CV_8UC1);
const circleMask = cv.Mat.zeros(size, size, cv.CV_8UC1);
cv.circle(
circleMask,
{ x: Math.floor(keyPoint.size / 2), y: Math.floor(keyPoint.size / 2) },
keyPoint.size / 2 - 1,
{ x: Math.floor(radius), y: Math.floor(radius) },
radius - 1,
[255, 255, 255, 0],
-1
);
Expand Down Expand Up @@ -135,9 +143,9 @@ function findShape(shapeToFill, neighborIndexes, lengthLeft) {
return false;
}

function colorIndexesForShape(shape, keyPoints, videoMat, colorsRGB) {
function colorIndexesForShape(shape, keyPoints, videoMat, colorsRGB, scaleFactor) {
const shapeColors = shape.map(
keyPointIndex => keyPointToAvgColor(keyPoints[keyPointIndex], videoMat),
keyPointIndex => keyPointToAvgColor(keyPoints[keyPointIndex], videoMat, scaleFactor),
colorsRGB
);

Expand Down Expand Up @@ -173,16 +181,28 @@ export default function detectPrograms({

const knobPointMatrix = forwardProjectionMatrixForPoints(config.knobPoints);
const mapToKnobPointMatrix = point => {
return mult(projectPoint(point, knobPointMatrix), { x: videoMat.cols, y: videoMat.rows })
return mult(projectPoint(point, knobPointMatrix), { x: videoMat.cols, y: videoMat.rows });
};

if (displayMat) {
videoMat.copyTo(displayMat);
const knobPoints = [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 }].map(mapToKnobPointMatrix);
const knobPoints = [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 1 }].map(
mapToKnobPointMatrix
);

for (let i = 0; i < 4; i++) {
cv.line(displayMat, knobPoints[i], knobPoints[(i + 1) % 4], [255, 0, 0, 255]);
}

if (config.showOverlayAlignmentHelper) {
const knobPointHelpers = [{ x: 0.25, y: 0.25 }, { x: 0.75, y: 0.25 }, { x: 0.75, y: 0.75 }, { x: 0.25, y: 0.75 }].map(
mapToKnobPointMatrix
);

for (let i = 0; i < 4; i++) {
cv.line(displayMat, knobPointHelpers[i], knobPointHelpers[(i + 1) % 4], [255, 0, 0, 255]);
}
}
}

const videoROI = knobPointsToROI(config.knobPoints, videoMat);
Expand All @@ -203,7 +223,7 @@ export default function detectPrograms({
keyPoint.pt.y += videoROI.y;

// Give each `keyPoint` an `avgColor` and `colorIndex`.
keyPoint.avgColor = keyPointToAvgColor(keyPoint, videoMat);
keyPoint.avgColor = keyPointToAvgColor(keyPoint, videoMat, scaleFactor);
keyPoint.colorIndex =
keyPoint.colorIndex || colorIndexForColor(keyPoint.avgColor, config.colorsRGB);
});
Expand Down Expand Up @@ -247,6 +267,7 @@ export default function detectPrograms({
const keyPointSizes = [];
const pointsById = {};
const directionVectorsById = {};
const corners = [];
for (let i = 0; i < keyPoints.length; i++) {
if (neighborIndexes[i].length == 1 && !seenIndexes.has(i)) {
const shape = [i]; // Initialise with the first index, then run findShape with 7-1.
Expand All @@ -263,11 +284,17 @@ export default function detectPrograms({
shape.reverse();
}

const colorIndexes = colorIndexesForShape(shape, keyPoints, videoMat, config.colorsRGB);
const colorIndexes = colorIndexesForShape(shape, keyPoints, videoMat, config.colorsRGB, scaleFactor);
const id = shapeToId(colorIndexes);
const cornerNum = shapeToCornerNum(colorIndexes);

if (cornerNum > -1) {
const cornerPoints =
[0, 3, 6]
.map(index => keyPoints[shape[index]].pt)
.map(point => projectPointToUnitSquare(point, videoMat, config.knobPoints));
corners.push(cornerPoints);

// Store the colorIndexes so we can render them later for debugging.
colorIndexes.forEach((colorIndex, shapePointIndex) => {
keyPoints[shape[shapePointIndex]].colorIndex = colorIndex;
Expand All @@ -283,6 +310,8 @@ export default function detectPrograms({

shape.forEach(index => keyPointSizes.push(keyPoints[index].size));

shape.forEach(index => keyPoints[index].matchedShape = true);

if (displayMat && config.showOverlayShapeId) {
// Draw id and corner name.
cv.putText(
Expand All @@ -301,12 +330,26 @@ export default function detectPrograms({
const avgKeyPointSize =
keyPointSizes.reduce((sum, value) => sum + value, 0) / keyPointSizes.length;

const cornerShadowScale = 1;
const cornerWidth = norm(diff(
projectPointToUnitSquare({ x: 0, y: 0}, videoMat, config.knobPoints),
projectPointToUnitSquare({ x: avgKeyPointSize * cornerShadowScale, y: 0}, videoMat, config.knobPoints)
));

// Make every key point that is not a corner point a marker
for (let i = 0; i < keyPoints.length; i++) {
if (!seenIndexes.has(i)) {
markers.push(keyPoints[i])
}
}

allPoints.forEach(keyPoint => {
if (displayMat) {
if (config.showOverlayKeyPointCircles) {
// Draw circles around `keyPoints`.
const color = config.colorsRGB[keyPoint.colorIndex];
cv.circle(displayMat, keyPoint.pt, keyPoint.size / 2 + 3, color, 2);
const isMarker = markers.indexOf(keyPoint) > -1
cv.circle(displayMat, keyPoint.pt, keyPoint.size / 2 + 3, color, isMarker ? 4 : 2);
}

if (config.showOverlayKeyPointText) {
Expand Down Expand Up @@ -485,6 +528,8 @@ export default function detectPrograms({

return {
keyPoints,
corners,
cornerWidth,
programsToRender,
markers,
dataToRemember: { vectorsBetweenCorners },
Expand Down
6 changes: 6 additions & 0 deletions client/camera/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const defaultConfig = {
showOverlayComponentLines: true,
showOverlayShapeId: true,
showOverlayProgram: true,
showOverlayAlignmentHelper: true,
spaceUrl: new URL(`api/spaces/${uuidv4().slice(0, 8)}`, window.location.origin).toString(),
autoPrintEnabled: false,
freezeDetection: false,
Expand Down Expand Up @@ -65,6 +66,11 @@ function render() {
localStorage.paperProgramsMarkers = JSON.stringify(markers);
render();
}}
onCornersChange={(corners, cornerWidth) => {
localStorage.corners = JSON.stringify(corners);
localStorage.cornerWidth = cornerWidth;
render();
}}
onProgramsChange={programs => {
localStorage.paperProgramsProgramsToRender = JSON.stringify(programs);
render();
Expand Down
60 changes: 2 additions & 58 deletions client/camera/helloWorld.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,4 @@
const helloWorld = `// Hello world

importScripts('paper.js');

(async () => {
// Get a canvas object for this paper.
const canvas = await paper.get('canvas');

// Draw "Hello world" on the canvas.
const ctx = canvas.getContext('2d');
ctx.font = '20px sans-serif';
ctx.textAlign = 'center';
ctx.fillStyle = 'rgb(255,0,0)';
ctx.fillText('Hello', canvas.width / 2, canvas.height / 2 - 10);
ctx.fillStyle = 'rgb(0,255,0)';
ctx.fillText('world', canvas.width / 2, canvas.height / 2 + 20);
ctx.commit();

// Get a "supporter canvas", which is a canvas for the entire
// projection surface.
const supporterCanvas = await paper.get('supporterCanvas');
const supporterCtx = supporterCanvas.getContext('2d');

// Get the paper number of this piece of paper (which should not change).
const myPaperNumber = await paper.get('number');

// Repeat every 100 milliseconds.
setInterval(async () => {
// Get a list of all the papers.
const papers = await paper.get('papers');

// Clear out the supporter canvas. We get our own canvas, so we won't
// interfere with other programs by doing this.
supporterCtx.clearRect(0, 0, supporterCanvas.width, supporterCanvas.height);

// Draw a circle in the center of our paper.
const myCenter = papers[myPaperNumber].points.center;
supporterCtx.fillStyle = supporterCtx.strokeStyle = 'rgb(0, 255, 255)';
supporterCtx.beginPath();
supporterCtx.arc(myCenter.x, myCenter.y, 10, 0, 2*Math.PI);
supporterCtx.fill();

// Draw a line from our paper to each other paper.
Object.keys(papers).forEach(otherPaperNumber => {
if (otherPaperNumber !== myPaperNumber) {
const otherCenter = papers[otherPaperNumber].points.center;

supporterCtx.beginPath();
supporterCtx.moveTo(myCenter.x, myCenter.y);
supporterCtx.lineTo(otherCenter.x, otherCenter.y);
supporterCtx.stroke();
}
});

// Finally, commit to the canvas, which actually renders.
supporterCtx.commit();
}, 100);
})();
const helloWorld = `// hello world
Wish \`\${you} is labelled \${'hello world'}\`;
`;
export default helloWorld;
Loading