olfactometer package

Submodules

olfactometer.PID_reader module

class olfactometer.PID_reader.PID_Tester(ui=False, smell_engine=False, PID_MODE=False, cont_read_conc=False, sampling_rate=50)

Bases: object

read_concentration_values()
timer_pause()

Halts thread instance.

timer_resume()

Continues thread after being paused.

timer_run()

The ‘update’ method for the thread instance. Writes digital valve states to olfactometer at the defined timer_interval rate. If writing values is unsuccessfull thread instance halts.

timer_setup(interval=None)

Configuration of thread instance. Thread runs at a user-defined time interval to which it issues commands to hardware.

Parameters

interval (float) – Rate at which thread executs write commands to olfactometer.

timer_start()

Starts thread instance.

timer_stop()

Pauses thread, turns off all valve, ends defined virtual communication, and releases Task object instance from memory.

olfactometer.PID_reader.random() → x in the interval [0, 1).

olfactometer.contour_plot_generator module

class olfactometer.contour_plot_generator.plotterDim(saveData=False, saveRootPath='./')

Bases: object

append_value(key, value)
constants_text(variables, index1, index2, desired_conc)
generate_contour_points(variables_in, index1, index2, desired_conc)
graphPair(x, y)
graph_contour_points(variables, index1, index2, desired_conc, save_fig=False, save_fig_name='1')
residuals(variables, x)

This method is invoked by the non-linear least squares solver. wNA: The fraction of time that valve N is in state A wNB: The fraction of the remaining time that valve N is in state B fA: The flow rate through MFC A fB: The flow rate through MFC B

rowPlot(index)
row_graph_contour_points(variables, index, desired_conc, root_path='./', make_path=True)
set_data()

olfactometer.data_container module

class olfactometer.data_container.DataContainer

Bases: object

append_target_concentration(key, value)
append_value(key, value)
create_json()

olfactometer.equipment module

class olfactometer.equipment.Jar(label)

Bases: object

property area
property contents
property density
diameter = array(0.) * cm
fill(mixture_or_odorant, volume)
height = array(0.) * cm
liquid_volume = None
property max_evaporation_rates

Rates of evaporation if vapor is cleared (e.g. by strong air flow) This is just the max evaporation rate for each molecule times its mole fraction in the solution

property max_vapor_flow_rates
property max_volume
mixture = None
property molarities
property vapor_concs
property vapor_fractions
property vapor_molecules
vendor = None
class olfactometer.equipment.MFC(label)

Bases: olfactometer.equipment.Pneumatic

ao_channel = None
property flow_rate
flow_rate_to_voltage(flow_rate)
property flow_rate_uncertainty
max_flow_rate = array(0.) * L/min
voltage = array(0.) * V
voltage_max = array(5.) * V
voltage_min = array(0.) * V
property voltage_range
class olfactometer.equipment.Olfactometer(jars, mfcs, data_container=None)

Bases: object

The whole olfactometer

add_valve(valve, station)
cid_to_molecule(cid)
connect_jars(jars)
find_odorant_id_by_index(m_index)
flow_units = array(1.) * cc/min
host = None
property loaded_molecules

Provide a list of molecules loaded into this olfactometer

mfc_flat_list(mfcs=None)
mfcs = None
class olfactometer.equipment.Opening(host, label)

Bases: object

property area
connections = None
property density
diameter = array(0.) * cm
host = None
label = ''
property mass_flow_rate
pressure = array(0.) * psi
velocity = array(0.) * m/s
property volume_flow_rate
class olfactometer.equipment.Pneumatic(label)

Bases: object

Base class for pneumatic components

label = None
position = None
positions = None
routes = None
olfactometer.equipment.calc_conc_jit(variables, n_jars, max_flow_rates, A)
olfactometer.equipment.get_unique_tuples(x)

Get unique 2-tuples, ignoring order, from the list x.

olfactometer.equipment.host_class_names(node)

olfactometer.my_equipment module

Equipment currently in use.

class olfactometer.my_equipment.MyHighMFC(label)

