Exemplo n.º 1
0
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.file = None

        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row = 0

        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(master=sr.master, instName=InstName, helpURL=HelpURL)
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # standard exposure input widget
        self.expWdg = ExposeInputWdg(master=sr.master, instName=InstName, expTypes="object", helpURL=HelpURL)
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        gr = self.expWdg.gridder

        # add file widget
        self.fileWdg = RO.Wdg.FileWdg(master=self.expWdg, helpText="file of x y z etalon positions", helpURL=HelpURL)
        gr.gridWdg("Data File", self.fileWdg, colSpan=3)

        if sr.debug:
            self.expWdg.timeWdg.set(3)
            self.expWdg.fileNameWdg.set("debugtest")
Exemplo n.º 2
0
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.file = None

        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row = 0

        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master=sr.master,
            instName=InstName,
            helpURL=HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master=sr.master,
            instName=InstName,
            expTypes="object",
            helpURL=HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        gr = self.expWdg.gridder

        # add file widget
        self.fileWdg = RO.Wdg.FileWdg(
            master=self.expWdg,
            helpText="file of x y z etalon positions",
            helpURL=HelpURL,
        )
        gr.gridWdg("Data File", self.fileWdg, colSpan=3)

        if sr.debug:
            self.expWdg.timeWdg.set(3)
            self.expWdg.fileNameWdg.set("debugtest")
Exemplo n.º 3
0
    def __init__(self, sr):
        """Display exposure status and a few user input widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.expModel = ExposeModel.getModel(InstName)
        self.spicamModel = TUI.Inst.SPIcam.SPIcamModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.sr = sr

        row = 0

        expStatusWdg = ExposeStatusWdg(
            master=sr.master,
            instName=InstName,
            helpURL=HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        self.expWdg = ExposeInputWdg(
            master=sr.master,
            instName=InstName,
            expTypes="object",
            helpURL=HelpURL,
        )
        self.expWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        wdgFrame = Tkinter.Frame(sr.master)
        gr = RO.Wdg.Gridder(wdgFrame, sticky="w")
        self.filterWdg = RO.Wdg.OptionMenu(
            master=self.expWdg,
            items=[],
            helpText="filter",
            helpURL=HelpURL,
            defMenu="Current",
            autoIsCurrent=True,
        )
        self.expWdg.gridder.gridWdg("Filter",
                                    self.filterWdg,
                                    sticky="w",
                                    colSpan=3)

        self.spicamModel.filterNames.addCallback(self.filterWdg.setItems)
        self.spicamModel.filterName.addIndexedCallback(
            self.filterWdg.setDefault, 0)
Exemplo n.º 4
0
    def __init__(self, sr):
        """Display exposure status and a few user input widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.expModel = ExposeModel.getModel(InstName)
        self.spicamModel = TUI.Inst.SPIcam.SPIcamModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.sr = sr

        row = 0

        expStatusWdg = ExposeStatusWdg(
            master = sr.master,
            instName = InstName,
            helpURL = HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        self.expWdg = ExposeInputWdg(
            master = sr.master,
            instName = InstName,
            expTypes = "object",
            helpURL = HelpURL,
        )
        self.expWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        self.filterWdg = RO.Wdg.OptionMenu(
            master = self.expWdg,
            items = [],
            helpText = "filter",
            helpURL = HelpURL,
            defMenu = "Current",
            autoIsCurrent = True,
        )
        self.expWdg.gridder.gridWdg("Filter", self.filterWdg, sticky="w", colSpan=3)

        self.spicamModel.filterNames.addCallback(self.filterWdg.setItems)
        self.spicamModel.filterName.addIndexedCallback(self.filterWdg.setDefault, 0)
Exemplo n.º 5
0
class ScriptClass(object):
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.file = None

        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row = 0

        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(master=sr.master, instName=InstName, helpURL=HelpURL)
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # standard exposure input widget
        self.expWdg = ExposeInputWdg(master=sr.master, instName=InstName, expTypes="object", helpURL=HelpURL)
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        gr = self.expWdg.gridder

        # add file widget
        self.fileWdg = RO.Wdg.FileWdg(master=self.expWdg, helpText="file of x y z etalon positions", helpURL=HelpURL)
        gr.gridWdg("Data File", self.fileWdg, colSpan=3)

        if sr.debug:
            self.expWdg.timeWdg.set(3)
            self.expWdg.fileNameWdg.set("debugtest")

    def run(self, sr):
        """Take a calibration sequence.
        """
        # get current NICFPS focal plane geometry from the TCC
        # but first make sure the current instrument
        # is actually NICFPS
        if not sr.debug:
            currInstName = sr.getKeyVar(self.tccModel.instName)
            if not currInstName.lower().startswith(InstName.lower()):
                raise sr.ScriptError("%s is not the current instrument!" % InstName)

        # get exposure data and verify we have enough info to proceed
        numExp = self.expWdg.numExpWdg.getNum()
        expCmdPrefix = self.expWdg.getString()
        if not expCmdPrefix:
            return

        # get data file and parse it
        fileName = self.fileWdg.getPath()
        if not fileName:
            raise sr.ScriptError("specify a calibration data file")

        self.file = file(fileName, "rU")
        if not self.file:
            raise sr.ScriptError("could not open %r" % fileName)

        if sr.debug:
            print "Reading file %r" % (fileName,)

        # read the file in advance, so we know how many lines of data there are
        xyzList = []
        for rawLine in self.file:
            if sr.debug:
                print "Read:", rawLine,
            line = rawLine.strip()
            if not line:
                continue
            if line.startswith("#"):
                continue
            try:
                x, y, z = [int(val) for val in line.split(None, 3)]
            except Exception:
                raise sr.ScriptError("could not parse %r" % rawLine)
            xyzList.append((x, y, z))

        self.file.close()
        self.file = None

        if sr.debug:
            print "xyzList =", xyzList

        numPositions = len(xyzList)

        totNumExp = numExp * numPositions

        for seqInd in range(numPositions):
            xyzPos = xyzList[seqInd]

            # Set etalon position one axis at a time
            sr.showMsg("Step %s of %s: set etalon x,y,z = %s " % (seqInd + 1, numPositions, xyzPos))
            for axis, pos in zip(("x", "y", "z"), xyzPos):
                yield sr.waitCmd(actor=self.nicfpsModel.actor, cmdStr="fp set%s=%d" % (axis, pos))

            # compute # of exposures & format expose command
            startNum = seqInd * numExp
            expCmdStr = "%s startNum=%d totNum=%d" % (expCmdPrefix, startNum, totNumExp)

            # take exposure sequence
            sr.showMsg("Step %s of %s: expose at etalon x,y,z = %s" % (seqInd + 1, numPositions, xyzPos))
            yield sr.waitCmd(actor=self.expModel.actor, cmdStr=expCmdStr, abortCmdStr="abort")

    def end(self, sr):
        if self.file:
            self.file.close()
Exemplo n.º 6
0
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.errStr = ""

        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row = 0

        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master=sr.master,
            instName=InstName,
            helpURL=HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master=sr.master,
            instName=InstName,
            expTypes="object",
            helpURL=HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        gr = self.expWdg.gridder

        # add etalon controls to exposure input widget
        self.begSeqIndWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=0,
            width=SpacingWidth,
            helpText="initial z index (to finish a partial run)",
            helpURL=HelpURL,
        )
        gr.gridWdg("Initial Index", self.begSeqIndWdg,
                   "(normally leave blank)")

        self.fpBegZWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=self.nicfpsModel.fpXYZLimConst[0],
            maxValue=self.nicfpsModel.fpXYZLimConst[1],
            width=SpacingWidth,
            helpText="initial etalon Z spacing",
            helpURL=HelpURL,
        )
        gr.gridWdg("Initial Z", self.fpBegZWdg, "steps")

        self.fpDeltaZWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=self.nicfpsModel.fpXYZLimConst[0],
            maxValue=self.nicfpsModel.fpXYZLimConst[1],
            width=SpacingWidth,
            helpText="etalon Z spacing interval",
            helpURL=HelpURL,
        )
        gr.gridWdg("Delta Z", self.fpDeltaZWdg, "steps")

        self.fpNumZWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=1,
            maxValue=9999,
            width=SpacingWidth,
            helpText="number of etalon Z spacings",
            helpURL=HelpURL,
        )
        gr.gridWdg("Num Zs", self.fpNumZWdg, "steps")

        self.fpEndZWdg = RO.Wdg.IntLabel(
            master=self.expWdg,
            width=SpacingWidth,
            helpText="final etalon Z spacing",
            helpURL=HelpURL,
            anchor="e",
        )
        self.fpEndZUnitsWdg = RO.Wdg.StrLabel(
            master=self.expWdg,
            text="steps",
            helpURL=HelpURL,
            anchor="w",
        )
        gr.gridWdg("Final Z", self.fpEndZWdg, self.fpEndZUnitsWdg)

        self.fpNumPassesWdg = RO.Wdg.OptionMenu(
            master=self.expWdg,
            items=("1", "2", "3"),
            defValue="2",
            helpText="number of passes in which to sample Z",
            helpURL=HelpURL,
        )
        gr.gridWdg("Num Passes", self.fpNumPassesWdg)

        self.currSeqIndWdg = RO.Wdg.IntLabel(
            master=self.expWdg,
            width=SpacingWidth,
            helpText="index of current Z spacing",
            helpURL=HelpURL,
            anchor="e",
        )
        gr.gridWdg("Current Index", self.currSeqIndWdg)

        fpCurrWdg = RO.Wdg.IntLabel(
            master=self.expWdg,
            width=SpacingWidth,
            helpText="current actual etalon Z spacing",
            helpURL=HelpURL,
            anchor="e",
        )
        gr.gridWdg("Current Z", fpCurrWdg, "steps")

        self.nicfpsModel.fpZ.addROWdg(fpCurrWdg)

        self.fpBegZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpDeltaZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpNumZWdg.addCallback(self.updEndZ, callNow=True)
