Files
Pier_v1/pierzainer_ground/decode_pdf_soundings.py

71 lines
4.2 KiB
Python

"""Decode the original DahojI PDF's outlined digits without OCR or AI.
Digit signatures are from visually checked vector glyphs of this exact PDF.
No rounding/borrowing of Excel ground elevations is used as source data.
"""
from pathlib import Path
import collections,json,math,re
import numpy as np
from build_sample_grounds import OUTPUT,save
folder=OUTPUT/'03_대호지교';raw=json.loads((folder/'original_profile_vectors.json').read_text(encoding='utf-8'))
groups=json.loads((folder/'glyph_groups.json').read_text(encoding='utf-8'))
mapping={0:'0',1:'.',2:'1',4:'2',5:'3',6:'9',7:'4',8:'5',9:'8',10:'7',11:'6'}
signature_map={groups[k]['signature']:v for k,v in mapping.items()}
angles=[]
for index in groups[2]['indices']:
path=raw['curves'][index]['path'];delta=np.array(path[2][1])-path[1][1]
u=np.array([-delta[1],delta[0]])
if u[0]<0:u=-u
angles.append(math.atan2(u[1],u[0]))
hist,edges=np.histogram(angles,bins=90,range=(-math.pi/2,math.pi/2));mode=int(hist.argmax())
angle=float(np.median([v for v in angles if edges[mode]<=v<=edges[mode+1]]))
u=np.array([math.cos(angle),math.sin(angle)]);v=np.array([-u[1],u[0]])
transform=np.column_stack([u,v]);glyphs=[];previous=[]
for index,c in enumerate(raw['curves']):
color=c['non_stroking_color']
if not(c['fill'] and isinstance(color,list) and len(color)==3 and abs(color[0]-.54902)<.01 and 20<c['x0']<700 and 310<c['top']<415 and c['width']<3 and c['height']<3):continue
signature=''.join(cmd[0] for cmd in c['path'])
# PDFMiner exposes inner subpaths separately. They are holes in 0/4/6/8/9,
# not additional glyphs; their exact drawing-order containment identifies them.
inside=any(index-j<=3 and c['x0']>=p['x0']-.001 and c['x1']<=p['x1']+.001 and c['top']>=p['top']-.001 and c['bottom']<=p['bottom']+.001 for j,p in previous[-3:])
previous.append((index,c))
if inside or signature not in signature_map:continue
character=signature_map[signature]
points=np.array([pt for cmd in c['path'] for pt in cmd[1:] if isinstance(pt,list)])
rotated=points@transform;lo=rotated.min(0);hi=rotated.max(0)
if character=='.':
sides=np.linalg.norm(np.diff(points[:4],axis=0),axis=1)
if sides.min()>0 and sides.max()/sides.min()>2:character='-'
glyphs.append(dict(index=index,character=character,lo=lo.tolist(),hi=hi.tolist(),
bbox=[c['x0'],c['top'],c['x1'],c['bottom']]))
height=float(np.median([g['hi'][1]-g['lo'][1] for g in glyphs if g['character'].isdigit()]))
tokens=[];token=[]
def flush():
if not token:return
text=''.join(g['character'] for g in token)
if re.fullmatch(r'-?\d+\.\d{1,3}',text):
bounds=np.array([g['bbox'] for g in token]);xy=(bounds[:,:2].min(0)+bounds[:,2:].max(0))/2
tokens.append(dict(text=text,elevation_m=float(text),pdf_center=xy.tolist(),glyph_indices=[g['index'] for g in token],
bbox=[*bounds[:,:2].min(0),*bounds[:,2:].max(0)]))
token.clear()
for g in glyphs:
if token:
last=token[-1];normal=[x for x in token if x['character']!='-']
baseline=np.median([x['hi'][1] for x in normal]) if normal else g['hi'][1]
adjacent=(-.08*height<g['lo'][0]-last['hi'][0]<.65*height and
abs(g['hi'][1]-baseline)<.22*height and g['index']-last['index']<6)
if not adjacent:flush()
token.append(g)
flush()
save(folder/'decoded_soundings_text.json',dict(pdf_text_angle_degrees=math.degrees(angle),digit_height_pt=height,
method='exact_original_PDF_vector_glyph_signatures',tokens=tokens,glyphs=glyphs))
print('angle',math.degrees(angle),'height',height,'tokens',len(tokens),'negative',sum(t['elevation_m']<0 for t in tokens))
print(tokens[:12])
print('range',min((t['elevation_m'] for t in tokens),default=None),max((t['elevation_m'] for t in tokens),default=None))
first=tokens[0] if tokens else None
if first:
xy=np.array(first['pdf_center']);near=[line for line in raw['lines'] if min(np.linalg.norm(np.array(pt)-xy) for pt in line['pts'])<2]
save(folder/'sounding_marker_examples.json',dict(token=first,nearby_lines=near))
print('nearby lines',[(line['pts'],line['stroking_color'],line['linewidth']) for line in near])