Bases: olfactometer.my_equipment.MyMFC

ao_channel = 2
max_flow_rate = array(10.) * L/min
model_name = 'MC-10SLPM-D'
class olfactometer.my_equipment.MyJar(label)

Bases: olfactometer.equipment.Jar

diameter = array(5.) * cm
height = array(5.) * cm
class olfactometer.my_equipment.MyLowMFC(label)

Bases: olfactometer.my_equipment.MyMFC

ao_channel = 0
max_flow_rate = array(10.) * cc/min
model_name = 'MC-10SCCM-D'
class olfactometer.my_equipment.MyMFC(label)

Bases: olfactometer.equipment.MFC

property curr_flow_rate_uncertainty
class olfactometer.my_equipment.MyMediumMFC(label)

Bases: olfactometer.my_equipment.MyMFC

ao_channel = 1
max_flow_rate = array(1.) * L/min
model_name = 'check label'
class olfactometer.my_equipment.MyOlfactometer(jars, mfcs, data_container=None)

Bases: olfactometer.equipment.Olfactometer

class olfactometer.my_equipment.MyValve(v_num)

Bases: object

olfactometer.odor_table_look_up module

class olfactometer.odor_table_look_up.OdorTableLookup(data_frame=Empty DataFrame Columns: [] Index: [], split_num=0)

Bases: object

get_index(index)
query(target)

olfactometer.odorants module

Classes for odorants, mixtures, chemical orders, etc.

class olfactometer.odorants.ChemicalOrder(molecule, vendor, part_id, purity=1, known_impurities=None)

Bases: object

known_impurities = None
molecule = None
part_id = ''
purity = 1
vendor = None
class olfactometer.odorants.Compound(chemical_order, stock='', date_arrived=None, date_opened=None, is_solvent=False)

Bases: object

chemical_order = None
date_arrived = None
date_opened = None
is_solvent = False
stock = ''
class olfactometer.odorants.Molecule(cid=None, name=None, fill=False, vapor_press=None, dens=None)

Bases: object

This module extracts various chemical and physical properties of odorous molecular compounds using PUG REST API. Portions of the codebase have benefitted from work by Maxim Shevelev <mdshev7@gmail.com>

For details about PubChem’s PUG REST API please visit https://pubchem.ncbi.nlm.nih.gov/pug_rest/PUG_REST.html

cas = ''
fill_details()

Populate odorant molecule properties to include molecular weight and cid/name.

get_addtional_om_props(required_properties)

Found method as courtesy of Maxim Shevelev (Github: @mawansui) 1. Accepts the compound name 2. Gets the CID for this compound 3. Searches PubChem for data for the specified CID 4. Cycles through the fetched data to select required fields 5. Returns a dictionary with specified properties of specified compound

get_cid_by_name(compound_name)
  1. Accepts the compound name

  2. Searches PubChem by this name

  3. Returns the compound’s PubChem CID

get_cid_from_api()

Obtain pub chem ID from OM name.

get_density()

Obtain odorant molecule density by searching density property and extra additional properties from PubChem.

get_name_from_api()
get_vapor_pressure()

Obtain odorant molecule Vapor Pressure from PubChem.

iupac = ''
property molar_evaporation_rate
property molarity
pubchem_parsing(url)

Get the link to PubChem API, parse it for JSON and then translate that to Python dictionary; This is just to follow the DRY principle

synonyms = ''
url_to_json(url)
class olfactometer.odorants.Solution(components, date_created=None)

Bases: object

property compounds
property dilutions
property molar_evaporation_rates
property molarities

Returns a dictionary with the molarity of each Molecule

mole_fraction(molecule)
property mole_fractions

Returns a dictionary with the mole fraction of each Molecule

property molecules

Returns a dictionary with the moles of each Molecule

partial_pressure(molecule)
property partial_pressures

Computes partial pressures for each odorant in the mixture using Raoult’s law

property total_pressure

Computes total pressure of the vapor using Dalton’s law

vapor_concentration(molecule)
property vapor_concentrations

Concentrations of each component in the vapor headspace

