
    `gjO                    b   U d Z ddlmZ ddlZddlZddlZddlmZ ddlm	Z	m
Z
  ej                  e      Ze
Ze	Zg dZded<    ee      Z ed	 eD              Zeez  Zeez  ZeD  ci c]	  \  } }}| | c}}} Zd
ed<   dZdZdZdZd<dZd=dZd>dZd?d@dZ d Z!ddddAdZ"d Z#d Z$d Z%d Z&dBdZ'd Z(d=dZ)dCdZ*dDd Z+dEd!Z,d"d#dFd$Z-dGd%Z.d"d#dFd&Z/d'd(dHd)Z0dId*Z1dJd+Z2dKd,Z3dd-d.d/	 	 	 	 	 	 	 	 	 dLd0Z4dMd1Z5dNd2Z6ed3dOd4Z7d.d5dPd6Z8d7 Z9dQd8Z:dRd9Z;dSd:Z<dTd;Z=yc c}}} w )Uu  Deterministic spritesheet assembly — generated row strips → Hermes atlas.

Image-generation models are good at *drawing* a row of poses but bad at exact
grid geometry, so the model never owns the atlas layout: it produces one loose
horizontal strip per state, and these deterministic ops slice that strip into
clean, centered, transparent ``192x208`` cells and pack them into the sheet our
renderer reads.

The atlas follows the **petdex/Codex standard**: 8 columns x 9 rows of
``192x208`` cells (``1536x1872``), with the row order + per-row frame counts
from OpenAI's ``hatch-pet`` skill. Our renderer (:mod:`agent.pet.render`) keys
frames as ``rows = states, cols = frames`` via
:data:`agent.pet.constants.CODEX_STATE_ROWS`, and a pet built here is a valid
``petdex submit`` spritesheet. Rows shorter than 8 columns leave the trailing
cells fully transparent.

Note ``running`` is the *working* state (in-place processing), NOT locomotion —
``running-right`` / ``running-left`` are the actual directional walk cycles.

The frame-segmentation, fit-to-cell, and transparency-residue logic is adapted
from OpenAI's ``hatch-pet`` skill (openai/skills, Apache-2.0).
    )annotationsN)Path)FRAME_HFRAME_W)	)idler      )zrunning-right      )zrunning-left   r
   )waving      )jumpingr      )failedr   r
   )waitingr   r   )running   r   )reviewr
   r   zlist[tuple[str, int, int]]	ROW_SPECSc              #  (   K   | ]
  \  }}}|  y wN ).0_counts      K/root/.hermes/venv/lib/python3.12/site-packages/agent/pet/generate/atlas.py	<genexpr>r   8   s     11ee1s   zdict[str, int]FRAME_COUNTS   
      g
ףp=
?c                n    t        j                  | |d   z
  dz  ||d   z
  dz  z   ||d   z
  dz  z         S )Nr   r   r	   )mathsqrt)rgbkeys       r   _color_distancer*   N   sB    99a#a&jQ&!c!f*)::a#a&jQ=NNOO    c                   | j                  d      j                         }|d   t        kD  ry| j                  d      j                         }t	        |dt        dz          }| j
                  | j                  z  }||dz  kD  S )z:True if the strip already carries a real alpha background.Ar   FNr	   g?)
getchannel
getextrema_ALPHA_FLOOR	histogramsumwidthheight)imageextremahisttransparenttotals        r   _has_transparencyr:   R   s{    s#..0GqzL C **,Dd-\A-./KKK%,,&E%%r+   c                ,   ddl m} | j                  | j                  }}| j	                         } |       }d|dz
  dfd|dz
  f|dz
  |dz
  ffD ]+  \  }}|||f   \  }}	}
}|t
        kD  s|||	|
fxx   dz  cc<   - |sy|j                  d      d   d   S )z@Sample the four corners and return the most common opaque color.r   )Counterr   r   r	   )r      r   )collectionsr<   r3   r4   loadr0   most_common)r5   r<   whpxcounterxyr&   r'   r(   as               r   _dominant_corner_colorrI   ^   s    #;;qA	ByG!a%aQZ!a%Q@ $11X
1a|Q1I!#$ q!!$Q''r+   c           	       	
 ddl m} | j                         \  }}}}|\  
	|j                  |j                  |j	                  
fd      |j	                  	fd            |j	                  fd            S )a)  An ``L`` mask, 255 where a pixel is within *tol* per-channel of *key*.

    Tight on purpose: it only marks near-pure backdrop so trapped chroma pockets
    seed the flood, while chroma-*tinted* character pixels stay outside it. Built
    with channel point-ops (fast C), no per-pixel Python.
    r   )
ImageChopsc                .    t        | z
        k  rdS dS Nr>   r   abs)vkrtols    r   <lambda>z _near_key_mask.<locals>.<lambda>{       SR[C%7c Q r+   c                .    t        | z
        k  rdS dS rM   rN   )rP   kgrR   s    r   rS   z _near_key_mask.<locals>.<lambda>|   rT   r+   c                .    t        | z
        k  rdS dS rM   rN   )rP   kbrR   s    r   rS   z _near_key_mask.<locals>.<lambda>~   s    QV!3#  r+   )PILrK   splitdarkerpoint)r5   r)   rR   rK   r&   r'   r(   _arX   rV   rQ   s     `     @@@r   _near_key_maskr^   n   sp     ++-KAq!RJBBGG>?GG>?	
 	
:; r+   c                    ddl m} | j                  | j                  d      j	                  |j                  d                   | S )u  Shave the 1px antialiased edge ring left after keying.

    Chroma keying can't catch the antialiased band where the sprite meets the
    backdrop — those pixels are a key/sprite blend, too far from the key to be
    removed, so they ring the cutout in magenta/green. Erode the alpha by one
    pixel (a 3x3 min filter) to drop that contaminated ring; the sprite's own
    thick dark outline keeps the silhouette intact. Built on a C-level filter, no
    per-pixel Python.
    r   )ImageFilterr-   r   )rY   r`   putalphar.   filter	MinFilter)rgbar`   s     r   	_defringere      s7      MM$//#&--k.C.CA.FGHKr+   g     V@)
chroma_key	thresholdc          	     b   ddl m} ddlm}m} | j                  d      }t        |      rt        |      S |xs t        |      |j                  |j                  }}|j                         dfd}	t              t              z
  dk\  rxt        |      }
|j                  d      j!                  d       }|j#                  |
|      }|j%                  |j'                  d|j(                  d	      ||      }t+        |      S t-        ||z        }t-        ||z        } |       }t/        |      D ]B  }d|d
z
  fD ]6  } |	||      s|||z  |z      rd
|||z  |z   <   |j1                  ||f       8 D t/        |      D ]B  }d|d
z
  fD ]6  } |	||      s|||z  |z      rd