Exemplo n.º 7
0
class ScriptClass(object):
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.errStr = ""

        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row = 0

        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master=sr.master,
            instName=InstName,
            helpURL=HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master=sr.master,
            instName=InstName,
            expTypes="object",
            helpURL=HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        gr = self.expWdg.gridder

        # add etalon controls to exposure input widget
        self.begSeqIndWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=0,
            width=SpacingWidth,
            helpText="initial z index (to finish a partial run)",
            helpURL=HelpURL,
        )
        gr.gridWdg("Initial Index", self.begSeqIndWdg,
                   "(normally leave blank)")

        self.fpBegZWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=self.nicfpsModel.fpXYZLimConst[0],
            maxValue=self.nicfpsModel.fpXYZLimConst[1],
            width=SpacingWidth,
            helpText="initial etalon Z spacing",
            helpURL=HelpURL,
        )
        gr.gridWdg("Initial Z", self.fpBegZWdg, "steps")

        self.fpDeltaZWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=self.nicfpsModel.fpXYZLimConst[0],
            maxValue=self.nicfpsModel.fpXYZLimConst[1],
            width=SpacingWidth,
            helpText="etalon Z spacing interval",
            helpURL=HelpURL,
        )
        gr.gridWdg("Delta Z", self.fpDeltaZWdg, "steps")

        self.fpNumZWdg = RO.Wdg.IntEntry(
            master=self.expWdg,
            minValue=1,
            maxValue=9999,
            width=SpacingWidth,
            helpText="number of etalon Z spacings",
            helpURL=HelpURL,
        )
        gr.gridWdg("Num Zs", self.fpNumZWdg, "steps")

        self.fpEndZWdg = RO.Wdg.IntLabel(
            master=self.expWdg,
            width=SpacingWidth,
            helpText="final etalon Z spacing",
            helpURL=HelpURL,
            anchor="e",
        )
        self.fpEndZUnitsWdg = RO.Wdg.StrLabel(
            master=self.expWdg,
            text="steps",
            helpURL=HelpURL,
            anchor="w",
        )
        gr.gridWdg("Final Z", self.fpEndZWdg, self.fpEndZUnitsWdg)

        self.fpNumPassesWdg = RO.Wdg.OptionMenu(
            master=self.expWdg,
            items=("1", "2", "3"),
            defValue="2",
            helpText="number of passes in which to sample Z",
            helpURL=HelpURL,
        )
        gr.gridWdg("Num Passes", self.fpNumPassesWdg)

        self.currSeqIndWdg = RO.Wdg.IntLabel(
            master=self.expWdg,
            width=SpacingWidth,
            helpText="index of current Z spacing",
            helpURL=HelpURL,
            anchor="e",
        )
        gr.gridWdg("Current Index", self.currSeqIndWdg)

        fpCurrWdg = RO.Wdg.IntLabel(
            master=self.expWdg,
            width=SpacingWidth,
            helpText="current actual etalon Z spacing",
            helpURL=HelpURL,
            anchor="e",
        )
        gr.gridWdg("Current Z", fpCurrWdg, "steps")

        self.nicfpsModel.fpZ.addROWdg(fpCurrWdg)

        self.fpBegZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpDeltaZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpNumZWdg.addCallback(self.updEndZ, callNow=True)

    def updEndZ(self, *args, **kargs):
        """Call when beg Z, delta Z or num Z changed to update end Z.
        """
        begSpacing = self.fpBegZWdg.getNumOrNone()
        numSpacings = self.fpNumZWdg.getNumOrNone()
        deltaZ = self.fpDeltaZWdg.getNumOrNone()

        endZ = None
        self.errStr = ""
        if begSpacing == None:
            self.errStr = "specify initial Z"
        elif deltaZ == None:
            self.errStr = "specify delta z"
        elif numSpacings == None:
            self.errStr = "specify number of zs"
        else:
            endZ = begSpacing + (deltaZ * (numSpacings - 1))

            # check range
            minZ, maxZ = self.nicfpsModel.fpXYZLimConst
            if endZ < minZ:
                self.errStr = "final Z < %s" % minZ
            elif endZ > maxZ:
                self.errStr = "final Z > %s" % maxZ

        if self.errStr:
            isCurrent = False
            self.fpEndZUnitsWdg.set("error: %s" % self.errStr,
                                    isCurrent=isCurrent)
        else:
            isCurrent = True
            self.fpEndZUnitsWdg.set("steps", isCurrent=isCurrent)

        self.fpEndZWdg.set(endZ, isCurrent=isCurrent)

    def run(self, sr):
        """Take an exposure sequence.
        """
        # Make sure the current instrument is NICFPS
        if not sr.debug:
            currInstName = sr.getKeyVar(self.tccModel.instName)
            if not currInstName.lower().startswith(InstName.lower()):
                raise sr.ScriptError("%s is not the current instrument!" %
                                     InstName)

        # exposure command without startNum and totNum
        # get it now so that it will not change if the user messes
        # with the controls while the script is running
        numExp = self.expWdg.numExpWdg.getNum()
        expCmdPrefix = self.expWdg.getString()
        if not expCmdPrefix:
            raise sr.ScriptError("missing inputs")

#         print "got here; errStr =", self.errStr

        if self.errStr:
            raise sr.ScriptError(self.errStr)

        # get user data in advance
        begSeqInd = self.begSeqIndWdg.getNum()
        begSpacing = self.fpBegZWdg.getNum()
        numSpacings = self.fpNumZWdg.getNum()
        deltaZ = self.fpDeltaZWdg.getNum()
        numPasses = int(self.fpNumPassesWdg.getString())

        totNumExp = numExp * numSpacings

        # for each pass through the data, create a list of multipliers,
        # where z = zo + delta-z * mult
        multList = range(numSpacings)
        seqPassMultList = []
        for passInd in range(numPasses):
            for zMult in multList[passInd::numPasses]:
                seqInd = len(seqPassMultList)
                seqPassMultList.append((seqInd, passInd, zMult))

    #   print "seqPassMultList =", seqPassMultList

        for seqInd, passInd, zMult in seqPassMultList[begSeqInd:]:
            currSpacing = begSpacing + (deltaZ * zMult)

            self.currSeqIndWdg.set(seqInd)

            # command etalon spacing
            sr.showMsg("Set etalon Z = %d %s" % (currSpacing, "steps"))
            yield sr.waitCmd(
                actor=self.nicfpsModel.actor,
                cmdStr="fp setz=%d" % (currSpacing, ),
            )

            # compute # of exposures & format expose command
            startNum = seqInd * numExp
            expCmdStr = "%s startNum=%d totNum=%d" % (expCmdPrefix, startNum,
                                                      totNumExp)

            # take exposure sequence
            sr.showMsg("Expose at etalon Z = %d %s" % (currSpacing, "steps"))
            yield sr.waitCmd(
                actor=self.expModel.actor,
                cmdStr=expCmdStr,
                abortCmdStr="abort",
            )
