
    >j,                        d Z ddlZddlZddlZddlZddlZddlmZ  ee      j                         j                  Zedz  ZdZdZdZdZd	 Zdd
Zd Zd ZddZd Zd Zd Zd Zd Zd Zd Zd Zd Zd Ze dk(  r e        yy)a  
Python runner for the MATLAB/Octave reproduction scaffold of:
Charlton et al. / Pimentel et al. respiratory-rate estimation from PPG.

The companion MATLAB-compatible script is `reproduce_ppg_rr_matlab.m`.
This Python script executes the same pipeline in the current environment where MATLAB/Octave
is not installed, so the numerical results can be generated and verified.

Dataset: BIDMC PPG and Respiration Dataset, PhysioNet.
Inputs expected under ./data:
  bidmc_##_Signals.csv, bidmc_##_Breaths.csv
    N)Pathdatag     @_@g      P@g      @@)      @      N@c                     |dk  r| d d  S g }d}g }| D ]W  }|j                  |       ||z  }t        |      |kD  r||j                  d      z  }|j                  |t        |      z         Y |S )N           r   )appendlenpop)xnoutsqvs         ]/root/.openclaw/workspace/reproductions/ppg_rr_matlab_pimentel2016/run_ppg_rr_reproduction.pymoving_averager      sz    Avt
CA
A 	Q!VQq6A:qMA

1s1v:	
 J    c           
          t        | t        dt        ||z                    }t        | |      D cg c]
  \  }}||z
   c}}S c c}}w )N   )r   maxintzip)r   fssectrendabs         r   
detrend_mar    '   s?    1c!Sc]34E!!Um,daAE,,,s   Ac                     t        j                  |       }t        j                  |       xs d}| D cg c]
  }||z
  |z   c}S c c}w )Ng      ?)
statisticsmeanpstdev)r   musdr   s       r   zscorer'   +   sA    		B			1		$B#$%aQVrM%%%s   Ac                 &   t        |       dk  rg g fS t        ||z
  |z        dz   }t        |      D cg c]
  }|||z  z    }}g }d}	|D ]  }
|	dz   t        |       k  r-| |	dz      |
k  r"|	dz  }	|	dz   t        |       k  r| |	dz      |
k  r"|	dz   t        |       k\  r|j                  |d          g| |	   | |	dz      }}||	   ||	dz      }}||k(  r|j                  |       |
|z
  ||z
  z  }|j                  ||||z
  z  z           ||fS c c}w )N   r   r   )r   r   ranger
   )timesvaluesfs_newstartstopr   igridr   jtt0t1v0v1r   s                   r   resample_linearr9   0   sH   
5zA~2vTE\V#$q(A(-a11EAJ1D1
C	A /!ec%j U1q5\A%5FA !ec%j U1q5\A%5q5CJJJvbz"1XuQU|BAYq1uBRx

2VR(

2R"W-./ 9! 2s   Dc                    t        |       }|dk  rt        d      dfS t        j                  |       }t	        |       D cg c]@  \  }}||z
  ddt        j                  dt
        j                  z  |z  |dz
  z        z  z
  z  B }}}|dz  |dz  }
}	t        dt        t        j                  |	|z  |z                    }t        |dz  t        t        j                  |
|z  |z                    }d\  }}g }t        ||dz         D ]  }d}d}t	        |      D ]W  \  }}d	t
        j                  z  |z  |z  |z  }||t        j                  |      z  z  }||t        j                  |      z  z  }Y ||z  ||z  z   }|j                  |       ||kD  s|}|} |t        d      dfS |rt        j                   |      nd}||d
