0Curious0's picture
Added src files
6ca1e94 verified
Raw
History Blame Contribute Delete
4.25 kB
import torch
import torch.nn as nn
class RoIPool(nn.Module):
def __init__(self, output_size, pooling_mode="loop"):
super().__init__()
self.output_size = output_size
self.pooling_mode = pooling_mode
self.adaptive_pool = nn.AdaptiveMaxPool2d(output_size)
def forward(self, feature_maps, batch_proposals, batch_img_height, batch_img_width):
channels = feature_maps.shape[1]
feat_height, feat_width = feature_maps.shape[2], feature_maps.shape[3]
out_h, out_w = self.output_size
stride_x = batch_img_width // feat_width
stride_y = batch_img_height // feat_height
pooled_batch = []
for feature_map, proposals in zip(feature_maps, batch_proposals):
if proposals.shape[0] == 0:
# No proposals survived for this image — keep a well-shaped empty tensor
pooled_batch.append(feature_map.new_empty((0, channels, out_h, out_w)))
continue
projected = self._project_to_feature_map(proposals, feat_height, feat_width, stride_x, stride_y)
pooled_proposals = []
for proposal_coords in projected:
roi_feature_map = self._select_roi_feature_map(feature_map, proposal_coords)
if self.pooling_mode == "adaptive":
pooled_proposals.append(self._max_pool_roi_adaptive(roi_feature_map))
else:
pooled_proposals.append(self._max_pool_roi(roi_feature_map))
pooled_batch.append(torch.stack(pooled_proposals, dim=0)) # [N_i, C, out_h, out_w]
return pooled_batch
def _project_to_feature_map(self, proposals, feat_height, feat_width, stride_x, stride_y):
x1 = proposals[:, 0]
y1 = proposals[:, 1]
x2 = proposals[:, 2]
y2 = proposals[:, 3]
fx1 = torch.round(x1 / stride_x)
fy1 = torch.round(y1 / stride_y)
fx2 = torch.round(x2 / stride_x)
fy2 = torch.round(y2 / stride_y)
# Convert to long type for holding larger values and prevent overflow due to decimal double
fx1 = fx1.long()
fy1 = fy1.long()
fx2 = fx2.long()
fy2 = fy2.long()
fx1 = torch.clamp(fx1, 0, feat_width - 1)
fx2 = torch.clamp(fx2, 0, feat_width - 1)
fy1 = torch.clamp(fy1, 0, feat_height - 1)
fy2 = torch.clamp(fy2, 0, feat_height - 1)
return torch.stack([fx1, fy1, fx2, fy2], dim=-1) # [N, 4]
def _select_roi_feature_map(self, feature_map, proposal_coords):
fx1, fy1, fx2, fy2 = proposal_coords
return feature_map[:, fy1:fy2 + 1, fx1:fx2 + 1] # [C, roi_h, roi_w]
def _max_pool_roi(self, roi_feature_map):
channels, roi_h, roi_w = roi_feature_map.shape
out_h, out_w = self.output_size # output_size is (H, W) of the pooled map
device = roi_feature_map.device
# Bin boundaries per axis (each axis uses its own bin count): floor for
# start, ceil for end so every bin covers >= 1 pixel even when the ROI
# is smaller than the number of bins along that axis.
h_idx = torch.arange(out_h, device=device)
h_starts = torch.clamp(torch.floor(h_idx * roi_h / out_h).long(), 0, roi_h)
h_ends = torch.clamp(torch.ceil((h_idx + 1) * roi_h / out_h).long(), 0, roi_h)
w_idx = torch.arange(out_w, device=device)
w_starts = torch.clamp(torch.floor(w_idx * roi_w / out_w).long(), 0, roi_w)
w_ends = torch.clamp(torch.ceil((w_idx + 1) * roi_w / out_w).long(), 0, roi_w)
pooled = roi_feature_map.new_empty((channels, out_h, out_w))
for ph in range(out_h):
for pw in range(out_w):
bin_region = roi_feature_map[:, h_starts[ph]:h_ends[ph], w_starts[pw]:w_ends[pw]]
pooled[:, ph, pw] = bin_region.amax(dim=(1, 2)) # max over the bin, per channel
return pooled # [C, out_h, out_w]
def _max_pool_roi_adaptive(self, roi_feature_map):
# Use adaptive pooling to directly get the desired output size, adding 1 dim for batch_processing
pooled = self.adaptive_pool(roi_feature_map.unsqueeze(0))
return pooled.squeeze(0) # [C, out_h, out_w]