Published
Edited
Jun 21, 2020
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
src = {
const cv = window.cv; // BUG!!! This will need to be reevaluated after loadOpenCv returns successfully
var src = cv.imread(image); // load source
console.log('image width: ' + src.cols + '\n' +
'image height: ' + src.rows + '\n' +
'image size: ' + src.size().width + '*' + src.size().height + '\n' +
'image depth: ' + src.depth() + '\n' +
'image channels ' + src.channels() + '\n' +
'image type: ' + src.type() + '\n')
return src;
}
Insert cell
grey = { // convert input to greyscale - seems to be better definition against the background than in HSV
const cv = window.cv;
var grey = src.clone(); // setup greyscale image
cv.cvtColor(src, grey, cv.COLOR_BGR2GRAY, 0);
cv.imshow('greyscale', grey);
return grey;
}
Insert cell
blur = { // blur image
const cv = window.cv;
const ksize = {width : 7, height : 7}; // blurring kernel size
const sigma = [0, 0]; // gaussian kernal standard deviation [x, y]
const borderType = cv.BORDER_DEFAULT; // blur pixel extrapolation method
var blur = src.clone(); // setup blurred image
cv.GaussianBlur(grey, blur, ksize, sigma[0], sigma[1], borderType);
cv.imshow('blur', blur);
return blur;
}
Insert cell
{ // perform edge detection (for display only, incorporated within HoughCircles algorithm already)
const cv = window.cv;
var canny = src.clone(); // setup canny edge detected image - for display only
cv.Canny(blur, canny, param1/2, param1, 3, false);
cv.imshow('edges', canny);
}
Insert cell
Insert cell
circlesMat = { // find circles
const cv = window.cv;
const dp = 1; // resolution of accumulator array set to find perfect circles
const minDist = Math.max(src.rows, src.cols); // distance from one circle to another is the largest dimension of the image - forcing only a single detection
const param2 = 1; // low accumulator threshold for the cv2.HOUGH_GRADIENT method meaning more detection
const pieImageLimits = [0.05, 0.98]; // pie should be between 5% and 98% of the minimum image dimension
const minRadius = pieImageLimits[0]*Math.min(src.rows, src.cols)/2;
const maxRadius = pieImageLimits[1]*Math.min(src.rows, src.cols)/2;
const circles = new cv.Mat(); // setup circles vector
cv.HoughCircles(blur, circles, cv.HOUGH_GRADIENT, dp, minDist, param1, param2, minRadius, maxRadius);
return circles;
}
Insert cell
circle = { // draw circles
let circleOut = []; // store circle details
var detect = src.clone(); // setup detection image - for display only
for (let i = 0; i < circlesMat.cols; ++i) {
let x = circlesMat.data32F[i * 3];
let y = circlesMat.data32F[i * 3 + 1];
let radius = circlesMat.data32F[i * 3 + 2];
circleOut.push({x: x, y: y, radius: radius});
}
return circleOut;
}
Insert cell
totalMask = { // draw circles and build mask
const cv = window.cv;
var totalMask = cv.Mat.zeros(src.rows, src.cols, cv.CV_8U); // setup mask
const pieCentre = 0.2; // remove 20% of radius in the centre
var detect = src.clone(); // setup detection image - for display only
circle.forEach( d => {
cv.circle(detect, {x: d.x, y: d.y}, d.radius, [255, 0, 0, 255], 1, 8, 0); // outline
cv.circle(detect, {x: d.x, y: d.y}, 1, [0, 255, 0, 255], -1, 8, 0); // centre dot
cv.circle(totalMask, {x: d.x, y: d.y}, d.radius, [255, 255, 255, 255], -1, 8, 0); // make mask
cv.circle(totalMask, {x: d.x, y: d.y}, d.radius*pieCentre, [0, 0, 0, 0], -1, 8, 0); // remove centre of mask
})
cv.imshow('output', detect);
return totalMask;
}
Insert cell
{ // crop image - for display only
const cv = window.cv;
var crop = new cv.Mat(src.rows, src.cols, cv.CV_8U); // setup cropped image
src.copyTo(crop, totalMask);
cv.imshow('crop', crop);
return crop;
}
Insert cell
segStep = 3 // degrees
Insert cell
segPxls = { // segment
const cv = window.cv;
var segMask = cv.Mat.zeros(src.rows, src.cols, cv.CV_8U); // mask for increasing segment from start point
var segCrop = new cv.Mat(src.rows, src.cols, cv.CV_8U); // setup segment cropped image
var segCropCombine = new cv.Mat(src.rows, src.cols, cv.CV_8U); // setup segment cropped image
var segPxls = []; // store details for each segment
for (let seg = segStep; seg <= 360; seg = seg + segStep) {
circle.forEach( d => {
cv.ellipse(segMask, {x: d.x, y: d.y}, {width: d.radius, height: d.radius}, 0, 0, seg, [255, 255, 255, 255], -1, 8, 0); // draw segment mask
} )
cv.bitwise_and(totalMask, segMask, segCropCombine); // combine mask with segment mask
cv.subtract(totalMask, segMask, totalMask); // apply mask to segment mask
segCrop = cv.Mat.zeros(src.rows, src.cols, cv.CV_8U); // clear segment cropped image
src.copyTo(segCrop, segCropCombine); // apply segment mask to source

// get segment pixels
var segPix = [];
for (let i = 0; i < src.data.length/4; i++) {
if (segCropCombine.data[i]>0) segPix.push([
src.data[i*4],
src.data[i*4 + 1],
src.data[i*4 + 2],
]);
}

segPxls.push(segPix); // store segment pixels
}
cv.imshow('segment', segCrop);
return segPxls;
}
Insert cell
segValue = {
const cv = window.cv;
var segValue = [];
segPxls.forEach( segment => {
let means = kmeans(segment, 5, null, 8, 10);
segValue.push(means.centroids.reduce((prev, current) => (prev.points.length > current.points.length) ? prev : current)); // keep centroid with the most pixels
})
return segValue;
}
Insert cell
pie = {
const cv = window.cv;
// find values by looking for discontinuities
const colDifThresh = 7;
let values = [];
let thisVal = {startAng: 0, endAng: segStep, colour: d3.rgb(segValue[0][0],segValue[0][1],segValue[0][2]), segColours: [d3.rgb(segValue[0][0],segValue[0][1],segValue[0][2])]};
for (let i = 1; i < segValue.length; i++) {
if (LABdistance(segValue[i-1],segValue[i]) > colDifThresh) { // segment is a new value
values.push(thisVal); // record last value
thisVal = {startAng: segStep*i, endAng: segStep*(i+1), colour: d3.rgb(segValue[i][0],segValue[i][1],segValue[i][2]), segColours: [d3.rgb(segValue[i][0],segValue[i][1],segValue[i][2])]}; // start new value
} else { // segment continues old value
thisVal.endAng = thisVal.endAng + segStep; // increment value end point
thisVal.segColours.push(d3.rgb(segValue[i][0],segValue[i][1],segValue[i][2])); // add in the colour of this segment
}
}
// wrap around from end to start?
if (LABdistance(segValue[0],segValue[segValue.length-1]) > colDifThresh) { // start is different from end
values.push(thisVal); // record last value
} else { // start continues from end, so merge
values[0].startAng = thisVal.startAng;
values[0].segColours = thisVal.segColours.concat(values[0].segColours);
}

// calculate percentages
values.map( d => {d.fraction = d.segColours.length*segStep/360; return d;});
return values;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// import {kmeans, d3} with {LABdistance as distance} from '@romaklimenko/finding-palettes-of-dominant-colors-with-the-k-means-cluste'
Insert cell
// import {distance} from '@severo/color-distance'
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more