diff --git a/docs/MODULES.md b/docs/MODULES.md index 6ca06983..b4baeecd 100644 --- a/docs/MODULES.md +++ b/docs/MODULES.md @@ -9,6 +9,7 @@ List of Module Documentations 4. [Add QR](#Add-QR-module) 5. [Average](#average-module) 6. [Blend](#blend-module) +7. [Blob-Analysis](#blob-analysis) 7. [Blur](#blur-module) 8. [Brightness](#brightness-module) 9. [Channel](#channel-module) @@ -155,6 +156,20 @@ where `options` is an object with the following properties: * offset: step of image with which current image is to be blended(Two steps back is -2, three steps back is -3 etc; default -2) * func: function used to blend two images (default : function(r1, g1, b1, a1, r2, g2, b2, a2) { return [ r1, g2, b2, a2 ] }) +## Blob Analysis + +This module uses Opencv.js for detecting and marking blob/region in microscopic images. It requires an opencv.js file to +be loaded before using the functionalities which is currently being loaded to the webpage via script.It supports both environments, Node.js and browser for processing. + +As the size of opencv.js file is quite large, the future versions will focus on loading it asynchronously, on demand of the the module to optimise performance. + +#### Usage +```js + sequencer.loadImage('PATH') + .addSteps('blob-analysis') + .run() +``` + ## blur-module This module is used for applying a Gaussian blur effect. diff --git a/examples/index.html b/examples/index.html index 34c317c8..5be0cb02 100644 --- a/examples/index.html +++ b/examples/index.html @@ -232,6 +232,7 @@ var sequencer; }) + diff --git a/package.json b/package.json index a4c22ca8..8847ae23 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "jsqr": "^1.1.1", "lodash": "^4.17.11", "ndarray": "^1.0.18", + "opencv.js": "^1.2.1", "ora": "^3.0.0", "pace": "0.0.4", "puppeteer": "^1.14.0", diff --git a/src/Modules.js b/src/Modules.js index a3f9cffe..68bd2a16 100644 --- a/src/Modules.js +++ b/src/Modules.js @@ -5,6 +5,7 @@ module.exports = { 'add-qr': require('./modules/AddQR'), 'average': require('./modules/Average'), 'blend': require('./modules/Blend'), + 'blob-analysis': require('./modules/BlobAnalysis'), 'blur': require('./modules/Blur'), 'brightness': require('./modules/Brightness'), 'canvas-resize': require('./modules/CanvasResize'), diff --git a/src/modules/BlobAnalysis/BlobAnalysis.js b/src/modules/BlobAnalysis/BlobAnalysis.js new file mode 100644 index 00000000..aa633301 --- /dev/null +++ b/src/modules/BlobAnalysis/BlobAnalysis.js @@ -0,0 +1,81 @@ +module.exports = function(pixels, options, priorStep){ + + var $ = require('jquery'); // to make Blob-analysis work for node.js + + var img = $(priorStep.imgElement); + if(Object.keys(img).length === 0){ + img = $(priorStep.options.step.imgElement); + } + + var canvas = document.createElement('canvas'); + canvas.width = pixels.shape[0]; + canvas.height = pixels.shape[1]; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img[0], 0, 0); + let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + + let src = cv.matFromImageData(imgData); + let dst = new cv.Mat(); + let gray = new cv.Mat(); + let opening = new cv.Mat(); + let imageBg = new cv.Mat(); + let imageFg = new cv.Mat(); + let distTrans = new cv.Mat(); + let unknown = new cv.Mat(); + let markers = new cv.Mat(); + + // gray and threshold image + cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); + cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU); + + // get background + let M = cv.Mat.ones(3, 3, cv.CV_8U); + cv.erode(gray, gray, M); + cv.dilate(gray, opening, M); + cv.dilate(opening, imageBg, M, new cv.Point(-1, -1), 3); + + // distance transform + cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5); + cv.normalize(distTrans, distTrans, 1, 0, cv.NORM_INF); + + // get foreground + cv.threshold(distTrans, imageFg, 0.7 * 1, 255, cv.THRESH_BINARY); + imageFg.convertTo(imageFg, cv.CV_8U, 1, 0); + cv.subtract(imageBg, imageFg, unknown); + + // get connected components markers + cv.connectedComponents(imageFg, markers); + for (let i = 0; i < markers.rows; i++) { + for (let j = 0; j < markers.cols; j++) { + markers.intPtr(i, j)[0] = markers.ucharPtr(i, j)[0] + 1; + if (unknown.ucharPtr(i, j)[0] == 255) { + markers.intPtr(i, j)[0] = 0; + } + } + } + + cv.cvtColor(src, src, cv.COLOR_RGBA2RGB, 0); + cv.watershed(src, markers); + + // draw barriers + for (let i = 0; i < markers.rows; i++) { + for (let j = 0; j < markers.cols; j++) { + if (markers.intPtr(i, j)[0] == -1) { + src.ucharPtr(i, j)[0] = 255; // R + src.ucharPtr(i, j)[1] = 0; // G + src.ucharPtr(i, j)[2] = 0; // B + } + } + } + + cv.imshow(canvas, src); + + src.delete(); dst.delete(); gray.delete(); opening.delete(); imageBg.delete(); + imageFg.delete(); distTrans.delete(); unknown.delete(); markers.delete(); M.delete(); + + var myImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + pixels.data = myImageData.data; + + return pixels; +}; \ No newline at end of file diff --git a/src/modules/BlobAnalysis/Module.js b/src/modules/BlobAnalysis/Module.js new file mode 100644 index 00000000..788b5ec1 --- /dev/null +++ b/src/modules/BlobAnalysis/Module.js @@ -0,0 +1,43 @@ + +module.exports = function BlobAnalysis(options, UI){ + + var output; + + function draw(input, callback, progressObj) { + + progressObj.stop(true); + progressObj.overrideFlag = true; + + var step = this; + + var priorStep = this.getStep(-1); // get the previous step to process it + + function extraManipulation(pixels){ + + pixels = require('./BlobAnalysis')(pixels, options, priorStep); + return pixels; + } + + function output(image, datauri, mimetype){ + + step.output = { src: datauri, format: mimetype}; + } + + return require('../_nomodule/PixelManipulation.js')(input, { + output: output, + extraManipulation: extraManipulation, + format: input.format, + image: options.image, + inBrowser: options.inBrowser, + callback: callback + }); + + } + + return { + options: options, + draw: draw, + output: output, + UI: UI + }; +}; \ No newline at end of file diff --git a/src/modules/BlobAnalysis/index.js b/src/modules/BlobAnalysis/index.js new file mode 100644 index 00000000..71549002 --- /dev/null +++ b/src/modules/BlobAnalysis/index.js @@ -0,0 +1,4 @@ +module.exports = [ + require('./Module'), + require('./info.json') +]; \ No newline at end of file diff --git a/src/modules/BlobAnalysis/info.json b/src/modules/BlobAnalysis/info.json new file mode 100644 index 00000000..d0b1dae3 --- /dev/null +++ b/src/modules/BlobAnalysis/info.json @@ -0,0 +1,6 @@ +{ + "name": "Blob Analysis", + "description": "Blob/Region identification for microscopic images.", + "inputs": {}, + "docs-link":"https://github.com/publiclab/image-sequencer/blob/main/docs/MODULES.md#blob-analysis" +}