void BBFind::squash(Map2 &map, float scaleMin, float scaleMax) { // Overloaded to work on Map2 or Map3. Normalizes, but sets // initial min / max values to given arguments. If the given // range is larger than the data's range, the data is scaled // down proportionally. Otherwise, it just normalizes to the // given range. int mapWidth = (int)map[0].size(); int mapHeight = (int)map.size(); float maxVal = scaleMax; float minVal = scaleMin; for(int x = 0; x < mapWidth; x++) { for(int y = 0; y < mapHeight; y++) { float val = map[y][x]; maxVal = val > maxVal ? val : maxVal; minVal = val < minVal ? val : minVal; } } float range = maxVal - minVal; #ifdef PV_USE_OPENMP_THREADS #pragma omp parallel for #endif for(int x = 0; x < mapWidth; x++) { for(int y = 0; y < mapHeight; y++) { map[y][x] = scaleMin + ((map[y][x] - minVal) / range) * scaleMax; } } }
float BBFind::sigmoidedRMS(const Map2 confMap, const Rectangle &bounds) { // Takes the RMS of the given sub area and applies a sigmoid to // smoothly cap values at 1.0 int mapWidth = confMap[0].size(); int mapHeight = confMap.size(); float sum = 0.0f; for(int x = 0; x < bounds.width; x++) { for(int y = 0; y < bounds.height; y++) { int _x = bounds.left() + x; int _y = bounds.top() + y; if(_x < 0 || _x >= mapWidth || _y < 0 || _y >= mapHeight) continue; sum += pow(confMap[_y][_x], 2); } } float avg = sqrt(sum / (mapWidth*mapHeight)); float e = exp(1); // This is a very easy curve that still soft caps at 1.0. // Combined with the increaseContrast function, it allows // values > 0.85 to bring the average up based on the contrast argument. return (1.0f / (1.0f + pow(e,-e*avg)) - 0.5f) * 2.0f; }
BBFind::Map2 BBFind::scale(const Map2 &source, int newWidth, int newHeight, bool bilinear) { // Rescales the map's dimensions using either // bilinear interpolation or nearest neighbor. Map2 result(newHeight); int sourceWidth = source[0].size(); int sourceHeight = source.size(); if(bilinear) // Bilinear scaling { for(int j = 0; j < newHeight; j++) { result[j].resize(newWidth); for(int i = 0; i < newWidth; i++) { float xSource = i / (float)(newWidth-1) * (sourceWidth-1); float ySource = j / (float)(newHeight-1) * (sourceHeight-1); int leftIndex = (int)xSource; int rightIndex = (int)ceil(xSource); int topIndex = (int)ySource; int bottomIndex = (int)ceil(ySource); if(topIndex < 0) topIndex = 0; if(leftIndex < 0) leftIndex = 0; if(rightIndex >= sourceWidth) rightIndex = sourceWidth-1; if(bottomIndex >= sourceHeight) bottomIndex = sourceHeight-1; float xAlign = xSource - leftIndex; float yAlign = ySource - topIndex; float tl = source[topIndex][leftIndex] * (1.0f - xAlign) * (1.0f - yAlign); float tr = source[topIndex][rightIndex] * xAlign * (1.0f - yAlign); float bl = source[bottomIndex][leftIndex] * (1.0f - xAlign) * yAlign; float br = source[bottomIndex][rightIndex] * xAlign * yAlign; result[j][i] = tl+tr+bl+br; } } } else // Nearest neighbor scaling, no interpolation { float xRatio = sourceWidth / (float)(newWidth); float yRatio = sourceHeight / (float)(newHeight); #ifdef PV_USE_OPENMP_THREADS #pragma omp parallel for #endif for(int j = 0; j < newHeight; j++) { result[j].resize(newWidth); for(int i = 0; i < newWidth; i++) { result[j][i] = source[(int)(j*yRatio)][(int)(i*xRatio)]; } } } return result; }
BBFind::Map2 BBFind::applyThreshold(const Map2 confMap, float threshold) { // Clips any values below threshold // TODO: Can this just modify the map in-place? int mapWidth = confMap[0].size(); int mapHeight = confMap.size(); Map2 resultMap = confMap; #ifdef PV_USE_OPENMP_THREADS #pragma omp parallel for #endif for(int x = 0; x < mapWidth; x++) { for(int y = 0; y < mapHeight; y++) { resultMap[y][x] = (confMap[y][x] >= threshold ? confMap[y][x] : 0.0f); } } return resultMap; }
BBFind::Map2 BBFind::makeEdgeDistanceMap(const Map2 confMap) { // Modified Dijkstra map algorithm based on: // http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps // The original algorithm finds nearest distance to a goal. // This modified version finds the larger distance, horizontal or vertical, // to a 0 confidence value (used to find object edges after threshold clipping) int mapWidth = confMap[0].size(); int mapHeight = confMap.size(); int maxVal = std::max(mapWidth, mapHeight); Map2 horizMap(mapHeight); Map2 vertMap(mapHeight); for(int y = 0; y < mapHeight; y++) { horizMap[y].resize(mapWidth); vertMap[y].resize(mapWidth); for(int x = 0; x < mapWidth; x++) { if(confMap[y][x] > 0) { horizMap[y][x] = maxVal; vertMap[y][x] = maxVal; } } } // Finds the shortest distance to 0 conf value in horizontal and vertical direction, // and stores the biggest one into result. This allows long, thin confidence chunks // to avoid clipping bool changed = true; while(changed) { changed = false; for(int y = 1; y < mapHeight-1; y++) { for(int x = 1; x < mapWidth-1; x++) { float lowest = horizMap[y][x]; lowest = std::min(lowest, horizMap[y][x-1]); lowest = std::min(lowest, horizMap[y][x+1]); if(horizMap[y][x] > lowest+1) { horizMap[y][x] = lowest + 1; changed = true; } } } } changed = true; while(changed) { changed = false; for(int y = 1; y < mapHeight-1; y++) { for(int x = 1; x < mapWidth-1; x++) { float lowest = vertMap[y][x]; lowest = std::min(lowest, vertMap[y-1][x]); lowest = std::min(lowest, vertMap[y+1][x]); if(vertMap[y][x] > lowest+1) { vertMap[y][x] = lowest + 1; changed = true; } } } } Map2 resultMap = horizMap; #ifdef PV_USE_OPENMP_THREADS #pragma omp parallel for #endif for(int y = 0; y < mapHeight; y++) { for(int x = 0; x < mapWidth; x++) { if(vertMap[y][x] > resultMap[y][x]) { resultMap[y][x] = vertMap[y][x]; } } } return resultMap; }
void intersectValues(Map const& map, Map2 const& map2, DotProductResult& dotResult) { if (map.size() < map2.size()) intersectValues(map.begin(), map.end(), map2, dotResult); else intersectValues(map, map2.begin(), map2.end(), dotResult); }