|||z  |z   <   |j1                  ||f       8 D t              t              z
  dk\  rRt3        t        |      j5                               D ],  \  }}
|
s	||   rd
||<   |j1                  ||z  ||z  f       . |r|j7                         \  }}d
|||z  |z   <   |d
z   |f|d
z
  |f||d
z   f||d
z
  ffD ]S  \  }}d|cxk  r|k  sn d|cxk  r|k  s!n $||z  |z   }||   r2d
||<    |	||      sA|j1                  ||f       U |r|j9                  d||ft;        |            j!                  d       }t+        |j%                  |j'                  d|j(                  d	      ||            S )u  Return *image* (RGBA) with its flat background keyed out to transparent.

    If the strip already has a transparent background we leave it alone; else we
    key out *chroma_key* (or the dominant corner color when not given) via a
    **border flood-fill**: only background-coloured pixels *connected to an edge*
    are removed. A global color match (the old approach) punched holes in the pet
    wherever an interior highlight happened to match the backdrop — e.g. a pug's
    light belly against a near-white background — which then showed through as the
    window behind. Flood-fill keeps those interior pixels because they aren't
    reachable from the border without crossing the (non-background) pet.
    r   deque)ImagerK   RGBAc                T    | |f   \  }}}}|t         kD  xr t        |||      k  S r   )r0   r*   )	rF   rG   r&   r'   r(   rH   r)   rD   rg   s	         r   _is_bgz!remove_background.<locals>._is_bg   s8    1X
1a<NOAq!S$AY$NNr+   x   r-   c                    | t         kD  rdS dS rM   r0   )rH   s    r   rS   z#remove_background.<locals>.<lambda>   s    Q=Mc ST r+   r   r   r   r   r	   Lc                    | rdS dS rM   r   )rP   s    r   rS   z#remove_background.<locals>.<lambda>   s    as UV r+   rF   intrG   rv   returnbool)r?   rj   rY   rk   rK   convertr:   _repair_internal_alpha_holesrI   r3   r4   r@   maxminr^   r.   r\   r[   	compositenewsizere   	bytearrayrangeappend	enumerategetdatapopleft	frombytesbytes)r5   rf   rg   rj   rk   rK   rd   rB   rC   rn   nearopaqueremove_maskkeyedvisitedremovequeuerF   rG   inxnyidxmaskr)   rD   s     `                     @@r   remove_backgroundr      s;    "%== D+D11

4.t4C::t{{qA	BO 3x#c(c!dC(%++,TU ''f5		&$))\ JDR]^AG q1uF$)GE 1X %QU 	%Aa|GAEAI$6%&A	"aV$	%%
 1X %QU 	%Aa|GAEAI$6%&A	"aV$	%% 3x#c(c! c!:!B!B!DE 	.GAtGAJ
a!eQ!V_-	.
 }}1q1uqyAqzAE1:1q5zAq1u:F 	/FBB{{qB{{1frks|#$GCLb"~b"X.	/  ??3Af6<<=VWDU__UYYvtyy,%OQUW[\]]r+   c                h   ddl m | j                  d      }|j                  \  |j	                         t        z        d	fdd
fd}t              D ]-  }ddz
  fD ]!  } ||      s|z  |z      r |||       # / t              D ]-  }ddz
  fD ]!  } ||      s|z  |z      r |||       # / dfd}dfd}t              D ]A  \  }}|   r|z  }|z  } ||      s  |||      }	 ||	      }
|	D ]  \  }}|
||f<    C |S )a  Fill transparent islands fully enclosed by opaque sprite pixels.

    Some providers return "transparent" PNGs with swiss-cheese alpha inside the
    character. Border flood-fill cannot see those because there is no opaque
    backdrop to key, so repair the alpha mask itself: transparent components that
    touch an image edge remain background; transparent components enclosed by
    the sprite are filled with the average color of their opaque neighbours.
    r   ri   rl   c                &    | |f   d   t         k  S )Nr   rq   )rF   rG   rD   s     r   _is_transparentz5_repair_internal_alpha_holes.<locals>._is_transparent   s    !Q$x{l**r+   c                D    	| |fg      }d|z  | z   <   |r|j                         \  }}|dz   |f|dz
  |f||dz   f||dz
  ffD ]S  \  }}d|cxk  rk  sn d|cxk  r
k  s!n $|z  |z   }|   r2 ||      s<d|<   |j                  ||f       U |ry y Nr	   r   r   r   )sxsyr   rF   rG   r   r   r   r   rj   rC   r   rB   s           r   _mark_border_componentz<_repair_internal_alpha_holes.<locals>._mark_border_component   s    (-Bxj(9 Q==?DAqE1:AqzAq1u:1q5zJ /B;Q;1;Q;q&2+C"3<OB,C'(b"X./ r+   r	   c                n    
| |fg      }d|z  | z   <   g }|r|j                         \  }}|j                  ||f       |dz   |f|dz
  |f||dz   f||dz
  ffD ]S  \  }}d|cxk  rk  sn d|cxk  rk  s!n $|z  |z   }|   r2 	||      s<d|<   |j                  ||f       U |r|S r   r   )r   r   r   pixelsrF   rG   r   r   r   r   rj   rC   r   rB   s            r   _collect_holez3_repair_internal_alpha_holes.<locals>._collect_hole  s    (-Bxj(9 Q(*==?DAqMM1a&!E1:AqzAq1u:1q5zJ /B;Q;1;Q;q&2+C"3<OB,C'(b"X./  r+   c                   g }t        |       }| D ]v  \  }}|dz   |f|dz
  |f||dz   f||dz
  ffD ]T  \  }}d|cxk  rk  sn d|cxk  rk  s!n $||f|vs+||f   \  }}}	}
|
t        kD  sA|j                  |||	f       V x |syt        t	        d |D              t        |      z        t        t	        d |D              t        |      z        t        t	        d |D              t        |      z        dfS )Nr	   r   )r   r   r   r>   c              3  &   K   | ]	  }|d      ywr   Nr   r   cs     r   r   zD_repair_internal_alpha_holes.<locals>._fill_color.<locals>.<genexpr>,       ,qad,   c              3  &   K   | ]	  }|d      ywr	   Nr   r   s     r   r   zD_repair_internal_alpha_holes.<locals>._fill_color.<locals>.<genexpr>-  r   r   c              3  &   K   | ]	  }|d      ywr   Nr   r   s     r   r   zD_repair_internal_alpha_holes.<locals>._fill_color.<locals>.<genexpr>.  r   r   r>   )setr0   r   roundr2   len)holesamplesseenrF   rG   r   r   r&   r'   r(   rH   rC   rD   rB   s              r   _fill_colorz1_repair_internal_alpha_holes.<locals>._fill_color   s   .04y 	2DAqE1:AqzAq1u:1q5zJ 2B;Q;1;Q;B843G!#BFJAq!Q<'1ay1	2	2 !#,G,,s7|;<#,G,,s7|;<#,G,,s7|;<	
 	
