def add_edf_file(self, edf_file_name):
     """
     add_edf_file is the first step in adding a run's edf data 
     to the sessions hdf5 file indicated by self.inputObject
     
     add_edf_file creates an EDFOperator object using edf_file_name, and thereby
     immediately converts the edf file to two asc files.
     add_edf_file then uses the EDFOperator object to read all kinds
     of event information (trials, keys, etc) from the event asc file into
     internal variables of the EDFOperator object,
     and also reads the sample data per block (i.e. interval between startrecording
     and stoprecording) into a separate internal variable of the EDFOperator object.
     Putting these data into a hdf5 file is not done here, but in 
     self.edf_message_data_to_hdf and self.edf_gaze_data_to_hdf
     """
     
     self.edf_operator = EDFOperator(edf_file_name)
     # now create all messages
     self.edf_operator.read_all_messages()
     
     # set up blocks for the floating-point data
     self.edf_operator.take_gaze_data_for_blocks()
Example #2
0
	def add_edf_file(self, edf_file_name):
		"""
		add_edf_file is the first step in adding a run's edf data 
		to the sessions hdf5 file indicated by self.input_object
		
		add_edf_file creates an EDFOperator object using edf_file_name, and thereby
		immediately converts the edf file to two asc files.
		add_edf_file then uses the EDFOperator object to read all kinds
		of event information (trials, keys, etc) from the event asc file into
		internal variables of the EDFOperator object,
		and also reads the sample data per block (i.e. interval between startrecording
		and stoprecording) into a separate internal variable of the EDFOperator object.
		Putting these data into a hdf5 file is not done here, but in 
		self.edf_message_data_to_hdf and self.edf_gaze_data_to_hdf
		"""
		
		self.edf_operator = EDFOperator(edf_file_name)
		# now create all messages
		self.edf_operator.read_all_messages()
		
		# set up blocks for the floating-point data
		self.edf_operator.take_gaze_data_for_blocks()