Exemplo n.º 8
0
    def __init__(self, sr):
        """The setup script; run once when the script runner
        window is created.
        """
        # if sr.debug True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.sr = sr

        self.begOffset = numpy.array((numpy.nan, numpy.nan))
        self.currOffset = self.begOffset[:]
        
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
    
        row=0
        
        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(sr.master, InstName)
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1
        
        # create dither node controls
        ditherFrame = Tkinter.Frame(sr.master)
        
        # information about the dither nodes; each entry is:
        # - name of quadrant
        # - boresight offset multiplier in image x, image y
        ditherNodeData = [
            ("Ctr", (0, 0)),
            ("UL", (-1, 1)),
            ("UR", (1, 1)),
            ("LR", (1, -1)),
            ("LL", (-1, -1)),
        ]
        self.ditherWdgSet = [] # (stateWdg, orderWdg, boolWdg), one per dither node
        for name, offMult in ditherNodeData:
            nodeFrame = Tkinter.Frame(ditherFrame)

            stateWdg = RO.Wdg.StrLabel(
                master = nodeFrame,
                width = 7,
                relief = "flat",
                helpText = "State of node in dither sequence",
                helpURL = HelpURL,
            )
            orderWdg = RO.Wdg.IntLabel(
                master = nodeFrame,
                width = 1,
                relief = "flat",
                helpText = "Order of node in dither sequence",
                helpURL = HelpURL,
            )
            boolWdg = RO.Wdg.Checkbutton(
                master = nodeFrame,
                text = name,
                callFunc = self.updOrder,
                defValue = True,
                relief = "flat",
                helpText = "Check to use this dither node",
                helpURL = HelpURL,
            )
            # add attribute "offMult" to widget
            # so it can be read by "run"
            boolWdg.offMult = numpy.array(offMult, dtype=float)
            
            self.ditherWdgSet.append((stateWdg, orderWdg, boolWdg))

            stateWdg.pack(side="left")
            orderWdg.pack(side="left")
            boolWdg.pack(side="left")
            
            # display quadrant checkbutton in appropriate location
            row = 1 - offMult[1]
            col = 1 + offMult[0]
            nodeFrame.grid(row=row, column=col)
            
        
        ditherFrame.grid(row=row, column=0, sticky="news")
        row += 1
        # standard exposure input widget
        self.expWdg = ExposeInputWdg(sr.master, InstName, expTypes="object")
        self.expWdg.numExpWdg.helpText = "# of pairs of exposures at each node"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # add controls to exposure input widget frame
        self.boxSizeWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 0,
            defValue = DefBoxSize,
            helpText = "size of dither box",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Box Size", self.boxSizeWdg, "arcsec")

        self.doRandomWdg = RO.Wdg.Checkbutton(
            master = self.expWdg,
            defValue = DefDoRandom,
            helpText = "Add random scatter to dither pattern?",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Randomize?", self.doRandomWdg)

        self.skyOffsetWdgSet = []
        for ii in range(2):
            axisStr = ("RA", "Dec")[ii]
            unitsVar = Tkinter.StringVar()
            offsetWdg = RO.Wdg.DMSEntry(
                master = self.expWdg,
                minValue = -MaxOffset,
                maxValue = MaxOffset,
                defValue = DefOffset,
                isHours = False,
                isRelative = True,
                helpText = "Offset to sky in %s (typically)" % (axisStr,),
                helpURL = HelpURL,
                unitsVar = unitsVar,
            )
            self.skyOffsetWdgSet.append(offsetWdg)
            self.expWdg.gridder.gridWdg(
                "Sky Offset %s" % (ii + 1,),
                offsetWdg,
                units = unitsVar,
            )

        
        self.expWdg.gridder.allGridded()
        
        if sr.debug:
            # set useful debug defaults
            self.expWdg.timeWdg.set("1.0")
            self.expWdg.numExpWdg.set(2)
            self.expWdg.fileNameWdg.set("debug")
            self.ditherWdgSet[1][-1].setBool(False)
            self.ditherWdgSet[3][-1].setBool(False)
        
        self.updOrder()
Exemplo n.º 9
0
class ScriptClass(object):  
    def __init__(self, sr):
        """The setup script; run once when the script runner
        window is created.
        """
        # if sr.debug True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.sr = sr

        self.begOffset = numpy.array((numpy.nan, numpy.nan))
        self.currOffset = self.begOffset[:]
        
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
    
        row=0
        
        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(sr.master, InstName)
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1
        
        # create dither node controls
        ditherFrame = Tkinter.Frame(sr.master)
        
        # information about the dither nodes; each entry is:
        # - name of quadrant
        # - boresight offset multiplier in image x, image y
        ditherNodeData = [
            ("Ctr", (0, 0)),
            ("UL", (-1, 1)),
            ("UR", (1, 1)),
            ("LR", (1, -1)),
            ("LL", (-1, -1)),
        ]
        self.ditherWdgSet = [] # (stateWdg, orderWdg, boolWdg), one per dither node
        for name, offMult in ditherNodeData:
            nodeFrame = Tkinter.Frame(ditherFrame)

            stateWdg = RO.Wdg.StrLabel(
                master = nodeFrame,
                width = 7,
                relief = "flat",
                helpText = "State of node in dither sequence",
                helpURL = HelpURL,
            )
            orderWdg = RO.Wdg.IntLabel(
                master = nodeFrame,
                width = 1,
                relief = "flat",
                helpText = "Order of node in dither sequence",
                helpURL = HelpURL,
            )
            boolWdg = RO.Wdg.Checkbutton(
                master = nodeFrame,
                text = name,
                callFunc = self.updOrder,
                defValue = True,
                relief = "flat",
                helpText = "Check to use this dither node",
                helpURL = HelpURL,
            )
            # add attribute "offMult" to widget
            # so it can be read by "run"
            boolWdg.offMult = numpy.array(offMult, dtype=float)
            
            self.ditherWdgSet.append((stateWdg, orderWdg, boolWdg))

            stateWdg.pack(side="left")
            orderWdg.pack(side="left")
            boolWdg.pack(side="left")
            
            # display quadrant checkbutton in appropriate location
            row = 1 - offMult[1]
            col = 1 + offMult[0]
            nodeFrame.grid(row=row, column=col)
            
        
        ditherFrame.grid(row=row, column=0, sticky="news")
        row += 1
        # standard exposure input widget
        self.expWdg = ExposeInputWdg(sr.master, InstName, expTypes="object")
        self.expWdg.numExpWdg.helpText = "# of pairs of exposures at each node"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # add controls to exposure input widget frame
        self.boxSizeWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 0,
            defValue = DefBoxSize,
            helpText = "size of dither box",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Box Size", self.boxSizeWdg, "arcsec")

        self.doRandomWdg = RO.Wdg.Checkbutton(
            master = self.expWdg,
            defValue = DefDoRandom,
            helpText = "Add random scatter to dither pattern?",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Randomize?", self.doRandomWdg)

        self.skyOffsetWdgSet = []
        for ii in range(2):
            axisStr = ("RA", "Dec")[ii]
            unitsVar = Tkinter.StringVar()
            offsetWdg = RO.Wdg.DMSEntry(
                master = self.expWdg,
                minValue = -MaxOffset,
                maxValue = MaxOffset,
                defValue = DefOffset,
                isHours = False,
                isRelative = True,
                helpText = "Offset to sky in %s (typically)" % (axisStr,),
                helpURL = HelpURL,
                unitsVar = unitsVar,
            )
            self.skyOffsetWdgSet.append(offsetWdg)
            self.expWdg.gridder.gridWdg(
                "Sky Offset %s" % (ii + 1,),
                offsetWdg,
                units = unitsVar,
            )

        
        self.expWdg.gridder.allGridded()
        
        if sr.debug:
            # set useful debug defaults
            self.expWdg.timeWdg.set("1.0")
            self.expWdg.numExpWdg.set(2)
            self.expWdg.fileNameWdg.set("debug")
            self.ditherWdgSet[1][-1].setBool(False)
            self.ditherWdgSet[3][-1].setBool(False)
        
        self.updOrder()
            
    def end(self, sr):
        """If telescope offset, restore original position.
        """
        self.updOrder(doForce=True)
        
        # restore original boresight position, if changed
        if self.needMove(self.begOffset):
            tccCmdStr = "offset arc %.7f, %.7f/pabs/vabs/computed" % tuple(self.begOffset)
            #print "sending tcc command %r" % tccCmdStr
            sr.startCmd(
                actor = "tcc",
                cmdStr = tccCmdStr,
            )

    def needMove(self, desOffset):
        """Return True if telescope not at desired offset"""
        if numpy.any(numpy.isnan(self.currOffset)):
            return False
        return not numpy.allclose(self.currOffset, desOffset)         
     
    def run(self, sr):
        """Take an exposure sequence.
        """
        # clear node state
        for wdgSet in self.ditherWdgSet:
            wdgSet[0].set(None)

        # get current NICFPS focal plane geometry from the TCC
        # but first make sure the current instrument is actually NICFPS
        if not sr.debug:
            currInstName = sr.getKeyVar(self.tccModel.instName)
            if not currInstName.lower().startswith(InstName.lower()):
                raise sr.ScriptError("%s is not the current instrument!" % InstName)
    
        # record the current object offset position
        begArcPVTs = sr.getKeyVar(self.tccModel.objArcOff, ind=None)
        if not sr.debug:
            begOffset = [pvt.getPos() for pvt in begArcPVTs]
            if None in begOffset:
                raise sr.ScriptError("Current arc offset unknown")
            self.begOffset = numpy.array(begOffset, dtype=float)
        else:
            self.begOffset = numpy.zeros(2, dtype=float)
        self.currOffset = self.begOffset[:]
        #print "self.begOffset=%r" % self.begOffset

        ditherSize = self.boxSizeWdg.getNum() / 2.0
        doRandom = self.doRandomWdg.getBool()

        # vector describing how far away from the object to move
        # in order to do the second dither pattern
        skyOffsetDeg = numpy.array([self.skyOffsetWdgSet[ii].getNum() for ii in range(2)]) / 3600.0
        
        # record which points to use in the dither pattern in advance
        # (rather than allowing the user to change it during execution)
        doPtArr = [wdgs[-1].getBool() for wdgs in self.ditherWdgSet]

        # exposure command without startNum
        # get it now so that it will not change if the user messes
        # with the controls while the script is running
        numExp = self.expWdg.numExpWdg.getNumOrNone()
        if numExp == None:
            raise sr.ScriptError("must specify #Exp")
            
        numNodes = sum(doPtArr)
        totNumExp = numNodes * numExp * 2
        if doRandom:
            # use randomization: take just one exposure and then apply a random offset
            expCmdPrefix = self.expWdg.getString(numExp = 1, totNum = totNumExp)
        else:
            # no randomization: take all #Exp exposures at once
            expCmdPrefix = self.expWdg.getString(totNum = totNumExp)
        if not expCmdPrefix:
            raise sr.ScriptError("missing inputs")
        
        # loop through each dither node
        # taking nExp exposures at each of:
        # node 1 source, node 1 sky, node 2 sky, node 2 source...
        ditherSizeDeg = ditherSize / 3600.0
        #randomRangeDeg = ditherSizeDeg / 2.0
        randomRangeDeg = RandomBoxSize / 3600.0
        numExpTaken = 0
        onSkyIter = itertools.cycle((False, True, True, False))
        for ind, wdgSet in enumerate(self.ditherWdgSet):
            stateWdg, orderWdg, boolWdg = wdgSet
            if not doPtArr[ind]:
                stateWdg.set("Skipped")
                continue
            nodeName = str(boolWdg["text"])

            # Expose on object and sky at this dither point
            for i in range(2):
                onSky = onSkyIter.next()
                if onSky:
                    srcName = "Sky"
                else:
                    srcName = "Source"

                stateWdg.set(srcName)
                
                srcNodeName = "%s %s" % (srcName, nodeName)

                desOffset = self.begOffset + (skyOffsetDeg * onSky) + (boolWdg.offMult * ditherSizeDeg)
                if doRandom:
                    # apply random offset before each exposure at this position
                    for expInd in range(numExp):
                        if numExpTaken == 0:
                            # do not randomize the first point; this saves a bit of time
                            randomScatter = numpy.zeros(2, dtype=float)
                            fullNodeName = "%s with no random scatter" % (srcNodeName,)
                        else:
                            randomScatter = (numpy.random.random(2) * randomRangeDeg) - (randomRangeDeg / 2.0)
                            randomScatterArcSec = randomScatter * 3600.0
                            fullNodeName = "%s + %0.1f, %0.1f random scatter" % \
                                (srcNodeName, randomScatterArcSec[0], randomScatterArcSec[1])
                        #print "Adding randomScatter", randomScatter
                        randomizedOffset = desOffset + randomScatter
                        if self.needMove(randomizedOffset):
                            # slew telescope
                            randomScatterArcSec = randomScatter * 3600.0
                            sr.showMsg("Offset to %s" % (fullNodeName,))
                            yield self.waitOffset(randomizedOffset)
                            
                        
                        # format exposure command
                        startNum = numExpTaken + 1
                        expCmdStr = "%s startNum=%d" % (expCmdPrefix, startNum)
                        
                        # take exposure sequence
                        sr.showMsg("Expose at %s" % (fullNodeName,))
                        yield sr.waitCmd(
                            actor = self.expModel.actor,
                            cmdStr = expCmdStr,
                            abortCmdStr = "abort",
                        )
                        numExpTaken += 1
                else:
                    # compute # of exposures & format expose command
                    startNum = numExpTaken + 1
    
                    expCmdStr = "%s startNum=%d" % (expCmdPrefix, startNum)
                
                    # offset telescope
                    if self.needMove(desOffset):
                        sr.showMsg("Offset to %s position" % (srcNodeName,))
                        yield self.waitOffset(desOffset)
                
                    # take exposure sequence
                    sr.showMsg("Expose on %s position" % (srcNodeName,))
                    yield sr.waitCmd(
                        actor = self.expModel.actor,
                        cmdStr = expCmdStr,
                        abortCmdStr = "abort",
                    )
                    
                    numExpTaken += numExp
            
            stateWdg.set("Done")
        
        # slew back to starting position
        if self.needMove(self.begOffset):
            sr.showMsg("Finishing up: slewing to initial position")
            yield self.waitOffset(self.begOffset)

    def updOrder(self, wdg=None, doForce=False):
        """Update the order widgets
        
        If the script is executing then the widgets are left untouched
        unless doForce is True. This allows the order widgets to be correct
        while running even if the user messes with the checkboxes.
        """
        if not doForce and self.sr.isExecuting():
            return
        orderNum = 1
        for stateWdg, orderWdg, boolWdg in self.ditherWdgSet:
            if boolWdg.getBool():
                orderWdg.set(orderNum)
                orderNum += 1
            else:
                orderWdg.set(None)
    
    def waitOffset(self, desOffset):
        """Offset the telescope"""
        tccCmdStr = "offset arc %.7f, %.7f/pabs/vabs/computed" % tuple(desOffset)
        self.currOffset = desOffset[:]
        yield self.sr.waitCmd(
            actor = "tcc",
            cmdStr = tccCmdStr,
        )
Exemplo n.º 10
0
class ScriptClass(object):
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.file = None

        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row = 0

        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master=sr.master,
            instName=InstName,
            helpURL=HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1

        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master=sr.master,
            instName=InstName,
            expTypes="object",
            helpURL=HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1

        gr = self.expWdg.gridder

        # add file widget
        self.fileWdg = RO.Wdg.FileWdg(
            master=self.expWdg,
            helpText="file of x y z etalon positions",
            helpURL=HelpURL,
        )
        gr.gridWdg("Data File", self.fileWdg, colSpan=3)

        if sr.debug:
            self.expWdg.timeWdg.set(3)
            self.expWdg.fileNameWdg.set("debugtest")

    def run(self, sr):
        """Take a calibration sequence.
        """
        # get current NICFPS focal plane geometry from the TCC
        # but first make sure the current instrument
        # is actually NICFPS
        if not sr.debug:
            currInstName = sr.getKeyVar(self.tccModel.instName)
            if not currInstName.lower().startswith(InstName.lower()):
                raise sr.ScriptError("%s is not the current instrument!" %
                                     InstName)

        # get exposure data and verify we have enough info to proceed
        numExp = self.expWdg.numExpWdg.getNum()
        expCmdPrefix = self.expWdg.getString()
        if not expCmdPrefix:
            return

        # get data file and parse it
        fileName = self.fileWdg.getPath()
        if not fileName:
            raise sr.ScriptError("specify a calibration data file")

        self.file = file(fileName, 'rU')
        if not self.file:
            raise sr.ScriptError("could not open %r" % fileName)

        if sr.debug:
            print "Reading file %r" % (fileName, )

        # read the file in advance, so we know how many lines of data there are
        xyzList = []
        for rawLine in self.file:
            if sr.debug:
                print "Read:", rawLine,
            line = rawLine.strip()
            if not line:
                continue
            if line.startswith("#"):
                continue
            try:
                x, y, z = [int(val) for val in line.split(None, 3)]
            except Exception:
                raise sr.ScriptError("could not parse %r" % rawLine)
            xyzList.append((x, y, z))

        self.file.close()
        self.file = None

        if sr.debug:
            print "xyzList =", xyzList

        numPositions = len(xyzList)

        totNumExp = numExp * numPositions

        for seqInd in range(numPositions):
            xyzPos = xyzList[seqInd]

            # Set etalon position one axis at a time
            sr.showMsg("Step %s of %s: set etalon x,y,z = %s " %
                       (seqInd + 1, numPositions, xyzPos))
            for axis, pos in zip(("x", "y", "z"), xyzPos):
                yield sr.waitCmd(
                    actor=self.nicfpsModel.actor,
                    cmdStr="fp set%s=%d" % (axis, pos),
                )

            # compute # of exposures & format expose command
            startNum = seqInd * numExp
            expCmdStr = "%s startNum=%d totNum=%d" % (expCmdPrefix, startNum,
                                                      totNumExp)

            # take exposure sequence
            sr.showMsg("Step %s of %s: expose at etalon x,y,z = %s" %
                       (seqInd + 1, numPositions, xyzPos))
            yield sr.waitCmd(
                actor=self.expModel.actor,
                cmdStr=expCmdStr,
                abortCmdStr="abort",
            )

    def end(self, sr):
        if self.file:
            self.file.close()
Exemplo n.º 11
0
class ScriptClass(object):
    """Take a series of SPIcam twilight or morning flats
    """
    def __init__(self, sr):
        """Display exposure status and a few user input widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.expModel = ExposeModel.getModel(InstName)
        self.spicamModel = TUI.Inst.SPIcam.SPIcamModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.sr = sr

        row = 0

        expStatusWdg = ExposeStatusWdg(
            master = sr.master,
            instName = InstName,
            helpURL = HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        self.expWdg = ExposeInputWdg(
            master = sr.master,
            instName = InstName,
            expTypes = "object",
            helpURL = HelpURL,
        )
        self.expWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        self.filterWdg = RO.Wdg.OptionMenu(
            master = self.expWdg,
            items = [],
            helpText = "filter",
            helpURL = HelpURL,
            defMenu = "Current",
            autoIsCurrent = True,
        )
        self.expWdg.gridder.gridWdg("Filter", self.filterWdg, sticky="w", colSpan=3)

        self.spicamModel.filterNames.addCallback(self.filterWdg.setItems)
        self.spicamModel.filterName.addIndexedCallback(self.filterWdg.setDefault, 0)
    
    def run(self, sr):
        """Take a series of SPIcam flats"""

        # record user inputs
        filtName = self.filterWdg.getString()
        expTime = self.expWdg.timeWdg.getNum()
        numExp = self.expWdg.numExpWdg.getNum()
        fileName = self.expWdg.fileNameWdg.getString()
        comment = self.expWdg.commentWdg.getString()
        
        if not filtName:
            raise sr.ScriptError("Specify filter")
        if expTime <= 0:
            raise sr.ScriptError("Specify exposure time")
        if numExp <= 0:
            raise sr.ScriptError("Specify number of exposures")
        nTimeFields = self.expWdg.timeWdg.getString().count(":") + 1
        nTimeFields = max(1, min(3, nTimeFields))
        self.expWdg.timeWdg.defFormat = (nTimeFields, 1)
            
        # morning or evening?
        utcHours = time.gmtime().tm_hour
        locHours = utcHours + (TUI.TCC.TelConst.Longitude / 15.0)
        locHours = RO.MathUtil.wrapPos(locHours * 15.0) / 15.0
        isMorning = locHours < 12.0
        
        # if filter is different, set it
        if not self.filterWdg.isDefault():
            desFiltNum = self.filterWdg.getIndex() + 1
            cmdStr = "filter %d" % (desFiltNum,)
            yield sr.waitCmd(
                actor = self.spicamModel.actor,
                cmdStr = cmdStr,
            )
        
        for expNum in range(numExp):
            # offset telescope
            sr.showMsg("Dither %s of %s" % (expNum+1, numExp))
            yield self.sr.waitCmd(
                actor = "tcc",
                cmdStr = "offset arc/computed %0.7f, %0.7f" % (DitherOffsetArcSec / 3600.0, 0.0)
            )

            # compute next exposure time
            if isMorning:
                expTime = self.nextMorningExpTime(expTime)
            else:
                expTime = self.nextTwilightExpTime(expTime)
            
            self.expWdg.timeWdg.set(expTime)
            
            cmdStr = self.expModel.formatExpCmd(
                expType = "flat",
                expTime = expTime,
                fileName = fileName,
                numExp = 1,
                comment = comment,
                startNum = expNum + 1,
                totNum = numExp,
            )
    
            sr.showMsg("Exposure %s of %s: %.1f sec" % (expNum+1, numExp, expTime))
            yield sr.waitCmd(
                actor = "spicamExpose",
                cmdStr = cmdStr,
                abortCmdStr = "abort",
            )
    
    def nextTwilightExpTime(self, prevExpTime):
        """Compute next exposure time for a twilight flat
        (compensating for the darkening sky)
        
        This equation is from the original SPIcam scripts;
        it blows up around 180 seconds, so a ceiling is used.
        """
        maxExpTime = 160.0
        temp = math.exp(-prevExpTime / 288.0) + math.exp(-(prevExpTime + 45.0) / 288.0) - 1.0
        if temp <= 0:
            return maxExpTime
        desExpTime = -288.0 * (math.log(temp)) - (prevExpTime + 45.0)
        return min(maxExpTime, desExpTime)
    
    def nextMorningExpTime(self, prevExpTime):
        """Compute next exposure time for a morning flat
        (compensating for the brightening sky)
        
        The equation is from the original SPIcam scripts.
        """
        temp = math.exp(prevExpTime / 288.0) + math.exp((prevExpTime + 45.0) / 288.0) - 1.0
        desExpTime = 288.0 * (math.log(temp)) - (prevExpTime + 45.0)
        return max(self.expModel.instInfo.minExpTime, desExpTime)
Exemplo n.º 12
0
class ScriptClass(object):
    """Take a series of SPIcam twilight or morning flats
    """
    def __init__(self, sr):
        """Display exposure status and a few user input widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.expModel = ExposeModel.getModel(InstName)
        self.spicamModel = TUI.Inst.SPIcam.SPIcamModel.getModel()
        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.sr = sr

        row = 0

        expStatusWdg = ExposeStatusWdg(
            master=sr.master,
            instName=InstName,
            helpURL=HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        self.expWdg = ExposeInputWdg(
            master=sr.master,
            instName=InstName,
            expTypes="object",
            helpURL=HelpURL,
        )
        self.expWdg.grid(row=row, column=0, columnspan=3, sticky="w")
        row += 1

        wdgFrame = Tkinter.Frame(sr.master)
        gr = RO.Wdg.Gridder(wdgFrame, sticky="w")
        self.filterWdg = RO.Wdg.OptionMenu(
            master=self.expWdg,
            items=[],
            helpText="filter",
            helpURL=HelpURL,
            defMenu="Current",
            autoIsCurrent=True,
        )
        self.expWdg.gridder.gridWdg("Filter",
                                    self.filterWdg,
                                    sticky="w",
                                    colSpan=3)

        self.spicamModel.filterNames.addCallback(self.filterWdg.setItems)
        self.spicamModel.filterName.addIndexedCallback(
            self.filterWdg.setDefault, 0)

    def run(self, sr):
        """Take a series of SPIcam flats"""

        # record user inputs
        filtName = self.filterWdg.getString()
        expTime = self.expWdg.timeWdg.getNum()
        numExp = self.expWdg.numExpWdg.getNum()
        fileName = self.expWdg.fileNameWdg.getString()
        comment = self.expWdg.commentWdg.getString()

        if not filtName:
            raise sr.ScriptError("Specify filter")
        if expTime <= 0:
            raise sr.ScriptError("Specify exposure time")
        if numExp <= 0:
            raise sr.ScriptError("Specify number of exposures")
        nTimeFields = self.expWdg.timeWdg.getString().count(":") + 1
        nTimeFields = max(1, min(3, nTimeFields))
        self.expWdg.timeWdg.defFormat = (nTimeFields, 1)

        # morning or evening?
        utcHours = time.gmtime().tm_hour
        locHours = utcHours + (TUI.TCC.TelConst.Longitude / 15.0)
        locHours = RO.MathUtil.wrapPos(locHours * 15.0) / 15.0
        isMorning = locHours < 12.0

        # if filter is different, set it
        if not self.filterWdg.isDefault():
            desFiltNum = self.filterWdg.getIndex() + 1
            cmdStr = "filter %d" % (desFiltNum, )
            yield sr.waitCmd(
                actor=self.spicamModel.actor,
                cmdStr=cmdStr,
            )

        for expNum in range(numExp):
            # offset telescope
            sr.showMsg("Dither %s of %s" % (expNum + 1, numExp))
            yield self.sr.waitCmd(actor="tcc",
                                  cmdStr="offset arc/computed %0.7f, %0.7f" %
                                  (DitherOffsetArcSec / 3600.0, 0.0))

            # compute next exposure time
            if isMorning:
                expTime = self.nextMorningExpTime(expTime)
            else:
                expTime = self.nextTwilightExpTime(expTime)

            self.expWdg.timeWdg.set(expTime)

            cmdStr = self.expModel.formatExpCmd(
                expType="flat",
                expTime=expTime,
                fileName=fileName,
                numExp=1,
                comment=comment,
                startNum=expNum + 1,
                totNum=numExp,
            )

            sr.showMsg("Exposure %s of %s: %.1f sec" %
                       (expNum + 1, numExp, expTime))
            yield sr.waitCmd(
                actor="spicamExpose",
                cmdStr=cmdStr,
                abortCmdStr="abort",
            )

    def nextTwilightExpTime(self, prevExpTime):
        """Compute next exposure time for a twilight flat
        (compensating for the darkening sky)
        
        This equation is from the original SPIcam scripts;
        it blows up around 180 seconds, so a ceiling is used.
        """
        maxExpTime = 160.0
        temp = math.exp(-prevExpTime / 288.0) + math.exp(
            -(prevExpTime + 45.0) / 288.0) - 1.0
        if temp <= 0:
            return maxExpTime
        desExpTime = -288.0 * (math.log(temp)) - (prevExpTime + 45.0)
        return min(maxExpTime, desExpTime)

    def nextMorningExpTime(self, prevExpTime):
        """Compute next exposure time for a morning flat
        (compensating for the brightening sky)
        
        The equation is from the original SPIcam scripts.
        """
        temp = math.exp(prevExpTime / 288.0) + math.exp(
            (prevExpTime + 45.0) / 288.0) - 1.0
        desExpTime = 288.0 * (math.log(temp)) - (prevExpTime + 45.0)
        return max(self.expModel.instInfo.minExpTime, desExpTime)
Exemplo n.º 13
0
    def __init__(self, sr):
        """The setup script; run once when the script runner
        window is created.
        """
        # if sr.debug True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.sr = sr

        self.begOffset = numpy.array((numpy.nan, numpy.nan))
        self.currOffset = self.begOffset[:]

        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
    
        row=0
        
        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master = sr.master,
            instName = InstName,
            helpURL = HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1
        
        # create dither node controls
        ditherFrame = Tkinter.Frame(sr.master)
        
        # information about the dither nodes; each entry is:
        # - name of quadrant
        # - boresight offset multiplier in image x, image y
        ditherNodeData = [
            ("Ctr", (0, 0)),
            ("UL", (-1, 1)),
            ("UR", (1, 1)),
            ("LR", (1, -1)),
            ("LL", (-1, -1)),
        ]
        self.ditherWdgSet = [] # (stateWdg, orderWdg, boolWdg), one per dither node
        for name, offMult in ditherNodeData:
            nodeFrame = Tkinter.Frame(ditherFrame)

            stateWdg = RO.Wdg.StrLabel(
                master = nodeFrame,
                width = 7,
                relief = "flat",
                helpText = "State of node in dither sequence",
                helpURL = HelpURL,
            )
            orderWdg = RO.Wdg.IntLabel(
                master = nodeFrame,
                width = 1,
                relief = "flat",
                helpText = "Order of node in dither sequence",
                helpURL = HelpURL,
            )
            boolWdg = RO.Wdg.Checkbutton(
                master = nodeFrame,
                text = name,
                callFunc = self.updOrder,
                defValue = True,
                relief = "flat",
                helpText = "Check to use this dither node",
                helpURL = HelpURL,
            )
            # add attribute "offMult" to widget
            # so it can be read by "run"
            boolWdg.offMult = numpy.array(offMult, dtype=float)
            
            self.ditherWdgSet.append((stateWdg, orderWdg, boolWdg))

            stateWdg.pack(side="left")
            orderWdg.pack(side="left")
            boolWdg.pack(side="left")
            
            # display quadrant checkbutton in appropriate location
            row = 1 - offMult[1]
            col = 1 + offMult[0]
            nodeFrame.grid(row=row, column=col)
            
        
        ditherFrame.grid(row=row, column=0, sticky="news")
        row += 1
    
        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master = sr.master,
            instName = InstName,
            expTypes = "object",
            helpURL = HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each point"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1
    
        # add controls to exposure input widget frame
        self.boxSizeWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 0,
            defValue = DefBoxSize,
            helpText = "size of dither box",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Box Size", self.boxSizeWdg, "arcsec")
        
        self.doRandomWdg = RO.Wdg.Checkbutton(
            master = self.expWdg,
            defValue = DefDoRandom,
            helpText = "Add random scatter to dither pattern?",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Randomize?", self.doRandomWdg)
        
        if sr.debug:
            # set useful debug defaults
            self.expWdg.timeWdg.set("1.0")
            self.expWdg.numExpWdg.set(2)
            self.expWdg.fileNameWdg.set("debug")
            self.ditherWdgSet[1][-1].setBool(False)
            self.ditherWdgSet[3][-1].setBool(False)

        self.updOrder()
Exemplo n.º 14
0
class ScriptClass(object):  
    def __init__(self, sr):
        """The setup script; run once when the script runner
        window is created.
        """
        # if sr.debug True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False
        self.sr = sr

        self.begOffset = numpy.array((numpy.nan, numpy.nan))
        self.currOffset = self.begOffset[:]

        self.tccModel = TUI.TCC.TCCModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
    
        row=0
        
        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master = sr.master,
            instName = InstName,
            helpURL = HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1
        
        # create dither node controls
        ditherFrame = Tkinter.Frame(sr.master)
        
        # information about the dither nodes; each entry is:
        # - name of quadrant
        # - boresight offset multiplier in image x, image y
        ditherNodeData = [
            ("Ctr", (0, 0)),
            ("UL", (-1, 1)),
            ("UR", (1, 1)),
            ("LR", (1, -1)),
            ("LL", (-1, -1)),
        ]
        self.ditherWdgSet = [] # (stateWdg, orderWdg, boolWdg), one per dither node
        for name, offMult in ditherNodeData:
            nodeFrame = Tkinter.Frame(ditherFrame)

            stateWdg = RO.Wdg.StrLabel(
                master = nodeFrame,
                width = 7,
                relief = "flat",
                helpText = "State of node in dither sequence",
                helpURL = HelpURL,
            )
            orderWdg = RO.Wdg.IntLabel(
                master = nodeFrame,
                width = 1,
                relief = "flat",
                helpText = "Order of node in dither sequence",
                helpURL = HelpURL,
            )
            boolWdg = RO.Wdg.Checkbutton(
                master = nodeFrame,
                text = name,
                callFunc = self.updOrder,
                defValue = True,
                relief = "flat",
                helpText = "Check to use this dither node",
                helpURL = HelpURL,
            )
            # add attribute "offMult" to widget
            # so it can be read by "run"
            boolWdg.offMult = numpy.array(offMult, dtype=float)
            
            self.ditherWdgSet.append((stateWdg, orderWdg, boolWdg))

            stateWdg.pack(side="left")
            orderWdg.pack(side="left")
            boolWdg.pack(side="left")
            
            # display quadrant checkbutton in appropriate location
            row = 1 - offMult[1]
            col = 1 + offMult[0]
            nodeFrame.grid(row=row, column=col)
            
        
        ditherFrame.grid(row=row, column=0, sticky="news")
        row += 1
    
        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master = sr.master,
            instName = InstName,
            expTypes = "object",
            helpURL = HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each point"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1
    
        # add controls to exposure input widget frame
        self.boxSizeWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 0,
            defValue = DefBoxSize,
            helpText = "size of dither box",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Box Size", self.boxSizeWdg, "arcsec")
        
        self.doRandomWdg = RO.Wdg.Checkbutton(
            master = self.expWdg,
            defValue = DefDoRandom,
            helpText = "Add random scatter to dither pattern?",
            helpURL = HelpURL,
        )
        self.expWdg.gridder.gridWdg("Randomize?", self.doRandomWdg)
        
        if sr.debug:
            # set useful debug defaults
            self.expWdg.timeWdg.set("1.0")
            self.expWdg.numExpWdg.set(2)
            self.expWdg.fileNameWdg.set("debug")
            self.ditherWdgSet[1][-1].setBool(False)
            self.ditherWdgSet[3][-1].setBool(False)

        self.updOrder()
    
    def end(self, sr):
        """If telescope offset, restore original position.
        """
        self.updOrder(doForce=True)
        
        # restore original boresight position, if changed
        if self.needMove(self.begOffset):
            tccCmdStr = "offset boresight %.7f, %.7f/pabs/vabs/computed" % tuple(self.begOffset)
            #print "sending tcc command %r" % tccCmdStr
            sr.startCmd(
                actor = "tcc",
                cmdStr = tccCmdStr,
            )

    def needMove(self, desOffset):
        """Return True if telescope not at desired offset"""
        if numpy.any(numpy.isnan(self.currOffset)):
            return False
        return not numpy.allclose(self.currOffset, desOffset)         
    
    def run(self, sr):
        """Take an exposure sequence.
        """
        # clear node state
        for wdgSet in self.ditherWdgSet:
            wdgSet[0].set(None)

        # get current NICFPS focal plane geometry from the TCC
        # but first make sure the current instrument is actually NICFPS
        if not sr.debug:
            currInstName = sr.getKeyVar(self.tccModel.instName)
            if not currInstName.lower().startswith(InstName.lower()):
                raise sr.ScriptError("%s is not the current instrument!" % InstName)
    
        # record the current boresight position
        begBorePVTs = sr.getKeyVar(self.tccModel.boresight, ind=None)
        if not sr.debug:
            begOffset = [pvt.getPos() for pvt in begBorePVTs]
            if None in begOffset:
                raise sr.ScriptError("Current boresight position unknown")
            self.begOffset = numpy.array(begOffset, dtype=float)
        else:
            self.begOffset = numpy.zeros(2, dtype=float)
        self.currOffset = self.begOffset[:]
        #print "self.begOffset=%r" % self.begOffset
        
        ditherSize = self.boxSizeWdg.getNum() / 2.0
        doRandom = self.doRandomWdg.getBool()
        
        # record which points to use in the dither pattern in advance
        # (rather than allowing the user to change it during execution)
        doPtArr = [wdgs[-1].getBool() for wdgs in self.ditherWdgSet]

        # exposure command without startNum and totNumExp
        # get it now so that it will not change if the user messes
        # with the controls while the script is running
        numExp = self.expWdg.numExpWdg.getNumOrNone()
        if numExp is None:
            raise sr.ScriptError("must specify #Exp")

        numNodes = sum(doPtArr)
        totNumExp = numNodes * numExp
        if doRandom:
            # use randomization: take just one exposure and then apply a random offset
            expCmdPrefix = self.expWdg.getString(numExp = 1, totNum = totNumExp)
        else:
            # no randomization: take all #Exp exposures at once
            expCmdPrefix = self.expWdg.getString(totNum = totNumExp)
        if not expCmdPrefix:
            raise sr.ScriptError("missing inputs")

        # loop through each dither node,
        # taking numExp exposures at each node
        ditherSizeDeg = ditherSize / 3600.0
        #randomRangeDeg = ditherSizeDeg / 2.0
        randomRangeDeg = RandomBoxSize / 3600.0
        numExpTaken = 0
        for ind, wdgSet in enumerate(self.ditherWdgSet):
            stateWdg, orderWdg, boolWdg = wdgSet
            if not doPtArr[ind]:
                stateWdg.set("Skipped")
                continue

            stateWdg.set("Running")
            nodeName = str(boolWdg["text"])
            
            desOffset = self.begOffset + (boolWdg.offMult * ditherSizeDeg)
            if doRandom:
                # apply random offset before each exposure at this position
                for expInd in range(numExp):
                    if numExpTaken == 0:
                        # do not randomize the first point; this saves a bit of time
                        randomScatter = numpy.zeros(2, dtype=float)
                        fullNodeName = "%s with no random scatter" % (nodeName,)
                    else:
                        randomScatter = (numpy.random.random(2) * randomRangeDeg) - (randomRangeDeg / 2.0)
                        randomScatterArcSec = randomScatter * 3600.0
                        fullNodeName = "%s + %0.1f, %0.1f random scatter" % \
                            (nodeName, randomScatterArcSec[0], randomScatterArcSec[1])
                    #print "Adding randomScatter", randomScatter
                    randomizedOffset = desOffset + randomScatter
                    if self.needMove(randomizedOffset):
                        # slew telescope
                        randomScatterArcSec = randomScatter * 3600.0
                        sr.showMsg("Offset to %s" % (fullNodeName,))
                        yield self.waitOffset(randomizedOffset)
                        
                    
                    # format exposure command
                    startNum = numExpTaken + 1
                    expCmdStr = "%s startNum=%d" % (expCmdPrefix, startNum)
                    
                    # take exposure sequence
                    sr.showMsg("Expose at %s" % (fullNodeName,))
                    yield sr.waitCmd(
                        actor = self.expModel.actor,
                        cmdStr = expCmdStr,
                        abortCmdStr = "abort",
                    )
                    numExpTaken += 1
            else:
                # no randomization; take all numExp exposures at this point
                if self.needMove(desOffset):
                    # slew telescope
                    sr.showMsg("Offset to %s position" % nodeName)
                    yield self.waitOffset(desOffset)
                    
                # format exposure command
                startNum = numExpTaken + 1
                expCmdStr = "%s startNum=%d" % (expCmdPrefix, startNum)
                
                # take exposure sequence
                sr.showMsg("Expose at %s position" % nodeName)
                yield sr.waitCmd(
                    actor = self.expModel.actor,
                    cmdStr = expCmdStr,
                    abortCmdStr = "abort",
                )
        
                numExpTaken += numExp

            stateWdg.set("Done")
        
        # slew back to starting position
        if self.needMove(self.begOffset):
            sr.showMsg("Finishing up: slewing to initial position")
            yield self.waitOffset(self.begOffset)
    
    def updOrder(self, wdg=None, doForce=False):
        """Update the order widgets
        
        If the script is executing then the widgets are left untouched
        unless doForce is True. This allows the order widgets to be correct
        while running even if the user messes with the checkboxes.
        """
        if not doForce and self.sr.isExecuting():
            return
        orderNum = 1
        for stateWdg, orderWdg, boolWdg in self.ditherWdgSet:
            if boolWdg.getBool():
                orderWdg.set(orderNum)
                orderNum += 1
            else:
                orderWdg.set(None)
    
    def waitOffset(self, desOffset):
        """Offset the telescope"""
        tccCmdStr = "offset boresight %.7f, %.7f/pabs/vabs/computed" % tuple(desOffset)
        self.currOffset = desOffset[:]
        yield self.sr.waitCmd(
            actor = "tcc",
            cmdStr = tccCmdStr,
        )
Exemplo n.º 15
0
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.errStr = ""
        
        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row=0
        
        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master = sr.master,
            instName = InstName,
            helpURL = HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1
    
        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master = sr.master,
            instName = InstName,
            expTypes = "object",
            helpURL = HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1
        
        gr = self.expWdg.gridder
            
        # add etalon controls to exposure input widget
        self.begSeqIndWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 0,
            width = SpacingWidth,
            helpText = "initial z index (to finish a partial run)",
            helpURL = HelpURL,
        )
        gr.gridWdg("Initial Index", self.begSeqIndWdg, "(normally leave blank)")
    
        self.fpBegZWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = self.nicfpsModel.fpXYZLimConst[0],
            maxValue = self.nicfpsModel.fpXYZLimConst[1],
            width = SpacingWidth,
            helpText = "initial etalon Z spacing",
            helpURL = HelpURL,
        )
        gr.gridWdg("Initial Z", self.fpBegZWdg, "steps")
    
        self.fpDeltaZWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = self.nicfpsModel.fpXYZLimConst[0],
            maxValue = self.nicfpsModel.fpXYZLimConst[1],
            width = SpacingWidth,
            helpText = "etalon Z spacing interval",
            helpURL = HelpURL,
        )
        gr.gridWdg("Delta Z", self.fpDeltaZWdg, "steps")
        
        self.fpNumZWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 1,
            maxValue = 9999,
            width = SpacingWidth,
            helpText = "number of etalon Z spacings",
            helpURL = HelpURL,
        )
        gr.gridWdg("Num Zs", self.fpNumZWdg, "steps")
        
        self.fpEndZWdg = RO.Wdg.IntLabel(
            master = self.expWdg,
            width = SpacingWidth,
            helpText = "final etalon Z spacing",
            helpURL = HelpURL,
            anchor = "e",
        )
        self.fpEndZUnitsWdg = RO.Wdg.StrLabel(
            master = self.expWdg,
            text = "steps",
            helpURL = HelpURL,
            anchor = "w",
        )
        gr.gridWdg("Final Z", self.fpEndZWdg, self.fpEndZUnitsWdg)
        
        self.fpNumPassesWdg = RO.Wdg.OptionMenu(
            master = self.expWdg,
            items = ("1", "2", "3"),
            defValue = "2",
            helpText = "number of passes in which to sample Z",
            helpURL = HelpURL,
        )
        gr.gridWdg("Num Passes", self.fpNumPassesWdg)
        
        self.currSeqIndWdg = RO.Wdg.IntLabel(
            master = self.expWdg,
            width = SpacingWidth,
            helpText = "index of current Z spacing",
            helpURL = HelpURL,
            anchor = "e",
        )
        gr.gridWdg("Current Index", self.currSeqIndWdg)
        
        fpCurrWdg = RO.Wdg.IntLabel(
            master = self.expWdg,
            width = SpacingWidth,
            helpText = "current actual etalon Z spacing",
            helpURL = HelpURL,
            anchor = "e",
        )
        gr.gridWdg("Current Z", fpCurrWdg, "steps")
        
        self.nicfpsModel.fpZ.addROWdg(fpCurrWdg)
        
        self.fpBegZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpDeltaZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpNumZWdg.addCallback(self.updEndZ, callNow=True)