r+   ru   )r   rv   r   rv   rw   None)r   rv   r   rv   rw   list[tuple[int, int]])r   r   rw   ztuple[int, int, int, int])r?   rj   ry   r   r@   r   r   r   )r5   rd   r   rF   rG   r   r   startr   r   colorhxhyr   rj   rC   rD   r   rB   s                @@@@@@r   rz   rz      sx    "== D99DAq	BAG+
/ 
/ 1X -QU 	-Aq!$WQUQY-?&q!,	-- 1X -QU 	-Aq!$WQUQY-?&q!,	--
 
$ g& 
q5>AIQJq!$Q"D! 	FBBr2vJ	
 Kr+   c           
        ddl m} |j                  dt        t        fd      }t        |       } | j                         }||S | j                  |      }t        t        z
  }t        t        z
  }t        ||j                  z  ||j                  z  d      }|dk7  rh|j                  t        dt        |j                  |z              t        dt        |j                  |z              f|j                  j                         }t        |j                  z
  dz  }t        |j                  z
  dz  }	|j#                  |||	f       |S )zGCrop to content, scale to fit a padded cell, and center on transparent.r   rk   rl   rr   g      ?r	   r   )rY   rk   r~   
CELL_WIDTHCELL_HEIGHT_drop_side_bleedgetbboxcrop	_CELL_PADr|   r3   r4   resizer{   r   
ResamplingNEARESTalpha_composite)
r5   rk   targetbboxspritemax_wmax_hscalelefttops
             r   _fit_to_cellr   C  s   YYv
K8,GFU#E==?D|ZZF"E)#E$efmm&;SAE|
 E&,,./0#av}}u?T9U2VW$$
 %!+D&1
,C
6D#;/Mr+   c           	        ddl m} | j                  d      }|j                  \  }}t	        |      }t        |      }t        |      dk  r|S |D cg c]  \  }}t        |||        }	}}t        |	      t        z  }
t        ||	      D cg c]  \  }}||
k\  s| }}}t        |      t        |      k(  r|S |j                         }|j                  d||fd      d}}|D ]3  \  }}||kD  r'|j                  |j                  |d||f      |df       |}5 ||k  r'|j                  |j                  |d||f      |df       |S c c}}w c c}}w )a  Remove tiny separated left/right lobes before fitting a frame.

    Frogger showed the failure mode: a good centered pose plus a thin vertical
    sliver from the neighbouring pose. By the time it reaches a cell, that sliver
    may be close enough to the subject that component extraction already grouped
    it. A horizontal alpha projection still reveals it as a small side lobe with
    a low mass compared to the main silhouette. Drop only those low-mass lobes;
    keep large lobes so wide poses and real limbs survive.
    r   r   rl   r   rr   )rY   rk   ry   r   _column_profile_content_runsr   r2   r{   _SIDE_LOBE_RATIOzipcopyr~   paster   )r5   rk   rd   rB   rC   profilerunslr&   masses	keep_massrunmkeepcutprevr   rights                     r   r   r   `  sS    == D99DAqd#G!D
4y1},01DAqc'!A,1F1F..I!$/BFC1	>CBDB
4yCI 99;D		&1a&,7C e$;JJsxxq$ 23dAY? ax

388T1aO,tQi8K 2Bs   E	EEc           	        ddl m} | j                  d      j                         }|j                  \  }}|j                  d      d
d}t        |      D cg c](  t        fdt        |      D              |dz  k\  r* }}t        |      D cg c](  t        fdt        |      D              |dz  k\  r* }	}|j                  d|j                  d	      }
 ||      D ],  \  }}|j                  |
j                  d|||f      d|f       .  ||	      D ],  \  }}|j                  |
j                  |d||f      |df       . |S c c}w c c}w )aj  Remove thin slot-spanning guide/floor/divider lines.

    Gemini will sometimes satisfy "baseline" / "cell" language by drawing
    literal horizontal floors or vertical panel dividers. They survive chroma
    keying and connect otherwise clean poses. Drop only *thin* rows/columns that
    span nearly the whole slot; thick sprite body rows are left alone.
    r   r   rl   r-   c                    g }d }d }| D ]?  }||x}}
|||dz   k(  r|}|#|!||z
  dz   dk  r|j                  ||dz   f       |x}}A |#|!||z
  dz   dk  r|j                  ||dz   f       |S )Nr	   r   )r   )indicesgroupsr   r   r   s        r   _thin_groupsz,_erase_long_axis_lines.<locals>._thin_groups  s    (*  		C}""C4!8O T%5$,:Ja:OudQh/0ED		 !1dUlQ6F!6KMM5$(+,r+   c              3  X   K   | ]!  }j                  |f      t        kD  sd  # ywr   getpixelr0   )r   rF   alpharG   s     r   r   z)_erase_long_axis_lines.<locals>.<genexpr>  %     LQennaV&<|&KqL   **g333333?c              3  X   K   | ]!  }j                  |f      t        kD  sd  # ywr   r   )r   rG   r   rF   s     r   r   z)_erase_long_axis_lines.<locals>.<genexpr>  r   r   rr   )r   	list[int]rw   r   )rY   rk   ry   r   r   r.   r   r2   r~   r   r   )r5   rk   rd   rB   rC   r   rG   	wide_rowsrF   	tall_colsclearr   bottomr   r   r   s         ` `      @r   _erase_long_axis_linesr     s`    == %%'D99DAqOOC E( qL%(LLPQTXPXX 	
I  qL%(LLPQTXPXX 	
I  IIfdii6E#I. >V

5::q#q&12QH=>#I. ?e

5::tQq12T1I>?K!
s   -E-Ec                n   ddl m} | j                  d      }|j                         }|g S |\  }}}}||z
  ||z
  }	}|j	                  d      j                         }
t        ||	z        }g }t        ||	z        D ]7  }||   r
||z  ||z  }}||z   ||z   }}d||<   |
||f   t        k  r2 |||fg      }|x}}|x}}d}|r|j                         \  }}|dz  }t        ||      t        ||      }}t        ||      t        ||      }}|dz   |f|dz
  |f||dz   f||dz
  ffD ]^  \  }}d|cxk  r|k  sn d|cxk  r|	k  s!n $||z  |z   }||   r2d||<   |
