New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

pam-diff

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pam-diff - npm Package Compare versions

Comparing version 0.2.0 to 0.3.2

232

index.js

@@ -6,2 +6,3 @@ // jshint esversion: 6, globalstrict: true, strict: true

const Transform = require('stream').Transform;
const PP = require('polygon-points');

@@ -13,6 +14,8 @@ function PamDiff(options) {

Transform.call(this, {objectMode: true});
this.setGrayscale(this._parseOptions('grayscale', options));
this.setDifference(this._parseOptions('difference', options));
this.setPercent(this._parseOptions('percent', options));
this._parseChunk = this._parseFirstChunk;
this.setGrayscale(this._parseOptions('grayscale', options));//global option
this.setDifference(this._parseOptions('difference', options));//global option, can be overridden per region
this.setPercent(this._parseOptions('percent', options));//global option, can be overridden per region
this.setRegions(this._parseOptions('regions', options));//can be no regions or a single region or multiple regions. if no regions, all pixels will be compared.
this._parseChunk = this._parseFirstChunk;//first parsing will be reading settings and configuring internal pixel reading
//todo add option for break on first region so that pixel is not measured in multiple overlapping regions
}

@@ -45,9 +48,40 @@

PamDiff.prototype.setDifference = function (value) {
this._difference = this._validateNumber(value, 5, 1, 255);
this._difference = this._validateNumber(parseInt(value), 5, 1, 255);
};
PamDiff.prototype.setPercent = function (value) {
this._percent = this._validateNumber(value, 5, 1, 100);
this._percent = this._validateNumber(parseInt(value), 5, 1, 100);
};
PamDiff.prototype.setRegions = function (regions) {
if (!regions) {
if (this._regions) {
delete this._regions;
delete this._regionsLength;
}
this._diffs = 0;
return;
} else if (!Array.isArray(regions) || regions.length < 1) {
throw new Error(`Regions must be an array of at least 1 region object {name: 'region1', difference: 10, percent: 10, polygon: [[0, 0], [0, 50], [50, 50], [50, 0]]}`);
}
this._regions = [];
for (const region of regions) {
if (!region.hasOwnProperty('name') || !region.hasOwnProperty('polygon')) {
throw new Error('Region must include a name and a polygon property');
}
const polygonPoints = new PP(region.polygon);
this._regions.push(
{
name: region.name,
polygon: polygonPoints,
pointsLength: polygonPoints.pointsLength(),
difference: this._validateNumber(parseInt(region.difference), this._difference, 1, 255),
percent: this._validateNumber(parseInt(region.percent), this._percent, 1, 100),
diffs: 0
}
);
}
this._regionsLength = this._regions.length;
};
PamDiff.prototype._parseOptions = function (option, options) {

@@ -105,16 +139,39 @@ if (options && options.hasOwnProperty(option)) {

this._newPix = chunk.pixels;
let diffPix = [];
for (let i = 0, x = 0, y = 0; i < this._length; i++, x++) {
if (x === this._width) {
x = 0;
y++;
for (let y = 0, i = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++, i++) {
if (this._regions) {
for (let j = 0; j < this._regionsLength; j++) {
if (this._regions[j].polygon.containsPoint([x,y]) === true) {
if (this._oldPix[i] !== this._newPix[i]) {
this._regions[j].diffs++;
}
break;//todo add option for break on first region
}
}
} else {
if (this._oldPix[i] !== this._newPix[i]) {
this._diffs++;
}
}
}
if (this._oldPix[i] !== this._newPix[i]) {
diffPix.push([x, y]);
}
if (this._regions) {
const regionDiffArray = [];
for (let i = 0; i < this._regionsLength; i++) {
const percent = Math.ceil(this._regions[i].diffs / this._regions[i].pointsLength * 100);
if (percent >= this._regions[i].percent) {
regionDiffArray.push({name: this._regions[i].name, percent: percent});
}
this._regions[i].diffs = 0;
}
if (regionDiffArray.length > 0) {
this.emit('diff', {trigger: regionDiffArray, pam: chunk.pam});
}
} else {
const percent = Math.ceil(this._diffs / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam});
}
this._diffs = 0;
}
let percent = Math.ceil(diffPix.length / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {diffPix: diffPix, percent: percent});
}
this._oldPix = this._newPix;

@@ -125,16 +182,39 @@ };