Exemplo n.º 16
0
class ScriptClass(object):  
    def __init__(self, sr):
        """Create widgets.
        """
        # if True, run in debug-only mode (which doesn't DO anything, it just pretends)
        sr.debug = False

        self.errStr = ""
        
        self.nicfpsModel = TUI.Inst.NICFPS.NICFPSModel.getModel()
        self.expModel = TUI.Inst.ExposeModel.getModel(InstName)
        self.tccModel = TUI.TCC.TCCModel.getModel()

        row=0
        
        # standard exposure status widget
        expStatusWdg = ExposeStatusWdg(
            master = sr.master,
            instName = InstName,
            helpURL = HelpURL,
        )
        expStatusWdg.grid(row=row, column=0, sticky="news")
        row += 1
    
        # standard exposure input widget
        self.expWdg = ExposeInputWdg(
            master = sr.master,
            instName = InstName,
            expTypes = "object",
            helpURL = HelpURL,
        )
        self.expWdg.numExpWdg.helpText = "# of exposures at each spacing"
        self.expWdg.grid(row=row, column=0, sticky="news")
        row += 1
        
        gr = self.expWdg.gridder
            
        # add etalon controls to exposure input widget
        self.begSeqIndWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 0,
            width = SpacingWidth,
            helpText = "initial z index (to finish a partial run)",
            helpURL = HelpURL,
        )
        gr.gridWdg("Initial Index", self.begSeqIndWdg, "(normally leave blank)")
    
        self.fpBegZWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = self.nicfpsModel.fpXYZLimConst[0],
            maxValue = self.nicfpsModel.fpXYZLimConst[1],
            width = SpacingWidth,
            helpText = "initial etalon Z spacing",
            helpURL = HelpURL,
        )
        gr.gridWdg("Initial Z", self.fpBegZWdg, "steps")
    
        self.fpDeltaZWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = self.nicfpsModel.fpXYZLimConst[0],
            maxValue = self.nicfpsModel.fpXYZLimConst[1],
            width = SpacingWidth,
            helpText = "etalon Z spacing interval",
            helpURL = HelpURL,
        )
        gr.gridWdg("Delta Z", self.fpDeltaZWdg, "steps")
        
        self.fpNumZWdg = RO.Wdg.IntEntry(
            master = self.expWdg,
            minValue = 1,
            maxValue = 9999,
            width = SpacingWidth,
            helpText = "number of etalon Z spacings",
            helpURL = HelpURL,
        )
        gr.gridWdg("Num Zs", self.fpNumZWdg, "steps")
        
        self.fpEndZWdg = RO.Wdg.IntLabel(
            master = self.expWdg,
            width = SpacingWidth,
            helpText = "final etalon Z spacing",
            helpURL = HelpURL,
            anchor = "e",
        )
        self.fpEndZUnitsWdg = RO.Wdg.StrLabel(
            master = self.expWdg,
            text = "steps",
            helpURL = HelpURL,
            anchor = "w",
        )
        gr.gridWdg("Final Z", self.fpEndZWdg, self.fpEndZUnitsWdg)
        
        self.fpNumPassesWdg = RO.Wdg.OptionMenu(
            master = self.expWdg,
            items = ("1", "2", "3"),
            defValue = "2",
            helpText = "number of passes in which to sample Z",
            helpURL = HelpURL,
        )
        gr.gridWdg("Num Passes", self.fpNumPassesWdg)
        
        self.currSeqIndWdg = RO.Wdg.IntLabel(
            master = self.expWdg,
            width = SpacingWidth,
            helpText = "index of current Z spacing",
            helpURL = HelpURL,
            anchor = "e",
        )
        gr.gridWdg("Current Index", self.currSeqIndWdg)
        
        fpCurrWdg = RO.Wdg.IntLabel(
            master = self.expWdg,
            width = SpacingWidth,
            helpText = "current actual etalon Z spacing",
            helpURL = HelpURL,
            anchor = "e",
        )
        gr.gridWdg("Current Z", fpCurrWdg, "steps")
        
        self.nicfpsModel.fpZ.addROWdg(fpCurrWdg)
        
        self.fpBegZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpDeltaZWdg.addCallback(self.updEndZ, callNow=False)
        self.fpNumZWdg.addCallback(self.updEndZ, callNow=True)

    def updEndZ(self, *args, **kargs):
        """Call when beg Z, delta Z or num Z changed to update end Z.
        """
        begSpacing = self.fpBegZWdg.getNumOrNone()
        numSpacings = self.fpNumZWdg.getNumOrNone()
        deltaZ = self.fpDeltaZWdg.getNumOrNone()
        
        endZ = None
        self.errStr = ""
        if begSpacing is None:
            self.errStr = "specify initial Z"
        elif deltaZ is None:
            self.errStr = "specify delta z"
        elif numSpacings is None:
            self.errStr = "specify number of zs"
        else:
            endZ = begSpacing + (deltaZ * (numSpacings - 1))
            
            # check range
            minZ, maxZ = self.nicfpsModel.fpXYZLimConst
            if endZ < minZ:
                self.errStr = "final Z < %s" % minZ
            elif endZ > maxZ:
                self.errStr = "final Z > %s" % maxZ

        if self.errStr:
            isCurrent = False
            self.fpEndZUnitsWdg.set("error: %s" % self.errStr, isCurrent=isCurrent)
        else:
            isCurrent = True
            self.fpEndZUnitsWdg.set("steps", isCurrent=isCurrent)
        
        self.fpEndZWdg.set(endZ, isCurrent = isCurrent)
        
    def run(self, sr):
        """Take an exposure sequence.
        """
        # Make sure the current instrument is NICFPS
        if not sr.debug:
            currInstName = sr.getKeyVar(self.tccModel.instName)
            if not currInstName.lower().startswith(InstName.lower()):
                raise sr.ScriptError("%s is not the current instrument!" % InstName)
        
        # exposure command without startNum and totNum
        # get it now so that it will not change if the user messes
        # with the controls while the script is running
        numExp = self.expWdg.numExpWdg.getNum()
        expCmdPrefix = self.expWdg.getString()
        if not expCmdPrefix:
            raise sr.ScriptError("missing inputs")
        
