|
14 | 14 | _log = logging.getLogger(__name__) |
15 | 15 |
|
16 | 16 |
|
| 17 | +def _popcount(x: np.ndarray) -> np.ndarray: |
| 18 | + """ |
| 19 | + Count the number of 1-bits in each element of x. |
| 20 | +
|
| 21 | + Compatible with NumPy 1.x (falls back to vectorized bit counting) |
| 22 | + and NumPy 2.0+ (uses np.bitwise_count). |
| 23 | + """ |
| 24 | + if hasattr(np, "bitwise_count"): |
| 25 | + # NumPy 2.0+ |
| 26 | + return np.bitwise_count(x) |
| 27 | + else: |
| 28 | + # NumPy 1.x fallback - vectorized popcount using lookup table |
| 29 | + # Works for uint64 by processing 8 bits at a time |
| 30 | + lookup = np.array([bin(i).count("1") for i in range(256)], dtype=np.uint8) |
| 31 | + x = x.astype(np.uint64) |
| 32 | + result = np.zeros_like(x, dtype=np.int64) |
| 33 | + for shift in range(0, 64, 8): |
| 34 | + result += lookup[(x >> shift) & 0xFF] |
| 35 | + return result |
| 36 | + |
| 37 | + |
17 | 38 | def unpackbits(x: np.ndarray, num_bits: int): |
18 | 39 | r""" |
19 | 40 | Unpack num_bits bits of each element of the numpy array x |
@@ -246,10 +267,10 @@ def _compute_confusion_matrix( |
246 | 267 | ) |
247 | 268 |
|
248 | 269 | # [num_pixels_with_extra_preds,] |
249 | | - case2_gt_multiplier = np.bitwise_count(case2_gt_pixels) |
| 270 | + case2_gt_multiplier = _popcount(case2_gt_pixels) |
250 | 271 |
|
251 | 272 | # [num_pixels_with_extra_preds,] |
252 | | - case2_preds_divider = np.bitwise_count(case2_preds_pixels) |
| 273 | + case2_preds_divider = _popcount(case2_preds_pixels) |
253 | 274 |
|
254 | 275 | # [num_pixels_with_extra_preds, num_categories, num_categories] |
255 | 276 | case2_gain = case2_gt_multiplier[:, None, None] * case2_gt_diagonals |
@@ -292,7 +313,7 @@ def _compute_confusion_matrix( |
292 | 313 | case3_gt_preds_diff = unpackbits(case3_gt_preds_diff, num_categories) |
293 | 314 |
|
294 | 315 | # [num_pixels_with_additional_gt_labels,] |
295 | | - case3_preds_divider = np.bitwise_count(case3_preds_pixels) |
| 316 | + case3_preds_divider = _popcount(case3_preds_pixels) |
296 | 317 |
|
297 | 318 | # [num_pixels_with_additional_gt_labels, num_categories, num_categories] |
298 | 319 | case3_preds_diagonals = ( |
@@ -342,7 +363,7 @@ def _compute_confusion_matrix( |
342 | 363 | case4_preds_pixels ^ case4_gt_pixels |
343 | 364 | ) & case4_preds_pixels |
344 | 365 | if len(case4_preds_gt_diff) > 0: |
345 | | - case4_divider = np.bitwise_count(case4_preds_gt_diff) |
| 366 | + case4_divider = _popcount(case4_preds_gt_diff) |
346 | 367 | case4_preds_gt_diff = unpackbits(case4_preds_gt_diff, num_categories) |
347 | 368 |
|
348 | 369 | # [num_pixels_with_mutual_gt_pred_deltas, num_categories] |
@@ -525,7 +546,7 @@ def _validate_contributions( |
525 | 546 |
|
526 | 547 | # Full sum check |
527 | 548 | full_sum = np.sum(row_sum) |
528 | | - expected_full_sum = np.sum(np.bitwise_count(selected_gt)) |
| 549 | + expected_full_sum = np.sum(_popcount(selected_gt)) |
529 | 550 | if full_sum != expected_full_sum: |
530 | 551 | self._handle_error(f"{info}: Wrong contributions full sums") |
531 | 552 |
|
|
0 commit comments