this._newPix = chunk.pixels;
let diffPix = [];
for (let i = 0, x = 0, y = 0; i < this._length; i++, x++) {
if (x === this._width) {
x = 0;
y++;
for (let y = 0, i = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++, i++) {
if (this._regions) {
for (let j = 0; j < this._regionsLength; j++) {
if (this._regions[j].polygon.containsPoint([x,y]) === true) {
if (Math.abs(this._oldPix[i] - this._newPix[i]) >= this._regions[j].difference) {
this._regions[j].diffs++;
}
break;//todo add option for break on first region
}
}
} else {
if (Math.abs(this._oldPix[i] - this._newPix[i]) >= this._difference) {
this._diffs++;
}
}
}
if (Math.abs(this._oldPix[i] - this._newPix[i]) >= this._difference) {
diffPix.push([x, y]);
}
if (this._regions) {
const regionDiffArray = [];
for (let i = 0; i < this._regionsLength; i++) {
const percent = Math.ceil(this._regions[i].diffs / this._regions[i].pointsLength * 100);
if (percent >= this._regions[i].percent) {
regionDiffArray.push({name: this._regions[i].name, percent: percent});
}
this._regions[i].diffs = 0;
}
if (regionDiffArray.length > 0) {
this.emit('diff', {trigger: regionDiffArray, pam: chunk.pam});
}
} else {
const percent = Math.ceil(this._diffs / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam});
}
this._diffs = 0;
}
let percent = Math.ceil(diffPix.length / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {diffPix: diffPix, percent: percent});
}
this._oldPix = this._newPix;

@@ -145,16 +225,39 @@ };

this._newPix = chunk.pixels;
let diffPix = [];
for (let i = 0, x = 0, y = 0; i < this._length; i+=3, x++) {
if (x === this._width) {
x = 0;
y++;
for (let y = 0, i = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++, i += 3) {
if (this._regions) {
for (let j = 0; j < this._regionsLength; j++) {
if (this._regions[j].polygon.containsPoint([x,y]) === true) {
if (Math.abs(this._grayscale(this._oldPix[i], this._oldPix[i + 1], this._oldPix[i + 2]) - this._grayscale(this._newPix[i], this._newPix[i + 1], this._newPix[i + 2])) >= this._regions[j].difference) {
this._regions[j].diffs++;
}
break;//todo add option for break on first region
}
}
} else {
if (Math.abs(this._oldPix[i] - this._newPix[i]) >= this._difference) {
this._diffs++;
}
}
}
if (Math.abs(this._grayscale(this._oldPix[i], this._oldPix[i + 1], this._oldPix[i + 2]) - this._grayscale(this._newPix[i], this._newPix[i + 1], this._newPix[i + 2])) >= this._difference) {
diffPix.push([x, y]);
}
if (this._regions) {
const regionDiffArray = [];
for (let i = 0; i < this._regionsLength; i++) {
const percent = Math.ceil(this._regions[i].diffs / this._regions[i].pointsLength * 100);
if (percent >= this._regions[i].percent) {
regionDiffArray.push({name: this._regions[i].name, percent: percent});
}
this._regions[i].diffs = 0;
}
if (regionDiffArray.length > 0) {
this.emit('diff', {trigger: regionDiffArray, pam: chunk.pam});
}
} else {
const percent = Math.ceil(this._diffs / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam});
}
this._diffs = 0;
}
let percent = Math.ceil(diffPix.length / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {diffPix: diffPix, percent: percent});
}
this._oldPix = this._newPix;

@@ -165,16 +268,39 @@ };

