Spaces:
Sleeping
Sleeping
Download src/annotations.py from farrell236/OCT_Segmentation_a1: direct link, hf CLI and curl.
- Browser
- Download file 5.87 kB
-
https://huggingface.co/spaces/farrell236/OCT_Segmentation_a1/resolve/main/src/annotations.py
- Command line
-
hf download hf://spaces/farrell236/OCT_Segmentation_a1/src/annotations.py
-
curl -L -o annotations.py https://huggingface.co/spaces/farrell236/OCT_Segmentation_a1/resolve/main/src/annotations.py
5.87 kB
| """Canonical boundary conversion primitives; cohort/geometry audit must precede use. | |
| Source depths are stored pixel coordinates. RWVM is retained for provenance and is | |
| not blindly applied: Zeiss metadata can carry lateral rather than axial scale. | |
| """ | |
| from pathlib import Path | |
| import numpy as np | |
| import pydicom | |
| def code_key(segment): | |
| codes=segment.get('SegmentedPropertyTypeCodeSequence',[]) | |
| if len(codes)!=1:raise ValueError('Boundary needs one unambiguous anatomical code') | |
| return str(codes[0].CodingSchemeDesignator)+'_'+str(codes[0].CodeValue) | |
| FRAME_MAPPING_VERSION='aireadi-heightmap-anatomy-v2' | |
| def heightmap_frame_numbers(ds,device=None): | |
| """Return effective anatomy IDs for stored frames, with a Topcon export erratum. | |
| AI-READI Topcon payloads are in anatomical depth order. Their sequential frame | |
| IDs use a different SegmentSequence order for OPL/ELM/EZ/RPE. The fixed mapping | |
| below is supported by train/validation raw B-scan overlays, layer definitions, | |
| and depth ordering; it does not sort or otherwise alter per-location values. | |
| Heidelberg and Zeiss follow their explicit frame IDs. See discovery evidence. | |
| """ | |
| declared=[int(f.SegmentIdentificationSequence[0].ReferencedSegmentNumber) for f in ds.get('PerFrameFunctionalGroupsSequence',[])] | |
| if device not in ['topcon_maestro2','topcon_triton']:return declared | |
| expected_n=9 if device=='topcon_maestro2' else 10 | |
| expected_codes=['SCT_280677004','DCM_128289','DCM_128290','DCM_128291','DCM_128320','DCM_128297','DCM_128299','DCM_128293','SCT_76710003']+(['DCM_128301'] if expected_n==10 else []) | |
| segments={int(s.SegmentNumber):s for s in ds.SegmentSequence} | |
| if sorted(segments)!=list(range(1,expected_n+1)) or [code_key(segments[n]) for n in sorted(segments)]!=expected_codes:raise ValueError('Unsupported Topcon codebook') | |
| effective=[1,2,3,4,8,9,5,6,7]+([10] if expected_n==10 else []) | |
| if declared==effective:return declared | |
| if declared!=list(range(1,expected_n+1)):raise ValueError('Unsupported Topcon frame identity pattern') | |
| return effective | |
| def read_heightmap(path,height,device=None): | |
| ds=pydicom.dcmread(path) | |
| if str(ds.get('SegmentationType',''))!='HEIGHTMAP':raise ValueError('Unsupported segmentation representation') | |
| raw=ds.pixel_array.astype(np.float32) | |
| if raw.ndim==2:raw=raw[None] | |
| frames=ds.get('PerFrameFunctionalGroupsSequence',[]) | |
| if len(frames)!=len(raw):raise ValueError('Missing frame-to-segment identities') | |
| frame_numbers=heightmap_frame_numbers(ds,device) | |
| segments={int(s.SegmentNumber):s for s in ds.SegmentSequence} | |
| if len(frame_numbers)!=len(set(frame_numbers)) or set(frame_numbers)!=set(segments):raise ValueError('Ambiguous or incomplete heightmap frame mapping') | |
| keys=[code_key(segments[n]) for n in sorted(segments)] | |
| if len(keys)!=len(set(keys)):raise ValueError('Duplicate anatomical boundary code') | |
| values=raw[[frame_numbers.index(n) for n in sorted(segments)]] | |
| valid=np.isfinite(values) | |
| padding=ds.get('FloatPixelPaddingValue',None) | |
| if padding is not None: | |
| low=float(padding);high=float(ds.get('FloatPixelPaddingRangeLimit',padding)) | |
| valid &= ~((values>=min(low,high))&(values<=max(low,high))) | |
| # Zero is a real location. Values outside the imaged axial support cannot be | |
| # scored in original-volume coordinates and remain source-invalid locations. | |
| valid &= (values>=0)&(values<=height-1) | |
| values=np.where(valid,values,0).astype(np.float32) | |
| return values,np.asarray(keys),valid,ds | |
| def source_support_statistics(ds,height,device=None): | |
| """Audit every source location without changing reference measurement support.""" | |
| raw=np.asarray(ds.pixel_array,dtype=np.float32) | |
| if raw.ndim==2:raw=raw[None] | |
| numbers=heightmap_frame_numbers(ds,device) | |
| segments={int(s.SegmentNumber):s for s in ds.SegmentSequence} | |
| padding=ds.get('FloatPixelPaddingValue',None) | |
| result={} | |
| for number in sorted(segments): | |
| values=raw[numbers.index(number)];finite=np.isfinite(values);pad=np.zeros(values.shape,bool) | |
| if padding is not None: | |
| low=float(padding);high=float(ds.get('FloatPixelPaddingRangeLimit',padding)) | |
| pad=(values>=min(low,high))&(values<=max(low,high)) | |
| outside=finite&~pad&((values<0)|(values>height-1));valid=finite&~pad&~outside | |
| result[code_key(segments[number])]={'locations':int(values.size),'valid_points':int(valid.sum()), | |
| 'nonfinite_points':int((~finite).sum()),'padding_points':int((finite&pad).sum()), | |
| 'outside_imaged_axial_support':int(outside.sum()),'valid_zero_depth_points':int(((values==0)&valid).sum()), | |
| 'b_scans_with_support':int(valid.any(-1).sum())} | |
| return result | |
| def axial_spacing_um(headers,device): | |
| spacings=[] | |
| for path,ds in headers: | |
| shared=ds.get('SharedFunctionalGroupsSequence',[]) | |
| base=shared[0] if shared else ds | |
| for fr in ds.get('PerFrameFunctionalGroupsSequence',[ds]): | |
| pm=fr.get('PixelMeasuresSequence',base.get('PixelMeasuresSequence',[])) | |
| if not pm:return None | |
| spacing=float(pm[0].PixelSpacing[0]) | |
| if not np.isfinite(spacing) or spacing<=0:return None | |
| spacings.append(spacing) | |
| if not spacings or not np.allclose(spacings,spacings[0],rtol=1e-5,atol=1e-9):return None | |
| return spacings[0]*1000 | |
| def convert_case(row,root,headers,geometry,with_audit=False): | |
| b,h,a=geometry | |
| if len(row['segmentation_paths'])!=1:raise ValueError('Multiple segmentation assembly requires explicit frozen mapping') | |
| values,keys,valid,ds=read_heightmap(Path(root)/row['segmentation_paths'][0],h,row['device']) | |
| if values.shape[1:]!=(b,a):raise ValueError('Heightmap and structural grid differ') | |
| # Frame order and axes are verified by frozen metadata audit plus source overlays. | |
| spacing=axial_spacing_um(headers,row['device']) | |
| data={'positions':values,'boundary_keys':keys,'valid':valid,'height':np.asarray(h,dtype=np.int32),**({'axial_spacing_um':np.asarray(spacing)} if spacing else {})} | |
| if with_audit:return data,source_support_statistics(ds,h,row['device']) | |
| return data | |