z   z  }||z  |z  dz  |fS c c}}w )N   nanr	   g      ?r)   r   r   )Ng      g-q=)r   floatr"   r#   	enumeratemathcospir   r   ceilminfloorr+   sinr
   median)r   r   lo_bpmhi_bpmr   r%   r1   r   siglo_hzhi_hzk0k1best_kbest_ppowerskreimangpmedr   s                          r   dft_peak_bpmrX   F   s   AA1uU|S  		BT]^_T`
aDAqAFsS488AK!Oq1u,E#FFFG
aC
aD=&4-5E	QDIIeai"n-.	/B	Q!VSEAIN34	5BNFFF2rAv 	#s"cN 	$DAqtww,"Q&*C!dhhsm##B!dhhsm##B	$ Gb2gav:F	# ~U|S  '-*

F
#3C#+AB;?T!1$$+ bs   AGc                 r   t        | d      5 }t        j                  |d      }|j                  }d}d}|d   }g g g }	}}|D ]Y  }
|j	                  t        |
|                |j	                  t        |
|                |	j	                  t        |
|                [ 	 d d d        	fS # 1 sw Y   xY w)N newlineT)skipinitialspacePLETHRESPr   )opencsv
DictReader
fieldnamesr
   r>   )pathfrdrcolsppg_colresp_colt_colr4   ppgresprows              r   read_signal_filern   e   s    	dB	 
.1nnQ6~~Q2r3 	.CHHU3u:&'JJuS\*+KKc(m,-	.
. c4<
. 
.s   BB--B6c                 t   g }t        | d      5 }t        j                  |      }t        |d        |D ]k  }g }|d d D ]6  }|j	                         }|s|j                  t        |      t        z         8 |sF|j                  t        |      t        |      z         m 	 d d d        |S # 1 sw Y   |S xY w)NrZ   r[   r)   )
r`   ra   readernextstripr
   r>   FSsumr   )rd   annsre   rf   rm   valscells          r   read_breathsrx   s   s    D	dB	 
31jjmS$ 	3CDBQ 2zz|KKdb 012 CID	12	3
3 K
3 Ks   AB-%B-:(B--B7c           
         t        | t        dt        d|z                    }t        d|z        }g }t        dt	        |      dz
        D ]1  }||dz
     ||   cxk  r||dz      k\  sn !|j                  |       3 |sg S t        j                  |      dt        j                  |      z  z   }|D cg c]  }||   |k\  s| }}g }|D ]5  }|r||d   z
  |k\  r|j                  |       "||   ||d      kD  s1||d<   7 |S c c}w )Nr   g{Gz?g333333?g?r*   )	r   r   r   r+   r   r
   r"   r#   r$   )r   r   ymin_distcandr1   threshpeakss           r   
find_peaksr      s   q#aTBY01A4"9~HD1c!fqj! QU8ad&aAh&KKN 	__Q$):):1)="==F.!qtv~A.D.E E"I1LLOqTAeBiL E"I	
 L /s   5DDc                 v   t        t        |t        d            t        t              }g }t	        |d d |dd        D ]9  \  }}||dz   kD  st        t        ||      fd      }|j                  |       ; i }t        |      dkD  r/|D cg c]  }|t        z   }	}|D cg c]  }|   	 }
}|	|
f|d<   g g }}|D ]Q  }|D cg c]
  }||k  s	| }}|s|d   }|j                  |t        z         |j                  |   |   z
         S t        |      dkD  r||f|d	<   t        |      d
kD  rht        dt        |            D cg c]  }||   t        z   }}t        dt        |            D cg c]  }||   ||dz
     z
  t        z   }}||f|d<   |S c c}w c c}w c c}w c c}w c c}w )Ng      ?r*   r   r)   c                     |    S N )idxr   s    r   <lambda>z#ppg_resp_features.<locals>.<lambda>   s    3 r   key   BW_trough_baselineAM_pulse_amplitude   FM_inter_pulse_interval)	r'   r    rs   r   r   rD   r+   r
   r   )r4   rk   r~   troughsr   r   r3   streamsr   times_bwvals_bw	amp_timesamp_valspktrprev_trr1   times_fmvals_fmr   s                      @r   ppg_resp_featuresr      s   z#r3'(Aq"EGE#2Jab	* 1q1u9E!QK%78ANN1 G
7|a(/0C"H00%,-c1S6--)17(;$%bxI + '3"27233BR"W%OOAbEAbEM*+ 9~)2H(=$%
5zA~+0CJ+?@aE!HrM@@9>q#e*9MNAE!HuQqSz)R/NN.6-@)*N! 1- 4 ANs$   F"&F'
F,F,F1;F6c                     | D cg c]  }||cxk  r|k  sn n| }}t        |      dk  rt        d      S dt        |      dz
  z  |d   |d   z
  z  S c c}w )Nr)   r<   r   r   r*   r   )r   r>   )breathsr/   r0   r   bss        r   reference_rrr      s_    	3 2d 2!	3B	3