this._newPix = chunk.pixels;
let diffPix = [];
for (let i = 0, x = 0, y = 0; i < this._length; i+=4, x++) {
if (x === this._width) {
x = 0;
y++;
for (let y = 0, i = 0; y < this._height; y++) {
for (let x = 0; x < this._width; x++, i += 4) {
if (this._regions) {
for (let j = 0; j < this._regionsLength; j++) {
if (this._regions[j].polygon.containsPoint([x,y]) === true) {
if (Math.abs(this._grayscale(this._oldPix[i], this._oldPix[i + 1], this._oldPix[i + 2]) - this._grayscale(this._newPix[i], this._newPix[i + 1], this._newPix[i + 2])) >= this._regions[j].difference) {
this._regions[j].diffs++;
}
break;//todo add option for break on first region
}
}
} else {
if (Math.abs(this._oldPix[i] - this._newPix[i]) >= this._difference) {
this._diffs++;
}
}
}
if (Math.abs(this._grayscale(this._oldPix[i], this._oldPix[i + 1], this._oldPix[i + 2]) - this._grayscale(this._newPix[i], this._newPix[i + 1], this._newPix[i + 2])) >= this._difference) {
diffPix.push([x, y]);
}
if (this._regions) {
const regionDiffArray = [];
for (let i = 0; i < this._regionsLength; i++) {
const percent = Math.ceil(this._regions[i].diffs / this._regions[i].pointsLength * 100);
if (percent >= this._regions[i].percent) {
regionDiffArray.push({name: this._regions[i].name, percent: percent});
}
this._regions[i].diffs = 0;
}
if (regionDiffArray.length > 0) {
this.emit('diff', {trigger: regionDiffArray, pam: chunk.pam});
}
} else {
const percent = Math.ceil(this._diffs / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {trigger: [{name: 'percent', percent: percent}], pam: chunk.pam});
}
this._diffs = 0;
}
let percent = Math.ceil(diffPix.length / this._length * 100);
if (percent >= this._percent) {
this.emit('diff', {diffPix: diffPix, percent: percent});
}
this._oldPix = this._newPix;

@@ -185,2 +311,3 @@ };

this._width = parseInt(chunk.width);
this._height = parseInt(chunk.height);
this._oldPix = chunk.pixels;

@@ -197,5 +324,7 @@ this._length = this._oldPix.length;

this._parseChunk = this._rgbPixelDiff;
//this._increment = 3;//future use
break;
case 'rgb_alpha' :
this._parseChunk = this._rgbAlphaPixelDiff;
//this._increment = 4;//future use
break;

@@ -221,2 +350,3 @@ default :

module.exports = PamDiff;
module.exports = PamDiff;
//todo get bounding box of all regions combined to exclude some pixels before checking if they exist inside specific regions
{
"name": "pam-diff",
"version": "0.2.0",
"version": "0.3.2",
"description": "Measure differences between pixel arrays extracted from pam images",

@@ -18,6 +18,9 @@ "main": "index.js",

"pixel",
"difference"
"difference",
"motion",
"detection",
"region"
],
"author": "Kevin Godell <kevin.godell@gmail.com>",
"license": "Apache-2.0",
"license": "MIT",
"bugs": {

@@ -27,6 +30,8 @@ "url": "https://github.com/kevinGodell/pam-diff/issues"

"homepage": "https://github.com/kevinGodell/pam-diff#readme",
"dependencies": {},
"dependencies": {
"polygon-points": "0.0.1"
},
"devDependencies": {
"pipe2pam": "^0.2.1"
"pipe2pam": "^0.3.0"
}
}
# pam-diff
Measure differences between pixel arrays extracted from pam images. Works well with node module [pipe2pam](https://www.npmjs.com/package/pipe2pam) to extract pam images from an ffmpeg pipe. Supported tupltypes are rgb24, rgb_alpha, grayscale, and monob.
Measure differences between pixel arrays extracted from pam images. Works well with node module [pipe2pam](https://www.npmjs.com/package/pipe2pam) to extract pam images from an ffmpeg pipe. Supported tupltypes are rgb24, rgb_alpha, grayscale, and monob. It is currently being used for a video motion detection project.

@@ -15,3 +15,3 @@

The following [example](https://github.com/kevinGodell/pam-diff/tree/master/examples/example.js) uses ffmpeg's testsrc to simulate a video input and generate 1000 downscaled grayscale pam images at a rate of 1 per second. The pam images are piped from ffmpeg's stdout into pipe2pam to parse them into into pam objects. The pam objects are then piped into pam-diff to measure pixel differences. For each compared pixel that has a **difference** that exceeds the setting, it will be added to an array of x y coordinates. If the **percent** of changed pixels exceeds the setting, a **diff** event will be emitted which contains an array of pixel coordinates that have changed.
The following [example](https://github.com/kevinGodell/pam-diff/tree/master/examples/example.js) uses ffmpeg to connect to a rtsp ip camera video feed and generates 1000 downscaled rgb24 pam images at a rate of 1 per second. The pam images are piped from ffmpeg's stdout into pipe2pam to parse them into into pam objects. The pam objects are then piped into pam-diff to measure pixel differences. For each compared pixel that has a **difference** that exceeds the setting, it will be calculated to determine the percent of difference. If the **percent** of changed pixels exceeds the setting, a **diff** event will be emitted which contains a data object containing details. This example also shows how to take the pam image that triggered the diff event and convert it to a jpeg using ffmpeg.

@@ -21,3 +21,5 @@ ```

const PamDiff = require('pam-diff');
const spawn = require('child_process').spawn;
const ChildProcess = require('child_process');
const spawn = ChildProcess.spawn;
const execFile = ChildProcess.execFile;

@@ -27,29 +29,31 @@ const params = [

'quiet',
/* use hardware acceleration */
//'-hwaccel',
//'auto', //vda, videotoolbox, none, auto
'-hwaccel',
'auto', //vda, videotoolbox, none, auto
/* use an artificial video input */
'-re',
'-f',
'lavfi',
'-i',
'testsrc=size=1920x1080:rate=15',
/*'-re',
'-f',
'lavfi',
'-i',
'testsrc=size=1920x1080:rate=15',*/
/* use an rtsp ip cam video input */
/*'-rtsp_transport',
'tcp', //udp, http, tcp
'-rtsp_transport',
'tcp',
'-i',
'rtsp://192.168.1.22:554/user=admin_password=pass_channel=1_stream=0.sdp',*/
'rtsp://192.168.1.4:554/user=admin_password=pass_channel=1_stream=0.sdp',
/* set output flags */
'-an',
'-c:v',
'pam',
'-pix_fmt',
'rgb24',//rgba, rgb24, gray
'-f',
'image2pipe',
'-pix_fmt',
'gray',//rgb24, rgba, monob, gray
'-vf',
'fps=1,scale=iw*1/6:ih*1/6',
'fps=1,scale=320:180',//1920:1080 scaled down: 400:225, 384:216, 368:207, 352:198, 336:189, 320:180
//'fps=1,scale=iw*1/6:ih*1/6',
'-frames',

@@ -77,11 +81,29 @@ '1000',

p2p.on('pam', function(data) {
//you do not have to do anything here if you are just piping this data to pam-diff
console.log('received pam', ++counter);
//you do not have to listen to this event if you are just piping this data to pam-diff
console.log(`received pam ${++counter}`);
});
const pamDiff = new PamDiff({grayscale: 'average', difference: 4, percent: 5});
const pamDiff = new PamDiff({grayscale: 'average', difference: 5, percent: 5});
pamDiff.on('diff', function(data) {
//further analyze the pixels for regions or trigger motion detection from this event
console.log(`${data.diffPix.length} pixels different, ${data.percent}%`);
console.log(data);
//comment out the following line if you want to use ffmpeg to create a jpeg from the pam image that triggered an image difference event
if(true){return;}
const date = new Date();
let name = `${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}_${date.getHours()}-${date.getUTCMinutes()}-${date.getUTCSeconds()}-${date.getUTCMilliseconds()}`;
for (const region of data.trigger) {
name += `(${region.name}=${region.percent})`;
}
const jpeg = `${name}.jpeg`;
const ff = execFile('ffmpeg', ['-report', '-f', 'pam_pipe', '-c:v', 'pam', '-i', 'pipe:0', '-c:v', 'mjpeg', '-pix_fmt', 'yuvj422p', '-q:v', '1', '-huffman', 'optimal', jpeg]);
ff.stdin.end(data.pam);
ff.on('exit', function (data) {
if (data === 0) {
console.log(`FFMPEG clean exit after creating ${jpeg}`);
} else {
throw new Error('FFMPEG is not working with current parameters');
}
});
});

@@ -88,0 +110,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc