mjev-spatial / src /spatial_jev /point_probability_map.py
multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
85300f6 verified
Raw History Blame Contribute Delete
5.93 kB
"""Nine-way conditional trees and connected-component multi-point readout."""
import numpy as np
from .hierarchy import POINT_RC
def softmax9(logits):
values = np.asarray(logits, dtype=np.float64)
if values.shape != (9,) or not np.isfinite(values).all():
raise ValueError("Expected nine finite logits")
probabilities = np.exp(values - values.max())
return probabilities / probabilities.sum()
def grid_location(path):
row = col = 0
for choice in path:
dr, dc = POINT_RC[choice]
row, col = row * 3 + dr, col * 3 + dc
return row, col
def branch_spec(builder, parent, choice):
# Copy mutable histories, but share immutable PIL images and input metadata.
child = dict(parent)
for key in ("images", "image_keys", "counts", "mapping", "path"):
child[key] = list(parent[key])
if 'roi_boxes' in parent:
child['roi_boxes'] = list(parent['roi_boxes'])
child["path"].append(choice)
builder.advance(child, choice)
return child
def probability_map(builder, request, evaluate_nodes, mode="full", root_relative_cutoff=0.01):
"""Three levels, 729 leaves; labels/masks/counts are not accepted as arguments.
root_pruned uses only the first decision to omit low-mass root subtrees.
Unvisited leaves remain explicitly unknown, with their missing mass reported.
No probability renormalization is applied after pruning.
"""
if mode not in ("full", "root_pruned"):
raise ValueError("Unknown tree policy")
if not 0 <= root_relative_cutoff <= 1:
raise ValueError("Invalid root cutoff")
if request["input"]["answer_space"]["kind"] != "point":
raise ValueError("Point input required")
grid = np.zeros((27, 27), dtype=np.float64)
known = np.zeros((27, 27), dtype=bool)
nodes, pruned = [], []
frontier = [(builder.initial_spec(request["input"], request["id"]), 1.0)]
for depth in range(3):
logits = evaluate_nodes([state for state, _ in frontier])
if len(logits) != len(frontier):
raise ValueError("Node evaluator returned a different batch size")
following = []
for (state, mass), scores in zip(frontier, logits):
probabilities = softmax9(scores)
path = list(state["path"])
nodes.append({"path": path, "mass": mass, "logits": list(map(float, scores)),
"probabilities": probabilities.tolist()})
cutoff = probabilities.max() * root_relative_cutoff
for choice, probability in enumerate(probabilities):
child_path = [*path, choice]
child_mass = mass * float(probability)
if depth == 2:
row, col = grid_location(child_path)
if known[row, col]:
raise ValueError("Duplicate leaf location")
grid[row, col], known[row, col] = child_mass, True
elif depth == 0 and mode == "root_pruned" and probability < cutoff:
pruned.append({"path": child_path, "mass": child_mass})
else:
following.append((branch_spec(builder, state, choice), child_mass))
frontier = following
retained, omitted = float(grid.sum()), sum(r["mass"] for r in pruned)
if not np.isclose(retained + omitted, 1, atol=1e-12, rtol=0):
raise ValueError("Probability mass was lost or duplicated")
return {"probabilities": grid, "known": known, "nodes": nodes,
"evaluated_nodes": len(nodes), "evaluated_leaves": int(known.sum()),
"retained_mass": retained, "pruned_mass": omitted, "pruned_branches": pruned}
def component_points(probabilities, known=None, relative_threshold=0.1, connectivity=8):
"""Pick each superlevel component's maximum cell, with deterministic tie breaks."""
grid = np.asarray(probabilities, dtype=np.float64)
if grid.ndim != 2 or not np.isfinite(grid).all() or (grid < 0).any():
raise ValueError("Invalid spatial probability grid")
if not 0 < relative_threshold <= 1 or connectivity not in (4, 8):
raise ValueError("Invalid connected-component settings")
known = np.ones_like(grid, dtype=bool) if known is None else np.asarray(known, dtype=bool)
if known.shape != grid.shape:
raise ValueError("Known-cell mask shape mismatch")
peak = float(grid[known].max()) if known.any() else 0.0
threshold = peak * relative_threshold
mask = known & (grid >= threshold) & (grid > 0)
visited = np.zeros_like(mask)
neighbors = [(-1, 0), (1, 0), (0, -1), (0, 1)]
if connectivity == 8:
neighbors += [(-1, -1), (-1, 1), (1, -1), (1, 1)]
height, width = grid.shape
components, points = [], []
for row, col in zip(*np.where(mask)):
if visited[row, col]:
continue
visited[row, col] = True
stack, cells = [(int(row), int(col))], []
while stack:
y, x = stack.pop()
cells.append((y, x))
for dy, dx in neighbors:
yy, xx = y + dy, x + dx
if 0 <= yy < height and 0 <= xx < width and mask[yy, xx] and not visited[yy, xx]:
visited[yy, xx] = True
stack.append((yy, xx))
# Prefer the highest probability, then topmost/leftmost on exact ties.
y, x = min(cells, key=lambda cell: (-grid[cell], cell[0], cell[1]))
point = [(x + 0.5) / width, (y + 0.5) / height]
points.append(point)
components.append({"cells": [list(cell) for cell in cells], "peak_cell": [y, x],
"peak_probability": float(grid[y, x]),
"mass": sum(float(grid[cell]) for cell in cells)})
return {"points": points, "components": components, "threshold": threshold,
"relative_threshold": relative_threshold, "connectivity": connectivity}