Example #3
0
class HDFEyeOperator(Operator):
	"""
	HDFEyeOperator is typically used to deal with the data from 
	an entire session. In that case it is associated with a single
	hdf5 file that contains all eye data for the runs of that session,
	and which is given as input_object upon the creation of the
	HDFEyeOperator object 
	"""
	
	def __init__(self, input_object, **kwargs):
		super(HDFEyeOperator, self).__init__(input_object = input_object, **kwargs)
		"""input_object is the name of the hdf5 file that this operator will create"""
	
	def add_edf_file(self, edf_file_name):
		"""
		add_edf_file is the first step in adding a run's edf data 
		to the sessions hdf5 file indicated by self.input_object
		
		add_edf_file creates an EDFOperator object using edf_file_name, and thereby
		immediately converts the edf file to two asc files.
		add_edf_file then uses the EDFOperator object to read all kinds
		of event information (trials, keys, etc) from the event asc file into
		internal variables of the EDFOperator object,
		and also reads the sample data per block (i.e. interval between startrecording
		and stoprecording) into a separate internal variable of the EDFOperator object.
		Putting these data into a hdf5 file is not done here, but in 
		self.edf_message_data_to_hdf and self.edf_gaze_data_to_hdf
		"""
		
		self.edf_operator = EDFOperator(edf_file_name)
		# now create all messages
		self.edf_operator.read_all_messages()
		
		# set up blocks for the floating-point data
		self.edf_operator.take_gaze_data_for_blocks()
	
	def open_hdf_file(self, mode = "a"):
		"""
		open_hdf_file opens the hdf file that was indicated when
		first creating this HDFEyeOperator object
		"""
		self.h5f = open_file(self.input_object, mode = mode )
	
	def close_hdf_file(self):
		"""
		close_hdf_file closes the hdf file that was indicated when
		first creating this HDFEyeOperator object
		"""
		self.h5f.close()
	
	def add_table_to_hdf(self, run_group, type_dict, data, name = 'bla',filename = []):
		"""
		add_table_to_hdf adds a data table to the hdf file
		"""
		if filename == []:
			filename = self.edf_operator.input_file_name
			
		this_table = self.h5f.create_table(run_group, name, type_dict, '%s in file %s' % (name, self.edf_operator.input_file_name))
		
		row = this_table.row
		for r in data:
			for par in r.keys():
				row[par] = r[par]
			row.append()
		this_table.flush()
	
	def edf_message_data_to_hdf(self, alias = None, mode = 'a'):
		"""
		edf_message_data_to_hdf writes the message data
		from the run's edf file to the session's hdf5 file
		indicated by self.input_object.
		
		The data have typically been taken from the edf
		file and associated with self.edf_operator during an
		earlier call to self.add_edf_file(), but if this is not the case,
		and there is no self.edf_operator,
		then self.add_edf_file() can be called right here.
		
		within the hdf5 file, data are stored under, and can later be retrieved
		at /[source_edf_file_name_without_extension]/[data_kind_name],
		with the latter being e.g. 'trials'
		"""
		if not hasattr(self, 'edf_operator'):
			self.add_edf_file(edf_file_name = alias)
		
		if alias == None:
			alias = os.path.split(self.edf_operator.input_file_name)[-1]
		self.open_hdf_file( mode = mode )
		self.logger.info('Adding message data from %s to group  %s to %s' % (os.path.split(self.edf_operator.input_file_name)[-1], alias, self.input_object))
		thisRunGroup = self.h5f.create_group("/", alias, 'Run ' + alias +' imported from ' + self.edf_operator.input_file_name)
		
		#
		#	trials and trial phases
		#
		
		if hasattr(self.edf_operator, 'trials'):
			# create a table for the parameters of this run's trials
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.trial_type_dictionary, self.edf_operator.trials, 'trials')
		
		if hasattr(self.edf_operator, 'trial_phases'):
			# create a table for the parameters of this run's trials
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.trial_phase_type_dictionary, self.edf_operator.trial_phases, 'trial_phases')
		
		#
		#	supporting data types
		#
		
		if hasattr(self.edf_operator, 'parameters'):
			# create a table for the parameters of this run's trials
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.parameter_type_dictionary, self.edf_operator.parameters, 'parameters')
		
		if hasattr(self.edf_operator, 'events'):
			# create a table for the events of this run's trials
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.event_type_dictionary, self.edf_operator.events, 'events')
		
		if hasattr(self.edf_operator, 'sounds'):
			# create a table for the events of this run's trials
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.sound_type_dictionary, self.edf_operator.sounds, 'sounds')
		
		#
		#	eyelink data types
		#
		
		if hasattr(self.edf_operator, 'saccades_from_message_file'):
			# create a table for the saccades from the eyelink of this run's trials
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.saccade_type_dictionary, self.edf_operator.saccades_from_message_file, 'saccades_from_message_file')
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.blink_type_dictionary, self.edf_operator.blinks_from_message_file, 'blinks_from_message_file')
			self.add_table_to_hdf(thisRunGroup, self.edf_operator.fixation_type_dictionary, self.edf_operator.fixations_from_message_file, 'fixations_from_message_file')
		
		# first close the hdf5 file to write to it with pandas
		self.close_hdf_file()
	
	def edf_gaze_data_to_hdf(self, 
			alias = None, 
			which_eye = 0, 
			pupil_hp = 0.01, 
			pupil_lp = 6,
			sample_rate = 1000.,
			minimal_frequency_filterbank = 0.0025, 
			maximal_frequency_filterbank = 0.1, 
			nr_freq_bins_filterbank = 9, 
			n_cycles_filterbank = 1, 
			cycle_buffer_filterbank = 3,
			tf_decomposition_filterbank ='lp_butterworth' 
			):
		"""
		edf_gaze_data_to_hdf takes the gaze data
		that is in the run's edf file, processes it,
		and writes the results, as well as the raw data,
		to the sessions hdf5 file that is indicated by
		self.input_object, and also produces some visual
		feedback in the form of figures.
		
		The data have typically been taken from the edf
		file and associated with self.edf_operator during an
		earlier call to self.add_edf_file(), but if this is not the case,
		and there is no self.edf_operator,
		then self.add_edf_file() can be called right here.
		
		within the hdf5 file, data are stored under, and can later be retrieved
		at /[source_edf_file_name_without_extension]/[block_name],
		with the latter being e.g. 'block_1'
		
		edf_gaze_data_to_hdf also produces plots of the raw pupil data and
		blink-interpolated data (stored in pdf files named 'blink_interpolation_1_'[...])
		and of something akin to the derivative of the pupil data along with
		markers indicating that variable's peaks (stored in pdf files named 'blink_interpolation_2_'[...])
		"""
		
		# shell()
		
		if not hasattr(self, 'edf_operator'):
			self.add_edf_file(edf_file_name = alias)
		
		if alias == None:
			alias = os.path.split(self.edf_operator.input_file_name)[-1]
		self.logger.info('Adding gaze data from %s to group  %s to %s' % (os.path.split(self.edf_operator.input_file_name)[-1], alias, self.input_object))
		
		#
		#	gaze data in blocks
		#
		with pd.get_store(self.input_object) as h5_file:
			# shell()
			# recreate the non-gaze data for the block, that is, its sampling rate, eye of origin etc.
			blocks_data_frame = pd.DataFrame([dict([[i,self.edf_operator.blocks[j][i]] for i in self.edf_operator.blocks[0].keys() if i not in ('block_data', 'data_columns')]) for j in range(len(self.edf_operator.blocks))])
			h5_file.put("/%s/blocks"%alias, blocks_data_frame)
			
			# gaze data per block
			if not 'block_data' in self.edf_operator.blocks[0].keys():
				self.edf_operator.take_gaze_data_for_blocks()
			for i, block in enumerate(self.edf_operator.blocks):
				bdf = pd.DataFrame(block['block_data'], columns = block['data_columns'])
			
				#
				# preprocess pupil:
				#
				for eye in blocks_data_frame.eye_recorded[i]: # this is a string with one or two letters, 'L', 'R' or 'LR'
				# create dictionary of data per block:
					gazeX = bdf[eye+'_gaze_x']
					gazeY = bdf[eye+'_gaze_y']
					pupil = bdf[eye+'_pupil']
					eye_dict = {'timepoints':bdf.time, 'gaze_X':gazeX, 'gaze_Y':gazeY, 'pupil':pupil,}
					
					# create instance of class EyeSignalOperator, and include the blink data as detected by the Eyelink 1000:
					if hasattr(self.edf_operator, 'blinks_from_message_file'):
						blink_dict = self.read_session_data(alias, 'blinks_from_message_file')
						blink_dict[blink_dict['eye'] == eye]
						sac_dict = self.read_session_data(alias, 'saccades_from_message_file')
						sac_dict[sac_dict['eye'] == eye]
						eso = EyeSignalOperator(input_object=eye_dict, eyelink_blink_data=blink_dict,sample_rate=sample_rate, eyelink_sac_data = sac_dict)
					else:
						eso = EyeSignalOperator(input_object=eye_dict,sample_rate=sample_rate)
	
					# interpolate blinks:
					eso.interpolate_blinks(method='linear')
					eso.interpolate_blinks2()

					# low-pass and band-pass pupil data:
					eso.filter_pupil(hp=pupil_hp, lp=pupil_lp)

					# regress blink and saccade responses
					eso.regress_blinks()

					for dt in ['lp_filt_pupil','lp_filt_pupil_clean','bp_filt_pupil','bp_filt_pupil_clean']:
						# percent signal change filtered pupil data:
						eso.percent_signal_change_pupil(dtype=dt)
						eso.zscore_pupil(dtype=dt)
						eso.dt_pupil(dtype=dt)
					
					# add to existing dataframe:
					bdf[eye+'_pupil_int'] = eso.interpolated_pupil
					bdf[eye+'_pupil_hp'] = eso.hp_filt_pupil
					bdf[eye+'_pupil_lp'] = eso.lp_filt_pupil

					bdf[eye+'_pupil_lp_psc'] = eso.lp_filt_pupil_psc
					bdf[eye+'_pupil_lp_diff'] = np.concatenate((np.array([0]),np.diff(eso.lp_filt_pupil)))
					bdf[eye+'_pupil_bp'] = eso.bp_filt_pupil
					bdf[eye+'_pupil_bp_dt'] = eso.bp_filt_pupil_dt
					bdf[eye+'_pupil_bp_zscore'] = eso.bp_filt_pupil_zscore
					bdf[eye+'_pupil_bp_psc'] = eso.bp_filt_pupil_psc
					bdf[eye+'_pupil_baseline'] = eso.baseline_filt_pupil

					bdf[eye+'_gaze_x_int'] = eso.interpolated_x
					bdf[eye+'_gaze_y_int'] = eso.interpolated_y

					# blink/saccade regressed versions
					bdf[eye+'_pupil_lp_clean'] = eso.lp_filt_pupil_clean
					bdf[eye+'_pupil_lp_clean_psc'] = eso.lp_filt_pupil_clean_psc
					bdf[eye+'_pupil_lp_clean_zscore'] = eso.lp_filt_pupil_clean_zscore

					bdf[eye+'_pupil_bp_clean'] = eso.bp_filt_pupil_clean
					bdf[eye+'_pupil_bp_clean_psc'] = eso.bp_filt_pupil_clean_psc
					bdf[eye+'_pupil_bp_clean_zscore'] = eso.bp_filt_pupil_clean_zscore
					bdf[eye+'_pupil_bp_clean_dt'] = eso.bp_filt_pupil_clean_dt
				
					# plot interpolated pupil time series:
					fig = pl.figure(figsize = (16, 2.5))
					x = np.linspace(0,eso.raw_pupil.shape[0]/sample_rate, eso.raw_pupil.shape[0])
					pl.plot(x, eso.raw_pupil, 'b', rasterized=True)
					pl.plot(x, eso.interpolated_pupil, 'g', rasterized=True)
					pl.ylabel('pupil size (raw)')
					pl.xlabel('time (s)')
					pl.legend(['raw', 'int + filt'])
					fig.savefig(os.path.join(os.path.split(self.input_object)[0], 'blink_interpolation_1_{}_{}_{}.pdf'.format(alias, i, eye)))
					
					# plot results blink detection next to hdf5:
					fig = pl.figure(figsize = (16, 2.5))
					pl.plot(eso.pupil_diff, rasterized=True)
					pl.plot(eso.peaks, eso.pupil_diff[eso.peaks], '+', mec='r', mew=2, ms=8, rasterized=True)
					pl.ylim(ymin=-200, ymax=200)
					pl.ylabel('diff pupil size (raw)')
					pl.xlabel('samples')
					fig.savefig(os.path.join(os.path.split(self.input_object)[0], 'blink_interpolation_2_{}_{}_{}.pdf'.format(alias, i, eye)))

					# try time-frequency decomposition of the baseline signal
					try:
						eso.time_frequency_decomposition_pupil(
								minimal_frequency = minimal_frequency_filterbank, 
								maximal_frequency = maximal_frequency_filterbank, 
								nr_freq_bins = nr_freq_bins_filterbank, 
								n_cycles = n_cycles_filterbank, 
								cycle_buffer = cycle_buffer_filterbank,
								tf_decomposition=tf_decomposition_filterbank,
								)
						self.logger.info('Performed T-F analysis of type %s'%tf_decomposition_filterbank)
						for freq in eso.band_pass_filter_bank_pupil.keys():
							bdf[eye+'_pupil_filterbank_bp_%2.5f'%freq] = eso.band_pass_filter_bank_pupil[freq]
							self.logger.info('Saved T-F analysis %2.5f'%freq)
					except:
						self.logger.error('Something went wrong with T-F analysis of type %s'%tf_decomposition_filterbank)
						pass
					
				# put in HDF5:
				h5_file.put("/%s/block_%i"%(alias, i), bdf)
	
	def data_frame_to_hdf(self, alias, name, data_frame):
		"""docstring for data_frame_to_hdf"""
		with pd.get_store(self.input_object) as h5_file:
			h5_file.put("/%s/%s"%(alias, name), data_frame)
	
	#
	#	we also have to get the data from the hdf5 file. 
	#	first, based on simply a EL timestamp period
	#
	
	def sample_in_block(self, sample, block_table):
		"""docstring for sample_in_block"""
		return np.arange(block_table['block_end_timestamp'].shape[0])[np.array(block_table['block_end_timestamp'] > float(sample), dtype=bool)][0]
	
	def data_from_time_period(self, time_period, alias, columns = None):
		"""data_from_time_period delivers a set of data of type data_type for a given timeperiod"""
		# find the block in which the data resides, based on just the first time of time_period
		with pd.get_store(self.input_object) as h5_file:
			period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias]) 
			table = h5_file['%s/block_%i'%(alias, period_block_nr)]
			if columns == None:
				columns = table.keys()
		if 'L_vel' in columns:
			columns = table.keys()
		return table[(table['time'] > float(time_period[0])) & (table['time'] < float(time_period[1]))][columns]
	
	def eye_during_period(self, time_period, alias):
		"""eye_during_period returns the identity of the eye that was recorded during a given period"""
		with pd.get_store(self.input_object) as h5_file:
			period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias])
			eye = h5_file['%s/blocks'%alias]['eye_recorded'][period_block_nr]
		return eye
	
	def eye_during_trial(self, trial_nr, alias):
		"""docstring for signal_from_trial"""
		with pd.get_store(self.input_object) as h5_file:
			table = h5_file['%s/trials'%alias]
			time_period = np.array(table[table['trial_start_index'] == trial_nr][['trial_start_EL_timestamp', 'trial_end_EL_timestamp']])
		return self.eye_during_period(time_period[0], alias)

	def screen_dimensions_during_period(self, time_period, alias):
		"""docstring for eye_during_period"""
		with pd.get_store(self.input_object) as h5_file:
			period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias])
			return np.array(h5_file['%s/blocks'%alias][['screen_x_pix','screen_y_pix']][period_block_nr:period_block_nr+1]).squeeze()

	def screen_dimensions_during_trial(self, trial_nr, alias):
		"""docstring for eye_during_period"""
		with pd.get_store(self.input_object) as h5_file:
			table = h5_file['%s/trials'%alias]
			time_period = np.array(table[table['trial_start_index'] == trial_nr][['trial_start_EL_timestamp', 'trial_end_EL_timestamp']])[0]
		return self.screen_dimensions_during_period(time_period = time_period, alias = alias)	
	
	def sample_rate_during_period(self, time_period, alias):
		"""docstring for eye_during_period"""
		with pd.get_store(self.input_object) as h5_file:
			period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias])
			return h5_file['%s/blocks'%alias]['sample_rate'][period_block_nr]
	
	def sample_rate_during_trial(self, trial_nr, alias):
		"""docstring for signal_from_trial"""
		with pd.get_store(self.input_object) as h5_file:
			table = h5_file['%s/trials'%alias]
			time_period = np.array(table[table['trial_start_index'] == trial_nr][['trial_start_EL_timestamp', 'trial_end_EL_timestamp']])
		return float(self.sample_rate_during_period(time_period[0], alias))
	
	def signal_during_period(self, time_period, alias, signal, requested_eye = 'L'):
		"""docstring for gaze_during_period"""
		recorded_eye = self.eye_during_period(time_period, alias)
		if requested_eye == 'LR' and recorded_eye == 'LR':
			if np.any([signal == 'gaze', signal == 'vel']):
				columns = [s%signal for s in ['L_%s_x', 'L_%s_y', 'R_%s_x', 'R_%s_y']]
			elif signal == 'time':
				columns = [s%signal for s in ['%s']]		
			else:
				columns = [s%signal for s in ['L_%s', 'R_%s']]
		elif requested_eye in recorded_eye:
			if np.any([signal == 'gaze', signal == 'vel']):
				columns = [s%signal for s in [requested_eye + '_%s_x', requested_eye + '_%s_y']]
			elif signal == 'time':
				columns = [s%signal for s in ['%s']]
			else:
				columns = [s%signal for s in [requested_eye + '_%s']]
		else:
			with pd.get_store(self.input_object) as h5_file:
				self.logger.error('requested eye %s not found in block %i' % (requested_eye, self.sample_in_block(time_period[0], block_table = h5_file['%s/blocks'%alias])))
			return None	# assert something, dammit!
		return self.data_from_time_period(time_period, alias, columns)
	
	def saccades_during_period(self, time_period, alias, requested_eye = 'L', l = 5):
		xy_data = self.signal_during_period(time_period = time_period, alias = alias, signal = 'gaze', requested_eye = requested_eye)
		vel_data = self.signal_during_period(time_period = time_period, alias = alias, signal = 'vel', requested_eye = requested_eye) 
		return detect_saccade_from_data(xy_data = xy_data, vel_data = vel_data, l = l, sample_rate = self.sample_rate_during_period(time_period, alias))

	#
	#	second, based also on trials, using the above functionality
	#
	
	def signal_from_trial(self, trial_nr, alias, signal, requested_eye = 'L', time_extensions = [0,0]):
		"""docstring for signal_from_trial"""
		with pd.get_store(self.input_object) as h5_file:
			table = h5_file['%s/trials'%alias]
			time_period = np.array([
				table[table['trial_start_index'] == trial_nr]['trial_start_EL_timestamp'] + time_extensions[0],
				table[table['trial_start_index'] == trial_nr]['trial_end_EL_timestamp'] + time_extensions[1]
			]).squeeze()
		return self.signal_during_period(time_period, alias, signal, requested_eye = requested_eye)
	
	def time_period_for_trial_phases(self, trial_nr, trial_phases, alias ):
		"""the time period corresponding to the trial phases requested.
		"""
		with pd.get_store(self.input_object) as h5_file:
			table = h5_file['%s/trial_phases'%alias]
			# check whether one of the trial phases is the end or the beginning of the trial.
			# if so, then supplant the time of that phase with its trial's end or start time.
			if trial_phases[0] == 0:
				start_time = table[table['trial_start_index'] == trial_nr]['trial_start_EL_timestamp']
			else:
				start_time = table[((table['trial_phase_index'] == trial_phases[0]) * (table['trial_phase_trial'] == trial_nr))]['trial_phase_EL_timestamp']
			if trial_phases[-1] == -1:
				end_time = table[table['trial_start_index'] == trial_nr]['trial_end_EL_timestamp']
			else:
				end_time = table[((table['trial_phase_index'] == trial_phases[1]) * (table['trial_phase_trial'] == trial_nr))]['trial_phase_EL_timestamp']
			time_period = np.array([np.array(start_time), np.array(end_time)]).squeeze()
		return time_period

	def signal_from_trial_phases(self, trial_nr, trial_phases, alias, signal, requested_eye = 'L', time_extensions = [0,0]):
		"""docstring for signal_from_trial"""
		time_period = self.time_period_for_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias)
		time_period = np.array([time_period[0] + time_extensions[0], time_period[1] + time_extensions[1]]).squeeze()
		return self.signal_during_period(time_period, alias, signal, requested_eye = requested_eye)
	
	def saccades_from_trial_phases(self, trial_nr, trial_phases, alias, requested_eye = 'L', time_extensions = [0,0], l = 5):
		time_period = self.time_period_for_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias)
		time_period = np.array([time_period[0] + time_extensions[0], time_period[1] + time_extensions[1]]).squeeze()
		return self.saccades_during_period(time_period = time_period, alias = alias, requested_eye = requested_eye, time_extensions = time_extensions, l = l)

		# xy_data = self.signal_from_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias, signal = 'gaze', requested_eye = requested_eye, time_extensions = time_extensions)
		# vel_data = self.signal_from_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias, signal = 'vel', requested_eye = requested_eye, time_extensions = time_extensions) 
		# return detect_saccade_from_data(xy_data = xy_data, vel_data = vel_data, l = l, sample_rate = self.sample_rate_during_period(self.time_period_for_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias), alias))
			
	#
	#	read whole dataframes
	#
	
	def read_session_data(self, alias, name):
		"""
		read_session_data reads data from the hdf5 file indicated by self.input_object.
		Specifically, it reads the data associated with alias/name, with
		'alias' and 'name' typically referring to a run (e.g. 'QQ241214') and
		a data kind (e.g. 'trials'), respectively.
		"""
		
		with pd.get_store(self.input_object) as h5_file:
			session_data = h5_file['%s/%s'%(alias, name)]
		return session_data
