Aortic Root Z-Score Calculator

About...


About this site

This site is represents a re-writing and consolidation of several of the z-score calculators at parameterz.blogspot.com. The backstory behind the parameterz.com domain is that I was unsatisfied with the available z-score equations that were supplied with our professional-grade echo reporting software, and I thought I could do something better. I had originally set up a few calculators as a test to: a) see if I could write a web-based z-score calculator, and b) compare the calculated z-scores among the available references. I thought others might find the calculators useful for their own similar purpose.

I have tried to make the references and code available (see below) should you like to incorporate these calculations into your own echo reporting software. If, on the other hand, you find yourself using the site as the sole source for reporting your z-scores (which is fine, provided you understand the disclaimer), please consider making a donation:

paypal link




About the calculations

Python Modules

Each of the references cited (Boston, Detroit, Halifax, Paris, and Wessex) has it's own module. The modules are basically containers for classes, each providing a similar interface for accessing methods for the z-score, mean, and range calculations.

(click to show/hide the code samples)

Boston

#!/usr/bin/env python
'''boston - A module for  calculating z-scores based on data published from Boston Childrens Hospital

Validation and re-evaluation of a discriminant model predicting anatomic
suitability for biventricular repair in neonates with aortic stenosis.
Colan SD, McElhinney DB, Crawford EC, Keane JF, Lock JE.
J Am Coll Cardiol. 2006 May 2;47(9):1858-65.

'''
from __future__ import division  #required to avoid random 'classic'(integer) division
import math

class Base( object ):
    '''this is the basic form for calculating many of the Boston z-scores:
    one regression (based on an allometric relationship with bsa) for the mean
    and a separate regression for the sd
    '''
    def __init__(self, d):
        self.slope = d['slope']
        self.exponent = d['exponent']
        self.sd_int = d['sd_int']
        self.sd_slope = d['sd_slope']
        self.site = d['site']
        self.bsaMethod = 'Haycock'
    def zscore( self, pt ):
        try:
            score = getattr(pt, self.site)
            #the incoming score is in 'mm' and needs to be converted to expected 'cm'
            score /= 10
            return ( ( score  ) - self._mean( pt.bsa(self.bsaMethod) ) ) / self.sd( pt.bsa(self.bsaMethod) )
        except:
            return None #property of object 'pt' does not exist
    def mean( self, pt ):
        return 10 * self._mean( pt.bsa(self.bsaMethod) )
    def _mean( self, bsa ):
        #this is a 'private' method that allows for the calculation in expected units (cm)
        return self.slope * math.pow( bsa, self.exponent )
    def sd( self, bsa ):
        return self.sd_int + ( self.sd_slope * bsa )
    def range( self, pt, limit = 1.96 ):
        lower = -limit * self.sd( pt.bsa(self.bsaMethod) ) + self._mean( pt.bsa(self.bsaMethod) )
        upper = limit * self.sd( pt.bsa(self.bsaMethod) ) + self._mean( pt.bsa(self.bsaMethod) )
        return '%.2f - %.2f' %( 10 * lower, 10 * upper )


aov = Base( { 'site': 'aov', 'slope': 1.55, 'exponent': 0.5, 'sd_int': 0.060, 'sd_slope': 0.083 } )
sov = Base( { 'site': 'sov', 'slope': 2.02, 'exponent': 0.5, 'sd_int': 0.098, 'sd_slope': 0.120 } )

sites ={
    'aov': aov,
    'sov': sov
}

Detroit

#!/usr/bin/env python
'''detroit - A module for  calculating z-scores based on data published from
Detroit Med Ctr

Regression equations for calculation of z scores of cardiac structures in a
large cohort of healthy infants, children, and adolescents: an echocardiographic
study.
Pettersen MD, Du W, Skeens ME, Humes RA.
J Am Soc Echocardiogr. 2008 Aug;21(8):922-34. 

'''
from __future__ import division #required to avoid random classic/integer division
import math

 
class Base(object):
    '''this is the basic form for the Detroit equations:
    a third-order polynomial equation, log transforming only the measurement (and not bsa)
    '''
    def __init__(self, d):
        self.b1 = d['b1']
        self.b2 = d['b2']
        self.b3 = d['b3']
        self.int = d['int']
        self.mse = d['mse']
        self.site = d['site']
        self.bsaMethod = 'Dubois'
    def _mean( self, bsa ):
        #this is a 'private' method that allows calculation in the expected units, 'cm'
        return self.int + ( self.b1 * bsa ) + ( self.b2 * math.pow( bsa, 2 ) ) + ( self.b3 * math.pow( bsa, 3 ) )   
    def mean( self, pt ):
        bsa = pt.bsa(self.bsaMethod)
        return  10 * math.exp( self._mean( bsa ) )
    def sd( self ):
        return math.sqrt( self.mse )
    def zscore( self, pt ):
        bsa = pt.bsa(self.bsaMethod)        
        try:
            score = getattr(pt, self.site)
            #the incoming score is in 'mm' and needs to be converted to expected 'cm' 
            score /= 10
            return ( math.log( score ) - self._mean( bsa ) ) / self.sd( )
        except:
            return None
    def range( self, pt, limit = 1.96 ):
        bsa = pt.bsa(self.bsaMethod)        
        lower = math.exp( -limit * self.sd() + self._mean( bsa ) )
        upper = math.exp( limit * self.sd() + self._mean( bsa ) )
        return '%.2f - %.2f' %( 10 * lower, 10 * upper )

