Spaces:
Running
Running
| from __future__ import annotations | |
| from typing import List, Sequence | |
| import numpy as np | |
| import scipy.sparse as sp | |
| from analysis.features.feature_indexing import FeatureMatrix | |
| def _axis0_to_dense(values) -> np.ndarray: | |
| if hasattr(values, 'toarray'): | |
| return np.asarray(values.toarray()).ravel().astype(np.float32) | |
| return np.asarray(values).ravel().astype(np.float32) | |
| def _zscore_columns(x: np.ndarray) -> np.ndarray: | |
| mu = x.mean(axis=0, keepdims=True) | |
| sigma = x.std(axis=0, keepdims=True) | |
| sigma[sigma < 1e-8] = 1.0 | |
| return (x - mu) / sigma | |
| def build_feature_matrix( | |
| feature_id: int, | |
| features: FeatureMatrix, | |
| rows_img: Sequence[int], | |
| rows_patch: Sequence[int], | |
| n_images_used: int, | |
| patches_per_image: int, | |
| ) -> np.ndarray: | |
| """Build image-level matrix (n_images_used, patches_per_image) for one global SAE feature.""" | |
| col = features.column_for(feature_id) | |
| vals = features.codes[:, col].toarray().ravel().astype(np.float32) | |
| x = np.zeros((int(n_images_used), int(patches_per_image)), dtype=np.float32) | |
| x[rows_img, rows_patch] = vals | |
| return _zscore_columns(x) | |
| def build_image_feature_matrix( | |
| features: FeatureMatrix, | |
| image_row_indices: Sequence[Sequence[int]], | |
| n_images_used: int, | |
| aggregation_mode: str = 'max', | |
| ) -> np.ndarray: | |
| """Build image-level feature matrix (n_images_used, n_features_total). | |
| `image_row_indices` is a sequence where each element is an array of row indices | |
| (patch indices) belonging to that image. | |
| Aggregation modes: 'max', 'sum', 'mean_acts' (mean over positive activations only). | |
| """ | |
| return _zscore_columns( | |
| build_image_feature_matrix_raw( | |
| features, | |
| image_row_indices, | |
| n_images_used, | |
| aggregation_mode, | |
| ) | |
| ) | |
| def _to_dense_image_features(values) -> np.ndarray: | |
| if sp.issparse(values): | |
| return np.asarray(values.toarray(), dtype=np.float32) | |
| return np.asarray(values, dtype=np.float32) | |
| def _image_group_matrix(image_idx_arr: np.ndarray, n_images_used: int) -> sp.csr_matrix: | |
| """Map each patch row to one image column (n_patches, n_images).""" | |
| n_patches = int(len(image_idx_arr)) | |
| rows = np.arange(n_patches, dtype=np.int32) | |
| cols = np.asarray(image_idx_arr, dtype=np.int32) | |
| return sp.csr_matrix( | |
| (np.ones(n_patches, dtype=np.float32), (rows, cols)), | |
| shape=(n_patches, int(n_images_used)), | |
| ) | |
| def _grouped_sum(codes, group: sp.csr_matrix) -> np.ndarray: | |
| return _to_dense_image_features(group.T @ codes) | |
| def _grouped_mean_acts(codes, group: sp.csr_matrix) -> np.ndarray: | |
| if sp.issparse(codes): | |
| positive = codes.multiply(codes > 0) | |
| pos_count = group.T @ (codes > 0).astype(np.float32) | |
| else: | |
| codes_arr = np.asarray(codes, dtype=np.float32) | |
| positive = codes_arr * (codes_arr > 0) | |
| pos_count = group.T @ (codes_arr > 0).astype(np.float32) | |
| pos_sum = _to_dense_image_features(group.T @ positive) | |
| pos_count = _to_dense_image_features(pos_count) | |
| out = np.zeros_like(pos_sum, dtype=np.float32) | |
| nonzero = pos_count > 0 | |
| out[nonzero] = pos_sum[nonzero] / pos_count[nonzero] | |
| return out | |
| def _grouped_max(codes, image_idx_arr: np.ndarray, n_images_used: int, n_features_total: int) -> np.ndarray: | |
| x = np.zeros((int(n_images_used), int(n_features_total)), dtype=np.float32) | |
| for img_i in range(int(n_images_used)): | |
| patch_mask = image_idx_arr == img_i | |
| if not np.any(patch_mask): | |
| continue | |
| chunk = codes[patch_mask, :] | |
| x[img_i] = _axis0_to_dense(chunk.max(axis=0)) | |
| return x | |
| def _aggregate_codes_by_image_idx( | |
| codes, | |
| image_idx_arr: np.ndarray, | |
| n_images_used: int, | |
| aggregation_mode: str, | |
| ) -> np.ndarray: | |
| """Aggregate patch-level codes per image using dense image_idx labels.""" | |
| mode = str(aggregation_mode) | |
| if mode not in {'max', 'sum', 'mean_acts'}: | |
| raise ValueError(f'Unknown aggregation mode: {mode!r}') | |
| image_idx_arr = np.asarray(image_idx_arr, dtype=np.int32) | |
| n_images_used = int(n_images_used) | |
| n_features_total = int(codes.shape[1]) | |
| if mode == 'max': | |
| return _grouped_max(codes, image_idx_arr, n_images_used, n_features_total) | |
| group = _image_group_matrix(image_idx_arr, n_images_used) | |
| if mode == 'sum': | |
| return _grouped_sum(codes, group) | |
| return _grouped_mean_acts(codes, group) | |
| def build_image_feature_matrix_from_image_idx( | |
| codes, | |
| image_idx_arr: np.ndarray, | |
| n_images_used: int, | |
| aggregation_mode: str = 'max', | |
| ) -> np.ndarray: | |
| """Build z-scored image-level matrix using per-patch ``image_idx`` labels.""" | |
| return _zscore_columns( | |
| _aggregate_codes_by_image_idx( | |
| codes, | |
| image_idx_arr, | |
| n_images_used, | |
| aggregation_mode, | |
| ) | |
| ) | |
| def build_image_feature_matrix_raw( | |
| features: FeatureMatrix, | |
| image_row_indices: Sequence[Sequence[int]], | |
| n_images_used: int, | |
| aggregation_mode: str = 'max', | |
| ) -> np.ndarray: | |
| """Build an unnormalized image-level feature matrix. | |
| This keeps the same aggregation logic as ``build_image_feature_matrix`` but | |
| skips the final z-score normalization so it can be used for paired deltas. | |
| """ | |
| mode = str(aggregation_mode) | |
| if mode not in {'max', 'sum', 'mean_acts'}: | |
| raise ValueError(f'Unknown aggregation mode: {mode!r}') | |
| codes_subset = features.codes | |
| image_idx_arr = np.empty(int(codes_subset.shape[0]), dtype=np.int32) | |
| for img_i, row_ids in enumerate(image_row_indices): | |
| if len(row_ids) == 0: | |
| continue | |
| image_idx_arr[list(row_ids)] = int(img_i) | |
| return _aggregate_codes_by_image_idx( | |
| codes_subset, | |
| image_idx_arr, | |
| n_images_used, | |
| mode, | |
| ) | |