class HDFEyeOperator(Operator):
    """
    HDFEyeOperator is typically used to deal with the data from 
    an entire session. In that case it is associated with a single
    hdf5 file that contains all eye data for the runs of that session,
    and which is given as inputObject upon the creation of the
    HDFEyeOperator object 
    """
    
    def __init__(self, inputObject, **kwargs):
        super(HDFEyeOperator, self).__init__(inputObject = inputObject, **kwargs)
        """inputObject is the name of the hdf5 file that this operator will create"""
    
    def add_edf_file(self, edf_file_name):
        """
        add_edf_file is the first step in adding a run's edf data 
        to the sessions hdf5 file indicated by self.inputObject
        
        add_edf_file creates an EDFOperator object using edf_file_name, and thereby
        immediately converts the edf file to two asc files.
        add_edf_file then uses the EDFOperator object to read all kinds
        of event information (trials, keys, etc) from the event asc file into
        internal variables of the EDFOperator object,
        and also reads the sample data per block (i.e. interval between startrecording
        and stoprecording) into a separate internal variable of the EDFOperator object.
        Putting these data into a hdf5 file is not done here, but in 
        self.edf_message_data_to_hdf and self.edf_gaze_data_to_hdf
        """
        
        self.edf_operator = EDFOperator(edf_file_name)
        # now create all messages
        self.edf_operator.read_all_messages()
        
        # set up blocks for the floating-point data
        self.edf_operator.take_gaze_data_for_blocks()
    
    def open_hdf_file(self, mode = "a"):
        """
        open_hdf_file opens the hdf file that was indicated when
        first creating this HDFEyeOperator object
        """
        self.h5f = open_file(self.inputObject, mode = mode )
    
    def close_hdf_file(self):
        """
        close_hdf_file closes the hdf file that was indicated when
        first creating this HDFEyeOperator object
        """
        self.h5f.close()
    
    def add_table_to_hdf(self, run_group, type_dict, data, name = 'bla',filename = []):
        """
        add_table_to_hdf adds a data table to the hdf file
        """
        if filename == []:
            filename = self.edf_operator.inputFileName
            
        this_table = self.h5f.createTable(run_group, name, type_dict, '%s in file %s' % (name, self.edf_operator.inputFileName))
        
        row = this_table.row
        for r in data:
            for par in r.keys():
                row[par] = r[par]
            row.append()
        this_table.flush()
    
    def edf_message_data_to_hdf(self, alias = None, mode = 'a'):
        """
        edf_message_data_to_hdf writes the message data
        from the run's edf file to the session's hdf5 file
        indicated by self.inputObject.
        
        The data have typically been taken from the edf
        file and associated with self.edf_operator during an
        earlier call to self.add_edf_file(), but if this is not the case,
        and there is no self.edf_operator,
        then self.add_edf_file() can be called right here.
        
        within the hdf5 file, data are stored under, and can later be retrieved
        at /[source_edf_file_name_without_extension]/[data_kind_name],
        with the latter being e.g. 'trials'
        """
        if not hasattr(self, 'edf_operator'):
            self.add_edf_file(edf_file_name = alias)
        
        if alias == None:
            alias = os.path.split(self.edf_operator.inputFileName)[-1]
        self.open_hdf_file( mode = mode )
        self.logger.info('Adding message data from %s to group  %s to %s' % (os.path.split(self.edf_operator.inputFileName)[-1], alias, self.inputObject))
        thisRunGroup = self.h5f.create_group("/", alias, 'Run ' + alias +' imported from ' + self.edf_operator.inputFileName)
        
        #
        #    trials and trial phases
        #
        
        if hasattr(self.edf_operator, 'trials'):
            # create a table for the parameters of this run's trials
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.trial_type_dictionary, self.edf_operator.trials, 'trials')
        
        if hasattr(self.edf_operator, 'trial_phases'):
            # create a table for the parameters of this run's trials
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.trial_phase_type_dictionary, self.edf_operator.trial_phases, 'trial_phases')
        
        #
        #    supporting data types
        #
        
        if hasattr(self.edf_operator, 'parameters'):
            # create a table for the parameters of this run's trials
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.parameter_type_dictionary, self.edf_operator.parameters, 'parameters')
        
        if hasattr(self.edf_operator, 'events'):
            # create a table for the events of this run's trials
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.event_type_dictionary, self.edf_operator.events, 'events')
        
        if hasattr(self.edf_operator, 'sounds'):
            # create a table for the events of this run's trials
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.sound_type_dictionary, self.edf_operator.sounds, 'sounds')
        
        #
        #    eyelink data types
        #
        
        if hasattr(self.edf_operator, 'saccades_from_message_file'):
            # create a table for the saccades from the eyelink of this run's trials
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.saccade_type_dictionary, self.edf_operator.saccades_from_message_file, 'saccades_from_message_file')
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.blink_type_dictionary, self.edf_operator.blinks_from_message_file, 'blinks_from_message_file')
            self.add_table_to_hdf(thisRunGroup, self.edf_operator.fixation_type_dictionary, self.edf_operator.fixations_from_message_file, 'fixations_from_message_file')
        
        # first close the hdf5 file to write to it with pandas
        self.close_hdf_file()
    
    def edf_gaze_data_to_hdf(self, 
            alias = None, 
            which_eye = 0, 
            pupil_hp = 0.01, 
            pupil_lp = 6,
            sample_rate = 1000.,
            minimal_frequency_filterbank = 0.0025, 
            maximal_frequency_filterbank = 0.1, 
            nr_freq_bins_filterbank = 9, 
            n_cycles_filterbank = 1, 
            cycle_buffer_filterbank = 3,
            tf_decomposition_filterbank ='lp_butterworth' 
            ):
        """
        edf_gaze_data_to_hdf takes the gaze data
        that is in the run's edf file, processes it,
        and writes the results, as well as the raw data,
        to the sessions hdf5 file that is indicated by
        self.inputObject, and also produces some visual
        feedback in the form of figures.
        
        The data have typically been taken from the edf
        file and associated with self.edf_operator during an
        earlier call to self.add_edf_file(), but if this is not the case,
        and there is no self.edf_operator,
        then self.add_edf_file() can be called right here.
        
        within the hdf5 file, data are stored under, and can later be retrieved
        at /[source_edf_file_name_without_extension]/[block_name],
        with the latter being e.g. 'block_1'
        
        edf_gaze_data_to_hdf also produces plots of the raw pupil data and
        blink-interpolated data (stored in pdf files named 'blink_interpolation_1_'[...])
        and of something akin to the derivative of the pupil data along with
        markers indicating that variable's peaks (stored in pdf files named 'blink_interpolation_2_'[...])
        """
        
        # shell()
        
        if not hasattr(self, 'edf_operator'):
            self.add_edf_file(edf_file_name = alias)
        
        if alias == None:
            alias = os.path.split(self.edf_operator.inputFileName)[-1]
        self.logger.info('Adding gaze data from %s to group  %s to %s' % (os.path.split(self.edf_operator.inputFileName)[-1], alias, self.inputObject))
        
        #
        #    gaze data in blocks
        #
        with pd.get_store(self.inputObject) as h5_file:
            # shell()
            # recreate the non-gaze data for the block, that is, its sampling rate, eye of origin etc.
            blocks_data_frame = pd.DataFrame([dict([[i,self.edf_operator.blocks[j][i]] for i in self.edf_operator.blocks[0].keys() if i not in ('block_data', 'data_columns')]) for j in range(len(self.edf_operator.blocks))])
            h5_file.put("/%s/blocks"%alias, blocks_data_frame)
            
            # gaze data per block
            if not 'block_data' in self.edf_operator.blocks[0].keys():
                self.edf_operator.take_gaze_data_for_blocks()
            for i, block in enumerate(self.edf_operator.blocks):
                bdf = pd.DataFrame(block['block_data'], columns = block['data_columns'])
            
                #
                # preprocess pupil:
                #
                for eye in blocks_data_frame.eye_recorded[i]: # this is a string with one or two letters, 'L', 'R' or 'LR'
                # create dictionary of data per block:
                    # gazeXY = bdf[[s%'gaze' for s in [eye+'_%s_x', eye+'_%s_y',]]]
                    # pupil = bdf[[s%'pupil' for s in [eye+'_%s']]]
                    # eye_dict = {'timepoints':bdf.time, 'gazeXY':gazeXY, 'pupil':pupil,}
                    # shell()
                    gazeX = bdf[eye+'_gaze_x']
                    gazeY = bdf[eye+'_gaze_y']
                    pupil = bdf[eye+'_pupil']
                    eye_dict = {'timepoints':bdf.time, 'gaze_X':gazeX, 'gaze_Y':gazeY, 'pupil':pupil,}
                    
                    # create instance of class EyeSignalOperator, and include the blink data as detected by the Eyelink 1000:
                    if hasattr(self.edf_operator, 'blinks_from_message_file'):
                        blink_dict = self.read_session_data(alias, 'blinks_from_message_file')
                        blink_dict[blink_dict['eye'] == eye]
                        sac_dict = self.read_session_data(alias, 'saccades_from_message_file')
                        sac_dict[sac_dict['eye'] == eye]
                        eso = EyeSignalOperator(inputObject=eye_dict, eyelink_blink_data=blink_dict,sample_rate=sample_rate, eyelink_sac_data = sac_dict)
                    else:
                        eso = EyeSignalOperator(inputObject=eye_dict,sample_rate=sample_rate)
                    # detect blinks (coalese period in samples):
                    # eso.blink_detection_pupil(coalesce_period=sample_rate*250./1000.)
                    # interpolate blinks:
                    eso.interpolate_blinks(method='linear')
                    eso.interpolate_blinks2()
                    # low-pass and band-pass pupil data:
                    eso.filter_pupil(hp=pupil_hp, lp=pupil_lp)
                    # now dt the resulting pupil data:
                    eso.dt_pupil(dtype='lp_filt_pupil')
                    eso.dt_pupil(dtype='bp_filt_pupil')
                    # regress out blinks and sacs:
                    try:
                        eso.regress_blinks()
                    except:
                        eso.lp_filt_pupil_clean = eso.lp_filt_pupil.copy()
                        eso.bp_filt_pupil_clean = eso.bp_filt_pupil.copy()
                    # z-score filtered pupil data:
                    eso.zscore_pupil()
                    # zscore filtered pupil data:
                    eso.zscore_pupil(dtype='lp_filt_pupil')
                    eso.zscore_pupil(dtype='lp_filt_pupil_clean')
                    eso.zscore_pupil(dtype='bp_filt_pupil')
                    eso.zscore_pupil(dtype='bp_filt_pupil_clean')
                    # percent signal change filtered pupil data:
                    eso.percent_signal_change_pupil(dtype='lp_filt_pupil')
                    eso.percent_signal_change_pupil(dtype='lp_filt_pupil_clean')
                    eso.percent_signal_change_pupil(dtype='bp_filt_pupil')
                    eso.percent_signal_change_pupil(dtype='bp_filt_pupil_clean')
                    
                    # add to existing dataframe:
                    bdf[eye+'_interpolated_timepoints'] = eso.interpolated_time_points
                    bdf[eye+'_pupil_int'] = eso.interpolated_pupil
                    bdf[eye+'_pupil_hp'] = eso.hp_filt_pupil
                    bdf[eye+'_pupil_baseline'] = eso.baseline_filt_pupil
                    bdf[eye+'_gaze_x_int'] = eso.interpolated_x
                    bdf[eye+'_gaze_y_int'] = eso.interpolated_y
                    
                    bdf[eye+'_pupil_lp'] = eso.lp_filt_pupil
                    bdf[eye+'_pupil_lp_dt'] = eso.lp_filt_pupil_dt
                    bdf[eye+'_pupil_lp_zscore'] = eso.lp_filt_pupil_zscore
                    bdf[eye+'_pupil_lp_psc'] = eso.lp_filt_pupil_psc
                    bdf[eye+'_pupil_lp_clean'] = eso.lp_filt_pupil_clean
                    bdf[eye+'_pupil_lp_clean_zscore'] = eso.lp_filt_pupil_clean_zscore
                    bdf[eye+'_pupil_lp_clean_psc'] = eso.lp_filt_pupil_clean_psc
                    
                    bdf[eye+'_pupil_bp'] = eso.bp_filt_pupil
                    bdf[eye+'_pupil_bp_dt'] = eso.bp_filt_pupil_dt
                    bdf[eye+'_pupil_bp_zscore'] = eso.bp_filt_pupil_zscore
                    bdf[eye+'_pupil_bp_psc'] = eso.bp_filt_pupil_psc
                    bdf[eye+'_pupil_bp_clean'] = eso.bp_filt_pupil_clean
                    bdf[eye+'_pupil_bp_clean_zscore'] = eso.bp_filt_pupil_clean_zscore
                    bdf[eye+'_pupil_bp_clean_psc'] = eso.bp_filt_pupil_clean_psc
                    
                    # save summary plot:
                    fig = eso.summary_plot()
                    fig.savefig(os.path.join(os.path.split(self.inputObject)[0], 'pupil_preprocess_detection_{}_{}_{}.pdf'.format(alias, i, eye)))
                    
                    # try time-frequency decomposition of the baseline signal
                    try:
                        eso.time_frequency_decomposition_pupil(
                                minimal_frequency = minimal_frequency_filterbank, 
                                maximal_frequency = maximal_frequency_filterbank, 
                                nr_freq_bins = nr_freq_bins_filterbank, 
                                n_cycles = n_cycles_filterbank, 
                                cycle_buffer = cycle_buffer_filterbank,
                                tf_decomposition=tf_decomposition_filterbank,
                                )
                        self.logger.info('Performed T-F analysis of type %s'%tf_decomposition_filterbank)
                        for freq in eso.band_pass_filter_bank_pupil.keys():
                            bdf[eye+'_pupil_filterbank_bp_%2.5f'%freq] = eso.band_pass_filter_bank_pupil[freq]
                            self.logger.info('Saved T-F analysis %2.5f'%freq)
                    except:
                        self.logger.error('Something went wrong with T-F analysis of type %s'%tf_decomposition_filterbank)
                        pass
                    
                # put in HDF5:
                h5_file.put("/%s/block_%i"%(alias, i), bdf)
    
    def data_frame_to_hdf(self, alias, name, data_frame):
        """docstring for data_frame_to_hdf"""
        with pd.get_store(self.inputObject) as h5_file:
            h5_file.put("/%s/%s"%(alias, name), data_frame)
    
    #
    #    we also have to get the data from the hdf5 file. 
    #    first, based on simply a EL timestamp period
    #
    
    def sample_in_block(self, sample, block_table):
        """docstring for sample_in_block"""
        return np.arange(block_table['block_end_timestamp'].shape[0])[np.array(block_table['block_end_timestamp'] > float(sample), dtype=bool)][0]
    
    def data_from_time_period(self, time_period, alias, columns = None):
        """data_from_time_period delivers a set of data of type data_type for a given timeperiod"""
        # find the block in which the data resides, based on just the first time of time_period
        with pd.get_store(self.inputObject) as h5_file:
            period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias]) 
            table = h5_file['%s/block_%i'%(alias, period_block_nr)]
            if columns == None:
                columns = table.keys()
        if 'L_vel' in columns:
            columns = table.keys()
        return table[(table['time'] > float(time_period[0])) & (table['time'] < float(time_period[1]))][columns]
    
    def eye_during_period(self, time_period, alias):
        """eye_during_period returns the identity of the eye that was recorded during a given period"""
        with pd.get_store(self.inputObject) as h5_file:
            period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias])
            eye = h5_file['%s/blocks'%alias]['eye_recorded'][period_block_nr]
        return eye
    
    def eye_during_trial(self, trial_nr, alias):
        """docstring for signal_from_trial"""
        with pd.get_store(self.inputObject) as h5_file:
            table = h5_file['%s/trials'%alias]
            time_period = np.array(table[table['trial_start_index'] == trial_nr][['trial_start_EL_timestamp', 'trial_end_EL_timestamp']])
        return self.eye_during_period(time_period[0], alias)

    def screen_dimensions_during_period(self, time_period, alias):
        """docstring for eye_during_period"""
        with pd.get_store(self.inputObject) as h5_file:
            period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias])
            return np.array(h5_file['%s/blocks'%alias][['screen_x_pix','screen_y_pix']][period_block_nr:period_block_nr+1]).squeeze()

    def screen_dimensions_during_trial(self, trial_nr, alias):
        """docstring for eye_during_period"""
        with pd.get_store(self.inputObject) as h5_file:
            table = h5_file['%s/trials'%alias]
            time_period = np.array(table[table['trial_start_index'] == trial_nr][['trial_start_EL_timestamp', 'trial_end_EL_timestamp']])[0]
        return self.screen_dimensions_during_period(time_period = time_period, alias = alias)    
    
    def sample_rate_during_period(self, time_period, alias):
        """docstring for eye_during_period"""
        with pd.get_store(self.inputObject) as h5_file:
            period_block_nr = self.sample_in_block(sample = time_period[0], block_table = h5_file['%s/blocks'%alias])
            return h5_file['%s/blocks'%alias]['sample_rate'][period_block_nr]
    
    def sample_rate_during_trial(self, trial_nr, alias):
        """docstring for signal_from_trial"""
        with pd.get_store(self.inputObject) as h5_file:
            table = h5_file['%s/trials'%alias]
            time_period = np.array(table[table['trial_start_index'] == trial_nr][['trial_start_EL_timestamp', 'trial_end_EL_timestamp']])
        return float(self.sample_rate_during_period(time_period[0], alias))
    
    def signal_during_period(self, time_period, alias, signal, requested_eye = 'L'):
        """docstring for gaze_during_period"""
        recorded_eye = self.eye_during_period(time_period, alias)
        if requested_eye == 'LR' and recorded_eye == 'LR':
            if np.any([signal == 'gaze', signal == 'vel']):
                columns = [s%signal for s in ['L_%s_x', 'L_%s_y', 'R_%s_x', 'R_%s_y']]
            elif signal == 'time':
                columns = [s%signal for s in ['%s']]        
            else:
                columns = [s%signal for s in ['L_%s', 'R_%s']]
        elif requested_eye in recorded_eye:
            if np.any([signal == 'gaze', signal == 'vel']):
                columns = [s%signal for s in [requested_eye + '_%s_x', requested_eye + '_%s_y']]
            elif signal == 'time':
                columns = [s%signal for s in ['%s']]
            else:
                columns = [s%signal for s in [requested_eye + '_%s']]
        else:
            with pd.get_store(self.inputObject) as h5_file:
                self.logger.error('requested eye %s not found in block %i' % (requested_eye, self.sample_in_block(time_period[0], block_table = h5_file['%s/blocks'%alias])))
            return None    # assert something, dammit!
        return self.data_from_time_period(time_period, alias, columns)
    
    def saccades_during_period(self, time_period, alias, requested_eye = 'L', l = 5):
        xy_data = self.signal_during_period(time_period = time_period, alias = alias, signal = 'gaze', requested_eye = requested_eye)
        vel_data = self.signal_during_period(time_period = time_period, alias = alias, signal = 'vel', requested_eye = requested_eye) 
        return detect_saccade_from_data(xy_data = xy_data, vel_data = vel_data, l = l, sample_rate = self.sample_rate_during_period(time_period, alias))

    #
    #    second, based also on trials, using the above functionality
    #
    
    def signal_from_trial(self, trial_nr, alias, signal, requested_eye = 'L', time_extensions = [0,0]):
        """docstring for signal_from_trial"""
        with pd.get_store(self.inputObject) as h5_file:
            table = h5_file['%s/trials'%alias]
            time_period = np.array([
                table[table['trial_start_index'] == trial_nr]['trial_start_EL_timestamp'] + time_extensions[0],
                table[table['trial_start_index'] == trial_nr]['trial_end_EL_timestamp'] + time_extensions[1]
            ]).squeeze()
        return self.signal_during_period(time_period, alias, signal, requested_eye = requested_eye)
    
    def time_period_for_trial_phases(self, trial_nr, trial_phases, alias ):
        """the time period corresponding to the trial phases requested.
        """
        with pd.get_store(self.inputObject) as h5_file:
            table = h5_file['%s/trial_phases'%alias]
            # check whether one of the trial phases is the end or the beginning of the trial.
            # if so, then supplant the time of that phase with its trial's end or start time.
            if trial_phases[0] == 0:
                start_time = table[table['trial_start_index'] == trial_nr]['trial_start_EL_timestamp']
            else:
                start_time = table[((table['trial_phase_index'] == trial_phases[0]) * (table['trial_phase_trial'] == trial_nr))]['trial_phase_EL_timestamp']
            if trial_phases[-1] == -1:
                end_time = table[table['trial_start_index'] == trial_nr]['trial_end_EL_timestamp']
            else:
                end_time = table[((table['trial_phase_index'] == trial_phases[1]) * (table['trial_phase_trial'] == trial_nr))]['trial_phase_EL_timestamp']
            time_period = np.array([np.array(start_time), np.array(end_time)]).squeeze()
        return time_period

    def signal_from_trial_phases(self, trial_nr, trial_phases, alias, signal, requested_eye = 'L', time_extensions = [0,0]):
        """docstring for signal_from_trial"""
        time_period = self.time_period_for_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias)
        time_period = np.array([time_period[0] + time_extensions[0], time_period[1] + time_extensions[1]]).squeeze()
        return self.signal_during_period(time_period, alias, signal, requested_eye = requested_eye)
    
    def saccades_from_trial_phases(self, trial_nr, trial_phases, alias, requested_eye = 'L', time_extensions = [0,0], l = 5):
        time_period = self.time_period_for_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias)
        time_period = np.array([time_period[0] + time_extensions[0], time_period[1] + time_extensions[1]]).squeeze()
        return self.saccades_during_period(time_period = time_period, alias = alias, requested_eye = requested_eye, time_extensions = time_extensions, l = l)

        # xy_data = self.signal_from_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias, signal = 'gaze', requested_eye = requested_eye, time_extensions = time_extensions)
        # vel_data = self.signal_from_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias, signal = 'vel', requested_eye = requested_eye, time_extensions = time_extensions) 
        # return detect_saccade_from_data(xy_data = xy_data, vel_data = vel_data, l = l, sample_rate = self.sample_rate_during_period(self.time_period_for_trial_phases(trial_nr = trial_nr, trial_phases = trial_phases, alias = alias), alias))
            
    #
    #    read whole dataframes
    #
    
    def read_session_data(self, alias, name):
        """
        read_session_data reads data from the hdf5 file indicated by self.inputObject.
        Specifically, it reads the data associated with alias/name, with
        'alias' and 'name' typically referring to a run (e.g. 'QQ241214') and
        a data kind (e.g. 'trials'), respectively.
        """
        
        with pd.get_store(self.inputObject) as h5_file:
            session_data = h5_file['%s/%s'%(alias, name)]
        return session_data