||z   ||z   f   t        kD  sL|j                  ||f       ` |r|j                  ||z   ||z   ||z   dz   ||z   dz   f|f       : |S )a(  Connected opaque components as ``[(bbox, mass)]``.

    A full ML segmenter would be overkill here: after chroma keying, "the pet" is
    the dominant connected alpha component inside each known slot. Tiny detached
    sparkles, tears, UI dots, and neighbour slivers are separate components.
    r   ri   rl   r-   r	   )r?   rj   ry   r   r.   r@   r   r   r0   r   r|   r{   r   )r5   rj   rd   r   l0t0r0b0rB   rC   r   r   outr   r   r   axayr   r   r   r   r   massrF   rG   r   r   r   s                                r   _component_boxesr     s    "== D<<>D|	NBB7BGqAOOC %%'EAG79Cq1u S5>EQJBb"r'BR=L((-Bxj(9uf==?DAqAIDdA,E1%Dc1+s61~CE1:AqzAq1u:1q5zJ 3B;Q;1;Q;q&2+C"3<'( b"r'!12\A!LL"b23  	

R$YS"u*q."v+/JDQR3S4 Jr+   c           	     n   ddl m} t        |       }t        |      }|s|S t	        |d       \  }}|\  }}}}	t	        d||z
        }
g }|D ]  \  }}||k(  r|j                  |       |\  }}}}t	        dt        ||      t	        ||      z
        }||z   dz  }||
dz  z
  |cxk  xr ||
dz  z   k  nc }|t	        d|d	z        k\  s~||
d
z  k\  s|s|j                  |        |j                  d|j                  d      }|D ]+  }|j                  |j                  |      |d   |d   f       - |S )z:Keep the slot's real subject; drop detached effects/noise.r   r   c                    | d   S Nr	   r   items    r   rS   z'_isolate_slot_subject.<locals>.<lambda>  s
    d1g r+   r)   r	   r   g      ?   gQ?g333333?rl   rr   )rY   rk   r   r   r{   r   r|   r~   r   r   r   )r5   rk   rd   compsmain_box	main_massmlmtmrmbmwr   boxr   r   _topr   _bottomoverlapcenter_x	near_mainr   s                         r   _isolate_slot_subjectr    sU   !%(DT"Ee)=>HiNBB	QRB,.D 	T(?KK%("dE7aUB#dB-785LA%"t)^DR"t)^D	 3r9u,--7b3h3F)KK ))FDII|
4C >DIIcNSVSV,<=>Jr+   c           
        | j                         }|y| j                  \  }}|\  }}}}t        dt        dt	        |dz                    }t        dt        dt	        |dz                    }	||k\  xr ||	k\  xr ||z
  |k\  xr ||z
  |	k\  S )z8True when content has empty room on all four slot edges.Fr      g?r    {Gz?)r   r   r{   r|   r   )
r5   r   rB   rC   r   r   r   r   min_xmin_ys
             r   _has_slot_paddingr    s    ==?D|::DAq#D#uf3r5U+,-E3r5T?+,E5=XSE\Xa%i5.@XQZSXEXXr+   c                    t        |      D cg c]'  }t        || z  |z        t        |dz   | z  |z        f) c}S c c}w r   )r   r   )r3   frame_countr   s      r   _slot_boundsr    sN     {# 
q5y;&	'A/L)MN  s   ,=c                X   | sg S t        d | D              }t        d|t        |      dz     dz        }g }g }t        | d       D ]  }|d   |d   z   dz  }t        |      D ]S  \  }}t	        ||z
        |k  s||   j                  |       t        d	 ||   D              t        ||         z  ||<    q |j                  |g       |j                  |        t        t        ||d
      d       D 	
cg c]  \  }	}
|
	 }}	}
|D ]  }
|
j                  d         |S c c}
}	w )u?   Group component boxes into visual rows, then sort left→right.c              3  F   K   | ]  }t        d |d   |d    z
          yw)r	   r   N)r{   r   r(   s     r   r   z(_group_component_rows.<locals>.<genexpr>  s#     8QSAaD1Q4K(8s   !r  r   g?c                    | d   | d   z   dz  S )Nr	   r   r   r   r(   s    r   rS   z'_group_component_rows.<locals>.<lambda>#  s    AaD1Q4K1+< r+   r  r	   r   c              3  8   K   | ]  }|d    |d   z   dz    yw)r	   r   r   Nr   r  s     r   r   z(_group_component_rows.<locals>.<genexpr>(  s!      Dq!A$1+!2 Ds   F)strictc                    | d   S )Nr   r   r   s    r   rS   z'_group_component_rows.<locals>.<lambda>-  s    aefgah r+   c                    | d   | d   z   dz  S )Nr   r   r   r  s    r   rS   z'_group_component_rows.<locals>.<lambda>/  s    !qtq0 r+   )	sortedr{   r   r   rO   r   r2   r   sort)boxesheightsrow_tolrowscentersr  cyr   center_centerrowordereds               r   _group_component_rowsr0    sA   	8%88G"gc'la/0478G24DGe!<= 	!fs1vo""7+ 	IAv2;7*Qs#  DDG DDs4PQ7|S
		 KKNN2	 (.c'4.NTh'ij|wsjGj 2012N ks   ;D&c           
        t        |       } d}|rVd}g }dgt        |       z  }t        |       D ].  \  }}||   r|\  }}}}	d||<   t        |dz   t        |             D ]  }
||
   r	| |
   \  }}}}t	        dt        |	|      t	        ||      z
        }t	        dt        |	|z
  ||z
              }t	        dt	        ||      t        ||      z
        }t	        dt        ||z
  ||z
              }||dz  k\  s|t	        d|dz        k  st        ||      t        ||      t	        ||      t	        |	|      f\  }}}}	d||
<   d} |j                  ||||	f       1 |} |rV| S )ab  Merge disconnected parts that clearly belong to one subject.

    Capes, tails, horns, and held props sometimes key as separate components.
    Merge components on the same visual row when their vertical spans overlap and
    the horizontal gap is tiny compared with the component size. Do not bridge the
    much larger gaps between separate poses.
    TFr	   r   g?r"   g)\(?)listr   r   r   r{   r|   r   )r&  changedmergedusedr   rH   alatarabjblbtbrbb	v_overlapmin_hgapmin_ws                      r   _merge_related_boxesrC  3  s    KEG
24wU#e$ 	,DAqAwNBBDG1q5#e*- #7!&qBB3r2;R#<=	As27BG45!SR[3r2;67As27BG45,B8M1M%(R[#b"+s2r{CPRTVK%WNBB"DG"G# MM2r2r*+#	,$ - . Lr+   Frequire_paddingc               Z    ddl m dfd} ||       xs  |t        |             S )a  Extract frame subjects as connected non-background objects.

    This is the robust path for models that ignore "one horizontal row" and emit a
    2D sprite grid. We count real opaque subject components, discard tiny
    detached effects, sort in reading order, and return exactly *frame_count*
    frames. Slot slicing is only a fallback when object detection can't satisfy
    the contract.
    r   r   c                p   t        |       }|sy t        d |D              }t        |D cg c]  \  }}|t        d|dz        k\  s| c}}      }t        |      k  ry t	        |      }|D cg c]  }|D ]  }|  c}}d  }t        |      k  ry rt        dt        dt        | j                  dz                    }	t        dt        dt        | j                  dz                    }
|D ]7  \  }}}}||	k  s*||
k  s%| j                  |z
  |	k  s| j                  |z
  |
k  s7 y  t        |      d	kD  }g }|D ]f  \  }}}}t        d
t        ||z
  dz              }t        d
t        ||z
  dz              }|rOt        d||z
        t        d||z
        t        | j                  ||z         t        | j                  ||z         f}nTd	k(  rdd| j                  | j                  f}n4t        d||z
        dt        | j                  ||z         | j                  f}j                  d|d   |d   z
  |d   |d	   z
  fd      }||d   z
  ||d	   z
  ||d   z
  ||d	   z
  f}|j                  | j                  ||||f      |d   |d	   f       |j                  |       i |S c c}}w c c}}w )Nc              3  &   K   | ]	  \  }}|  y wr   r   )r   _boxr   s      r   r   z4_component_crops.<locals>.attempt.<locals>.<genexpr>g  s     .WT1q.r   @   Q?r   r  g{Gz?r    gQ?r	   r
   g{Gz?r   rl   r   r   rr   )r   r{   rC  r   r0  r|   r   r3   r4   r~   r   r   r   )sourcer  max_massr  r   subjectsr)  r.  r/  r  r  r   r   r   r   multirowframespad_xpad_ycrop_boxframerelrk   r  rE  s                         r   attemptz!_component_crops.<locals>.attemptb  s    (...'e(hdtsSUW_bfWfOgGg(hix=;&$X."&63#633636|Dw<+%3r5)<#=>?E3r5)>#?@AE,3  (c5&%<3;&,,2F2NRXR_R_bhRhkpRp  t9q=(/ 	!$D#uf5%$,$!678E5&3,$!678E4%<(3;'eem4v~6	 !q&,,>  4%<0!Suu}5UW]WdWdeIIfx{Xa['@(1+PXYZP[B[&\^jkE(1+%sXa['8%(1+:MvX`abXcOcdC!!&++tS%.H"ICPQFTWXYTZK[\ MM% -	!. S )i
 7s   J,
