// Rotate the grid by rotation, keeping cell contents. // rotation must be a multiple of 90 degrees. // NOTE: due to partial cells, cell coverage in the rotated grid will be // inexact. This is why there is no Rotate for the generic BBGrid. // TODO(rays) investigate fixing this inaccuracy by moving the origin after // rotation. void IntGrid::Rotate(const FCOORD& rotation) { ASSERT_HOST(rotation.x() == 0.0f || rotation.y() == 0.0f); ICOORD old_bleft(bleft()); ICOORD old_tright(tright()); int old_width = gridwidth(); int old_height = gridheight(); TBOX box(bleft(), tright()); box.rotate(rotation); int* old_grid = grid_; grid_ = NULL; Init(gridsize(), box.botleft(), box.topright()); // Iterate over the old grid, copying data to the rotated position in the new. int oldi = 0; FCOORD x_step(rotation); x_step *= gridsize(); for (int oldy = 0; oldy < old_height; ++oldy) { FCOORD line_pos(old_bleft.x(), old_bleft.y() + gridsize() * oldy); line_pos.rotate(rotation); for (int oldx = 0; oldx < old_width; ++oldx, line_pos += x_step, ++oldi) { int grid_x, grid_y; GridCoords(static_cast<int>(line_pos.x() + 0.5), static_cast<int>(line_pos.y() + 0.5), &grid_x, &grid_y); grid_[grid_y * gridwidth() + grid_x] = old_grid[oldi]; } } delete [] old_grid; }
// Tests each blob in the list to see if it is certain non-text using 2 // conditions: // 1. blob overlaps a cell with high value in noise_density_ (previously set // by ComputeNoiseDensity). // OR 2. The blob overlaps more than max_blob_overlaps in *this grid. This // condition is disabled with max_blob_overlaps == -1. // If it does, the blob is declared non-text, and is used to mark up the // nontext_mask. Such blobs are fully deleted, and non-noise blobs have their // neighbours reset, as they may now point to deleted data. // WARNING: The blobs list blobs may be in the *this grid, but they are // not removed. If any deleted blobs might be in *this, then this must be // Clear()ed immediately after MarkAndDeleteNonTextBlobs is called. // If the win is not NULL, deleted blobs are drawn on it in red, and kept // blobs are drawn on it in ok_color. void CCNonTextDetect::MarkAndDeleteNonTextBlobs(BLOBNBOX_LIST* blobs, int max_blob_overlaps, ScrollView* win, ScrollView::Color ok_color, Pix* nontext_mask) { int imageheight = tright().y() - bleft().x(); BLOBNBOX_IT blob_it(blobs); BLOBNBOX_LIST dead_blobs; BLOBNBOX_IT dead_it(&dead_blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); TBOX box = blob->bounding_box(); if (!noise_density_->RectMostlyOverThreshold(box, max_noise_count_) && (max_blob_overlaps < 0 || !BlobOverlapsTooMuch(blob, max_blob_overlaps))) { blob->ClearNeighbours(); #ifndef GRAPHICS_DISABLED if (win != NULL) blob->plot(win, ok_color, ok_color); #endif // GRAPHICS_DISABLED } else { if (noise_density_->AnyZeroInRect(box)) { // There is a danger that the bounding box may overlap real text, so // we need to render the outline. Pix* blob_pix = blob->cblob()->render_outline(); pixRasterop(nontext_mask, box.left(), imageheight - box.top(), box.width(), box.height(), PIX_SRC | PIX_DST, blob_pix, 0, 0); pixDestroy(&blob_pix); } else { if (box.area() < gridsize() * gridsize()) { // It is a really bad idea to make lots of small components in the // photo mask, so try to join it to a bigger area by expanding the // box in a way that does not touch any zero noise density cell. box = AttemptBoxExpansion(box, *noise_density_, gridsize()); } // All overlapped cells are non-zero, so just mark the rectangle. pixRasterop(nontext_mask, box.left(), imageheight - box.top(), box.width(), box.height(), PIX_SET, NULL, 0, 0); } #ifndef GRAPHICS_DISABLED if (win != NULL) blob->plot(win, ScrollView::RED, ScrollView::RED); #endif // GRAPHICS_DISABLED // It is safe to delete the cblob now, as it isn't used by the grid // or BlobOverlapsTooMuch, and the BLOBNBOXes will go away with the // dead_blobs list. // TODO(rays) delete the delete when the BLOBNBOX destructor deletes // the cblob. delete blob->cblob(); dead_it.add_to_end(blob_it.extract()); } } }
// Returns a full-resolution binary pix in which each cell over the given // threshold is filled as a black square. pixDestroy after use. // Edge cells, which have a zero 4-neighbour, are not marked. Pix* IntGrid::ThresholdToPix(int threshold) const { Pix* pix = pixCreate(tright().x() - bleft().x(), tright().y() - bleft().y(), 1); int cellsize = gridsize(); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { if (GridCellValue(x, y) > threshold && GridCellValue(x - 1, y) > 0 && GridCellValue(x + 1, y) > 0 && GridCellValue(x, y - 1) > 0 && GridCellValue(x, y + 1) > 0) { pixRasterop(pix, x * cellsize, tright().y() - ((y + 1) * cellsize), cellsize, cellsize, PIX_SET, NULL, 0, 0); } } } return pix; }
// Returns a new IntGrid containing values equal to the sum of all the // neighbouring cells. The returned grid must be deleted after use. // For ease of implementation, edge cells are double counted, to make them // have the same range as the non-edge cells. IntGrid* IntGrid::NeighbourhoodSum() const { IntGrid* sumgrid = new IntGrid(gridsize(), bleft(), tright()); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { int cell_count = 0; for (int yoffset = -1; yoffset <= 1; ++yoffset) { for (int xoffset = -1; xoffset <= 1; ++xoffset) { int grid_x = x + xoffset; int grid_y = y + yoffset; ClipGridCoords(&grid_x, &grid_y); cell_count += GridCellValue(grid_x, grid_y); } } if (GridCellValue(x, y) > 1) sumgrid->SetGridCell(x, y, cell_count); } } return sumgrid; }
// Computes and returns the noise_density IntGrid, at the same gridsize as // this by summing the number of small elements in a 3x3 neighbourhood of // each grid cell. good_grid is filled with blobs that are considered most // likely good text, and this is filled with small and medium blobs that are // more likely non-text. // The photo_map is used to bias the decision towards non-text, rather than // supplying definite decision. IntGrid* CCNonTextDetect::ComputeNoiseDensity(bool debug, Pix* photo_map, BlobGrid* good_grid) { IntGrid* noise_counts = CountCellElements(); IntGrid* noise_density = noise_counts->NeighbourhoodSum(); IntGrid* good_counts = good_grid->CountCellElements(); // Now increase noise density in photo areas, to bias the decision and // minimize hallucinated text on image, but trim the noise_density where // there are good blobs and the original count is low in non-photo areas, // indicating that most of the result came from neighbouring cells. int height = pixGetHeight(photo_map); int photo_offset = IntCastRounded(max_noise_count_ * kPhotoOffsetFraction); for (int y = 0; y < gridheight(); ++y) { for (int x = 0; x < gridwidth(); ++x) { int noise = noise_density->GridCellValue(x, y); if (max_noise_count_ < noise + photo_offset && noise <= max_noise_count_) { // Test for photo. int left = x * gridsize(); int right = left + gridsize(); int bottom = height - y * gridsize(); int top = bottom - gridsize(); if (ImageFind::BoundsWithinRect(photo_map, &left, &top, &right, &bottom)) { noise_density->SetGridCell(x, y, noise + photo_offset); } } if (debug && noise > max_noise_count_ && good_counts->GridCellValue(x, y) > 0) { tprintf("At %d, %d, noise = %d, good=%d, orig=%d, thr=%d\n", x * gridsize(), y * gridsize(), noise_density->GridCellValue(x, y), good_counts->GridCellValue(x, y), noise_counts->GridCellValue(x, y), max_noise_count_); } if (noise > max_noise_count_ && good_counts->GridCellValue(x, y) > 0 && noise_counts->GridCellValue(x, y) * kOriginalNoiseMultiple <= max_noise_count_) { noise_density->SetGridCell(x, y, 0); } } } delete noise_counts; delete good_counts; return noise_density; }
// Creates and returns a Pix with the same resolution as the original // in which 1 (black) pixels represent likely non text (photo, line drawing) // areas of the page, deleting from the blob_block the blobs that were // determined to be non-text. // The photo_map is used to bias the decision towards non-text, rather than // supplying definite decision. // The blob_block is the usual result of connected component analysis, // holding the detected blobs. // The returned Pix should be PixDestroyed after use. Pix* CCNonTextDetect::ComputeNonTextMask(bool debug, Pix* photo_map, TO_BLOCK* blob_block) { // Insert the smallest blobs into the grid. InsertBlobList(&blob_block->small_blobs); InsertBlobList(&blob_block->noise_blobs); // Add the medium blobs that don't have a good strokewidth neighbour. // Those that do go into good_grid as an antidote to spreading beyond the // real reaches of a noise region. BlobGrid good_grid(gridsize(), bleft(), tright()); BLOBNBOX_IT blob_it(&blob_block->blobs); for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { BLOBNBOX* blob = blob_it.data(); double perimeter_area_ratio = blob->cblob()->perimeter() / 4.0; perimeter_area_ratio *= perimeter_area_ratio / blob->enclosed_area(); if (blob->GoodTextBlob() == 0 || perimeter_area_ratio < kMinGoodTextPARatio) InsertBBox(true, true, blob); else good_grid.InsertBBox(true, true, blob); } noise_density_ = ComputeNoiseDensity(debug, photo_map, &good_grid); good_grid.Clear(); // Not needed any more. Pix* pix = noise_density_->ThresholdToPix(max_noise_count_); if (debug) { pixWrite("junknoisemask.png", pix, IFF_PNG); } ScrollView* win = NULL; #ifndef GRAPHICS_DISABLED if (debug) { win = MakeWindow(0, 400, "Photo Mask Blobs"); } #endif // GRAPHICS_DISABLED // Large and medium blobs are not text if they overlap with "a lot" of small // blobs. MarkAndDeleteNonTextBlobs(&blob_block->large_blobs, kMaxLargeOverlapsWithSmall, win, ScrollView::DARK_GREEN, pix); MarkAndDeleteNonTextBlobs(&blob_block->blobs, kMaxMediumOverlapsWithSmall, win, ScrollView::WHITE, pix); // Clear the grid of small blobs and insert the medium blobs. Clear(); InsertBlobList(&blob_block->blobs); MarkAndDeleteNonTextBlobs(&blob_block->large_blobs, kMaxLargeOverlapsWithMedium, win, ScrollView::DARK_GREEN, pix); // Clear again before we start deleting the blobs in the grid. Clear(); MarkAndDeleteNonTextBlobs(&blob_block->noise_blobs, -1, win, ScrollView::CORAL, pix); MarkAndDeleteNonTextBlobs(&blob_block->small_blobs, -1, win, ScrollView::GOLDENROD, pix); MarkAndDeleteNonTextBlobs(&blob_block->blobs, -1, win, ScrollView::WHITE, pix); if (debug) { #ifndef GRAPHICS_DISABLED win->Update(); #endif // GRAPHICS_DISABLED pixWrite("junkccphotomask.png", pix, IFF_PNG); #ifndef GRAPHICS_DISABLED delete win->AwaitEvent(SVET_DESTROY); delete win; #endif // GRAPHICS_DISABLED } return pix; }
// Search vertically for a blob that is aligned with the input bbox. // The search parameters are determined by AlignedBlobParams. // top_to_bottom tells whether to search down or up. // The return value is nullptr if nothing was found in the search box // or if a blob was found in the gutter. On a nullptr return, end_y // is set to the edge of the search box or the leading edge of the // gutter blob if one was found. BLOBNBOX* AlignedBlob::FindAlignedBlob(const AlignedBlobParams& p, bool top_to_bottom, BLOBNBOX* bbox, int x_start, int* end_y) { TBOX box = bbox->bounding_box(); // If there are separator lines, get the column edges. int left_column_edge = bbox->left_rule(); int right_column_edge = bbox->right_rule(); // start_y is used to guarantee that forward progress is made and the // search does not go into an infinite loop. New blobs must extend the // line beyond start_y. int start_y = top_to_bottom ? box.bottom() : box.top(); if (WithinTestRegion(2, x_start, start_y)) { tprintf("Column edges for blob at (%d,%d)->(%d,%d) are [%d, %d]\n", box.left(), box.top(), box.right(), box.bottom(), left_column_edge, right_column_edge); } // Compute skew tolerance. int skew_tolerance = p.max_v_gap / kMaxSkewFactor; // Calculate xmin and xmax of the search box so that it contains // all possibly relevant boxes up to p.max_v_gap above or below accoording // to top_to_bottom. // Start with a notion of vertical with the current estimate. int x2 = (p.max_v_gap * p.vertical.x() + p.vertical.y()/2) / p.vertical.y(); if (top_to_bottom) { x2 = x_start - x2; *end_y = start_y - p.max_v_gap; } else { x2 = x_start + x2; *end_y = start_y + p.max_v_gap; } // Expand the box by an additional skew tolerance int xmin = std::min(x_start, x2) - skew_tolerance; int xmax = std::max(x_start, x2) + skew_tolerance; // Now add direction-specific tolerances. if (p.right_tab) { xmax += p.min_gutter; xmin -= p.l_align_tolerance; } else { xmax += p.r_align_tolerance; xmin -= p.min_gutter; } // Setup a vertical search for an aligned blob. GridSearch<BLOBNBOX, BLOBNBOX_CLIST, BLOBNBOX_C_IT> vsearch(this); if (WithinTestRegion(2, x_start, start_y)) tprintf("Starting %s %s search at %d-%d,%d, search_size=%d, gutter=%d\n", p.ragged ? "Ragged" : "Aligned", p.right_tab ? "Right" : "Left", xmin, xmax, start_y, p.max_v_gap, p.min_gutter); vsearch.StartVerticalSearch(xmin, xmax, start_y); // result stores the best real return value. BLOBNBOX* result = nullptr; // The backup_result is not a tab candidate and can be used if no // real tab candidate result is found. BLOBNBOX* backup_result = nullptr; // neighbour is the blob that is currently being investigated. BLOBNBOX* neighbour = nullptr; while ((neighbour = vsearch.NextVerticalSearch(top_to_bottom)) != nullptr) { if (neighbour == bbox) continue; TBOX nbox = neighbour->bounding_box(); int n_y = (nbox.top() + nbox.bottom()) / 2; if ((!top_to_bottom && n_y > start_y + p.max_v_gap) || (top_to_bottom && n_y < start_y - p.max_v_gap)) { if (WithinTestRegion(2, x_start, start_y)) tprintf("Neighbour too far at (%d,%d)->(%d,%d)\n", nbox.left(), nbox.bottom(), nbox.right(), nbox.top()); break; // Gone far enough. } // It is CRITICAL to ensure that forward progress is made, (strictly // in/decreasing n_y) or the caller could loop infinitely, while // waiting for a sequence of blobs in a line to end. // NextVerticalSearch alone does not guarantee this, as there may be // more than one blob in a grid cell. See comment in AlignTabs. if ((n_y < start_y) != top_to_bottom || nbox.y_overlap(box)) continue; // Only look in the required direction. if (result != nullptr && result->bounding_box().y_gap(nbox) > gridsize()) return result; // This result is clear. if (backup_result != nullptr && p.ragged && result == nullptr && backup_result->bounding_box().y_gap(nbox) > gridsize()) return backup_result; // This result is clear. // If the neighbouring blob is the wrong side of a separator line, then it // "doesn't exist" as far as we are concerned. int x_at_n_y = x_start + (n_y - start_y) * p.vertical.x() / p.vertical.y(); if (x_at_n_y < neighbour->left_crossing_rule() || x_at_n_y > neighbour->right_crossing_rule()) continue; // Separator line in the way. int n_left = nbox.left(); int n_right = nbox.right(); int n_x = p.right_tab ? n_right : n_left; if (WithinTestRegion(2, x_start, start_y)) tprintf("neighbour at (%d,%d)->(%d,%d), n_x=%d, n_y=%d, xatn=%d\n", nbox.left(), nbox.bottom(), nbox.right(), nbox.top(), n_x, n_y, x_at_n_y); if (p.right_tab && n_left < x_at_n_y + p.min_gutter && n_right > x_at_n_y + p.r_align_tolerance && (p.ragged || n_left < x_at_n_y + p.gutter_fraction * nbox.height())) { // In the gutter so end of line. if (bbox->right_tab_type() >= TT_MAYBE_ALIGNED) bbox->set_right_tab_type(TT_DELETED); *end_y = top_to_bottom ? nbox.top() : nbox.bottom(); if (WithinTestRegion(2, x_start, start_y)) tprintf("gutter\n"); return nullptr; } if (!p.right_tab && n_left < x_at_n_y - p.l_align_tolerance && n_right > x_at_n_y - p.min_gutter && (p.ragged || n_right > x_at_n_y - p.gutter_fraction * nbox.height())) { // In the gutter so end of line. if (bbox->left_tab_type() >= TT_MAYBE_ALIGNED) bbox->set_left_tab_type(TT_DELETED); *end_y = top_to_bottom ? nbox.top() : nbox.bottom(); if (WithinTestRegion(2, x_start, start_y)) tprintf("gutter\n"); return nullptr; } if ((p.right_tab && neighbour->leader_on_right()) || (!p.right_tab && neighbour->leader_on_left())) continue; // Neighbours of leaders are not allowed to be used. if (n_x <= x_at_n_y + p.r_align_tolerance && n_x >= x_at_n_y - p.l_align_tolerance) { // Aligned so keep it. If it is a marked tab save it as result, // otherwise keep it as backup_result to return in case of later failure. if (WithinTestRegion(2, x_start, start_y)) tprintf("aligned, seeking%d, l=%d, r=%d\n", p.right_tab, neighbour->left_tab_type(), neighbour->right_tab_type()); TabType n_type = p.right_tab ? neighbour->right_tab_type() : neighbour->left_tab_type(); if (n_type != TT_NONE && (p.ragged || n_type != TT_MAYBE_RAGGED)) { if (result == nullptr) { result = neighbour; } else { // Keep the closest neighbour by Euclidean distance. // This prevents it from picking a tab blob in another column. const TBOX& old_box = result->bounding_box(); int x_diff = p.right_tab ? old_box.right() : old_box.left(); x_diff -= x_at_n_y; int y_diff = (old_box.top() + old_box.bottom()) / 2 - start_y; int old_dist = x_diff * x_diff + y_diff * y_diff; x_diff = n_x - x_at_n_y; y_diff = n_y - start_y; int new_dist = x_diff * x_diff + y_diff * y_diff; if (new_dist < old_dist) result = neighbour; } } else if (backup_result == nullptr) { if (WithinTestRegion(2, x_start, start_y)) tprintf("Backup\n"); backup_result = neighbour; } else { TBOX backup_box = backup_result->bounding_box(); if ((p.right_tab && backup_box.right() < nbox.right()) || (!p.right_tab && backup_box.left() > nbox.left())) { if (WithinTestRegion(2, x_start, start_y)) tprintf("Better backup\n"); backup_result = neighbour; } } } } return result != nullptr ? result : backup_result; }
int main(int argc, const char *argv[]) { // parse arguments /////////////////////////////////////////// // Declare the supported options. po::options_description desc("Visualize data"); desc.add_options() ("help", "produce help message") ("width", po::value<double>()->required(), "Width ") ("height", po::value<double>()->required(), "Height ") ("dir", po::value<std::string>()->default_value("."), "Data directory") ; po::positional_options_description pos; pos.add("width", 1); pos.add("height", 1); pos.add("dir", 1); po::variables_map vm; po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm); po::notify(vm); double width = vm["width"].as<double>(); double height = vm["height"].as<double>(); std::string datadirectory = vm["dir"].as<std::string>(); // end of parse arguments //////////////////////////////////// cv::Mat laser_pose, laser_ranges, scan_angles; loadMat(laser_pose, datadirectory + "/laser_pose_all.bin"); loadMat(laser_ranges, datadirectory + "/laser_range_all.bin"); loadMat(scan_angles, datadirectory + "/scan_angles_all.bin"); cv::Mat laser_reflectance(laser_ranges.rows, laser_ranges.cols, CV_8U); std::string floorplanfile = datadirectory + "/floorplan.png"; cv::Mat floorplan = cv::imread(floorplanfile, cv::IMREAD_GRAYSCALE); if(! floorplan.data ) // Check for invalid input { std::cout << "Could not open or find the image" << std::endl ; return -1; } cv::transpose(floorplan, floorplan); cv::flip(floorplan, floorplan, 1); cv::Vec2d size_bitmap(width, height); cv::Vec2d margin(1, 1); cv::Vec2d min_pt(- margin(0) - size_bitmap(0)/2, - margin(1) - size_bitmap(1)/2); double max_range = 8; cv::Vec2i gridsize(floorplan.size[0], floorplan.size[1]); cv::Vec2d cellsize; cv::divide(size_bitmap , gridsize, cellsize); //std::cout << cellsize(0) << cellsize(1) << std::endl; cv::Vec2i ncells; cv::divide(min_pt, cellsize, ncells, -2); OccupancyGrid2D<double, int> map( min_pt(0), min_pt(1), cellsize(0), cellsize(1), ncells(0), ncells(1)); // initialize map with occupancy with floorplan for (int r = 0; r < ncells(0); ++r) { for (int c = 0; c < ncells(1); ++c) { int fp_r = r - margin(0) / cellsize(0); int fp_c = c - margin(1) / cellsize(1); if ((0 <= fp_r) && (fp_r < floorplan.rows) && (0 <= fp_c) && (fp_c < floorplan.cols)) { map.og_.at<uint8_t>(r, c) = (floorplan.at<uint8_t>(fp_r, fp_c) > 127) ? map.FREE : map.OCCUPIED; } else { map.og_.at<uint8_t>(r, c) = map.OCCUPIED; } } } boost::mt19937 gen; boost::normal_distribution<> norm_dist(1, NOISE_VARIANCE); boost::variate_generator<boost::mt19937&, boost::normal_distribution<> > norm_rand(gen, norm_dist); for (int r = 0; r < scan_angles.rows; r++) { double* pose = laser_pose.ptr<double>(r); double* angles = scan_angles.ptr<double>(r); double robot_angle = pose[2]; for (int c = 0; c < scan_angles.cols; c++) { double total_angle = robot_angle + angles[c]; cv::Vec2d final_pos; bool refl; double range = map.ray_trace(pose[0], pose[1], total_angle, max_range, final_pos, refl); range *= norm_rand(); laser_ranges.at<double>(r, c) = range; laser_reflectance.at<uint8_t>(r, c) = (uint8_t) refl; } // draw input cv::Mat visin; cv::cvtColor(map.og_, visin, cv::COLOR_GRAY2BGR); cv::Vec2d position(pose[0], pose[1]); map.draw_lasers(visin, position, robot_angle, angles, laser_ranges.ptr<double>(r), laser_reflectance.ptr<uint8_t>(r), scan_angles.cols, CV_RGB(0, 255, 0)); cv::imshow("c", visin); cv::waitKey(33); } saveMat(laser_ranges, "laser_range_all.bin"); saveMat(laser_reflectance, "laser_reflectance_all.bin"); }
// Detects and marks leader dots/dashes. // Leaders are horizontal chains of small or noise blobs that look // monospace according to ColPartition::MarkAsLeaderIfMonospaced(). // Detected leaders become the only occupants of small_blobs list. // Non-leader small blobs get moved to the blobs list. // Non-leader noise blobs remain singletons in the noise list. // All small and noise blobs in high density regions are marked BTFT_NONTEXT. void StrokeWidth::FindLeadersAndMarkNoise(bool final, TO_BLOCK* block, TabFind* line_grid, ColPartition_LIST* leader_parts) { line_grid->InsertBlobList(true, true, false, &block->small_blobs, false, this); line_grid->InsertBlobList(true, true, false, &block->noise_blobs, false, this); int max_noise_count = static_cast<int>(kMaxSmallNeighboursPerPix * gridsize() * gridsize()); BlobGridSearch gsearch(this); BLOBNBOX* bbox; // For every bbox in the grid, set its neighbours. gsearch.StartFullSearch(); while ((bbox = gsearch.NextFullSearch()) != NULL) { int noise_count = noise_density_->GridCellValue(gsearch.GridX(), gsearch.GridY()); if (noise_count <= max_noise_count) { SetNeighbours(true, bbox); } else { bbox->set_flow(BTFT_NONTEXT); } } ColPartition_IT part_it(leader_parts); gsearch.StartFullSearch();