2w{U|3r7Q;2b6BqE>22 
4s
   AAc                    t        dt        |t        z              }t        t	        |      t        |t        z              }||z
  t        dt        z        k  ry ||| }||| }	t        |||      }
t        j                  |
      sy t        t        |	t        d            }t        |t              \  }}i }i }t        | || |      j                         D ]n  \  }\  }}t	        |      dk  rt        ||d||      \  }}t        t        |dd            }t        |d      \  }}t        j                  |      se|||<   |||<   p |st        d      }not        |j!                               }t	        |      dk\  r.t#        j$                  |      dk  rt#        j&                  |      }n|t        ||j(                  	         }|||
||d
|j                         D ci c]  \  }}d| | c}}S c c}}w )Nr      g       @   g      @r<   r)   r   r   )start_sstop_sref_rrip_rrppg_rr_fusedppg_)r   r   rs   rD   r   r   r@   isfiniter'   r    rX   r   itemsr9   r>   listr-   r"   r$   r#   get)r4   rk   rl   r   r/   r0   i0i1ppg_wresp_wref
resp_cleanr   ip_q	estimates	qualitiesnameftfvr2   rv   rrr   fusedrR   r   s                             r   estimate_window_rrr      s   	QEBJ	 Bs3s8S^'D"	BwR"W2JE"R[F
wt
,C==
62s34Jz2.KE4II+AbHe<BBD 6hr2r7Q;$RS%>
djsC01T3'A== IdOA)D/6 e I$$&'t9>j//5<OOD)Ec)?@EDC%Y^%.__%6
7TQT!:q=
7 
7s   G3c                     t        j                  |       }t        |       dkD  rt        j                  |       nd}||d|z  z
  |d|z  z   fS )Nr   r	   g\(\?)r"   r#   r   stdev)errorsbiasr&   s      r   loar      sK    ??6"D%([1_		&	!#Br	!4$)#333r   c                 :    t        j                  d | D              S )Nc              3   2   K   | ]  }t        |        y wr   )abs).0es     r   	<genexpr>zmae.<locals>.<genexpr>   s     2a3q62s   )r"   r#   )r   s    r   maer      s    ??26222r   c            
         g } t        dd      D ]  }t        d|ddz  }t        d|ddz  }|j                         r|j                         s@t        |      \  }}}t	        |      }|d   }d}	|	t
        z   |k  snt        |||||	|	t
        z         }
|
r||
d	<   | j                  |
       |	t        z  }	|	t
        z   |k  rE t        d
z  }t        | D 
ch c]  }
|
j                         D ]  }|  c}}
      }t        |dd      5 }t        j                  ||      }|j                          |j!                  |        d d d        ddt#        |       i d}dD ]  }| D 
cg c]D  }
||
v st%        j&                  |
|         s!t%        j&                  |
d         s:|
|   |
d   z
  F }}
|sUt)        |      \  }}}t#        |      |t+        |      ||d|d   |<    t        t        dz  d      5 }t-        j.                  ||d       d d d        t1        |       t3        t-        j4                  |d             y c c}}
w # 1 sw Y   xY wc c}
w # 1 sw Y   MxY w)Nr      bidmc_02dz_Signals.csvz_Breaths.csvr*   r	   subjectzwindow_results.csvwrZ   r[   )rc   z_Pimentel et al. / Charlton et al. RR estimation from PPG, BIDMC PhysioNet reproduction scaffold
   )papersubjectswindowsmethods)r   r   ppg_BW_trough_baselineppg_AM_pulse_amplitudeppg_FM_inter_pulse_intervalr   )r   bias_bpmmae_bpmloa95_low_bpmloa95_high_bpmr   zsummary.jsonr)   )indent)r+   DATAexistsrn   rx   WIN_SECr   r
   STEP_SECROOTsortedkeysr`   ra   
DictWriterwriteheader	writerowsr   r@   r   r   r   jsondump	write_svgprintdumps)rowssubjrJ   brr4   rk   rl   r   durationr/   rout_csvrR   r   re   wrsummarymethoderrsr   lohis                         r   mainr      s   Da vd3Z|44fT#Jl33zz|299;',3r"R5go)"1c4%QA#)AXE go) ))Gd5AFFH5q15156D	gsB	' V1^^A$/1A2<<PTCUVy  HJ  WZ  [_  W`  mo  pG~ M15  BA1WXY_W`Iafjfsfstuv~t  gA&	AhK'  B  Bt9LD"b/24ydWZ[_W`su  JL  *MGIv&	M
 