J,
2J2)rw   list | None)rY   rk   r   )stripr  rE  rV  rk   s    `` @r   _component_cropsrY  W  s*     /b 5>CW%;E%BCCr+   c           
        |dk  r| S | j                         }|j                         }|j                  |z  }t        dt	        dt        |dz                    }t        d|      D ]}  }t        ||z        }t        d||z
        }t	        |j                  ||z   dz         }	t        ||	      D ]3  }
t        |j                        D ]  }||
|f   \  }}}}|||df||
|f<    5  |S )a  Cut thin vertical gutters at expected frame boundaries before labeling.

    Generated rows often have a shared shadow, glow, motion smear, or 1px bridge
    that connects neighbouring poses. Component detection then sees one giant
    blob and either fails or falls back to slot slicing. We know the requested
    frame count, so cut a very narrow transparent band at each expected boundary
    before connected-component labeling. If a pose truly overlaps the boundary,
    losing a few pixels is better than exporting merged frames.
    r	   r      gQ?r   )r   r@   r3   r{   r|   r   r   r4   )rX  r  r   rD   slothalfr   rF   r   r   gxgyr&   r'   r(   r]   s                   r   _sever_expected_guttersr`    s     a
**,C	B99{"Dq#b%t,-.D1k" *!d(O1a$hCIIq4x!|,e$ 	*BCJJ' * Rj1aAq\2r6
*	*	* Jr+   c                   | j                   }g }t        | j                  |      D ]L  \  }}t        t	        | j                  |d||f                  }|rt        |      s y|j                  |       N |S )a  Slice *strip* into *frame_count* uniform columns (one coordinate space).

    Equal-width columns keep every frame in a single shared coordinate frame, so
    a later union-crop + shared placement (:func:`normalize_cells`) preserves the
    row's real motion without the per-frame re-centering that makes a pet visibly
    slide. Each slot is cleaned independently so detached effects, floors,
    dividers, and neighbour slivers do not become "frames".
    r   N)r4   r  r3   r   r  r   r  r   )rX  r  rE  rC   rP  r   r   r\  s           r   _slot_cropsrb    st     	AF#EKK= e 5ejj$5RSAT6U VW#4T#:d	
 Mr+   r   )rg   c                   g }d}t        t        |       dgz         D ](  \  }}||kD  r||}||j                  ||f       d}* |S )zContiguous column spans whose alpha mass exceeds *threshold*.

    A column-projection of the alpha mask: empty (background) columns separate
    one pose from the next, so the runs ARE the candidate frames.
    Nr   )r   r2  r   )r   rg   r   r   rF   rP   s         r   r   r     sd     #%DE$w-1#-. 1y=}KK
#E Kr+   c           	     6   t        |       }t        |      }|sy|D cg c]  \  }}t        |||        }}}t        |      dz  }t	        ||      D 	cg c]  \  }}	|	|k\  s| }}}	t        |      |k  ry|D cg c]	  \  }}||g c}}t              |kD  rKt        t        t              dz
        fd      }
|
dz      d   |
   d<   |
dz   = t              |kD  rKD cg c]	  \  }}||f c}}S c c}}w c c}	}w c c}}w c c}}w )uQ  Per-frame ``(left, right)`` column ranges from the row's empty gutters.

    The standard sprite-sheet slice — once poses are separated by real gaps
    (which generation now enforces), splitting is just "find the empty columns":

    * spans == frames → one span per frame.
    * spans  > frames → merge across the smallest gaps. A detached halo/ear sits
      a tiny gap from its body, while the inter-pose gutter is the big gap that
      survives — so over-segmentation (and any over-eager gutter sever) repairs
      itself by collapsing only the small internal gaps.
    * spans  < frames → poses are touching; not separable by gutters (the caller
      raises for ``components`` or falls back to even slots for ``auto``).

    Ranges span content only; the caller crops full cell height, so tall ears /
    halos are never cut.
    Nr  r	   c                ,    | dz      d   |    d   z
  S r   r   )r   r   s    r   rS   z!_frame_x_ranges.<locals>.<lambda>  s#    va!e}Q7G&QR)TU,7V r+   r  )r   r   r2   r{   r   r   r|   r   )rX  r  r   r   r   r&   r   floorr   r   gir   s              @r   _frame_x_rangesrh    s"   " e$G!D -11DAqc'!A,1F1K$E!$/>FC1:C>D>
