def check(self):
        """
        Checks values
        """
        status = True
        g = get_root(self).globals
        if self.mag.ok():
            self.mag.config(bg=g.COL['main'])
        else:
            self.mag.config(bg=g.COL['warn'])
            status = False

        if self.airmass.ok():
            self.airmass.config(bg=g.COL['main'])
        else:
            self.airmass.config(bg=g.COL['warn'])
            status = False

        if self.seeing.ok():
            self.seeing.config(bg=g.COL['main'])
        else:
            self.seeing.config(bg=g.COL['warn'])
            status = False

        return status
    def setExpertLevel(self):
        """
        Modifies widget according to expertise level, which in this
        case is just matter of hiding or revealing the LED option
        and changing the lower limit on the exposure button.
        """
        g = get_root(self).globals
        level = g.cpars['expert_level']

        if level == 0:
            self.expose.fmin = 0.0007
            self.ledLab.grid_forget()
            self.led.grid_forget()
            self.ledValue = self.led.value()
            self.led.set(0)

        elif level == 1:
            self.expose.fmin = 0.0003
            self.led.set(self.ledValue)
            self.ledLab.grid(row=6, column=0, sticky=tk.W)
            self.led.grid(row=6, column=1, pady=2, sticky=tk.W)

        elif level == 2:
            self.expose.fmin = 0.0
            self.led.set(self.ledValue)
            self.ledLab.grid(row=6, column=0, sticky=tk.W)
            self.led.grid(row=6, column=1, pady=2, sticky=tk.W)
    def update(self, *args):
        """
        Updates values. You should run a check on the instrument and
        target parameters before calling this.
        """
        g = get_root(self).globals
        expTime, deadTime, cycleTime, dutyCycle, frameRate = g.ipars.timing()

        total, peak, peakSat, peakWarn, ston, ston3 = \
            self.counts(expTime, cycleTime)

        if cycleTime < 0.01:
            self.cadence.config(text='{0:7.5f} s'.format(cycleTime))
        elif cycleTime < 0.1:
            self.cadence.config(text='{0:6.4f} s'.format(cycleTime))
        elif cycleTime < 1.:
            self.cadence.config(text='{0:5.3f} s'.format(cycleTime))
        elif cycleTime < 10.:
            self.cadence.config(text='{0:4.2f} s'.format(cycleTime))
        elif cycleTime < 100.:
            self.cadence.config(text='{0:4.1f} s'.format(cycleTime))
        elif cycleTime < 1000.:
            self.cadence.config(text='{0:4.0f} s'.format(cycleTime))
        else:
            self.cadence.config(text='{0:5.0f} s'.format(cycleTime))

        if expTime < 0.01:
            self.exposure.config(text='{0:7.5f} s'.format(expTime))
        elif expTime < 0.1:
            self.exposure.config(text='{0:6.4f} s'.format(expTime))
        elif expTime < 1.:
            self.exposure.config(text='{0:5.3f} s'.format(expTime))
        elif expTime < 10.:
            self.exposure.config(text='{0:4.2f} s'.format(expTime))
        elif expTime < 100.:
            self.exposure.config(text='{0:4.1f} s'.format(expTime))
        elif expTime < 1000.:
            self.exposure.config(text='{0:4.0f} s'.format(expTime))
        else:
            self.exposure.config(text='{0:5.0f} s'.format(expTime))

        self.duty.config(text='{0:4.1f} %'.format(dutyCycle))
        self.peak.config(text='{0:d} cts'.format(int(round(peak))))
        if peakSat:
            self.peak.config(bg=g.COL['error'])
        elif peakWarn:
            self.peak.config(bg=g.COL['warn'])
        else:
            self.peak.config(bg=g.COL['main'])

        self.total.config(text='{0:d} cts'.format(int(round(total))))
        self.ston.config(text='{0:.1f}'.format(ston))
        self.ston3.config(text='{0:.1f}'.format(ston3))
    def checkUpdate(self, *args):
        """
        Updates values after first checking instrument parameters are OK.
        This is not integrated within update to prevent ifinite recursion
        since update gets called from ipars.
        """
        g = get_root(self).globals
        if not self.check():
            g.clog.warn('Current observing parameters are not valid.')
            return False

        if not g.ipars.check():
            g.clog.warn('Current instrument parameters are not valid.')
            return False
    def counts(self, expTime, cycleTime, ap_scale=1.6, ndiv=5):
        """
        Computes counts per pixel, total counts, sky counts
        etc given current magnitude, seeing etc. You should
        run a check on the instrument parameters before calling
        this.

        expTime   : exposure time per frame (seconds)
        cycleTime : sampling, cadence (seconds)
        ap_scale  : aperture radius as multiple of seeing

        Returns: (total, peak, peakSat, peakWarn, ston, ston3)

        total    -- total number of object counts in aperture
        peak     -- peak counts in a pixel
        peakSat  -- flag to indicate saturation
        peakWarn -- flag to indication level approaching saturation
        ston     -- signal-to-noise per exposure
        ston3    -- signal-to-noise after 3 hours on target
        """

        # code directly translated from Java equivalent.
        g = get_root(self).globals
        # avalanche mode y/n?
        lnormal = not g.ipars.avalanche()

        # Set the readout speed
        readSpeed = g.ipars.readSpeed.value()

        if g.ipars.app == 'Windows':
            xbin, ybin = g.ipars.wframe.xbin.value(), g.ipars.wframe.ybin.value()
        else:
            xbin, ybin = g.ipars.pframe.xbin.value(), g.ipars.pframe.ybin.value()

        # calculate SN info.
        zero, sky, skyTot, gain, read, darkTot = 0., 0., 0., 0., 0., 0.
        total, peak, correct, signal, readTot, seeing = 0., 0., 0., 0., 0., 0.
        noise, _, narcsec, npix, signalToNoise3 = 1., 0., 0., 0., 0.

        tinfo = g.TINS[g.cpars['telins_name']]
        filtnam = self.filter.value()
        zero = tinfo['zerop'][filtnam]
        mag = self.mag.value()
        seeing = self.seeing.value()
        sky = g.SKY[self.moon.value()][filtnam]
        airmass = self.airmass.value()

        # GAIN, RNO
        if readSpeed == 'Fast':
            gain = GAIN_NORM_FAST if lnormal else GAIN_AV_FAST
            read = RNO_NORM_FAST if lnormal else RNO_AV_FAST

        elif readSpeed == 'Medium':
            gain = GAIN_NORM_MED if lnormal else GAIN_AV_MED
            read = RNO_NORM_MED if lnormal else RNO_AV_MED

        elif readSpeed == 'Slow':
            gain = GAIN_NORM_SLOW if lnormal else GAIN_AV_SLOW
            read = RNO_NORM_SLOW if lnormal else RNO_AV_SLOW

        plateScale = tinfo['plateScale']

        # calculate expected electrons
        total = 10.**((zero-mag-airmass*g.EXTINCTION[filtnam])/2.5)*expTime

        # compute fraction that fall in central pixel
        # assuming target exactly at its centre. Do this
        # by splitting each pixel of the central (potentially
        # binned) pixel into ndiv * ndiv points at
        # which the seeing profile is added. sigma is the
        # RMS seeing in terms of pixels.
        sigma = seeing/g.EFAC/plateScale

        sum = 0.
        for iyp in range(ybin):
            yoff = -ybin/2.+iyp
            for ixp in range(xbin):
                xoff = -xbin/2.+ixp
                for iys in range(ndiv):
                    y = (yoff + (iys+0.5)/ndiv)/sigma
                    for ixs in range(ndiv):
                        x = (xoff + (ixs+0.5)/ndiv)/sigma
                        sum += math.exp(-(x*x+y*y)/2.)
        peak = total*sum/(2.*math.pi*sigma**2*ndiv**2)
        # peak    = total*xbin*ybin*(plateScale/(seeing/EFAC))**2/(2.*math.pi)

        # Work out fraction of flux in aperture with radius AP_SCALE*seeing
        correct = 1. - math.exp(-(g.EFAC*ap_scale)**2/2.)

        # expected sky e- per arcsec
        skyPerArcsec = 10.**((zero-sky)/2.5)*expTime
        # skyPerPixel = skyPerArcsec*plateScale**2*xbin*ybin
        narcsec = math.pi*(ap_scale*seeing)**2
        skyTot = skyPerArcsec*narcsec
        npix = math.pi*(ap_scale*seeing/plateScale)**2/xbin/ybin

        signal = correct*total        # in electrons
        darkTot = npix*DARK_E*expTime  # in electrons
        readTot = npix*read**2         # in electrons
        cic = 0 if lnormal else CIC

        # noise, in electrons
        if lnormal:
            noise = math.sqrt(readTot + darkTot + skyTot + signal + cic)
        else:
            # assume high gain observations in proportional mode
            noise = math.sqrt(readTot/AVALANCHE_GAIN_9**2 +
                              2.0*(darkTot + skyTot + signal) + cic)

        # Now compute signal-to-noise in 3 hour seconds run
        signalToNoise3 = signal/noise*math.sqrt(3*3600./cycleTime)

        # if using the avalanche mode, check that the signal level
        # is safe. A single electron entering the avalanche register
        # results in a distribution of electrons at the output with
        # mean value given by the parameter avalanche_gain. The
        # distribution is close to exponential, hence the probability
        # of obtaining an amplification n times higher than the mean is
        # given by e**-n. A value of 3/5 for n is adopted here for
        # warning/safety, which will occur once in every ~20/100
        # amplifications

        # convert from electrons to counts
        total /= gain
        peak /= gain

        warn = 25000
        sat = 60000

        if not lnormal:
            sat = AVALANCHE_SATURATE/AVALANCHE_GAIN_9/5/gain
            warn = AVALANCHE_SATURATE/AVALANCHE_GAIN_9/3/gain

        peakSat = peak > sat
        peakWarn = peak > warn

        return (total, peak, peakSat, peakWarn, signal/noise, signalToNoise3)
    def __init__(self, master):
        """
        master : enclosing widget
        """
        tk.LabelFrame.__init__(self, master, text='Instrument parameters',
                               padx=10, pady=10)

        # left hand side
        lhs = tk.Frame(self)

        # Application (mode)
        tk.Label(lhs, text='Mode').grid(row=0, column=0, sticky=tk.W)
        self.app = w.Radio(lhs, ('Wins', 'Drift'), 2, self.check,
                           ('Windows', 'Drift'))
        self.app.grid(row=0, column=1, sticky=tk.W)

        # Clear enabled
        self.clearLab = tk.Label(lhs, text='Clear')
        self.clearLab.grid(row=1, column=0, sticky=tk.W)
        self.clear = w.OnOff(lhs, True, self.check)
        self.clear.grid(row=1, column=1, sticky=tk.W)

        # Avalanche settings
        tk.Label(lhs, text='Avalanche').grid(row=2, column=0, sticky=tk.W)
        aframe = tk.Frame(lhs)
        self.avalanche = w.OnOff(aframe, False, self.check)
        self.avalanche.pack(side=tk.LEFT)
        self.avgainLabel = tk.Label(aframe, text='gain ')
        self.avgainLabel.pack(side=tk.LEFT)
        self.avgain = w.RangedInt(aframe, 0, 0, 9, self.check,
                                  False, width=2)
        self.avgain.pack(side=tk.LEFT)
        aframe.grid(row=2, column=1, pady=2, sticky=tk.W)

        # Readout speed
        tk.Label(lhs, text='Readout speed').grid(row=3, column=0, sticky=tk.NW)
        self.readSpeed = w.Radio(lhs, ('Slow', 'Medium', 'Fast'), 1,
                                 self.check, ('Slow', 'Medium', 'Fast'))
        self.readSpeed.grid(row=3, column=1, pady=2, sticky=tk.W)

        # Exposure delay
        tk.Label(lhs, text='Exposure delay (s)').grid(row=4, column=0,
                                                      sticky=tk.W)

        g = get_root(self).globals
        elevel = g.cpars['expert_level']
        if elevel == 0:
            self.expose = w.Expose(lhs, 0.0007, 0.0007, 1677.7207,
                                   self.check, width=7)
        elif elevel == 1:
            self.expose = w.Expose(lhs, 0.0007, 0.0003, 1677.7207,
                                   self.check, width=7)
        else:
            self.expose = w.Expose(lhs, 0.0007, 0., 1677.7207,
                                   self.check, width=7)
        self.expose.grid(row=4, column=1, pady=2, sticky=tk.W)

        # Number of exposures
        tk.Label(lhs, text='Num. exposures  ').grid(row=5, column=0, sticky=tk.W)
        self.number = w.PosInt(lhs, 1, None, False, width=7)
        self.number.grid(row=5, column=1, pady=2, sticky=tk.W)

        # LED setting
        self.ledLab = tk.Label(lhs, text='LED setting')
        self.ledLab.grid(row=6, column=0, sticky=tk.W)
        self.led = w.RangedInt(lhs, 0, 0, 4095, None, False, width=7)
        self.led.grid(row=6, column=1, pady=2, sticky=tk.W)
        self.ledValue = self.led.value()

        # Right-hand side: the window parameters
        rhs = tk.Frame(self)

        # window mode frame (initially full frame)
        xs = (1, 101, 201, 301)
        xsmin = (1, 1, 1, 1)
        xsmax = (1056, 1056, 1056, 1056)
        ys = (1, 101, 201, 301)
        ysmin = (1, 1, 1, 1)
        ysmax = (1072, 1072, 1072, 1072)
        nx = (1056, 100, 100, 100)
        ny = (1072, 100, 100, 100)
        xbfac = (1, 2, 3, 4, 5, 6, 8)
        ybfac = (1, 2, 3, 4, 5, 6, 8)
        self.wframe = w.Windows(rhs, xs, xsmin, xsmax, ys, ysmin, ysmax,
                                nx, ny, xbfac, ybfac, self.check)
        self.wframe.grid(row=2, column=0, columnspan=3, sticky=tk.W+tk.N)

        # drift mode frame (just one pair)
        xsl = (100,)
        xslmin = (1,)
        xslmax = (1024,)
        xsr = (600,)
        xsrmin = (1,)
        xsrmax = (1024,)
        ys = (1,)
        ysmin = (1,)
        ysmax = (1024,)
        nx = (50,)
        ny = (50,)
        xbfac = (1, 2, 3, 4, 5, 6, 8)
        ybfac = (1, 2, 3, 4, 5, 6, 8)
        self.pframe = w.WinPairs(rhs, xsl, xslmin, xslmax, xsr, xsrmin,
                                 xsrmax, ys, ysmin, ysmax, nx, ny,
                                 xbfac, ybfac, self.check)

        # Pack two halfs
        lhs.pack(side=tk.LEFT, anchor=tk.N, padx=5)
        rhs.pack(side=tk.LEFT, anchor=tk.N, padx=5)

        # Store freeze state
        self.frozen = False

        # stores current avalanche setting to check for changes
        self.oldAvalanche = False

        self.setExpertLevel()
    def check(self, *args):
        """Callback function for running validity checks on the CCD
        parameters. It spots and flags overlapping windows, windows with null
        parameters, windows with invalid dimensions given the binning
        factors. It sets the correct number of windows according to the
        selected application and enables or disables the avalanche gain
        setting according to whether the avalanche output is being used.
        Finally it checks that the windows are synchronised and sets the
        status of the 'Sync' button accordingly.

        Returns True/False according to whether the settings are judged to be
        OK. True means they are thought to be in a fit state to be sent to the
        camera.

        This can only be run once the 'observe' are defined.
        """
        g = get_root(self).globals
        # Switch visible widget according to the application
        if self.isDrift():
            self.wframe.grid_forget()
            self.pframe.grid(row=2, column=0, columnspan=3, sticky=tk.W+tk.N)
            self.clearLab.config(state='disable')
            if not self.frozen:
                self.clear.config(state='disable')
                self.pframe.enable()
        else:
            self.pframe.grid_forget()
            self.wframe.grid(row=2, column=0, columnspan=3, sticky=tk.W+tk.N)
            self.clearLab.config(state='normal')
            if not self.frozen:
                self.clear.config(state='normal')
                self.wframe.enable()

        if self.avalanche():
            if not self.frozen:
                self.avgain.enable()
            if not self.oldAvalanche:
                # only update status if there has been a change
                # this is needed because any change to avGain causes
                # this check to be run and we must prevent the gain
                # automatically being set back to zero
                self.avgainLabel.configure(state='normal')
                self.avgain.set(0)
                self.oldAvalanche = True
        else:
            self.avgain.disable()
            self.avgainLabel.configure(state='disable')
            self.oldAvalanche = False

        # check the window settings
        if self.isDrift():
            status = self.pframe.check()
        else:
            status = self.wframe.check()

        # exposure delay
        if self.expose.ok():
            self.expose.config(bg=g.COL['main'])
        else:
            self.expose.config(bg=g.COL['warn'])
            status = False

        if status:
            # if valid, update timing and SN info
            g.count.update()

        return status
    def loadXML(self, xml):
        """
        Sets the values of instrument parameters given an
        ElementTree containing suitable XML
        """
        g = get_root(self).globals
        # find application
        xmlid = xml.attrib['id']
        for app, d in g.cpars['templates'].iteritems():
            if xmlid == d['id']:
                break
        else:
            raise ValueError('Do not recognize application id = ' + xmlid)

        # find parameters
        cconfig = xml.find('configure_camera')
        pdict = {}
        for param in cconfig.findall('set_parameter'):
            pdict[param.attrib['ref']] = param.attrib['value']

        xbin, ybin = int(pdict['X_BIN']), int(pdict['Y_BIN'])

        # Set them.

        # Number of exposures
        self.number.set(pdict['NUM_EXPS'] if pdict['NUM_EXPS'] != '-1' else 0)

        # LED level
        self.led.set(pdict['LED_FLSH'])

        # Avalanche or normal
        self.avalanche.set(pdict['OUTPUT'])

        # Avalanche gain
        self.avgain.set(pdict['HV_GAIN'])

        # Dwell
        self.expose.set(str(float(pdict['DWELL'])/10000.))

        # Readout speed
        speed = pdict['SPEED']
        self.readSpeed.set('Slow' if
                           speed == '0' else 'Medium' if speed == '1'
                           else 'Fast')

        if app == 'Windows':
            # Clear or not
            self.clear.set(pdict['EN_CLR'])

            # now for the windows which come in two flavours
            self.app.set('Windows')
            w = self.wframe

            # X-binning factor
            w.xbin.set(xbin)

            # Y-binning factor
            w.ybin.set(ybin)

            # Load up windows
            nwin = 0
            for nw in range(4):
                xs = 'X' + str(nw+1) + '_START'
                ys = 'Y' + str(nw+1) + '_START'
                nx = 'X' + str(nw+1) + '_SIZE'
                ny = 'Y' + str(nw+1) + '_SIZE'
                if xs in pdict and ys in pdict and nx in pdict and ny in pdict \
                        and pdict[nx] != '0' and pdict[ny] != 0:
                    xsv, ysv, nxv, nyv = int(pdict[xs]), int(pdict[ys]), int(pdict[nx]), int(pdict[ny])
                    nxv *= xbin
                    nyv *= ybin

                    nchop = max(0, 17-xsv)
                    if nchop % xbin != 0:
                        nchop = xbin * (nchop // xbin + 1)

                    if self.avalanche():
                        xsv = max(1, 1074 - xsv - nxv)
                    else:
                        xsv = max(1, xsv + nchop - 16)
                    nxv -= nchop

                    w.xs[nw].set(xsv)
                    w.ys[nw].set(ysv)
                    w.nx[nw].set(nxv)
                    w.ny[nw].set(nyv)
                    nwin += 1
                else:
                    break

            # Set the number of windows
            w.nwin.set(nwin)

        else:
            self.clear.set(0)

            # now for drift mode
            self.app.set('Drift')
            p = self.pframe

            # X-binning factor
            p.xbin.set(xbin)

            # Y-binning factor
            p.ybin.set(ybin)

            # Load up window pair values
            xslv, xsrv, ysv, nxv, nyv = (
                int(pdict['X1_START']), int(pdict['X2_START']),
                int(pdict['Y1_START']), int(pdict['X1_SIZE']), int(pdict['Y1_SIZE'])
            )
            nxv *= xbin
            nyv *= ybin

            nchop = max(0, 17-xslv)
            if nchop % xbin != 0:
                nchop = xbin * (nchop // xbin + 1)

            if self.avalanche():
                xslv = max(1, 1074-xslv-nxv)
                xsrv = max(1, 1074-xsrv-nxv)
            else:
                xslv = max(1, xslv+nchop-16)
                xsrv = max(1, xsrv+nchop-16)

            nxv -= nchop
            if xslv > xsrv:
                xsrv, xslv = xslv, xsrv

            # finally set the values
            p.xsl[0].set(xslv)
            p.xsr[0].set(xsrv)
            p.ys[0].set(ysv)
            p.nx[0].set(nxv)
            p.ny[0].set(nyv)
            p.npair.set(1)