class DataCapturePanel(wx.Panel):
	"""
	A panel to start the data capture process, optionally exporting the results to a file.
	"""

	def __init__(self, parent, global_store, *args, **kwargs):
		wx.Panel.__init__(self, parent, *args, **kwargs)

		self.global_store = global_store

		self.capture_dialogs = 0

		# Panel.
		panel_box = wx.BoxSizer(wx.HORIZONTAL)

		## Capture.
		capture_static_box = wx.StaticBox(self, label='Capture')
		capture_box = wx.StaticBoxSizer(capture_static_box, wx.VERTICAL)
		panel_box.Add(capture_box, flag=wx.CENTER|wx.ALL, border=5)

		### Start.
		self.start_button = wx.Button(self, label='Start')
		self.Bind(wx.EVT_BUTTON, self.OnBeginCapture, self.start_button)
		capture_box.Add(self.start_button, flag=wx.CENTER)

		### Continuous.
		self.continuous_checkbox = wx.CheckBox(self, label='Continuous')
		capture_box.Add(self.continuous_checkbox, flag=wx.CENTER)

		## Export.
		export_static_box = wx.StaticBox(self, label='Export')
		export_box = wx.StaticBoxSizer(export_static_box, wx.HORIZONTAL)
		panel_box.Add(export_box, proportion=1, flag=wx.CENTER|wx.ALL, border=5)

		### Enabled.
		self.export_enabled = wx.CheckBox(self, label='')
		self.export_enabled.Value = True
		export_box.Add(self.export_enabled, flag=wx.CENTER)

		### Export path.
		export_path_box = wx.BoxSizer(wx.VERTICAL)
		export_box.Add(export_path_box, proportion=1, flag=wx.CENTER)

		#### Directory.
		self.directory_browse_button = DirBrowseButton(self, labelText='Directory:')
		export_path_box.Add(self.directory_browse_button, flag=wx.EXPAND)

		#### Last file.
		last_file_box = wx.BoxSizer(wx.HORIZONTAL)
		export_path_box.Add(last_file_box, flag=wx.EXPAND)

		last_file_box.Add(wx.StaticText(self, label='Last output: '),flag=wx.ALIGN_CENTER_VERTICAL)#|) wx.ALIGN_RIGHT)
		self.last_file_name = wx.TextCtrl(self, style=wx.TE_READONLY)
		self.last_file_name.BackgroundColour = wx.LIGHT_GREY
		last_file_box.Add(self.last_file_name, proportion=1)

		self.SetSizer(panel_box)

	def OnBeginCapture(self, evt=None):
		# Prevent accidental double-clicking.
		self.start_button.Disable()
		def enable_button():
			sleep(1)
			wx.CallAfter(self.start_button.Enable)
		thr = Thread(target=enable_button)
		thr.daemon = True
		thr.start()

		all_variables = [var for var in list(self.global_store.variables.values()) if var.enabled]
		output_variables = sift(all_variables, OutputVariable)
		input_variables = [var for var in sift(all_variables, InputVariable) if var.resource_name != '']
		condition_variables =  sift(all_variables, ConditionVariable)

		if not output_variables:
			output_variables.append(OutputVariable(order=0, name='<Dummy>', enabled=True))

		output_variables, num_items = sort_output_variables(output_variables)
		condition_variables = sort_condition_variables(condition_variables)

		resource_names = [tuple(var.resource_name for var in group) for group in output_variables]
		measurement_resource_names = [var.resource_name for var in input_variables]
		condition_resource_names = [tuple(set(flatten([var.resource_names for var in group]))) for group in condition_variables]


		continuous = self.continuous_checkbox.Value

		missing_resources = set()
		unreadable_resources = set()
		unwritable_resources = set()
		missing_devices = set()

		pulse_program = self.global_store.pulse_program

		if pulse_program is not None:
			pulse_program = pulse_program.with_resources

			try:
				pulse_program.generate_waveforms(dry_run=True)
			except PulseError as e:
				MessageDialog(self, '\n'.join(e[0]), 'Pulse program error', monospace=True).Show()
				return
			except Exception as e:
				MessageDialog(self, str(e), 'Pulse program error').Show()
				return

			pulse_awg, pulse_oscilloscope = None, None
			pulse_channels = {}

			try:
				pulse_awg = self.global_store.devices[pulse_program.awg].device
				if pulse_awg is None:
					raise KeyError
			except KeyError:
				missing_devices.add(pulse_program.awg)
			else:
				# Gather used channel numbers.
				pulse_channels = dict((k, v) for k, v in list(pulse_program.output_channels.items()) if v is not None)

				actual_channels = list(range(1, len(pulse_awg.channels)))
				invalid_channels = [k for k, v in list(pulse_channels.items()) if v not in actual_channels]

				if invalid_channels:
					MessageDialog(self, 'Invalid channels for: {0}'.format(', '.join(invalid_channels)), 'Invalid channels').Show()
					return

			try:
				pulse_oscilloscope = self.global_store.devices[pulse_program.oscilloscope].device
				if pulse_oscilloscope is None:
					raise KeyError
			except KeyError:
				missing_devices.add(pulse_program.oscilloscope)

			try:
				pulse_config = PulseConfiguration(pulse_program, pulse_channels, pulse_awg, pulse_oscilloscope)
			except TypeError as e:
				MessageDialog(self, str(e), 'Device configuration error').Show()
				return
		else:
			pulse_config = None

		resources = []
		for group in resource_names:
			group_resources = []

			for name in group:
				if name == '':
					group_resources.append((str(len(resources)), None))
				elif name not in self.global_store.resources:
					missing_resources.add(name)
				else:
					resource = self.global_store.resources[name]

					if resource.writable:
						group_resources.append((name, resource))
					else:
						unwritable_resources.add(name)

			resources.append(tuple(group_resources))
			
		measurement_resources = []
		measurement_units = []
		for name in measurement_resource_names:
			if name not in self.global_store.resources:
				missing_resources.add(name)
			else:
				resource = self.global_store.resources[name]

				if resource.readable:
					measurement_resources.append((name, resource))
					measurement_units.append(resource.display_units)
				else:
					unreadable_resources.add(name)
				
		condition_resources = []
		for group in condition_resource_names:
			group_resources = []
			
			for name in group:
				if name not in self.global_store.resources:
					missing_resources.add(name)
				else:
					resource = self.global_store.resources[name]

					if resource.readable:
						group_resources.append((name, resource))
					else:
						#the name may already have been put here by the loop assigning
						#to measurement_resources
						if name not in unreadable_resources:
							unreadable_resources.add(name)

			condition_resources.append(tuple(group_resources))

		mismatched_resources = []
		for (res_name, resource), var in zip(flatten(resources), flatten(output_variables)):
			if resource is None:
				continue

			if resource.units is not None:
				if not (var.type == 'quantity' and
						resource.verify_dimensions(var.units, exception=False, from_string=True)):
					mismatched_resources.append((res_name, var.name))
			else:
				if var.type not in ['float', 'integer']:
					mismatched_resources.append((res_name, var.name))

		for items, msg in [
			(missing_resources, 'Missing resources'),
			(unreadable_resources, 'Unreadable resources'),
			(unwritable_resources, 'Unwritable resources'),
			(missing_devices, 'Missing devices')]:

			if items:
				MessageDialog(self, ', '.join('"{0}"'.format(x) for x in sorted(items)), msg).Show()

		if mismatched_resources:
			MessageDialog(self, ', '.join('Mismatched resource type for resource name {0} with variable name {1}'.format(x[0], x[1]) for x in mismatched_resources),
					'Mismatched resources').Show()

		if (missing_resources or unreadable_resources or unwritable_resources or
				missing_devices or mismatched_resources):
			return
		
		# Check that all the condition arguments are compatible with one another.
		
		for cvar in flatten(condition_variables):
			
			# Get a condition.
			for cond in cvar.conditions:
								
				value1 = cond.arg1
				value2 = cond.arg2
				resource1 = None
				resource2 = None
				
				# If working with resources, use their values as the values, and make the resource available
				
				if cond.type1 == 'resource name':
					resource1 = [resource for (name, resource) in flatten(condition_resources) if name == cond.arg1][0]
					value1 = resource1.value
				if cond.type2 == 'resource name':
					resource2 = [resource for (name, resource) in flatten(condition_resources) if name == cond.arg2][0]
					value2 = resource2.value
				
				# Check if the other argument is in the allowed values
				
				if hasattr(resource1,'allowed_values') and resource1.allowed_values is not None:
					if value2 not in resource1.allowed_values:
						MessageDialog(self, 'In the condition {0}, {1} is not in allowed_values of {2}.'.format(cond, value2, cond.arg1),'Condition error').Show()
						return
				if hasattr(resource2,'allowed_values') and resource2.allowed_values is not None:
					if value1 not in resource2.allowed_values:
						MessageDialog(self, 'In the condition {0}, {1} is not in allowed_values of {2}.'.format(cond, value1, cond.arg2),'Condition error').Show()
						return

				# Check if units agree.
				
				if resource1 is not None and resource1.units is not None:
					try:
						value1.assert_dimensions(value2)
					except ValueError:
						MessageDialog(self, 'In the condition {0}, {1} does not have a dimension.'.format(cond, value2),'Condition error').Show()
						return
					except IncompatibleDimensions:
						MessageDialog(self, 'In the condition {0}, {1} and {2} do not have matching dimensions.'.format(cond, value1, value2),'Condition error').Show()
						return
				if resource2 is not None and resource2.units is not None:
					try:
						value2.assert_dimensions(value1)
					except ValueError:
						MessageDialog(self, 'In the condition {0}, {1} does not have a dimension.'.format(cond, value1),'Condition error').Show()
						return
					except IncompatibleDimensions:
						MessageDialog(self, 'In the condition {0}, {1} and {2} do not have matching dimensions.'.format(cond, value1, value2),'Condition error').Show()
						return


		exporting = False
		if self.export_enabled.Value:
			dir = self.directory_browse_button.GetValue()
			# YYYY-MM-DD_HH-MM-SS.csv
			name = '{0:04}-{1:02}-{2:02}_{3:02}-{4:02}-{5:02}.csv'.format(*localtime())

			if not dir:
				MessageDialog(self, 'No directory selected.', 'Export path').Show()
				return

			if not os.path.isdir(dir):
				MessageDialog(self, 'Invalid directory selected', 'Export path').Show()
				return

			file_path = os.path.join(dir, name)
			if os.path.exists(file_path):
				MessageDialog(self, file_path, 'File exists').Show()
				return

			# Everything looks alright, so open the file.
			export_file = open(file_path, 'w')
			export_csv = csv.writer(export_file)
			exporting = True

			# Show the path in the GUI.
			self.last_file_name.Value = file_path

			# Write the header.
			export_csv.writerow(['Time (s)'] +
					['{0.name} ({0.units})'.format(var) if var.units is not None else var.name
							for var in flatten(output_variables)] +
					['{0.name} ({1})'.format(var, units) if units is not None else var.name
						for var, units in zip(input_variables, measurement_units)])

		self.capture_dialogs += 1

		dlg = DataCaptureDialog(self, resources, output_variables, num_items, measurement_resources,
				input_variables, condition_resources, condition_variables, pulse_config, continuous=continuous)
		dlg.SetMinSize((500, -1))

		for name in measurement_resource_names:
			wx.CallAfter(pub.sendMessage, 'data_capture.start', name=name)

		# Export buffer.
		max_buf_size = 10
		buf = []
		buf_lock = Lock()

		def flush():
			export_csv.writerows(buf)
			export_file.flush()

			while buf:
				buf.pop()

		def data_callback(cur_time, values, measurement_values):
			for name, value in zip(measurement_resource_names, measurement_values):
				wx.CallAfter(pub.sendMessage, 'data_capture.data', name=name, value=value)

			# Extract values out of quantities, since the units have already been taken care of in the header.
			values = [x.original_value if hasattr(x, 'original_value') else x for x in values]
			measurement_values = [x.original_value if hasattr(x, 'original_value') else x for x in measurement_values]

			if exporting:
				with buf_lock:
					buf.append([cur_time] + values + measurement_values)

					if len(buf) >= max_buf_size:
						flush()

		def close_callback():
			self.capture_dialogs -= 1

			if exporting:
				with buf_lock:
					flush()
					export_file.close()

			for name in measurement_resource_names:
				wx.CallAfter(pub.sendMessage, 'data_capture.stop', name=name)

		dlg.data_callback = data_callback
		dlg.close_callback = close_callback
		dlg.Show()
		dlg.start()