vapor_fraction(molecule)
property vapor_fractions

Fractions of each component in the vapor phase at steady state. Units are fraction of volume. Air is assumed to make up the balance

class olfactometer.odorants.Vendor(name, url)

Bases: object

name = ''
url = ''
olfactometer.odorants.fix_concs(molecules_concs)

olfactometer.physics module

olfactometer.physics.bernoulli(v=None, p=None, rho=None, g=array(9.8) * m / s ** 2, z=0, k=None)
olfactometer.physics.mackay(vp)

Mackay, D., & van Wesenbeeck, I. (2014). Correlation of Chemical Evaporation Rate with Vapor Pressure. Environmental Science & Technology, 48(17), 10259–10263. doi:10.1021/es5029074 Note typo in intercept parameter in Figure 1.

olfactometer.physics.venturi(rho=None, p1=None, p2=None, v1=None, v2=None)

olfactometer.plotter module

olfactometer.plotter.main()
class olfactometer.plotter.plotter(numOdorants, numValves)

Bases: object

digValueInit(figure)
digValueUpdate(value)
draw()
mfcLineInit(figure)
mfcLineUpdate(data)
odLineInit(figure)
odLineUpdate(data)

olfactometer.read_json_PID_sensor_readings module

olfactometer.smell_controller module

class olfactometer.smell_controller.SmellController(olfactometer, data_container=None, valve_driver=None, kdtree_flag=False, kdtree=None, smell_data_frame=None)

Bases: object

classmethod calc_conc(variables, n_jars, max_flow_rates, A)

This method is invoked externally to calculate concentration given olfactometer configuration variables: MUST BE np.array! Contains olfactometer mfc values and valve duty cycles n_jars: assigneed from class instance max_flow_rates: assigneed from class instance A: Gas Phase Concentration of jars x odorants

clean_valve_mfc_values(values=None, print_flow_rates=False)

Convert dictionary that looks like: {‘w1MFC_A_High’: 0.8840516038671588, ‘w2MFC_A_High’: 0, ‘w1MFC_B_Low’: 0.11551628411711523, ‘w2MFC_B_Low’: 0, ‘fMFC_A_High’: array(1.9601) * cc/min, ‘fMFC_B_Low’: array(0.01) * cc/min, ‘MFC_Carrier’: array(2198.0299) * cc/min}

find_odorant_id_by_index(m_index)
get_max_flow_rates()
get_vapor_concs_dense(target_odorants)
kdtree_lookup(target_dense, mfc_names, F)
lls_olfactometer_scheduler(vapor_concs_dense, target_outflow_rate_ccm, target_dense)
property max_outflow_rate

The maximum possible flow rate if all connected-in-parallel MFCs are at their maximum flow rates

mfc_flow_rates_to_voltages(values)
property olfactometer_schedule
optimization_report()

Report on optimization quality

optimize(target_outflow_concs=None, target_outflow_rate=None, report=False)

Find the valve duty cycles and MFC settings that bring this route closest to target. This will be done by varying actual valve positions and flow rates, so it should only be done on a virtual olfactometer

property outflow_fractions
property outflow_rates
classmethod residuals(variables, x, n_jars, max_flow_rates, total_vapor, fixed_A, fixed_B, alpha)

This method is invoked by the non-linear least squares solver. wNA: The fraction of time that valve N is in state A wNB: The fraction of the remaining time that valve N is in state B fA: The flow rate through MFC A fB: The flow rate through MFC B

property target_outflow
property target_outflow_concs
property target_outflow_rate
update_target(report=False)

Executed externally, ideally through the SmellEngineCommunicator class. This method takes the assigned target concentrations and flow rate then message passes target states through the Smell Engine pipeline.

update_target_from_logical()
olfactometer.smell_controller.calc_conc_jit(variables, n_jars, max_flow_rates, A)

olfactometer.smell_engine module

class olfactometer.smell_engine.SmellEngine(total_flow_rate=4000, n_odorants=3, data_container=None, debug_mode=True, write_flag=False, PID_mode=False, look_up_table_path=None, oms=None)