#         print "got here; errStr =", self.errStr
        
        if self.errStr:
            raise sr.ScriptError(self.errStr)
        
        # get user data in advance
        begSeqInd = self.begSeqIndWdg.getNum()
        begSpacing = self.fpBegZWdg.getNum()
        numSpacings = self.fpNumZWdg.getNum()
        deltaZ = self.fpDeltaZWdg.getNum()
        numPasses = int(self.fpNumPassesWdg.getString())
    
        totNumExp = numExp * numSpacings
    
        # for each pass through the data, create a list of multipliers,
        # where z = zo + delta-z * mult
        multList = range(numSpacings)
        seqPassMultList = []
        for passInd in range(numPasses):
            for zMult in multList[passInd::numPasses]:
                seqInd = len(seqPassMultList)
                seqPassMultList.append((seqInd, passInd, zMult))
        
    #   print "seqPassMultList =", seqPassMultList
    
        for seqInd, passInd, zMult in seqPassMultList[begSeqInd:]:
            currSpacing = begSpacing + (deltaZ * zMult)
            
            self.currSeqIndWdg.set(seqInd)
            
            # command etalon spacing
            sr.showMsg("Set etalon Z = %d %s" % (currSpacing, "steps"))
            yield sr.waitCmd(
                actor = self.nicfpsModel.actor,
                cmdStr = "fp setz=%d" % (currSpacing,),
            )
    
            # compute # of exposures & format expose command
            startNum = seqInd * numExp
            expCmdStr = "%s startNum=%d totNum=%d" % (expCmdPrefix, startNum, totNumExp)
            
            # take exposure sequence
            sr.showMsg("Expose at etalon Z = %d %s" % (currSpacing, "steps"))
            yield sr.waitCmd(
                actor = self.expModel.actor,
                cmdStr = expCmdStr,
                abortCmdStr = "abort",
            )