Example #2
0
class PreferencesDialog(wx.Dialog):
    __doc__ = globals()['__doc__']
    def __init__(self,parent,prefs=None,tablist=None):

                
        if not prefs:
            prefs = neveredit.util.Preferences.getPreferences()
        self.preferences = prefs
        if not tablist:
            tablist = ["GeneralPanel","ScriptEditorPanel","TextPanel", "UserControlsPanel"]
            # tablist list all tabs that will be activated, the other ones do
            # not show
        self.tablist = tablist
        resourceText = PreferencesDialog_xrc.data
#        resource = wx.xrc.EmptyXmlResource()
        resource = wx.xrc.XmlResource()
#        resource.LoadFromString(resourceText)
        resource.LoadFromBuffer(resourceText)

        dialog = resource.LoadDialog(parent,"PrefDialog")
        notebook = wx.xrc.XRCCTRL(dialog,"PrefNotebook")
        
        generalPanel = wx.xrc.XRCCTRL(dialog,"GeneralPanel")
        if "GeneralPanel" in self.tablist:
            self.appDirButton = DirBrowseButton(generalPanel,-1,(500,30),
                                                labelText=_('NWN Directory'),
                                                buttonText=_('Select...'),
                                                startDirectory=
                                                prefs['NWNAppDir'])
            self.appDirButton.SetValue(prefs['NWNAppDir'])            
            resource.AttachUnknownControl('AppDir',
                                          self.appDirButton,
                                          generalPanel)
        else:
            index = self.__getPanelIndex(notebook,generalPanel)
            notebook.DeletePage(index)
        if "ScriptEditorPanel" in self.tablist:
            self.scriptAntiAlias = wx.xrc.XRCCTRL(dialog,"ScriptAntiAlias")
            self.scriptAntiAlias.SetValue(prefs['ScriptAntiAlias'])
            self.scriptAutoCompile = wx.xrc.XRCCTRL(dialog,"ScriptAutoCompile")
            self.scriptAutoCompile.SetValue(prefs['ScriptAutoCompile'])
        else:
            index = self.__getPanelIndex(notebook,
                                         wx.xrc.XRCCTRL(dialog,
                                                        "ScriptEditorPanel"))
            notebook.DeletePage(index)
        if "TextPanel" in self.tablist :
                self.DefaultLocStringLang = wx.xrc.XRCCTRL(
                                        dialog,"DefaultLocStringLang")
                self.DefaultLocStringLang.SetSelection(neveredit.file.Language.\
                        convertFromBIOCode(prefs["DefaultLocStringLang"]))
        else:
                index = self.__getPanelIndex(notebook,
                                         wx.xrc.XRCCTRL(dialog, "TextPanel"))
                notebook.DeletePage(index)

        # Set up the User Controls Panel
        # Create Controls
        # Fix length 
        # Set value
        if "UserControlsPanel" in self.tablist :
                self.mwUpKey = wx.xrc.XRCCTRL(dialog,"mwUpKey")
                self.mwUpKey.SetMaxLength(1)
                self.mwUpKey.SetValue(prefs['GLW_UP'])
                self.mwDownKey = wx.xrc.XRCCTRL(dialog,"mwDownKey")
                self.mwDownKey.SetMaxLength(1)
                self.mwDownKey.SetValue(prefs['GLW_DOWN'])
                self.mwLeftKey = wx.xrc.XRCCTRL(dialog,"mwLeftKey")
                self.mwLeftKey.SetMaxLength(1)
                self.mwLeftKey.SetValue(prefs['GLW_LEFT'])
                self.mwRightKey = wx.xrc.XRCCTRL(dialog,"mwRightKey")
                self.mwRightKey.SetMaxLength(1)
                self.mwRightKey.SetValue(prefs['GLW_RIGHT'])
                
        else:
                index = self.__getPanelIndex(notebook,
                                         wx.xrc.XRCCTRL(dialog, "UserControlsPanel"))
                notebook.DeletePage(index)

        dialog.Bind(wx.EVT_BUTTON, self.OnOk, id=wx.xrc.XRCID("ID_OK"))
        dialog.Bind(wx.EVT_BUTTON, self.OnCancel, id=wx.xrc.XRCID("ID_CANCEL"))