Bases: object

add_molecule_id(id)

Add Odorant Molecule to system

id

int values uniquely identifying OM

append(val)
automation_set_mfc_setpoints(mfc_setpoints)

Override mfc setpoints in order B, A, C

ids

List[] specifying each MFCs flow rate relative to its max.

calculate_target_achieved_error()
close_smell_engine()
get_desired_concentrations()
get_mfc_setpoints()
get_molecular_ids()
get_valve_duty_cycles()
initialize_equipment()

Configure olfactometer equipment via Smell Composer component(s). First we specify molecules by CID number and define their vapor pressure, densities, and solution. Instantiate the olfactometer. Determines the jars and dilutions required to achieve the target specified per molecule. Perform optimization to configure olfactometer Format values for ValveDriver to execute.

initialize_olfactometer()

Initialize Smell Driver instance which instantiates a ValveDriver instance. This instance is then assigned to the Smell Composer so the communication line of odorant mixtures is established.

debug_mode

Flag denoting physical vs simulated hardware.

initialize_smell_engine_system(with_nidaq=True)

Presumes user has manually set OM ids before initializing system pipeline

property om_dilutions
property om_ids
print_data_binary(concentration)

A debug method for printing binary representations of concentrations (digital states of olfactometer).

Parameters

concentration (list of float) – List of concentration states.

set_desired_concentrations(concentrations)

Reads and writes concentration values to Smell Engine and Valve Driver

concentrations

List containing concentration values

set_mfc_setpoints(mfc_setpoints)

Override mfc setpoints

ids

mfc setpoints

set_odorant_molecule_dilutions(dilutions)

Assign PubChemID’s on startup to the Valve Driver cid’s.

ids

List containing uniquely identifiable int values of OM ids

set_odorant_molecule_ids(ids)

Assign PubChemID’s on startup to the Valve Driver cid’s.

ids

List containing uniquely identifiable int values of OM ids

set_olfactometer_target_outflow(m_flowrate)
set_starting_concentration(starting_vector)
set_valve_duty_cycles(valves)

Override valve duty cycles for system tests.

ids

Valve states

olfactometer.smell_engine_communicator module

class olfactometer.smell_engine_communicator.SmellEngineCommunicator(debug_mode=False, odor_table=None, write_flag=False)

Bases: object

SmellEngineCommunicator establishes a network comm line between Unity and Smell Engine. First the equipment is configured, then the socket waits for a connection (client devices) Once client connection is established, PubChemID’s are assigned, ValveDriver is instantiated, then socket loops continuously listening for sets of data.

debug_mode

flag for physical vs simulated hardware specified via command-line.

init_main_loop()
load_concentrations(concentration_mixtures)

Append list of concentrations to mixtures deque within Smell Composer, which in turn issues odorants to the Valve Driver for the Olfactometer. The desired odorant concentration vector is formatted then passed down the Smell Engine pipeline.

concentration_mixtures

List of desired odorant concentrations

main_thread_loop()

LoopConnection continuously listens for a list of doubles, converts the bytes, then issues them to the Smell Composer and Valve Driver via the method load_concentrations()

receive_pub_chemIDs()

Assign PubChemID’s on startup to the LogicalOlfactomete cid’s. Method is called from LogicalOlfactometer instantiation, so it waits until the first set of values are transmitted from Unity (which are the PubChemIDs)

receive_quantity_odorants()

Assign PubChemID’s on startup to the LogicalOlfactomete cid’s. Method is called from LogicalOlfactometer instantiation, so it waits until the first set of values are transmitted from Unity (which are the PubChemIDs)

recieve_dilutions()

Recieve Dilutions values for self.smell_engine.set_odorant_molecule_dilutions([10, 10,10])

olfactometer.smell_engine_communicator.main(debug_mode: bool, odor_table_mode: Optional[str] = <typer.models.ArgumentInfo object>, write_data: Optional[bool] = <typer.models.ArgumentInfo object>)

olfactometer.ui module