aov = Base( { 'site': 'aov', 'int': -0.874, 'b1': 2.708, 'b2': -1.841, 'b3': 0.452, 'mse': 0.010 } )
sov = Base( { 'site': 'sov', 'int': -0.500, 'b1': 2.537, 'b2': -1.707, 'b3': 0.420, 'mse': 0.012 } )
stj = Base( { 'site': 'stj', 'int': -0.759, 'b1': 2.643, 'b2': -1.797, 'b3': 0.442, 'mse': 0.018 } )

sites = {
    'aov': aov,
    'sov': sov,
    'stj': stj    
}

Halifax

#!/usr/bin/env python
'''halifax - A module for  calculating aortic root z-scores based on data
published from Dalhousie University, Halifax, Nova Scotia, Canada

Dilatation of the ascending aorta in paediatric patients with bicuspid aortic
valve: frequency, rate of progression and risk factors.
Warren AE, Boyd ML, OConnell C, Dodds L.
Heart. 2006 Oct;92(10):1496-500. Epub 2006 Mar 17

'''
import math

class Base( object ):
    '''this is the basic form for the Halifax equations:
    a log-log transformation and "stablized" variance    
    '''
    def __init__( self, d):
        self.slope = d['slope']
        self.int = d['int']
        self.mse = d['mse']
        self.site = d['site']
        self.bsaMethod = 'Boyd'
    def _mean( self, bsa ):
        #returns the log-transformed mean value
        return self.int + self.slope * math.log( bsa )
    def mean( self, pt ):
        #returns the back-transformed mean value
        bsa = pt.bsa(self.bsaMethod)
        return math.exp( self._mean( bsa ) )
    def sd( self ):
        return self.mse
    def zscore( self, pt ):
        bsa = pt.bsa(self.bsaMethod)
        try:
            score = getattr(pt, self.site) 
            return ( math.log( score ) - self._mean( bsa ) ) / self.sd()
        except:
            return None
    def range( self, pt, limit = 1.96 ):
        '''returns a string representing the "lln - uln" '''
        bsa = pt.bsa(self.bsaMethod)
        lower = math.exp( -limit * self.sd() + self._mean( bsa ) )
        upper = math.exp( limit * self.sd() + self._mean( bsa ) )
        return '%.2f - %.2f' %( lower, upper )

aov = Base( { 'site': 'aov', 'slope': 0.426, 'int': 2.732, 'mse': 0.10392 })
sov = Base( { 'site': 'sov', 'slope': 0.443, 'int': 3.021, 'mse': 0.10173 })
stj = Base( { 'site': 'stj', 'slope': 0.434, 'int': 2.819, 'mse': 0.10961 })
aao = Base( { 'site': 'aao', 'slope': 0.421, 'int': 2.898, 'mse': 0.09111 })

sites ={
    'aov': aov,
    'sov': sov,
    'stj': stj,
    'aao': aao
}

Paris

#!/usr/bin/env python
'''paris - A module for  calculating aortic root z-scores based on data
published from Hopital Bichat, AP-HP, Paris, France;

Nomograms for aortic root diameters in children using two-dimensional
echocardiography.
Gautier M, Detaint D, Fermanian C, Aegerter P, Delorme G, Arnoult F,
Milleron O, Raoux F, Stheneur C, Boileau C, Vahanian A, Jondeau G.
Am J Cardiol. 2010 Mar 15;105(6):888-94.

'''
import math

class Gender( object ):
    pass #an empty object for 'boys' and 'girls' aortic root data

