-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor blob detection outside of the Touch class #20
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
9d31909
apparently wrap needs a non-default constructor
vberthiaume 77ab3e4
starting the refactor
vberthiaume 2d13db4
Substantial refactor.
vberthiaume f337d01
rename the file, do more exhaustive tests, all seem good
vberthiaume 7cdc6c8
fix missing initialization bug. Add documentation
vberthiaume 3def1e1
cleanup and add moved touch test
vberthiaume 73c095f
rename blobDetector
vberthiaume d0d6a78
remove library.json for now
vberthiaume 4084901
prComments
vberthiaume 428dac6
prComments
vberthiaume 12a02fa
remove extra file
vberthiaume 695be19
add library.json to .gitignore
vberthiaume d0677fe
remove extraneous wrap constructor
vberthiaume File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ build/ | |
.vscode/ | ||
*.user | ||
*code-workspace | ||
library.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
@@ -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) | ||
{ | ||
|
@@ -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)); | ||
Comment on lines
+97
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we can promote brush and rub as specific classes (separate from the less interesting gestures from this class) |
||
} | ||
} | ||
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); | ||
|
@@ -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) | ||
|
@@ -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++; | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
vberthiaume marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
jcelerier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may leave this as is for now, but my suggestion is to have the blobdetector as a separate class and the touch class only dealing with extracting the features.
In that case, there's no need for multi versions of the gestures as the class itself will deal with 1 DoF at a time. The user can implement multi versions (or we can create wrappers for that) from the main touch class.