class olfactometer.ui.UI(molecules={439250: 'l-limonene', 439570: 'l-carvone', 440917: 'd-limonene'}, print_PID_average=False)

Bases: object

RepresentsFloat(s)
RepresentsInt(s)
allValues()
dutyCyclesUI()
dutyCyclesValidation(widget, event, data)
dutyCyclesValues()
getMaxConcentrations(smell_engine)
mfcUI()
mfcValues()
numberPrefix(x)
numberPrefixR(x)
occupancyTimeUI()
occupancyTimeValidation(widget, event, data)
occupancyTimeValues()
odorConcentrationSetMaxself(widget, event, data)
odorConcentrationSetMinself(widget, event, data)
odorConcentrationSetStepself(widget, event, data)
odorConcentrationUI(min_setpt=- 12, max_setpt=0, step_size=0.5)
odorConcentrationValues()
odorSelectorUI()
odorSelectorsValues()
sliderLog(x)
sliderLogR(x)
timeSeriesUpdate(value, plotLast=5000)
updateSliderValue(widget, event, data)

olfactometer.valve_driver module

class olfactometer.valve_driver.ValveDriver(olfactometer=None, data_container=None, debug_mode=False, PID_mode=False)

Bases: object

ValveDriver is responsible for interfacing with NI-DAQmx hardware using the nidaqmx API. Provides continuous virtual communication lines with MFCs (analog) and olfactometer valves (digital).

olf

List of mfc voltage values indexed accurately.

cids

A ordered list of concentration ids for each valve.

abort()

Resets NI-DAQmx device to initialized state. All prior tasks and defined channel lines are aborted.

close_tasks()

Closes all created/open NIDAQ tasks.

convert_concentration_format(data_input, debug=False)