d^#S	) (Q		'1Q'(g	$**WQ
'( 6V V B( (s6   I
9I
%	I/II!I7I
II%c                    dD cg c]  }|| d   v s| }}dddddd}dd	d
ddd}d\  }}d\  }}}	|D cg c]  }| d   |   d    }
}|
rt        |
      dz  nd}d| d| d| d| d	dddg}t        |      D ]  \  }}d}||dz  z   }| d   |   d   }||z  |	z  }||	z   |z
  }|j                  d| d|dd| d|dd ||    d!       |j                  d"||d#z  z    d|d$z
  dd%|d&d'       |j                  d"||d#z  z    d||	z   d(z    d)||    d*        |j                  d+|d,z
   d-||	z    d.||	z    d/       d0| d   v r5| d   d0   }|j                  d1|d2   d&d3|d4   d&d5|d6   d&d7|d8    d*	       |j                  d9       t        t        d:z  d;      5 }|j                  d<j                  |             d d d        y c c}w c c}w # 1 sw Y   y xY w)=N)r   r   r   r   r   r   z	PPG fusedzPPG BWzPPG AMzPPG FMzIP respz#f97316z#60a5faz#38bdf8z#a78bfaz#22c55e)i  i  )P         r   g333333?r   z/<svg xmlns="http://www.w3.org/2000/svg" width="z
" height="z" viewBox="0 0  z">z1<rect width="100%" height="100%" fill="#0b1020"/>z<text x="44" y="48" fill="#f8fafc" font-family="Inter,Arial" font-size="24" font-weight="700">PPG respiratory-rate reproduction scaffold</text>u   <text x="44" y="78" fill="#94a3b8" font-family="Arial" font-size="14">BIDMC PhysioNet · 10 subjects · PPG modulation extraction + spectral RR + smart fusion · lower MAE is better</text>n      z	<rect x="z" y="z.1fz	" width="z" rx="10" fill="z"/>z	<text x="r)   	   zI" text-anchor="middle" fill="#f8fafc" font-family="Arial" font-size="14">z.2fz bpm</text>   zI" text-anchor="middle" fill="#cbd5e1" font-family="Arial" font-size="13">z</text>z
<line x1="   z" y1="z" x2="900" y2="z" stroke="#334155"/>r   zW<text x="44" y="465" fill="#e2e8f0" font-family="Arial" font-size="15">PPG fused: bias r   z bpm, 95% LoA [r   z, r   z], n=r   z</svg>zppg_rr_results.svgr   
)r   r?   r
   r`   r   writejoin)r   mr   labelscolorsWHx0y0chrv   maxvlinesr1   bwr   valbhrz   r   re   s                        r   r   r     s    C  _Q  GH  LS  T]  L^  G^q  _G  _((dl  LT  ]f  gF&	cl  LU  ^g  hF
CAaJ82b6=>GIq!),>D> 3t9S=aD>qcA3o^_]``abcaddfg  i\  ^o  qo  pE! U!"QsU(	 21 5i @CSXb["TVWYTYZ\T\RSy53yJr#hN^_efg_h^iilmny2a4ac#Y6  AD  EH  @I  IT  U  	Vy2a4beBhZ  8A  BH  IJ  BK  AL  LS  T  	U	U
 
LL:beWF2b5'BG[\]++
)
^
,nopq{o|  ~A  oB  BQ  RS  Tc  Rd  eh  Qi  ik  lm  n~  l  @C  kD  DI  JK  LO  JP  IQ  QX  Y  	Z	LL	d''	,L177499U;K3LLL# _ ? MLs   GGG"!GG__main__)g       @)!__doc__ra   r   r@   osr"   pathlibr   __file__resolveparentr   r   rs   r   r   RESP_BAND_BPMr   r    r'   r9   rX   rn   rx   r   r   r   r   r   r   r   r   __name__r   r   r   <module>r     s    ' & & H~&&f}

-&
,%>(<3"H4
3)@M( zF r   