4y;!%&Aq!f&F
f+
#s6{Q'-VWrAvq)r
126N f+
#  &&tq!QF&& 2> '
 's   DD	&D	D1Dc                    t        |       }|sg S t        d |D              }t        |D cg c]  \  }}|t        d|dz        k\  s| c}}      S c c}}w )Nc              3  &   K   | ]	  \  }}|  y wr   r   )r   rI  r   s      r   r   z-_significant_subject_boxes.<locals>.<genexpr>   s     0JD$40r       rK  )r   r{   rC  )r5   r  rM  r  r   s        r   _significant_subject_boxesrl    sT    U#E	0%00He `dts2xZ^G_?_ `aa `s   A
A
c                   t        |       |k7  rt        d| dt        |              g }t        |       D ]_  \  }}|j                         }|t        d| d      t	        |      }t        |      dk\  rt        d| d      |j                  |       a |dk  ryt        d	 |D              }t        d
 |D              }t        d|t        |      dz           }	t        d|t        |      dz           }
t        |      D ]A  \  }\  }}}}||z
  }||z
  }|t        |	dz  |	dz         kD  s+||
dz  k  s4t        d| d       y)aw  Reject rows where one "frame" is really multiple poses.

    A bad provider roll can collapse a strip into tiny repeated poses. If we let
    that through, normalization sees a huge motion envelope and shrinks the
    entire pet to postage-stamp size. Catch the row here so hatch can regenerate
    it instead of saving a technically non-empty but visually broken atlas.
    	expected z frames, got Nzframe z	 is emptyr   z% contains multiple separated subjectsr	   c              3  2   K   | ]  }|d    |d   z
    yw)r   r   Nr   r  s     r   r   z-_validate_extracted_frames.<locals>.<genexpr>  s     /AAaD1Q4K/   c              3  2   K   | ]  }|d    |d   z
    yw)r   r	   Nr   r  s     r   r   z-_validate_extracted_frames.<locals>.<genexpr>  s     0QQqTAaD[0rp  r         @`   皙?z is a multi-pose width outlier)r   
ValueErrorr   r   rl  r   r$  r{   )rP  r  r&  r   rT  r   rN  widthsr'  med_wmed_hr   r   r   r   r3   r4   s                    r   _validate_extracted_framesry    st    6{k!9[Ms6{mLMMEf% 5}}<vaS	233-e4x=AvaS(MNOOT a///F0%00G6#f+*+,E73w<1,-.E)25)9 I%%D#uf# 3us{EBJ//Feck4IvaS(FGHHIr+   autoT)rf   methodfitc                  ddl m} t        | t        t        f      r,|j                  |       5 }|j                  d      } ddd       n| j                  d      } t        | |      } t        | |d      }|t        | |d      }|"|dk(  rt        d	| d
      t        | |d      }|| }t        ||      }	|	t        | |      }t        ||      }	|	t        ||d      xs g }n|j                  }
t        dt        dt!        |j"                  t        d|      z  dz                    }|	D cg c]P  \  }}t%        t'        |j)                  t        d||z
        dt        |j"                  ||z         |
f                  R }}}t+        ||       |r|D cg c]  }t-        |       c}S |S # 1 sw Y   cxY wc c}}w c c}w )u  Turn one generated row strip into *frame_count* frames.

    The background is keyed out, then strict extraction treats the requested
    frame count as the source of truth: slice known equal slots, isolate the real
    subject in each slot, and require empty padding on X and Y. Empty chroma
    gutters are only a lenient salvage fallback.

    Each frame is cropped at full cell height so tall ears / halos are never
    clipped; detached effects and neighbour slivers are dropped per slot. When a
    pose does not have required space around it, ``components`` raises and
    ``auto`` falls back to best-effort slicing.

    *fit* (default) fits+centers each frame into a 192x208 cell — the standalone
    contract for callers that don't normalize. Hatching passes ``fit=False`` to
    keep raw, coordinate-aligned columns for :func:`normalize_cells`, which lays
    one shared scale + baseline across the whole pet (no slide, no size pulse).
    r   r   rl   N)rf   TrD  
componentszcould not segment z padded sprites from stripFr   r    r	   g{Gz?)rY   rk   
isinstancestrr   openry   r   rY  rb  ru  rh  r`  r4   r{   r|   r   r3   r   r  r   ry  r   )rX  r  rf   r{  r|  rk   openedrP  rL  rangesrC   padr   r   fs                  r   extract_strip_framesr  *  s   2 %#t%ZZ 	+&NN6*E	+ 	+ f%e
;E
 e[$GF~UKF~\!1+>XYZZ "%eL~ 5>,UK@F$V[9F> eLRPRFAaRs1k7J(Jd'R!STUC $*D% !!6v{{C4RU:DVXY[^_e_k_kmruxmx[y{|C}7~!  AF  v{303V,LO,??O	+ 	+D
 -s   F32AG G3F=c                    ddl m} t        | j                  d      j	                  | j
                  df|j                        j                               S )uM   Per-column alpha mass — collapse the frame to a 1px-tall strip (fast in C).r   r   r-   r	   )rY   rk   r2  r.   r   r3   BILINEARr   )r5   rk   s     r   r   r   p  s>      %,,ekk1-=u~~NVVXYYr+   c           	         t        |       }d}d}t        | |dz         D ]I  }d}t        t        d|      t        |||z               D ]  }|| |   |||z
     z  z  } |||kD  sF|}|}K |S )a  Integer dx that best aligns *prof* onto *ref* by cross-correlation.

    This is 1-D phase correlation: the body is the dominant mass in the column
    profile, so the peak overlap locks onto the body and a flipping arm/cape (a
    small secondary bump) doesn't move the match. Proven on the jitter case to
    cut body drift from ~9px to ~1px where a centroid/bbox anchor cannot.
    Nr   r	   )r   r   r{   r|   )	refprofwindown
best_scorebestdscorerF   s	            r   _best_shiftr  w  s     	CA#JDF7FQJ' s1ay#aQ-0 	*ASVd1q5k))E	*!3JD Kr+   )r  c               2  . ddl m. .fd}d }i }i }t        |z
  }t        |z
  }| j	                         D ]7  \  }}	|	D 
cg c]  }
|
j                  d       }}
t        d |D              s|	D cg c]	  } |        c}||<   Pt        d |D              }t        d |D              }g }|D ]M  }
|
j                  ||fk7  r).j                  d||fd	      }|j                  |
d
       |}
|j                  |
       O |D 
cg c]  }
t        |
       }}
t        |      D cg c]%  t        fd|D              t        |      dz     ' }}t        d|dz        }|}g }t!        ||      D ]S  \  }
}.j                  d|d|z  z   |fd	      }|j                  |
|t#        |||      z   df       |j                  |       U d |D        D cg c]  }|s|	 }}t%        d |D              }t%        d |D              }t        d |D              }t        d |D              }|||||f ||D cg c]  }|d   |d   z
   c}       ||D cg c]  }|d   |d   z
   c}      ff||<   : |s|S |}|j'                         D ]E  \  } \  }}}}\  }!}"||z
  ||z
  }$}#t%        |||"z  t        d|$      z  ||"z  t        d|#      z        }G |j	                         D ]  \  }\  }\  }}}}\  }!}"||z
  ||z
  }$}#|t        d|"      z  }%t        dt)        |#|%z              t        dt)        |$|%z              }'}&t)        t        |&z
  dz        t)        t        |dz  z
  |'z
        })}(g }*|D ]|  }+|+j+                  ||||f      },|,j                  |&|'fk7  r(|,j-                  |&|'f.j.                  j0                        }, |       }-|-j                  |,|(|)f       |*j                  |-       ~ |*||<    |S c c}