class Base( object ):
    def __init__( self, d ):
        self.int = d['int']
        self.slope = d['slope']
        self.mse = d['mse']
        self.site = d['site']
        self.bsaMethod = 'Dubois'
    def __mean( self, bsa ):
        #'private' method for returning log-transformed mean value
        #note the DOUBLE UNDERSCORE
        return self.int + self.slope * math.log(bsa);
    def mean( self, pt ):
        bsa = pt.bsa(self.bsaMethod)
        return math.exp( self.__mean( bsa ) )
    def sd( self ):
        return self.mse
    def zscore( self, pt ):
        bsa = pt.bsa(self.bsaMethod)
        try:
            score = getattr(pt, self.site) 
            return ( math.log( score ) - self.__mean( bsa ) ) / self.sd()
        except:
            return None
    def range( self, pt, limit = 1.96 ):
        bsa = pt.bsa(self.bsaMethod)
        lower = math.exp( -limit * self.sd() + self.__mean( bsa ) )
        upper = math.exp( limit * self.sd() + self.__mean( bsa ) )
        return '%.2f - %.2f' %( lower, upper )

boys = Gender()
girls = Gender()

boys.aov = Base( { 'site': 'aov', 'int': 2.78, 'slope': 0.47, 'mse': 0.10 } )
boys.sov = Base( { 'site': 'sov', 'int': 3.10, 'slope': 0.49, 'mse': 0.10 } )
boys.stj = Base( { 'site': 'stj', 'int': 2.90, 'slope': 0.47, 'mse': 0.10 } )
boys.aao = Base( { 'site': 'aao', 'int': 2.90, 'slope': 0.46, 'mse': 0.11 } )

girls.aov = Base( { 'site': 'aov', 'int': 2.75, 'slope': 0.44, 'mse': 0.10 } )
girls.sov = Base( { 'site': 'sov', 'int': 3.10, 'slope': 0.44, 'mse': 0.09 } )
girls.stj = Base( { 'site': 'stj', 'int': 2.90, 'slope': 0.42, 'mse': 0.09 } )
girls.aao = Base( { 'site': 'aao', 'int': 2.90, 'slope': 0.46, 'mse': 0.10 } )

#to do: re-work entire class to handle 'gender' as a property of 'pt'

Wessex

#!/usr/bin/env python
'''wessex - A module for  calculating z-scores based on data published from
Wessex Cardiothoracic Unit, Southampton General Hospital, UK

Relationship of the dimension of cardiac structures to body size: an
echocardiographic study in normal infants and children.
Daubeney PE, Blackstone EH, Weintraub RG, Slavik Z, Scanlon J, Webber SA.
Cardiol Young. 1999 Jul;9(4):402-10

'''
from __future__ import division #required to avoid random classic/integer division
import math

class Base( object ):
    '''this is the basic form for the Wessex equations:
    a log-log transformation and "stablized" variance    
    '''
    def __init__( self, d):
        self.slope = d['slope']
        self.int = d['int']
        self.mse = d['mse']
        self.site = d['site']
        self.bsaMethod = 'Boyd'
        self.reference = "Wessex"
    def _mean( self, bsa ):
        return self.int + self.slope * math.log( bsa )
    def mean( self, pt ):
        
        bsa = pt.bsa(self.bsaMethod)
        return 10 * math.exp( self._mean(bsa) )
    def sd( self ):
        return self.mse
    def zscore( self, pt ):
        bsa = pt.bsa(self.bsaMethod)
        try:
            score = getattr(pt, self.site)
            score /= 10
            return ( math.log( score ) - self._mean( bsa ) ) / self.sd()
        except:
            return None
    def range( self, pt, limit = 1.96 ):
        bsa = pt.bsa(self.bsaMethod)
        lower = math.exp( -limit * self.sd() + self._mean( bsa ) )
        upper = math.exp( limit * self.sd() + self._mean( bsa ) )
        return '%.2f - %.2f' %( 10* lower, 10 * upper )

aov = Base( { 'site': 'aov', 'slope': 0.5347, 'int': 0.5183, 'mse': 0.06726 })
sov = Base( { 'site': 'sov', 'slope': 0.5082, 'int': 0.7224, 'mse': 0.07284 })
stj = Base( { 'site': 'stj', 'slope': 0.5490, 'int': 0.5417, 'mse': 0.08656 })

sites = {
    'aov': aov,
    'sov': sov,
    'stj': stj    
}







About the charts

The data for the charts are built using a method that returns a list of lists, one each for the upper limit and lower limit based on the following basic form:

(click to show/hide the code samples)

Charts

def plotData( self ):
    '''returns an object with the bsa + uln/lln over a range of values for client-side plotting'''
    bsaData = [x * 0.1 for x in range(1, 21)]
    lln = [[x, 10 * math.exp( -1.96 * self.sd() + self._mean( x ) )] for x in bsaData]
    uln = [[x, 10 * math.exp( 1.96 * self.sd() + self._mean( x ) )] for x in bsaData]
    return {'uln': uln, 'lln': lln}

The lists are built with Python's list comprehension technique, saving a whole bunch of for: loop code. The actual charts themselves, and links to the charts, are built with client-side JavaScript- if your browser does not have JavaScript enabled, you won't be able to view the charts.
Sorry.