Skip to content

Commit

Permalink
Merge pull request #20 from Puara/refactor/18-blobDetection1D
Browse files Browse the repository at this point in the history
Refactor blob detection outside of the Touch class
  • Loading branch information
edumeneses authored Dec 19, 2024
2 parents 33441a5 + d0677fe commit 5c0cdbf
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 81 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ build/
.vscode/
*.user
*code-workspace
library.json
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ add_library(puara_gestures
include/puara/descriptors/tilt.h
include/puara/descriptors/touch.h

include/puara/utils/blobDetector.h
include/puara/utils/calibration.h
include/puara/utils/circularbuffer.h
include/puara/utils/leakyintegrator.h
Expand Down
95 changes: 23 additions & 72 deletions include/puara/descriptors/touch.h
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
#pragma once

#include <puara/utils.h>
#include <puara/utils/blobDetector.h>
#include <puara/utils/leakyintegrator.h>

#include <cmath>
#include<iostream>

namespace puara_gestures
{

class Touch
{
public:
static constexpr int maxNumBlobs = BlobDetector::maxNumBlobs;
float touchAll = 0.0f; // f, 0--1
float touchTop = 0.0f; // f, 0--1
float touchMiddle = 0.0f; // f, 0--1
float touchBottom = 0.0f; // f, 0--1
float brush = 0.0f; // f, 0--? (~cm/s)
double multiBrush[4]{}; // ffff, 0--? (~cm/s)
double multiBrush[maxNumBlobs]{}; // ffff, 0--? (~cm/s)
float rub{}; // f, 0--? (~cm/s)
double multiRub[4]{}; // ffff, 0--? (~cm/s)
double multiRub[maxNumBlobs]{}; // ffff, 0--? (~cm/s)

// touch array
int touchSizeEdge
= 4; // amount of touch stripes for top and bottom portions (arbitrary)
int lastState_blobPos[4]{};
int maxBlobs = 4; // max amount of blobs to be detected
int blobAmount{}; // amount of detected blobs
int blobCenter[4]{}; // shows the "center" (index) of each blob (former blobArray)
int blobPos[4]{}; // starting position (index) of each blob
float blobSize[4]{}; // "size" (amount of stripes) of each blob
int brushCounter[4]{};
int touchSizeEdge = 4; // amount of touch stripes for top and bottom portions (arbitrary)

BlobDetector blobDetector;
int brushCounter[maxNumBlobs]{};

// Arrays of LeakyIntegrator instances
utils::LeakyIntegrator multiBrushIntegrator[4];
utils::LeakyIntegrator multiRubIntegrator[4];
utils::LeakyIntegrator multiBrushIntegrator[maxNumBlobs];
utils::LeakyIntegrator multiRubIntegrator[maxNumBlobs];

Touch()
{
for(int i = 0; i < 4; ++i)
for(int i = 0; i < maxNumBlobs; ++i)
{
multiBrushIntegrator[i] = utils::LeakyIntegrator(0.0f, 0.0f, 0.7f, 100, 0);
multiRubIntegrator[i] = utils::LeakyIntegrator(0.0f, 0.0f, 0.7f, 100, 0);
Expand Down Expand Up @@ -67,28 +65,15 @@ class Touch
// normalized between 0 and 1
touchBottom = touchAverage(discrete_touch, (touchSize - touchSizeEdge), touchSize);

// Save last blob detection state before reading new data
for(int i = 0; i < (sizeof(blobPos) / sizeof(blobPos[0])); ++i)
{
lastState_blobPos[i] = blobPos[i];
}

// 1D blob detection: used for brush
blobDetection1D(discrete_touch, touchSize);
// 1D blob detection: used for brush
const auto movement = blobDetector.detect1D(discrete_touch, touchSize);

// brush: direction and intensity of capsense brush motion
// rub: intensity of rub motion
// in ~cm/s (distance between stripes = ~1.5cm)
for(int i = 0; i < (sizeof(blobPos) / sizeof(blobPos[0])); ++i)
for(int i = 0; i < movement.size(); ++i)
{
float movement = blobPos[i] - lastState_blobPos[i];
if(blobPos[i] == -1)
{
multiBrush[i] = 0;
multiRub[i] = 0;
brushCounter[i] = 0;
}
else if(movement == 0)
if(movement[i] == 0)
{
if(brushCounter[i] < 10)
{
Expand All @@ -109,11 +94,11 @@ class Touch
// (std::abs(movement * 0.15)), multiRub[i], 0.7, leakyRubFreq,
// leakyRubTimer);
//
multiBrush[i] = multiBrushIntegrator[i].integrate(movement * 0.15);
multiRub[i] = multiRubIntegrator[i].integrate(std::abs(movement * 0.15));
multiBrush[i] = multiBrushIntegrator[i].integrate(movement[i] * 0.15);
multiRub[i] = multiRubIntegrator[i].integrate(std::abs(movement[i] * 0.15));
}
}
else if(std::abs(movement) > 1)
else if(std::abs(movement[i]) > 1)
{
// multiBrush[i] = multiBrushIntegrator[i].integrate(
// 0, multiBrush[i], 0.6, leakyBrushFreq, leakyBrushTimer);
Expand All @@ -128,14 +113,14 @@ class Touch
// (std::abs(movement * 0.15)) * 0.15, multiRub[i], 0.99, leakyRubFreq,
// leakyRubTimer);

multiBrush[i] = multiBrushIntegrator[i].integrate(movement * 0.15);
multiRub[i] = multiRubIntegrator[i].integrate((std::abs(movement * 0.15)));
multiBrush[i] = multiBrushIntegrator[i].integrate(movement[i] * 0.15);
multiRub[i] = multiRubIntegrator[i].integrate((std::abs(movement[i] * 0.15)));

brushCounter[i] = 0;
}
}
brush = utils::arrayAverageZero(multiBrush, 4);
rub = utils::arrayAverageZero(multiRub, 4);
brush = utils::arrayAverageZero(multiBrush, maxNumBlobs);
rub = utils::arrayAverageZero(multiRub, maxNumBlobs);
}

float touchAverage(float* touchArrayStrips, int firstStrip, int lastStrip)
Expand All @@ -155,39 +140,5 @@ class Touch

return ((float)sum) / (lastStrip - firstStrip);
}

//TODO: move to utils
void blobDetection1D(int* discrete_touch, int touchSize)
{
blobAmount = 0;
int sizeCounter = 0;
int stripe = 0;
for(int i = 0; i < 4; i++)
{
blobCenter[i] = 0;
blobPos[i] = 0;
blobSize[i] = 0;
}

for(; stripe < touchSize; stripe++)
{
if(blobAmount < maxBlobs)
{
if(discrete_touch[stripe] == 1)
{ // check for beggining of blob...
sizeCounter = 1;
blobPos[blobAmount] = stripe;
while(discrete_touch[stripe + sizeCounter] == 1)
{ // then keep checking for end
sizeCounter++;
}
blobSize[blobAmount] = sizeCounter;
blobCenter[blobAmount] = stripe + (sizeCounter / 2);
stripe += sizeCounter + 1; // skip stripes already read
blobAmount++;
}
}
}
}
};
}
138 changes: 138 additions & 0 deletions include/puara/utils/blobDetector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#pragma once

#include <boost/container/small_vector.hpp>

namespace puara_gestures
{

/**
* @struct BlobDetector
* @brief A structure for detecting contiguous regions (blobs) of `1`s in binary arrays.
*
* The `BlobDetector` identifies contiguous blobs in a binary input array where elements are either
* `0` or `1`.
* For each blob, it computes:
* - The start position (`blobStartPos`)
* - The size in terms of consecutive `1`s (`blobSize`)
* - The center index (`blobCenter`)
* - Movement compared to the previous detection (`lastState_blobPos`)
* @warning Most of these values are reset when a detection function, e.g., detect1D(), is called,
* and calculated during the detection funtion calls -- so their value is only really valid right
* after such a call. There is no locking mechanism on any of these values so it is on the client
* to ensure there is no race conditions.
*
* @details
* The struct maintains internal arrays to store information about detected blobs, including their
* start positions, sizes, and centers. It also tracks blob positions from the previous invocation
* of the detection function to compute movement values.
*
* ## Key Functionalities:
* - **Blob Detection:** Detects contiguous regions of `1`s in a binary input array.
* - **Movement Calculation:** Computes the positional changes (movement) of blobs compared to
* their last positions.
* - **Data Management:** Maintains internal state between function calls to facilitate movement
* tracking.
*
* @note
* - The number of blobs processed is limited by `maxNumBlobs` (default is `4`).
* - If the input contains more blobs than `maxNumBlobs`, the additional blobs are ignored.
*/
class BlobDetector
{
public:
/** The maximum number of blobs that the algorithm should detect. */
static constexpr int maxNumBlobs = 4;

/** The start index of detected blobs. */
int blobStartPos[maxNumBlobs]{};

/** The cached start index of detected blobs, from the previous time detect1D
* was called.
*/
int lastState_blobPos[maxNumBlobs]{};

/** size (amount of stripes) of each blob */
int blobSize[maxNumBlobs]{};

/** shows the "center"(index)of each blob */
float blobCenter[maxNumBlobs]{};

/** amount of detected blobs */
int blobAmount{};

/**
* @brief Detects contiguous regions (blobs) of `1`s in a 1D binary array and computes their movement.
*
* This function identifies blobs in the input binary array `touchArray`, calculates their start
* positions, sizes, and centers, and returns the movement of the blobs compared to their positions
* from the previous function call.
*
* @param touchArray Pointer to the 1D binary array representing touch data. Each element is expected to be 0 or 1.
* @param touchArraySize The size of the `touchArray`, representing the number of touch sensor in the array.
* This is expected to be larger than maxNumBlobs, which by definition will be a portion of the number
* of sensors, or at most equal to the number of sensors.
* @return A small_vector of integers representing the movement of each blob's start position since the
* last invocation of `detect1D`.
*
* @note
* - The function updates the global variables `blobStartPos`, `blobSize`, `blobCenter`,
* and `lastState_blobPos`.
* - The number of blobs detected is limited by `maxNumBlobs`.
* - If the number of blobs exceeds `maxNumBlobs`, additional blobs are ignored.
*
* @warning
* - Ensure that `touchArray` has at least `size` elements to avoid out-of-bounds access.
* - The function relies on external global variables (`blobStartPos`, `blobSize`, `blobCenter`,
* `lastState_blobPos`, `maxNumBlobs`, `blobAmount`). Ensure they are initialized appropriately
* before calling the function.
*/
boost::container::small_vector<int, maxNumBlobs>
detect1D(const int* const touchArray, const int touchArraySize)
{
blobAmount = 0;
for(int i = 0; i < maxNumBlobs; i++)
{
//cache the last blobStartPos before clearing it
lastState_blobPos[i] = blobStartPos[i];
blobStartPos[i] = 0;
blobSize[i] = 0;
blobCenter[i] = 0;
}

for(int stripe = 0; stripe < touchArraySize;)
{
if(touchArray[stripe] == 1)
{
//start the blob
blobStartPos[blobAmount] = stripe;

//continue the blob until we no longer have 1s
int sizeCounter = 1;
while((stripe + sizeCounter) <= touchArraySize
&& touchArray[stripe + sizeCounter] == 1)
{
sizeCounter++;
}

blobSize[blobAmount] = sizeCounter;
blobCenter[blobAmount] = stripe + (sizeCounter - 1.0) / 2.0;
stripe += sizeCounter;

if(++blobAmount >= maxNumBlobs)
break;
}
else
{
++stripe;
}
}

//return the movement since the last time detect1D was called
boost::container::small_vector<int, maxNumBlobs> movement(maxNumBlobs, 0);
for(int i = 0; i < maxNumBlobs; ++i)
movement[i] = blobStartPos[i] - lastState_blobPos[i];

return movement;
}
};
}
56 changes: 47 additions & 9 deletions tests/testing_touch.cpp
Original file line number Diff line number Diff line change
@@ -1,26 +1,64 @@
#include <puara/gestures.h>

#include <iostream>

using namespace puara_gestures;

int main()
{
Touch touch;

int touchSize = 16;
int discrete_touch[16] = {0};
constexpr int touchSize = 16;
int discrete_touch[touchSize] = {0};

// simulate a blob of size 1 starting at position 0
discrete_touch[0] = 1;

// simulate a touch (1) at a position 5
// simulate a blob of size 2 starting at position 5
discrete_touch[5] = 1;
discrete_touch[6] = 1;

// Update the touch data
touch.updateTouchArray(discrete_touch, touchSize);
// simulate a blob of size 3 starting at position 8
discrete_touch[8] = 1;
discrete_touch[9] = 1;
discrete_touch[10] = 1;

// simulate a blob of size 1 at position 12 -- commented out to test the end of the array in the next blob
//discrete_touch[12] = 1;

// Output the computed values
// simulate a blob of size 2 starting at position 14
discrete_touch[14] = 1;
discrete_touch[15] = 1;

// Update the touch data and print the computed values
touch.updateTouchArray(discrete_touch, touchSize);
std::cout << "touchAll: " << touch.touchAll << std::endl;
std::cout << "brush: " << touch.brush << std::endl;
std::cout << "rub: " << touch.rub << std::endl;

// multi touch
//reset the touch array, and next we'll repeat the above blobs but offset by 1 to simulate movement
for(int i = 0; i < touchSize; ++i)
discrete_touch[i] = 0;

// simulate a blob of size 1 starting at position 1
discrete_touch[1] = 1;

// simulate a blob of size 2 starting at position 6
discrete_touch[6] = 1;
discrete_touch[7] = 1;

// simulate a blob of size 3 starting at position 9
discrete_touch[9] = 1;
discrete_touch[10] = 1;
discrete_touch[11] = 1;

// simulate a blob of size 1 at position 13 -- commented out to test the end of the array in the next blob
//discrete_touch[13] = 1;

// simulate a blob of size 1 starting at position 15
discrete_touch[15] = 1;

// Update the touch data and print the computed values
touch.updateTouchArray(discrete_touch, touchSize);
std::cout << "touchAll: " << touch.touchAll << std::endl;
std::cout << "brush: " << touch.brush << std::endl;
std::cout << "rub: " << touch.rub << std::endl;
}

0 comments on commit 5c0cdbf

Please sign in to comment.