示例#1
0
 def __init__(self,inputStateSwitch=None,rawDataDir=WINSPEC_DEFAULT_DIR,*args,**kwargs):
     super(WinspecAnalyzer, self).__init__(*args,**kwargs)
     self._connection=Winspec()
     # self.inputStateSwitch allows the analyzer to turn on and off the optical signal
     self.inputStateSwitch=inputStateSwitch if inputStateSwitch!=None else self._connection.setMirrorState
     # self.attenuator controls an external attenuator with controllable attenuation
     self.attentuator=FilterWheel()
     # Set some default values
     self.setRange(DEFAULT_RANGE,forceSet=True)        
     self.efficiency=DEFAULT_EFFICIENCY
     self.running=True
     self.numSpectra=1
     self.gratingNumber=self._getGratingNumber()
     self.centerLambda=self.getCenter()
     # Set the Winspec filename to temporary
     self.rawDataDir=rawDataDir
     self.setDataFilename()
     self.setBackgroundFilename()
     self._connection.setOverwriteWarning(False)
     self.roi = None
示例#2
0
class WinspecAnalyzer(QtCore.QObject):
    """ High level convenience class for Winspec which gives it auto-range capability and conversion from cps to watts etc """
    updateProgress=QtCore.pyqtSignal(float)
    statusMessage=QtCore.pyqtSignal(str)
    plotDataReady=QtCore.pyqtSignal(dict)
    def __init__(self,inputStateSwitch=None,rawDataDir=WINSPEC_DEFAULT_DIR,*args,**kwargs):
        super(WinspecAnalyzer, self).__init__(*args,**kwargs)
        self._connection=Winspec()
        # self.inputStateSwitch allows the analyzer to turn on and off the optical signal
        self.inputStateSwitch=inputStateSwitch if inputStateSwitch!=None else self._connection.setMirrorState
        # self.attenuator controls an external attenuator with controllable attenuation
        self.attentuator=FilterWheel()
        # Set some default values
        self.setRange(DEFAULT_RANGE,forceSet=True)        
        self.efficiency=DEFAULT_EFFICIENCY
        self.running=True
        self.numSpectra=1
        self.gratingNumber=self._getGratingNumber()
        self.centerLambda=self.getCenter()
        # Set the Winspec filename to temporary
        self.rawDataDir=rawDataDir
        self.setDataFilename()
        self.setBackgroundFilename()
        self._connection.setOverwriteWarning(False)
        self.roi = None
     
    def readPowerAuto(self,*args, **kwargs):
        """ Read a Winspec spectrum, automatically setting the gain and exposure time to reasonable values, and return the power """
        wavelength,counts, dict=self.readSingleWinspecSpectrumAuto(*args,**kwargs)
        cps=counts/self.getExposureTime()
        return sum(cpsToWatts(wavelength,cps,self.efficiency))

    def obtainSpectrum(self,tau=DEFAULT_TAU,calibratedPower=None,timeout=DEFAULT_TIMEOUT):
        """ Acquire a spectrum by gluing together as many sub-spectra as necessary to get the full span.
        Uses the currently set center,span,and resolution in the instance object.
        Return the wavelength [m], power [W], and a dictionary containing useful information about the measurement for storage """
        # Force the physical settings of Winspec to be that of current range
        self.setRange(self.rangeIndex,forceSet=True)
        self.inputStateSwitch(True)
        # Acquire measurements
        if self.numSpectra == 1:
            wavelength,counts,spectrumDict=self.readSingleWinspecSpectrumAuto(tau)
        else:
            # Setup empty variables to hold the data
            pixels=self._connection.getNumberOfPixels()
            wavelength=np.zeros(self.numSpectra*pixels)
            counts=np.zeros(self.numSpectra*pixels)
            # Acquire the central spectrum
            assert (self.numSpectra % 2)!=0
            centerIndex=int((self.numSpectra-1)/2)
            self._setDataFilename(self.dataFilename+"_"+str(centerIndex))
            wavelength_i,counts_i,spectrumDict=self.readSingleWinspecSpectrumAuto(tau)
            wavelength[pixels*centerIndex:pixels*(centerIndex+1)]=wavelength_i
            counts[pixels*centerIndex:pixels*(centerIndex+1)]=counts_i
            # Find the center position we need to get all the other spectra to lineup nicely
            allCenterLambda=self.findCenterWavelengths(self.numSpectra,self.centerLambda,min(wavelength_i),max(wavelength_i))
            # Now measure the rest of the spectra, but using identical settings from the central spectrum
            for idx in range(self.numSpectra):
                if idx != centerIndex:
                    # abort the test if that's what the user wants
                    if not self.running:
                        raise MeasurementAbortedError
                    # update sub-progress
                    self.updateProgress.emit((idx+1.0)/(self.numSpectra+1))
                    # set the filename
                    self._setDataFilename(self.dataFilename+"_"+str(idx))
                    self.statusMessage.emit("Acquiring data for subspectrum " + str(idx+1)+"/"+str(self.numSpectra))
                    self._setCenter(allCenterLambda[idx])
                    wavelength_i,counts_i,spectrumDict_i=self.readSingleWinspecSpectrumAuto(tau,rangeMode="fixed")
                    wavelength[pixels*idx:pixels*(idx+1)]=wavelength_i
                    counts[pixels*idx:pixels*(idx+1)]=counts_i
                    self.plotDataReady.emit({"x":{"data":wavelength_i,"label":"Wavelength [nm]"},"y":{"data":counts_i,"label":"counts"}})
                    QtCore.QCoreApplication.processEvents()
            # move the spectrometer back to the center
            self._setCenter(self.centerLambda)
        # convert wavelength from nm to m
        wavelength=wavelength/1e9
        # Calculate CPS
        cps=counts/self.getExposureTime()/self.getAttenuation()/self.getAbsoluteGain()
        # If a calibration power was specified then calculate the efficiency from it
        if calibratedPower!=None:
            self.efficiency=calculateOpticalEfficiency(wavelength,cps,calibratedPower)
        # Calculate the absolute power using the efficiency (a default value is used if calibratedPower not given)
        intensity=cpsToWatts(wavelength,cps,self.efficiency)
        # Add some more stuff to spectrumDict
        spectrumDict["efficiency"]=self.efficiency
        spectrumDict["SNR"]=max(counts)/np.std(spectrumDict["noiseFloor"])
        # Return the final result
        return (wavelength,intensity,spectrumDict)                    

    def readSingleWinspecSpectrumAuto(self,tau=DEFAULT_TAU,timeout=DEFAULT_TIMEOUT,rangeMode="auto", mode=None):
        """ Reads the counts using auto-range functionality and averaged over specified time interval tau in ms, remeasuring as required if any errors.
        A timeout can be specified in seconds for the auto-range and re-measure, where we give up on trying to find a more accurate reading. 
        If timeout occurs during auto-ranging, it probably means the power is fluctuating too much with time, and so tau should be increased."""
        if mode: print("Mode argument " + mode + " ignored... winspec automatically returns mean")
        if self.has2dDetector() and not self.roi: raise ValueError, "You must set the ROI when using a 2D detector"
        self.t0=time.time()
        # Automatically remeasure if there was a comm. error until timeout occurs
        while 1:
            try:
                wavelength,counts,spectrumDict=self._readSingleWinspecSpectrumAuto(tau,timeout,rangeMode)
                break
            except CommError as e:
                if time.time()-self.t0 < timeout:
                    pass
                else:
                    # If timeout occurs, re-raise the (same) error
                    raise
        del self.t0
        return (wavelength, counts, spectrumDict)

    def _readSingleWinspecSpectrumAuto(self,tau,timeout,rangeMode):
        """ Read a single spectrum with the current range. If the SNR is sub-optimal then change range and recurse.
        tau defines the minimum integration period in ms
        timeout defines a timeout for the auto-range to prevent oscillation between different range states
        rangeMode can take values ('auto','fixed','optimum') where optimum uses a custom exposure time to get best SNR """
        # Read a single spectrum, averaged of time interval tau
        if self.bgMeasRequired:
            # Turn off the input signal, measure background, then turn it back on again
            self.inputStateSwitch(False)
            self._connection.acquireBackgroundSpectrum()
            self.inputStateSwitch(True) 
            self.bgMeasRequired = False
        wavelengthData,counts,spectrumDict=self._connection.acquireSpectrum(self.accumulations(tau))
        # Increase the range if signal too large
        if spectrumDict["saturating"] or (rangeMode=="auto" and max(counts) > 0.9*(2**16 - max(spectrumDict["noiseFloor"]))):
            if self.rangeIndex<(len(GAIN_SETTINGS)-1) and rangeMode!="fixed":
                self.setRange(self.rangeIndex+1)
                return self._readSingleWinspecSpectrumAuto(tau,timeout,rangeMode)
            else:
                raise CommError, "The measured power was outside the measurement range with rangeMode=%s"%rangeMode
        # Reduce the range if power smaller than 5% of the measurement range and no timeout has occured
        elif rangeMode!="fixed" and spectrumDict["maxSample"] < .05*MAX_COUNTS and self.rangeIndex > 0 and (time.time()-self.t0)<timeout:
            self.setRange(self.rangeIndex-1)
            return self._readSingleWinspecSpectrumAuto(tau,timeout,rangeMode)
        # If rangeMode=="optimum" and no timeout, check the signal is inside optimal SNR window, setting custom exposure if required
        elif rangeMode=="optimum" and self.rangeIndex > 0 and (spectrumDict["maxSample"] < OPTIMAL_SIGNAL_WIN[0] or spectrumDict["maxSample"] > OPTIMAL_SIGNAL_WIN[1]) and (time.time()-self.t0)<timeout:
            maxCPS=spectrumDict["maxSample"]/getExposureTime()
            noiseMaxCPS=max(spectrumDict["noiseFloor"])/getExposureTime()
            t=min(0.8*MAX_COUNTS/(maxCPS+noiseMaxCPS),WINSPEC_MAX_MEAS_TIME)
            self.setExposureTime(t)
            return self._readSingleWinspecSpectrumAuto(tau,timeout,rangeMode)
        # Otherwise return the measured data
        else:
            return (wavelengthData,counts,spectrumDict)

    def setRange(self,rangeIndex,forceSet=False):
        """ Set the power range, where rangeIndex is the index in the global variable GAIN_SETTINGS, which gives a tuple specifying gain parameters"""
        assert type(rangeIndex)==int
        attenuation,gain,exposure=GAIN_SETTINGS[rangeIndex]
        if forceSet or attenuation!=self.getAttenuatorPosition(): 
            self.attentuator.setPosition(attenuation)
        if forceSet or gain!=self.getGainSetting(): 
            self._connection.setGain(gain)
            self.bgMeasRequired = True
        if forceSet or exposure!=self.getExposureTime():
            self._connection.setExposureTime(exposure)
            self.bgMeasRequired = True
        self.rangeIndex=rangeIndex

    def autoSetROI(self):
        # TODO: implement this
        # image = self._connection.acquireImage()
        # Find either max x pixel strip or the middle of the saturating strip
        # Need to acquire image background before entering this method
        roi = (222,229)
        self.setROI(roi)
        
    def setROI(self, roi):
        self.roi = roi
        if self.has2dDetector(): self._connection.setVerticalROI(*roi)

    def measureOptimalCenter(self, tau = DEFAULT_TAU):
        """ Use the lowest resolution grating to find the wavelength of maximum luminesence """
        grating = self._getGratingNumber()
        self._setGratingNumber(3)
        wavelength,counts,spectrumDict=self.readSingleWinspecSpectrumAuto(tau)
        maxIdx = self._argMaxBlock(counts, PEAK_LUM_BLOCK_SIZE)
        self._setGratingNumber(grating)
        return wavelength[maxIdx]

    def _argMaxBlock(self, data, blockSize):
        """ Find the index of the data array such that the sum of the elements in the block centred at the index is global max """
        if len(data) < blockSize: return np.argmax(data)
        blockSum = np.sum(data[:blockSize])
        maxSum = blockSum
        maxIdx = blockSize - 1
        for i in xrange(blockSize, len(data)):
            blockSum -= data[i-blockSize]
            blockSum += data[i]
            if blockSum > maxSum:
                maxSum = blockSum
                maxIdx = i
        return maxIdx - blockSize//2


    def setDataFilename(self,filename="temp"):
        """ Sets the main filename relative to self.rawDataDir """
        self.dataFilename=filename
        self._setDataFilename(filename)        
    
    def _setDataFilename(self,filename):
        """ Sets the data filename without modifying self.dataFilename """
        self._connection.setDataFilename(self.rawDataDir,filename+".SPE")

    def setBackgroundFilename(self,filename="background"):
        """ Sets the background filename relative to self.rawDataDir """
        self._connection.setBackgroundFilename(self.rawDataDir,filename+".SPE")

    def setCenter(self,center):
        """ Set the center wavelength to be measured """
        self.centerLambda=center
        self._setCenter(self.centerLambda)

    def _setCenter(self,center):
        """ Set the center wavelength of Winspec itself """
        self._connection.setCenter(center)

    def getCenter(self):
        """ Gets the center wavlength """
        return self._connection.getCenter()
        
    def setNumPoints(self,numPoints):
        """ Set the number of points (i.e. spectra) to measure... must be a multiple of the number of pixels """
        self.numSpectra=int(numPoints/self._connection.getNumberOfPixels())
        
    def setResolution(self,resolution):
        """ Set the resolution in nm (i.e. the most appropriate grating) to use for measurements """
        bestGratingIndex = np.argmin(np.abs(np.array(self._connection.detector["resolution"])-resolution))
        self._setGratingNumber(bestGratingIndex+1)
    
    def _setGratingNumber(self,gratingNum):
        """ Sets the grating number """
        self.gratingNumber=gratingNum
        self._connection.setGrating(gratingNum)

    def _getGratingNumber(self):
        """ Gets the grating number """
        return self._connection.getGrating()

    def getAttenuatorPosition(self):
        """ Get attenuator setting """
        return GAIN_SETTINGS[self.rangeIndex][0]

    def getAttenuation(self):
        """ Get attenuator gain """
        return ATTENUATION[self.getAttenuatorPosition()]

    def getGainSetting(self):
        """ Return the current gain setting (i.e. the value of gain set in Winspec)"""
        return GAIN_SETTINGS[self.rangeIndex][1]

    def getAbsoluteGain(self):
        """ Return the approximate absolute gain """
        return DETECTOR_GAIN[self.getGainSetting()]

    def getExposureTime(self):
        """ Return the current exposure time setting in seconds"""
        return self._connection.getExposureTime()

    def setExposureTime(self,exposure):
        """ Return the current exposure time setting in seconds"""
        self._connection.setExposureTime(exposure)
        self.bgMeasRequired = True

    def accumulations(self,tau):
        """ Calculate the number of accumulations necessary to keep the measurement time above tau given current exposure time """
        return int(np.ceil(tau/1000/GAIN_SETTINGS[self.rangeIndex][2]))

    def tempLocked(self):
        return self._connection.checkIfTempLocked()

    def has2dDetector(self):
        return self._connection.getDetectorHeight() > 1

    def findCenterWavelengths(self,numSpectra,centerLambda,leftMinima,rightMaxima):
        """ Given numSpectra, finds the position to set the spectrometer at for each spectrum.
        Also requires the center, min, and max lambda for the central starting point [all in m]"""
        # Get some polynomials which specify the relation between the center wavelength and the real measured wavelength minima/maxima
        pCenterFromMinima,pCenterFromMaxima,pMinimaFromCenter,pMaximaFromCenter=self._connection.getDispersionCalibration()
        # Set the center wavelength of the central spectrum to the user specified center wavelength (numSpectra must be odd)
        allCenterLambda=np.zeros(numSpectra)
        numSideSpectra=int((numSpectra-1)/2) # number of spectra on either side of the central one
        allCenterLambda[numSideSpectra]=centerLambda*1e9
        # Step through the side spectra outwards from the central spectrum in pairs (left and right) and calculate the desired center wavelengths
        for idx in range(numSideSpectra):
            # Want the maxima/minima of each spectra to line up with the minima/maxima of the next spectra
            idealLeftMaxima=leftMinima
            idealRightMinima=rightMaxima
            # Calculate where we should center the spectrometer to get the edges to line up
            leftCenter=np.polyval(pCenterFromMaxima,idealLeftMaxima)
            rightCenter=np.polyval(pCenterFromMinima,idealRightMinima)
            # assign these to the allCenterLambda arrays
            allCenterLambda[numSideSpectra-idx-1]=leftCenter
            allCenterLambda[numSideSpectra+idx+1]=rightCenter
            # Calculate the outer edge for both of the current spectra
            leftMinima=np.polyval(pMinimaFromCenter,leftCenter)
            rightMaxima=np.polyval(pMaximaFromCenter,rightCenter)
        return allCenterLambda