def takeRun( self ) :
		self.analysisControl.reset()
		self.cbc2SCurveRun=SCurveRun( self, self.daqProgram, self.analysisControl, self.scanRange )
		self.cbc2SCurveRun.run()
		#analysisControl.restoreFromRootFile( "/tmp/calibrateChannelTrims-low.root" )
		fitParameters=self.analysisControl.fitParameters()
		for target in self.targets :
			channelNumber=target['channelNumber']
			cbcName=target['cbcName']
			target['previousResults'].append(
				{'trim':self.daqProgram.supervisor.getChannelTrim(channelNumber,[cbcName]),
				'mean':fitParameters[cbcName][channelNumber]['mean'] }
			)
class CalibrateChannelTrims(threading.Thread):
	def __init__( self, statusCallback, daqProgram, analysisControl, scanRange, midPointTarget, interimOutputFilename=None, maxLoops=10, channelsToCalibrate=None ) :
		super(CalibrateChannelTrims,self).__init__()
		self.statusCallback=statusCallback
		self.daqProgram=daqProgram
		self.analysisControl=analysisControl
		self.scanRange=scanRange
		self.midPointTarget=midPointTarget
		self.interimOutputFilename=interimOutputFilename
		self.maxLoops=maxLoops
		self.quit=False
		
		# Before making asking which CBCs are connected I have to initialise
		# the CBC information in the control program. This isn't in the __init__
		# because it requires starting the XDAQ process which might not be what the
		# user wants
		self.daqProgram.initialiseCBCs()
		# Set the targets for all of the channels
		self.targets=[]
		connectedCBCs=self.daqProgram.supervisor.connectedCBCNames()
		for index in range( 0, len(connectedCBCs) ) :
			cbcName=connectedCBCs[index]
			if channelsToCalibrate==None : # default is to calibrate all channels
				channels=range(0,254)
			else :
				try :
					channels=channelsToCalibrate[index]
				except IndexError :
					raise ValueError( "CalibrateChannelTrims - channelsToCalibrate was specified but it didn't have enough entries for the number of connected CBCs" )
				
			for channelNumber in channels :
				self.targets.append( {'cbcName':cbcName, 'channelNumber':channelNumber, 'target':self.midPointTarget, 'previousResults':[] } )

	def run( self ) :
		"""
		Tries to calibrate the channel trims on all the connected CBCs so that the midpoint
		of their s-curves sits on mitPointTarget.
		
		If interimOutputFilename is not 'None' then the files from each loop willbe saved to
		files and directories starting with that name.
		
		@author Mark Grimes ([email protected])
		@date 24/Jan/2014
		"""
		# I perform linear fits of the previous results to estimate what the trim to be.
		# For this to work I'll start off taking one run with very low trims and one with
		# high trims so that I have enough data points for the fit.
			
		# Take a run a quarter of the way along the range
		for target in self.targets :
			self.daqProgram.supervisor.setChannelTrim(target['channelNumber'],63,[target['cbcName']])
		if self.statusCallback!=None : self.statusCallback.currentStatus( 0.0, "Taking run with trims at 1/4" )
		self.loopDescription="Loop with low trims" # Set this for currentStatus() to report properly
		self.loop=-1 # little hack for currentStatus() to report the fractionComplete properly
		self.takeRun()
		if self.interimOutputFilename!=None : self.analysisControl.saveHistograms( self.interimOutputFilename+"-low.root" )

		# See if the user has decided to quit out of the thread.
		if self.quit :
			if self.statusCallback!=None : self.statusCallback.finished()
			return

		# Take a run a quarter from the end of the range
		for target in self.targets :
			self.daqProgram.supervisor.setChannelTrim(target['channelNumber'],191,[target['cbcName']])
		if self.statusCallback!=None : self.statusCallback.currentStatus( 1.0/float(self.maxLoops+2), "Taking run with trims at 3/4" )
		self.loopDescription="Loop with high trims" # Set this for currentStatus() to report properly
		self.loop=0 # little hack for currentStatus() to report the fractionComplete properly
		self.takeRun()
		if self.interimOutputFilename!=None : self.analysisControl.saveHistograms( self.interimOutputFilename+"-high.root" )
		
		# See if the user has decided to quit out of the thread.
		if self.quit :
			if self.statusCallback!=None : self.statusCallback.finished()
			return

		self.setNewTrims()
	
		scurvesAlign=False
		self.loop=0
		while (not scurvesAlign) and (not self.quit) :
			self.loop+=1
			self.loopDescription="Loop "+str(self.loop)
			if self.statusCallback!=None :
				self.statusCallback.currentStatus( float(self.loop+1)/float(self.maxLoops+2), "Starting loop "+str(self.loop)+". Channels still to calibrate="+str(len(self.targets)) )
			#for target in self.targets :
			#	print target
			self.takeRun()
			self.setNewTrims()
			if self.interimOutputFilename!=None : self.analysisControl.saveHistograms( self.interimOutputFilename+"-loop"+str(self.loop)+".root" )
			# Might as well save the trims as I go in case something goes wrong
			if self.interimOutputFilename!=None : self.daqProgram.supervisor.saveI2c( self.interimOutputFilename+"-loop"+str(self.loop) )
			# Decide whether to drop out or not
			if len(self.targets)==0 : scurvesAlign=True
			elif self.loop>self.maxLoops :
				# Create a message to let the user know which channels failed. Any channels
				# that have converged will have been taken out of the targets array.
				failMessage="Reached "+str(self.maxLoops)+" loops and the s-curves for channels "
				for failedTarget in self.targets :
					bestResult=self.setToBestPreviousResult( failedTarget )
					failMessage+=failedTarget['cbcName']+"["+str(failedTarget['channelNumber'])+"] (managed to calibrate to "+str(bestResult['mean'])+"), "
				failMessage+="failed to converge."
				print failMessage
				scurvesAlign=True

		# If the user wants to be update tell them we've finished.
		if self.statusCallback!=None : self.statusCallback.finished()

	def takeRun( self ) :
		self.analysisControl.reset()
		self.cbc2SCurveRun=SCurveRun( self, self.daqProgram, self.analysisControl, self.scanRange )
		self.cbc2SCurveRun.run()
		#analysisControl.restoreFromRootFile( "/tmp/calibrateChannelTrims-low.root" )
		fitParameters=self.analysisControl.fitParameters()
		for target in self.targets :
			channelNumber=target['channelNumber']
			cbcName=target['cbcName']
			target['previousResults'].append(
				{'trim':self.daqProgram.supervisor.getChannelTrim(channelNumber,[cbcName]),
				'mean':fitParameters[cbcName][channelNumber]['mean'] }
			)

	def channelsLeftToCalibrate( self ) :
		"""
		Method for the user to call to see how many channels are still left
		to calibrate.
		"""
		return len(self.targets)

	def currentStatus( self, fractionComplete, statusString ) :
		"""
		Method that SCurveRun will call to inform of me of its progress.
		"""
		# Pass this message on to the listener that was registered when this
		# class was created. Take account that 100% from s-curve is just the
		# end of this current loop.
		self.statusCallback.currentStatus( float(self.loop+1+fractionComplete)/float(self.maxLoops+2), self.loopDescription+": "+statusString )
		# Use this chance to quit out of the s-curve run if the user has set quit to True
		self.cbc2SCurveRun.quit=self.quit

	def finished( self ) :
		"""
		Method that SCurveRun will call to inform of me when its finished
		the current run.
		"""
		# This just signifies the end of the current run, so inform the
		# registered listener.
		self.statusCallback.currentStatus( float(self.loop+2)/float(self.maxLoops+2), self.loopDescription+" finished" )

	def setToBestPreviousResult( self, target ) :
		closestdifferenceFromTarget=9999
		newTrim=-1
		newMean=-1
		for previousResult in target['previousResults'] :
			differenceFromTarget=abs( target['target']-previousResult['mean'] )
			if differenceFromTarget<closestdifferenceFromTarget :
				newTrim=previousResult['trim'] # This previous result is better so use that
				closestdifferenceFromTarget=differenceFromTarget
				newMean=previousResult['mean'] # store this so that I can return it for info
		if newTrim==-1 : raise Exception( "Unable to find a previous trim result")
		self.daqProgram.supervisor.setChannelTrim(target['channelNumber'],newTrim,[target['cbcName']])
		# Return the trim and the mean you get from it in case the caller is intersted
		return {'trim':newTrim,'mean':newMean}
		
	def setNewTrims( self ) :
		# This will be filled with things to take out of the targets array,
		# either because the target has been met or because it looks like
		# a dead channel.
		targetsToRemove=[]
		
		for target in self.targets :
			# First see if the last iteration was successful
			if int(target['previousResults'][-1]['mean']+0.5)==target['target'] : # Add .5 to round properly
				targetsToRemove.append(target) # Found the trim, so no longer consider this
				continue
			# Look at all the previous results and try a linear fit to estimate
			# a trim that will give the relationship between trim and mean.
			fit=fitPreviousResults( target )
			# If the fit is exactly flat then the channel is probably dead
			# I'll take it out of the targets, but I can't remove it mid-loop.
			if fit['slope']==0 :
				targetsToRemove.append( target )
				continue
			#print "Slope="+str(fit['slope'])+" intercept="+str(fit['intercept'])
			# invert y=mx+c to get x=(y-c)/m
			newTrim=int( (target['target']-fit['intercept'])/fit['slope'] + 0.5 ) # add .5 to round properly
			if newTrim<0 : newTrim=0
			elif newTrim>255 : newTrim=255
			# Look through the previous results to make sure I haven't tried
			# this trim before.
			newTrimHasAlreadyBeenTried=True # Set to true to get it in the loop for the first time
			modificationDirection=0 # I'll set this to -1 if decreasing the trim, +1 if increasing, 0 if undefined
			while newTrimHasAlreadyBeenTried :
				newTrimHasAlreadyBeenTried=False
				for previousResult in target['previousResults'] :
					if previousResult['trim']==newTrim :
						if previousResult['mean']>target['target'] : directionToChange=1
						else : directionToChange=-1
						# If modificationDirection is not zero then I've changed the trim at least
						# once. If the direction to change this time is different to the previous
						# one then I'm not able to find a matching trim. Just set to the best match
						# and remove it from the list of channels to calibrate
						if modificationDirection!=0 and modificationDirection!=directionToChange :
							targetsToRemove.append(target)
							newTrim=-1 # Set this to something invalid so that I can check it later
							break
						else :
							modificationDirection=directionToChange
							newTrim+=modificationDirection
							newTrimHasAlreadyBeenTried=True
				
			#print "Setting trim for ["+target['cbcName']+"]["+str(target['channelNumber'])+"]="+str(newTrim)
			if newTrim!=-1 : self.daqProgram.supervisor.setChannelTrim(target['channelNumber'],newTrim,[target['cbcName']])
			else : self.setToBestPreviousResult(target)
			
		# Now I've finished looping over targets I can safely take
		# out the entries I want to.
		for item in targetsToRemove :
			self.targets.remove( item )