"""Reconhecimento de pessoas: marcar rostos, reconhecer, saudade por pessoa, afinidade e privacidade.""" import random import sys import tempfile import time import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from reconhecimento import DIAS_SEM_NOME, Reconhecimento, cosseno # noqa: E402 from vida import Vida # noqa: E402 DIA = time.mktime((2026, 10, 12, 15, 0, 0, 0, 0, -1)) def rosto_de(semente, ruido=0.25): """Impressão de 128 números: a mesma pessoa varia pouco, pessoas diferentes são bem diferentes.""" base = random.Random(semente) r = random.Random() return [base.gauss(0, 1) + r.gauss(0, ruido) for _ in range(128)] class VisaoFalsa: motivo = '' def __init__(self): self.fotos = {} def pronta(self): return True def rostos(self, jpeg): return [(v, b'\xff\xd8recorte\xff\xd9', 0.95, 120) for v in self.fotos.get(jpeg, [])] class ReconhecimentoTest(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.visao = VisaoFalsa() self.r = Reconhecimento(self.tmp.name, visao=self.visao, iniciar=False) self.vistos, self.linhas = [], [] self.r.ao_reconhecer = lambda p, conf, agora: self.vistos.append((p['nome'], round(conf, 2), p.get('visto_antes'))) self.r.console = lambda origem, texto, motivo='', nivel='info', agora=None: self.linhas.append(texto) def tearDown(self): self.tmp.cleanup() def foto(self, evento, *pessoas, agora=DIA): jpeg = ('foto-%d' % evento).encode() self.visao.fotos[jpeg] = [rosto_de(p) for p in pessoas] return self.r.analisar(evento, jpeg, agora) def test_impressoes_da_mesma_pessoa_se_parecem(self): self.assertGreater(cosseno(rosto_de('ana'), rosto_de('ana')), 0.8) self.assertLess(abs(cosseno(rosto_de('ana'), rosto_de('bia'))), 0.3) def test_marcar_e_depois_reconhecer_pelo_nome(self): (pessoa, _), = self.foto(1, 'ana') # ninguém cadastrado: rosto sem nome self.assertIsNone(pessoa) self.foto(2, 'ana'); self.foto(3, 'ana') sem_nome = [s['id'] for s in self.r.status()['sem_nome']] self.assertEqual(len(sem_nome), 3) self.r.marcar(sem_nome, nome='Ana') st = self.r.status() self.assertEqual([p['nome'] for p in st['pessoas']], ['Ana']) self.assertEqual(st['pessoas'][0]['amostras'], 3) self.assertEqual(st['sem_nome'], []) # foto nova: reconhece a Ana e não a Bia (pessoa, conf), = self.foto(4, 'ana', agora=DIA + 60) self.assertIsNotNone(pessoa) self.assertEqual(self.vistos[-1][0], 'Ana') self.assertIn('Reconheceu Ana', self.linhas) (pessoa, _), = self.foto(5, 'bia', agora=DIA + 120) self.assertIsNone(pessoa) self.assertEqual(len(self.r.status()['sem_nome']), 1) def test_marcar_a_pessoa_reconhece_os_rostos_antigos_sem_nome(self): for i in range(4): self.foto(10 + i, 'bia') ids = [s['id'] for s in self.r.status()['sem_nome']] self.r.marcar(ids[:1], nome='Bia') # marcou um: os outros parecidos já passam a ser dela st = self.r.status() self.assertEqual(st['sem_nome'], []) self.assertEqual(st['pessoas'][0]['amostras'], 1) def test_saudade_conta_quando_cada_um_foi_visto(self): self.foto(1, 'ana') self.r.marcar([s['id'] for s in self.r.status()['sem_nome']], nome='Ana') self.foto(2, 'ana', agora=DIA + 100) self.foto(3, 'ana', agora=DIA + 5000) self.assertEqual(self.vistos[-1][2], DIA + 100) # visto antes desta foto def test_rostos_parecidos_ficam_no_mesmo_grupo(self): self.foto(1, 'ana'); self.foto(2, 'bia'); self.foto(3, 'ana'); self.foto(4, 'ana') grupos = sorted(len(g) for g in self.r.status()['grupos']) self.assertEqual(grupos, [1, 3]) def test_descartar_renomear_apagar(self): self.foto(1, 'ana'); self.foto(2, 'gato') ana, gato = sorted(s['id'] for s in self.r.status()['sem_nome']) self.r.marcar([gato]) # não é ninguém self.r.marcar([ana], nome='ana') self.r.marcar([ana], nome='ANA') # mesmo nome: mesma pessoa pid = self.r.status()['pessoas'][0]['id'] self.r.renomear(pid, 'Ana Paula') self.assertEqual(self.r.status()['pessoas'][0]['nome'], 'Ana Paula') self.assertEqual(len(list((Path(self.tmp.name) / 'fotos' / 'rostos').glob('*.jpg'))), 1) self.r.apagar_pessoa(pid) # apaga a pessoa e tudo dela self.assertEqual(self.r.status()['pessoas'], []) self.assertEqual(self.r.status()['rostos'], 0) self.assertEqual(list((Path(self.tmp.name) / 'fotos' / 'rostos').glob('*.jpg')), []) with self.assertRaises(ValueError): self.r.marcar([], nome='x') def test_rostos_sem_nome_somem_depois_de_uns_dias(self): self.foto(1, 'ana', agora=DIA) self.foto(2, 'bia', agora=DIA) self.r.marcar([self.r.status()['sem_nome'][0]['id']], nome='Bia') self.assertEqual(self.r.limpar_antigos(DIA + DIAS_SEM_NOME * 86400 + 1), 1) # a amostra da Bia fica self.assertEqual(self.r.status()['rostos'], 1) def test_desligado_nao_analisa_fotos_novas(self): self.r.configurar(dict(ligado=False)) self.r.nova_foto(1, 'x.jpg') self.assertTrue(self.r.fila.empty()) with self.assertRaises(ValueError): self.r.configurar(dict(outra=True)) class VidaComNomesTest(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.v = Vida(Path(self.tmp.name) / 'vida.json', iniciar=False, rng=random.Random(1)) self.ordens, self.afinidade = [], [] self.v.enfileirar = self.ordens.append self.v.celular_online = lambda: True self.v.celular_dormindo = lambda: False self.v.madrugada = lambda agora: False self.v.reconhece = lambda: True self.v.gostou_de = lambda pessoa, quanto=1.0: self.afinidade.append((pessoa, quanto)) self.v.saudou_dia = '2026-10-12' self.perto = False self.v.alguem_perto = lambda: self.perto def tearDown(self): self.tmp.cleanup() def falas(self): return [o[6:] for o in self.ordens if o.startswith('dizer ')] def test_espera_saber_quem_chegou_para_cumprimentar_pelo_nome(self): self.v.ultimo_perto, self.perto = DIA - 2 * 3600, True self.v.tick(DIA) self.assertEqual(self.falas(), []) # esperando a foto self.v.reconhecido(dict(id=1, nome='Ana', afinidade=25, visto_antes=DIA - 6 * 3600), 0.8, DIA + 3) self.assertEqual(self.falas(), ['Ana! Senti sua falta!']) # 6 h sem a Ana, mesmo a casa tendo gente há 2 h self.assertIsNone(self.v.reencontro_pendente) def test_sem_reconhecer_cumprimenta_sem_nome_depois_de_esperar(self): self.v.ultimo_perto, self.perto = DIA - 2 * 3600, True self.v.tick(DIA) self.v.tick(DIA + 5) self.assertEqual(self.falas(), []) self.v.tick(DIA + 9) self.assertEqual(self.falas(), ['Oi! Você voltou!']) def test_outra_pessoa_chega_com_alguem_ja_por_perto(self): self.v.reconhecido(dict(id=2, nome='Bia', afinidade=0, visto_antes=DIA - 3 * 3600), 0.7, DIA) self.assertEqual(self.falas(), ['Oi, Bia! Você voltou!']) self.v.reconhecido(dict(id=2, nome='Bia', afinidade=0, visto_antes=DIA), 0.7, DIA + 60) # acabou de ver self.assertEqual(len(self.falas()), 1) self.v.estimulo('carinho', DIA + 90) # carinho logo depois: afinidade com a Bia self.assertEqual(self.afinidade, [(2, 1.0)]) if __name__ == '__main__': unittest.main()