diff --git a/.gitignore b/.gitignore index b5fabc7..61eb52e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build/ .vscode/ *.user *code-workspace +library.json diff --git a/CMakeLists.txt b/CMakeLists.txt index d30a4bf..1973592 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/puara/descriptors/touch.h b/include/puara/descriptors/touch.h index 912fabe..61550bb 100644 --- a/include/puara/descriptors/touch.h +++ b/include/puara/descriptors/touch.h @@ -1,9 +1,11 @@ #pragma once #include +#include #include #include +#include namespace puara_gestures { @@ -11,33 +13,29 @@ 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)); } } - 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++; - } - } - } - } }; } diff --git a/include/puara/utils/blobDetector.h b/include/puara/utils/blobDetector.h new file mode 100644 index 0000000..3cae64f --- /dev/null +++ b/include/puara/utils/blobDetector.h @@ -0,0 +1,138 @@ +#pragma once + +#include + +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 + 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 movement(maxNumBlobs, 0); + for(int i = 0; i < maxNumBlobs; ++i) + movement[i] = blobStartPos[i] - lastState_blobPos[i]; + + return movement; + } +}; +} diff --git a/tests/testing_touch.cpp b/tests/testing_touch.cpp index fb78453..32b5c41 100644 --- a/tests/testing_touch.cpp +++ b/tests/testing_touch.cpp @@ -1,26 +1,64 @@ #include -#include - 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; }