w c c}w c c}
w c c}w c c}w c c}w c c}w )ud  Register every frame into a 192x208 cell — the deterministic anti-jitter math.

    A per-frame "crop→scale→center" pipeline jitters because a moving limb/cape
    shifts the bbox (or even the centroid) and a per-frame scale pulses the size.
    The rigorous fix, matching image-registration practice (phase correlation)
    and AI-sprite pipelines (perfectpixel-studio / sprite-gen):

    1. **Cross-correlate** each frame's column profile against the per-state
       *median* profile to find the integer shift that locks the **body** in
       place — robust to limbs/cape because the body dominates the profile.
    2. **Union-crop** through one shared state window, then scale every state by a
       single global factor keyed to its median pose height, so the character is
       the same on-screen size in every row while a jump's lift still fits.
    r   r   c                 >     j                  dt        t        fd      S )Nrl   rr   )r~   r   r   r   s   r   rS   z!normalize_cells.<locals>.<lambda>  s    EIIfz;&?N r+   c                6    t        |       t        |       dz     S )Nr   )r$  r   )vss    r   rS   z!normalize_cells.<locals>.<lambda>  s    VBZB1- r+   rl   c              3  <   K   | ]  }|j                           y wr   r   r   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     -1199;-   c              3  4   K   | ]  }|j                     y wr   )r3   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     'Q'   c              3  4   K   | ]  }|j                     y wr   )r4   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     (a(r  rr   r=   c              3  (   K   | ]	  }|     y wr   r   )r   prF   s     r   r   z"normalize_cells.<locals>.<genexpr>  s     -qad-s   r   r
   r   c              3  <   K   | ]  }|j                           y wr   r  )r   rH   s     r   r   z"normalize_cells.<locals>.<genexpr>  s     :QQYY[:r  c              3  &   K   | ]	  }|d      ywr   r   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     'A1Q4'r   c              3  &   K   | ]	  }|d      ywr   r   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     &1!A$&r   c              3  &   K   | ]	  }|d      ywr   r   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     (QAaD(r   c              3  &   K   | ]	  }|d      yw)r   Nr   r  s     r   r   z"normalize_cells.<locals>.<genexpr>  s     )aQqT)r   r   r	   )rY   rk   r   r   itemsry   anyr{   r   r~   r   r   r   r   r$  r   r   r  r|   valuesr   r   r   r   r   )/frames_by_stater  blankmedr   preparedtarget_wtarget_hstaterP  r  rd   r   w0h0canvasr   profilesrF   r  r  marginalignedr  shiftedr(   r&  r   r   r   r   K_aligned_pose_wpose_huwuhr   swshrD   pycellsrH   r   cellrk   s/                     `                           @r   normalize_cellsr    sl    NE
-CCSUH CHS H(..0 '
v+12a		&!22---+12a%'2CJ '$''(4(( 	Avv"b!IIfr2h=!!!V,MM!	 1771OA&77KPQS9Uav-H--c(mq.@AUUQa68, 	$GAtiia&j"(=|LG##AS$1O(OQR'STNN7#	$ ;':@qa@@'''&&&(%(()5))3v&u-!!A$1+-.%4PQQqTAaD[4P0QR
G'
R 
 	AEM__EV SA-D#uf/@v|B8f$s1bz18f3Ds1bz3QRS LT>>K[ GG3$UF5Fgvv|BC6N"Qb5j)*C5e3D,EB
R1,-ukC1H6LPR5R/SB 	A664eV45DyyRH${{B8U-=-=-E-EF7D  Bx0LL	 E
  JI 32 8U A .4Ps0   O62O;P %*PP
P
3PP)r|  c                   ddl m} t        | t        t        f      r+|j                  |       5 }|j                  d      } ddd       t        |       }|rt        |      S t        |      S # 1 sw Y   ,xY w)a&  One frame from a standalone image (e.g. the base look).

    Used as an idle fallback so a pet always renders even if the idle row
    generation failed. *fit* yields a finished 192x208 cell; ``fit=False`` yields
    the raw keyed sprite for :func:`normalize_cells` to place with the rest.
    r   r   rl   N)
rY   rk   r  r  r   r  ry   r   r   r   )r5   r|  rk   r  r   s        r   single_framer    sh     %#t%ZZ 	+&NN6*E	+e$E"%<B+;E+BB	+ 	+s   A++A4c                &   ddl m} | j                  d      }t        |j	                               }t        dt        |      d      D ]#  }||dz      dk(  sdx||<   x||dz   <   ||dz   <   % |j                  d|j                  t        |            S )zCZero the RGB of fully-transparent pixels (no colored-halo residue).r   r   rl   r   r   r	   r   )
rY   rk   ry   r   tobytesr   r   r   r   r   )r5   rk   rd   datar   s        r   _clear_transparent_rgbr    s    == DT\\^$D1c$i# 4A;!233DG3d1q5kDQK4 ??6499eDk::r+   c                    ddl m} t        |d|      j                  }| D cg c]"  }|j	                  d      j                  |      $ c}S c c}w )ul  Horizontally flip each frame *in place* (RGBA-safe).

    Used to derive ``running-left`` from an approved ``running-right`` row. The
    flip is per-frame so the leftward loop preserves the rightward loop's frame
    order and timing — this is NOT a whole-strip reverse (which would play the
    animation backwards), matching the petdex/Codex mirror rule.
    r   r   	Transposerl   )rY   rk   getattrFLIP_LEFT_RIGHTry   	transpose)rP  rk   fliprT  s       r   mirror_framesr    sC     5+u-==D?EFeEMM&!++D1FFFs   'Ac                   ddl m} |j                  dt        t        fd      }t
        D ]  \  }}}| j                  |      xs g }t        |d|       D ]\  \  }}|j                  d      }	|	j                  t        t        fk7  rt        |	      }	|j                  |	|t        z  |t        z  f       ^  t        |      S )zPack per-state frame lists into the Hermes atlas (RGBA, residue-cleared).

    Missing/short states leave their trailing cells transparent; extra frames
    beyond a state's spec are dropped.
    r   r   rl   rr   N)rY   rk   r~   ATLAS_WIDTHATLAS_HEIGHTr   getr   ry   r   r   r   r   r   r  )
r  rk   atlasr  r.  r   rP  colrT  r  s
             r   compose_atlasr    s     IIf{L9<HE& OsE $$U+1r#F6EN3 	OJC==(DyyZ55#D)!!$z)93;L(MN		OO "%((r+   c                x    t        j                         }| j                  |ddddd       |j                         S )zFEncode an atlas image to lossless WebP bytes (the on-disk pet format).WEBPTd   r   )formatlosslessqualityr{  exact)ioBytesIOsavegetvalue)r  bufs     r   atlas_to_webp_bytesr  /  s1    
**,C	JJs6D#atJT<<>r+   c                2   ddl m} t        | t        t        f      r,|j                  |       5 }|j                  d      } ddd       n| j                  d      } g }g }| j                  t        t        fk7  rV|j                  dt         dt         d| j                   d| j                          d| j                  | j                  ||g d	S g }i }t        D ]  \  }}}	d}
g }t        |	      D ]  }|t        z  }|t         z  }| j#                  |||t        z   |t         z   f      }t%        |j'                  d
      j)                         dd       }|
|z  }
|j+                         }|{|j                  |        |
dkD  r|j                  |       |||<   |j                  d| d        |s|j                  d       t-        d |j/                         D              }t-        d |j/                         D              }d}d}|r\|rZ|t1        |      dz     }|t1        |      dz     }|}t3        dt5        t         dz              }||k  r|j                  d| d       |j7                         D ]  \  }}t1        |      dk  rt-        d |D              }t-        d |D              }t3        d|t1        |      dz           }t3        d|t1        |      dz           }|d   }|d   }|t3        |dz  |dz         kD  r||dz  k  r|j                  d| d       |s|st3        dt5        |dz              }t3        dt5        |d z              }||k  s||k  s|j                  d| d!| d| d"| d| d        | j9                         } d}!t        dt1        |       d#      D ])  }"| |"d$z      dk(  s| |"   s| |"dz      s	| |"dz      s%|!dz  }!+ |!r|j                  |! d%       | | j                  | j                  |||d	S # 1 sw Y   xY w)&u3  Check geometry, per-cell occupancy, and transparency invariants.

    Returns ``{ok, width, height, errors, warnings, filled_states}``. Errors are
    blockers (wrong size, empty used cell, opaque/dirty transparency); warnings
    are soft (a whole state row blank — generation likely dropped a row).
    r   r   rl   Nrn  rF   z, got F)okr3   r4   errorswarningsfilled_statesr-   r	   zstate 'z' has no framesu/   atlas is empty — no state produced any framesc              3  >   K   | ]  }|D ]  \  }}}}||z
     y wr   r   )r   r&  r   r  r   r  s         r   r   z!validate_atlas.<locals>.<genexpr>f  s9      */ 'D$w 	   c              3  >   K   | ]  }|D ]  \  }}}}||z
     y wr   r   )r   r&  _leftr   _rightr   s         r   r   z!validate_atlas.<locals>.<genexpr>k  s9      */ 'E3 	r  r   8   gQ?zEatlas sprites are too small after normalization (median frame height zpx)c              3  0   K   | ]  \  }}}}||z
    y wr   r   )r   r   r  r   r  s        r   r   z!validate_atlas.<locals>.<genexpr>}  s     M)CtUGM   c              3  0   K   | ]  \  }}}}||z
    y wr   r   )r   r  r   r  r   s        r   r   z!validate_atlas.<locals>.<genexpr>~  s     N*D%ff#Nr  rr  rs  rt  z%' contains a multi-pose frame outlierrk  gzG?(   g      ?z' appears collapsed (median zpx, global median r   r   z& transparent pixels retain RGB residue)rY   rk   r  r  r   r  ry   r   r  r  r   r3   r4   r   r   r   r   r   r2   r.   r1   r   r$  r  r   r{   r   r  r  )#r  rk   r  r  r  r  cell_boxes_by_stater  r.  r   
row_pixelsr&  r  r   r   r  nonblankr   
all_widthsall_heightsglobal_med_wglobal_med_hmedian_hr@  rv  r'  rw  rx  r   r   min_state_wmin_state_hr  residuer   s#                                      r   validate_atlasr  6  sz    %#t%ZZ 	+&NN6*E	+ 	+ f%FHzzk<00	+a~VEKK=PQRWR^R^Q_`aekkU\\U[iq  EG  H  	H!MFH& >sE
13< 	#C#D#C::tS$*;S;=NOPD4??3/99;AB?@H("J<<>DT"	# >  ').&OOgeWO<=!>$ GH
  (//1 J
  (//1 K
 LLk!#j/Q"67s;/145BkD012eMMabjakknop+113 uu:?MuMMNNNAvc&kQ./0Aws7|q012r
3us{EBJ//EUS[4HMMGE7*OPQ Lb%t(;"<=Kb%t(;"<=K{"ek&9eW$@qOabnaoopq}p~  B  C#, ==?DG1c$i# A;!aDQK4A;qLG 	!GHI j,,& q	+ 	+s   PP)
r&   rv   r'   rv   r(   rv   r)   tuple[int, int, int]rw   float)rw   rx   )rw   r  )0   )r)   r  rR   rv   )rf   tuple[int, int, int] | Nonerg   r   )rw   z+list[tuple[tuple[int, int, int, int], int]])r3   rv   r  rv   rw   r   )r&  list[tuple[int, int, int, int]]rw   z%list[list[tuple[int, int, int, int]]])r&  r  rw   r  )r  rv   rE  rx   rw   rW  )r  rv   )r   r   rg   rv   rw   r   )r  rv   rw   zlist[tuple[int, int]] | None)rw   r  )rP  r2  r  rv   rw   r   )
r  rv   rf   r  r{  r  r|  rx   rw   r2  )rw   r   )r  r   r  r   r  rv   rw   rv   )r  dict[str, list]r  rv   rw   r  )r|  rx   )rP  r2  rw   r2  )r  r  )rw   r   )rw   dict)>__doc__
__future__r   r  loggingr$   pathlibr   agent.pet.constantsr   r   	getLogger__name__loggerr   r   r   __annotations__r   ROWSr{   COLUMNSr  r  r   r0   r   _NORMALIZE_PADr   r*   r:   rI   r^   re   r   rz   r   r   r   r   r  r  r  r0  rC  rY  r`  rb  r   rh  rl  ry  r  r   r  r  r  r  r  r  r  r  )r  r   r   s   000r   <module>r     s  . # 	    0			8	$

)	% 
 9~
1y1
1
"k!DMNN5uNn N 	   P	&( (  KOcg S^lUv:"J0f-`@	Y0!H JO <D~8 EJ & ;< $"'Jb#IT /3C@C@ ,	C@
 C@ 
C@ 
C@LZ* ES `L (, C 	;G)&iu  Os   *D*