#        self.PostCreate(dialog)

    def __getPanelIndex(self,notebook,panel):
        index = [notebook.GetPage(i).GetId() for i in
                 range(notebook.GetPageCount())]\
                 .index(panel.GetId())
        return index
                
    def getValues(self):
        values = {}
        if "GeneralPanel" in self.tablist:
            values.update({'NWNAppDir':self.appDirButton.GetValue()})
        if "ScriptEditorPanel" in self.tablist:
            values.update({'ScriptAntiAlias':
                           self.scriptAntiAlias.GetValue(),
                           'ScriptAutoCompile':
                           self.scriptAutoCompile.GetValue()})
        if "TextPanel" in self.tablist:
            values.update({"DefaultLocStringLang":neveredit.file.Language.\
                           convertToBIOCode(self.DefaultLocStringLang.GetSelection())})
            
        # Update value of direction keys for Model/GLWindow
        if "UserControlsPanel" in self.tablist:
            values.update({'GLW_UP': self.mwUpKey.GetValue(),
                           'GLW_DOWN': self.mwDownKey.GetValue(),
                           'GLW_LEFT': self.mwLeftKey.GetValue(),
                           'GLW_RIGHT': self.mwRightKey.GetValue()})
        return values

    def ShowAndInterpret(self):
        self.CentreOnParent()
        if self.ShowModal() == wx.ID_OK:
            result = self.getValues()
            self.preferences.values.update(result)
            self.preferences.save()
            return True
        else:
            return False

    def OnCancel(self, event):
        self.EndModal(wx.ID_CANCEL)

    def OnOk(self, event):
        self.EndModal(wx.ID_OK)