Read in a list of concentrations, convert each individual concentration A/B/off state into a sequence of binary control signals for olfactometer. :param d_in: :type d_in: list of `

Returns

A list of 5 concentration values and there states in A/B.

determine_clean_air_pair(valve_sched)
format_bits(valve_index, state)

Each valve state is identified by A, B, None. The valve state is represented as a 32 bit unsigned integer. If state A, first 16 bits are used to configure digital state of olfactometer. If state B, last 16 bits are used.

Parameters
  • valve_index (int) – Integer indexing of valve state (non-zero).

  • state (str) – A,B,None.

Returns

Binary representation of olfactometer state.

Return type

uint32

Raises

Exception – Invalid state requested.

generate_analog_frame_writes(analog_states)

Creates list of analog states to be written to MFCs.

Parameters

analog_states (dictionary of (int, float)) – A index identifier for MFC and the requested voltage values supplied.

Returns

List of voltage values per MFC.

Return type

(list of float)

generate_digital_frame_writes(valve_durations)

Create list (of size samples_per_frame) to populate with 32 bit values representative of the digital states for each valve.

To turn a respective valve on, it’s index location within first 16 bits of a 32-bit write must be set to high. To turn a respective valve off, it’s index location within last 16 bits of a 32-bit write must be set to high.

Parameters

valve_durations (dictionary of (int, float)) – Dictionary of valve indexes and their respective states (time in ms for A,B,off).

Returns

Writes will be written to olfactometer as multiline/channel write.

Return type

(list of uint32)

init_analog_in_task(num_samples=50, task_name='Analog_In', channel_name='analog_in_channel')

Initializing an Analog Input Task to read samples from the specified channel.

Parameters
  • num_samples (int) – Specify # of samples per channel, default is 50

  • task_name (str) – Task name, used for uniquely identifying task instance.

Returns

Success/error status represented with 1/-1.

Raises

nidaqmx.DaqError – An error occured when trying to add an analog voltage virtual communication channel.

init_analog_task(mfc_flat_list, task_name='Analog_Task', channel_name='analog_channel')

Initializing an Analog Output Task and Channel communication line. List of analog channels [0:3] along with voltage rates are initialized via nidaqmx API.

Parameters
  • mfc_flat_list – A 1D list containing analog channels and voltages.

  • task_name (str) – Task name, used for uniquely identifying task instance.

Returns

Success/error status represented with 1/-1.

Raises

nidaqmx.DaqError – An error occured when trying to add an analog voltage virtual communication channel.

init_digital_task(task_name='DigitalTask', channel_name='digital_channel')

Create task object with digital out on port0. Configure task to use 1 channel for all liens of communication. Append to list of tasks and update state of olfactometer.

Parameters
  • task_name (str) – Task name, used for uniquely identifying task instance.

  • channel_name (str) – Channel name, used for unuquely identifying different channel comm lines.

Returns

Success/error status represented with 1/-1.

Raises

nidaqmx.DaqError – An error occured when trying to add an analog voltage virtual communication channel.

initialize()

Reading and saving device information from NI-DAQmx hardware. Existing device references and tasks are reset.

Returns

Success/error status.

Raises
  • nidaqmx.DaqError – An error occurred when attempting to read NIDAQ hardware information.

  • Error codes can be found in the nidaqmx errors.py system file.

issue_odorants(valve_mfc_values)

Executed externally to update olfactometer schedule of odor samples, this method converts the olfactometer schedule into digital and analog control signals. Olfactometer Schedule Examples: {‘valves’:

[

(Valve Number, time in state A, time in state B) (1, 0.6523873263385962, 0.3433522990024116), (2, 0.6523929695332448, 0.34373085569547107)

], ‘mfcs’:

{MyMediumMFC: MFC_A_High (1.0 L/min): array(value) * V, MyLowMFC: MFC_B_Low (10.0 cc/min): array(value) * V, MyHighMFC: MFC_Carrier (10.0 L/min): array(values) * V}

}

Parameters

valve_mfc_values (dictionary of (str, float)) – Expected dictionary of mfc voltages and valve state durations.

print_data_binary(concentration)

A debug method for printing binary representations of concentrations (digital states of olfactometer).

Parameters

concentration (list of float) – List of concentration states.

print_valve_durations(valves)
set_mfc_setpoints(setpoints)

Toggle override for analog mfc setpoints and assign user defined setpoints

Parameters

setpoints – List containing voltage values per mfc

set_task_clock(task, samples_per_frame=50, frames_per_s=1, repeats=1)

Sets rate, number of samples, and source of the Sample Clock for developer-specified task.

Parameters
  • task (nidaqmx.task.Task) – Task object to which sample clock is being configured for.

  • samples_per_frame (int) – Samples generated per frame.

  • frames_per_s (int) – Frames per second for sampling.

  • repeats (int, optional) – Duplicating samples across each channel.

set_valve_states(valve_states)

Toggle overrides for digital valve states and assign user-defined valve states

Parameters

valve_states – A list of tuples containing high/low toggles for valve

specify_analog_frame_writes(analog_states)

Provide a defined list of analog states to be written to MFCs.

Parameters

analog_states (list of (float, int)) – A index identifier for MFC and the requested voltage values supplied.

Returns

List of voltage values per MFC.

Return type

(list of float)

specify_digital_frame_writes(valve_states)

Given valve states, samples are generated with half containing given valve state other half being off

Parameters

valve_states (list) – List of tuples representing A,B states for each valve

timer_pause()

Halts thread instance.

timer_resume()

Continues thread after being paused.

timer_run()

The ‘update’ method for the thread instance. Writes digital valve states to olfactometer at the defined timer_interval rate. If writing values is unsuccessfull thread instance halts.

timer_setup(interval=None)

Configuration of thread instance. Thread runs at a user-defined time interval to which it issues commands to hardware.

Parameters

interval (float) – Rate at which thread executs write commands to olfactometer.

timer_start()

Starts thread instance.

timer_stop()

Pauses thread, turns off all valve, ends defined virtual communication, and releases Task object instance from memory.

write_output(digital_values, analog_values=[])

This method will write a list of valve commands to olfactometer.

Parameters

None

Returns

Success/error message. 0 success, -1 error.

Return type

int

write_output_digital(digital_values)
write_zeroes()

This method will write a list of valve commands to olfactometer.

Returns

A list of 5 concentration values and there states in A/B.

Module contents