def sahi_slicing(request):
search_line = "def sahi_slicing(request):"
pattern = "# end_sahi_slicing"
content_json = "/static/json/data-science/content.json"
project_key = "sahi-slicing"
template_path = "portfolio/templates/data-science/machine-learning/sahi_slicing.html"
views_code = get_views_code(search_line, pattern, module_dir)
content_json_data = get_content_json_data(content_json, project_key)
template_code = get_template_code(template_path)
# La simulación es determinista (semilla fija) y tarda < 1 s, pero los dos
# gráficos se cachean 15 min como el resto de demos: el dyno es uno solo.
cached = cache.get("sahi_demo:results")
context = {'views': views_code, 'content': content_json_data, 'template': template_code, **cached}
return render(request, 'data-science/machine-learning/sahi_slicing.html', context)
rng = np.random.default_rng(SEED)
# 1. Escena sintética: objetos pequeños (malezas, plantas, daños) repartidos
# por un fotograma 4K. Tamaños log-normales entre ~10 y ~90 px de lado,
# que es el rango donde el reescalado decide si algo existe o no.
sizes = np.clip(rng.lognormal(mean=np.log(28), sigma=0.55, size=N_OBJECTS), 8, 120)
xs = rng.uniform(0, IMAGE_W - sizes)
ys = rng.uniform(0, IMAGE_H - sizes)
# 2. Inferencia sobre el fotograma completo: la red recibe la imagen
# reescalada a INPUT_SIZE en su lado mayor → cada objeto se encoge 6x.
full_scale = INPUT_SIZE / max(IMAGE_W, IMAGE_H)
p_full = detection_probability(sizes * full_scale)
# 3. Inferencia por recortes: cada recorte de 640 px se procesa a resolución
# nativa (escala 1), así que el objeto conserva su tamaño. El precio son
# tantas pasadas por la red como recortes, más una del fotograma entero.
tiles = slice_grid(IMAGE_W, IMAGE_H, SLICE_SIZE, OVERLAP)
p_sliced = detection_probability(sizes)
detected_full = rng.random(N_OBJECTS) < p_full
detected_sliced = rng.random(N_OBJECTS) < p_sliced
# 4. Recall por tramo de tamaño: la tabla que en el proyecto real sale de
# pycocotools (AP small / medium / large), aquí con conteo directo.
bins = [0, 16, 32, 64, 1000]
labels = ['< 16 px', '16-32 px', '32-64 px', '> 64 px']
df = pd.DataFrame({'size_px': sizes, 'full': detected_full, 'sliced': detected_sliced})
df['bin'] = pd.cut(df['size_px'], bins=bins, labels=labels)
summary = df.groupby('bin', observed=False).agg(
objects=('size_px', 'size'), recall_full=('full', 'mean'), recall_sliced=('sliced', 'mean'),
summary.loc['All'] = [N_OBJECTS, detected_full.mean(), detected_sliced.mean()]
summary['objects'] = summary['objects'].astype(int)
summary['recall_full'] = (summary['recall_full'] * 100).round(1)
summary['recall_sliced'] = (summary['recall_sliced'] * 100).round(1)
summary = summary.rename(columns={
'objects': 'Objects', 'recall_full': 'Recall full-frame (%)', 'recall_sliced': 'Recall sliced (%)',
summary_html = th_scope(summary.to_html(classes='table table-bordered table-striped to_html', index=True))
N_OBJECTS, len(tiles), f"{full_scale:.3f}", round(float(detected_full.mean()) * 100, 1),
round(float(detected_sliced.mean()) * 100, 1), len(tiles) + 1,
# 5. Gráfico 1: la escena con la rejilla de recortes. Verde = detectado en
# ambos modos; naranja = sólo con recortes; rojo = perdido en ambos.
fig, ax = plt.subplots(figsize=(12, 6.75))
ax.set_facecolor('#e8f0e4')
ax.add_patch(Rectangle((tx, ty), SLICE_SIZE, SLICE_SIZE, fill=False, lw=0.6, ec='#5b7a9a', alpha=0.7))
colors = np.where(detected_full, '#2e7d32', np.where(detected_sliced, '#ef8f00', '#c62828'))
ax.scatter(xs + sizes / 2, ys + sizes / 2, s=(sizes / 4) ** 2, c=colors, alpha=0.85, edgecolors='k', lw=0.3)
ax.set_title(f'Synthetic 4K frame ({IMAGE_W}x{IMAGE_H}) with {len(tiles)} slices of {SLICE_SIZE} px, '
f'{int(OVERLAP * 100)} % overlap')
handles = [plt.Line2D([], [], marker='o', ls='', color=c, label=lbl) for c, lbl in [
('#2e7d32', 'Detected in both modes'), ('#ef8f00', 'Detected only with slicing'),
('#c62828', 'Missed in both')]]
ax.legend(handles=handles, loc='upper right', framealpha=0.9)
graphic = decode_graph(plt)
# 6. Gráfico 2: la curva del detector de juguete vista desde cada modo.
fig, ax = plt.subplots(figsize=(10, 5))
size_axis = np.linspace(4, 120, 300)
ax.plot(size_axis, detection_probability(size_axis * full_scale) * 100, color='#c62828', lw=2,
label=f'Full frame (objects shrink x{1 / full_scale:.0f} before the network)')
ax.plot(size_axis, detection_probability(size_axis) * 100, color='#2e7d32', lw=2,
label='Sliced inference (native resolution)')
ax.hist(sizes, bins=30, weights=np.full(N_OBJECTS, 100 / N_OBJECTS) * 3, color='#5b7a9a', alpha=0.25,
label='Object size distribution (scaled)')
ax.set_xlabel('Object size in the original 4K frame (px)')
ax.set_ylabel('Detection probability (%)')
ax.set_title('Toy detector: detection probability vs. object size, by inference mode')
ax.legend(loc='center right')
graphic2 = decode_graph(plt)
computed = {'answer': answer, 'graphic': graphic, 'graphic2': graphic2, 'summary_html': summary_html}
cache.set("sahi_demo:results", computed, 60 * 15)
context = {'views': views_code, 'content': content_json_data, 'template': template_code, **computed}
return render(request, 'data-science/machine-learning/sahi_slicing.html', context)
except FileNotFoundError:
context = {'description': 'File not found. Please check the file path or create the file if it does not',
'reference': "Sliced inference demo"}
return render(request, 'data-science/retrieve_error.html', context)
except TemplateDoesNotExist:
context = {'description': 'Template not found. Please check the file path or create the file if it does not',
'reference': "Sliced inference demo"}
return render(request, 'data-science/retrieve_error.html', context)