+ def __init__(self, config, afm=None):
+ self.config = config
+ self.afm = afm
+
+ def load_from_config(self, devices):
+ if self.afm is None:
+ self.afm = _AFM(config=self.config['afm'])
+ self.afm.load_from_config(devices=devices)
+
+ def setup_config(self):
+ if self.afm:
+ self.afm.setup_config()
+ self.config['afm'] = self.afm.config
+
+ def calibrate(self, filename=None, group='/'):
+ """Main calibration method.
+
+ Outputs:
+ k cantilever spring constant (in N/m, or equivalently nN/nm)
+ k_s standard deviation in our estimate of k
+ data the data used to determine k
+ """
+ data = self.acquire(filename=filename, group=group)
+ k = k_s = bumps = temperatures = vibrations = None
+ bumps = data.get('bump', None)
+ temperatures = data.get('temperature', None)
+ vibrations = data.get('vibration', None)
+ if None not in [bumps, temperatures, vibrations]:
+ k,k_s = _analyze(
+ bumps=bumps, temperatures=temperatures, vibrations=vibrations)
+ if filename is not None:
+ self.save_results(
+ filename=filename, group='{}calibration/'.format(group),
+ spring_constant=k, spring_constant_deviation=k_s, **data)
+ return (k, k_s, data)
+
+ def acquire(self, filename=None, group='/'):
+ """Acquire data for calibrating a cantilever in one function.
+
+ Outputs a dict of `action` -> `data_array` pairs, for each
+ action (bump, temperature, vibration) that is actually
+ configured. For example, if you wanted to skip the surface
+ approach, bumping, and retraction, you could just set
+ `.config['bump']` to `None`.
+
+ The temperatures are collected after moving far from the
+ surface but before and vibrations are measured to give
+ everything time to settle after the big move.
+
+ Because theres a fair amount of data coming in during a
+ calibration, we save the data as it comes in. So the
+ procedure is bump-0, save-bump-0, bump-1, save-bump-0, etc.
+ To disable the saving, just set `filename` to `None`.
+ """
+ if filename is not None:
+ assert group.endswith('/'), group
+ self.save(filename=filename, group='{}config/'.format(group))
+ data = {}
+ if self.config['bump'] and self.config['num-bumps'] > 0:
+ data['bump'] = _zeros((self.config['num-bumps'],), dtype=_float)
+ for i in range(self.config['num-bumps']):
+ _LOG.info('acquire bump {} of {}'.format(
+ i, self.config['num-bumps']))
+ data['bump'][i] = _bump(
+ afm=self.afm, config=self.config['bump'],
+ filename=filename, group='{}bump/{}/'.format(group, i))
+ _LOG.debug('bumps: {}'.format(data['bump']))
+ self.afm.move_away_from_surface(
+ distance=self.config['vibration-spacing'])
+ if self.config['temperature'] and self.config['num-temperatures'] > 0:
+ data['temperature'] = _zeros(
+ (self.config['num-temperatures'],), dtype=_float)
+ for i in range(self.config['num-temperatures']):
+ _LOG.info('acquire temperature {} of {}'.format(
+ i, self.config['num-temperatures']))
+ data['temperature'][i] = _temperature(
+ get=self.afm.get_temperature,
+ config=self.config['temperature'],
+ filename=filename,
+ group='{}temperature/{}/'.format(group, i))
+ _sleep(self.config['temperature']['sleep'])
+ _LOG.debug('temperatures: {}'.format(data['temperature']))
+ if self.config['vibration'] and self.config['num-vibrations'] > 0:
+ data['vibration'] = _zeros(
+ (self.config['num-vibrations'],), dtype=_float)
+ for i in range(self.config['num-vibrations']):
+ data['vibration'][i] = _vibration(
+ piezo=self.afm.piezo, config=self.config['vibration'],
+ filename=filename,
+ group='{}vibration/{}/'.format(group, i))
+ _LOG.debug('vibrations: {}'.format(data['vibration']))
+ return data
+
+ def save(self, filename=None, group='/'):
+ storage = _HDF5_Storage(filename=filename, group=group)
+ storage.save(config=self.config)
+
+ @staticmethod
+ def save_results(filename=None, group='/', bump=None,
+ temperature=None, vibration=None, spring_constant=None,
+ spring_constant_deviation=None):
+ specs = [
+ _SaveSpec(item=bump, relpath='raw/photodiode-sensitivity',
+ array=True, units='V/m'),
+ _SaveSpec(item=temperature, relpath='raw/temperature',
+ array=True, units='K'),
+ _SaveSpec(item=vibration, relpath='raw/vibration',
+ array=True, units='V^2/Hz'),
+ _SaveSpec(item=spring_constant, relpath='processed/spring-constant',
+ units='N/m', deviation=spring_constant_deviation),
+ ]
+ _save(filename=filename, group=group, specs=specs)
+
+ @staticmethod
+ def load_results(filename, group='/'):
+ """Load results saved with `.save_results()`."""
+ specs = [
+ _SaveSpec(key=('raw', 'bump'),
+ relpath='raw/photodiode-sensitivity',
+ array=True, units='V/m'),
+ _SaveSpec(key=('raw', 'temperature'), relpath='raw/temperature',
+ array=True, units='K'),
+ _SaveSpec(key=('raw', 'vibration'),
+ relpath='raw/vibration',
+ array=True, units='V^2/Hz'),
+ _SaveSpec(key=('processed', 'spring_constant'),
+ relpath='processed/spring-constant',
+ units='N/m', deviation='spring_constant_deviation'),
+ ]
+ return _load(filename=filename, group=group, specs=specs)
+
+ def load_raw(self, filename=None, group='/'):
+ """Load results saved during `.aquire()` by bumps, etc."""
+ data = {}
+ with _h5py.File(filename, 'r') as f:
+ for name,loader in [('bump',_bump_load),
+ ('temperature', _temperature_load),
+ ('vibration', _vibration_load),
+ ]:
+ n = self.config['num-{}s'.format(name)]
+ if n > 0:
+ data[name] = []
+ for i in range(n):
+ try:
+ cwg = f['{}{}/{}/'.format(group, name, i)]
+ except KeyError:
+ pass
+ else:
+ data[name].append(loader(group=cwg))
+ return data