Esempio n. 1
0
def sigmoid(*columns):
    """Fit a Sigmoid through the data of the last scan.

    The return value is a pair of tuples::

        ((a, b, x0, c), (d_a, d_b, d_x0, d_c))

    where the elemets of the second tuple the estimated standard errors of the
    fit parameters.  The fit parameters are:

    * a - amplitude of the Sigmoid
    * b - steepness of the curve
    * x0 - center
    * c - background

    if the fit failed, the result is ``(None, None)``.

    Example::

        cscan(...)
        values, stderr = sigmoid('h', 'adet')
    """
    xs, ys, dys, _, ds = _getData(columns)
    fit = SigmoidFit()
    res = fit.run(xs, ys, dys)
    if res._failed:
        return None, None
    session.notifyFitCurve(ds, 'sigmoid', res.curve_x, res.curve_y)
    descrs = ['amplitude', 'steepness', 'center', 'background']
    vals = []
    for par, err, descr in zip(res._pars[1], res._pars[2], descrs):
        vals.append((descr, '%.4f' % par, '%.4f' % err))
    printTable(('parameter', 'value', 'error'), vals, session.log.info)
    return CommandLineFitResult((tuple(res._pars[1]), tuple(res._pars[2])))
Esempio n. 2
0
def ListSetups(listall=False):
    """Print a list of setups.

    Example:

    >>> ListSetups()

    To list also low-level and simulation setups:

    >>> ListSetups(True)

    see also: `NewSetup`, `AddSetup`, `RemoveSetup`
    """
    session.log.info('Available setups:')
    items = []
    for name, info in iteritems(session.getSetupInfo()):
        if info is None:
            items.append((name, '', '<could not be read, check syntax>', ''))
            continue
        if info['group'] in ('special', 'configdata'):
            continue
        if info['group'] == 'lowlevel' and not listall:
            continue
        items.append((name, name in session.loaded_setups and 'yes' or '',
                      info['description'],
                      ', '.join(sorted(info['devices']))))
    items.sort()
    printTable(('name', 'loaded', 'description', 'devices'), items, session.log.info)
Esempio n. 3
0
 def _logvalues(self, values, isheader=False):
     if isheader:
         values = ['{0: <13}'.format(val) for val in values]
         printTable(values, [], self.log.info)
     else:
         values = ['{0: >13}'.format(val) for val in values]
         printTable([], [values], self.log.info)
Esempio n. 4
0
def info(*devlist):
    """Print general information of the given device or all devices.

    Information is the device value, status and any other parameters that are
    marked as "interesting" by giving them a category.

    Examples:

    >>> info()           # show all information
    >>> info(Sample)     # show information relevant to the Sample object
    """
    if not devlist:
        devlist = [
            dev for dev in itervalues(session.devices) if not dev.lowlevel
        ]
    bycategory = {}
    for dev in devlist:
        for key, _value, strvalue, unit, category in dev.info():
            bycategory.setdefault(category, []).append(
                (str(dev), key + ':', strvalue + ' ' + unit))
    for catname, catinfo in INFO_CATEGORIES:
        if catname not in bycategory:
            continue
        session.log.info(catinfo)
        session.log.info('=' * len(catinfo))
        printTable(None,
                   sorted(bycategory[catname]),
                   session.log.info,
                   minlen=8)
        session.log.info()
Esempio n. 5
0
def ListMethods(dev):
    """List user-callable methods for the device.

    Example:

    >>> ListMethods(phi)
    """
    dev = session.getDevice(dev, Device)
    items = []
    listed = builtins.set()

    def _list(cls):
        if cls in listed:
            return
        listed.add(cls)
        for name, (args, doc, mcls, is_user) in sorted(iteritems(cls.methods)):
            if cls is mcls and is_user:
                items.append((dev.name + '.' + name + args, cls.__name__, doc))
        for base in cls.__bases__:
            if issubclass(base, (Device, DeviceMixinBase)):
                _list(base)

    _list(dev.__class__)
    dev.log.info('Device methods:')
    printTable(('method', 'from class', 'description'), items,
               session.log.info)
Esempio n. 6
0
 def __call__(self, components=None):
     # Print the distances of specified components from
     # Laser, Sample and Chopper
     if not components:
         components = self._components()
     sample = getattr(self, 'sample', None)
     chopper = getattr(self, 'chopper', None)
     table = []
     self.log.info('Horizontal distances of components:')
     for component in components:
         distance = getattr(self, component, None)
         row = [component]
         if isinstance(distance, number_types):
             if isinstance(sample, number_types):
                 row.append(str(sample - distance))
             else:
                 row.append('-')
             if isinstance(chopper, number_types):
                 row.append(str(chopper - distance))
             else:
                 row.append('-')
             table.append(row)
     printTable(['Component', 'From SAMPLE', 'From CHOPPER'],
                table,
                self.log.info,
                rjust=True)
Esempio n. 7
0
def ListCommands():
    """List all available commands.

    Example:

    >>> ListCommands()
    """
    session.log.info('Available commands:')
    items = []
    for name, obj in session.getExportedObjects():
        if getattr(obj, 'is_usercommand', False):
            real_func = getattr(obj, 'real_func', obj)
            if getattr(real_func, 'is_hidden', False):
                continue
            if real_func.__name__ != name:
                # it's an alias, don't show it again
                continue
            if hasattr(real_func, 'help_arglist'):
                argspec = '(%s)' % real_func.help_arglist
            else:
                argspec = formatArgs(real_func)
            docstring = real_func.__doc__ or ' '
            signature = real_func.__name__ + argspec
            if len(signature) > 50:
                signature = signature[:47] + '...'
            if not real_func.__name__.startswith('_'):
                items.append((signature, docstring.splitlines()[0]))
    items.sort()
    printTable(('name', 'description'), items, session.log.info)
Esempio n. 8
0
 def listtunings(self):
     """List all existing tuning tables."""
     data = []
     for name, table in self.tunetables.items():
         data.append((name,
                      ', '.join(setting['_name_'] for setting in table)))
     self.log.info('all tuning tables:')
     printTable(('name', 'settings'), data, session.log.info)
Esempio n. 9
0
 def show(self):
     """
     Shows the list of stored positions
     """
     session.log.info('Stored positions on %s\n', self.name)
     items = []
     for key, val in self.positions.items():
         items.append((key, str(val)))
     printTable(('Name', 'Device positions'), items, session.log.info)
Esempio n. 10
0
def ListDatasinks():
    """List the standard data sinks."""
    if session.datasinks:
        session.log.info('Currently used data sinks are:')
        items = [(ds.name, ', '.join(ds.settypes), ', '.join(ds.detectors))
                 for ds in session.datasinks]
        printTable(('name', 'used for', 'active for detectors'), items,
                   session.log.info)
    else:
        session.log.info('At the moment no data sinks are set.')
Esempio n. 11
0
def ListFitters():
    """Print a table with all known fitters.

    These fitters are usable for `center()`, `checkoffset()` and related
    commands.
    """
    items = []
    for k, v in FitterRegistry.fitters.items():
        items.append((k, 'yes' if v.center_index is not None else 'no'))
    items.sort()
    printTable(('name', 'can center'), items, session.log.info)
Esempio n. 12
0
def TacoRes(dev):
    """List all resources for the given TACO device."""
    cl = _client(dev)
    items = []
    for res, info in sorted(cl.deviceQueryResourceInfo().items()):
        try:
            rv = cl.deviceQueryResource(res)
        except Exception:
            rv = '<n/a>'
        items.append((res, info['info'], rv))
    session.log.info('TACO resources for %s:', cl.deviceName())
    printTable(('name', 'info', 'value'), items, session.log.info)
Esempio n. 13
0
    def showModules(self):
        """show modules of the connected SECoP server

        and intended devices names using the given prefix
        """
        prefix = self._get_prefix()
        if prefix is None:
            self.log.error('secnode is not connected')
            return
        items = [(prefix + m, m, mod_desc.get('properties', {}).get('description', '').split('\n')[0])
                 for m, mod_desc in self._secnode.modules.items()]
        printTable(['foreseen device name', 'SECoP module', 'description'], items, self.log.info)
Esempio n. 14
0
def ListDevices():
    """List all currently created devices.

    Example:

    >>> ListDevices()
    """
    session.log.info('All created devices:')
    items = []
    for devname in sorted(session.explicit_devices, key=lambda d: d.lower()):
        dev = session.devices[devname]
        items.append((dev.name, dev.__class__.__name__, dev.description))
    printTable(('name', 'type', 'description'), items, session.log.info)
Esempio n. 15
0
def history(dev, key='value', fromtime=None, totime=None):
    """Print history of a device parameter.

    The optional argument *key* selects a parameter of the device.  "value" is
    the main value, and "status" is the device status.

    *fromtime* and *totime* are eithernumbers giving **hours** in the past, or
    otherwise strings with a time specification (see below).  The default is to
    list history of the last hour for "value" and "status", or from the last
    day for other parameters.  For example:

    >>> history(mth)              # show value of mth in the last hour
    >>> history(mth, 48)          # show value of mth in the last two days
    >>> history(mtt, 'offset')    # show offset of mth in the last day

    Examples for time specification:

    >>> history(mth, '1 day')                  # allowed: d/day/days
    >>> history(mth, 'offset', '1 week')       # allowed: w/week/weeks
    >>> history(mth, 'speed', '30 minutes')    # allowed: m/min/minutes

    >>> history(mth, 'speed', '2012-05-04 14:00')  # from that date/time on
    >>> history(mth, 'speed', '14:00', '17:00')    # between 14h and 17h today
    >>> history(mth, 'speed', '2012-05-04', '2012-05-08')  # between two days
    """
    # support calling history(dev, -3600)
    if isinstance(key, string_types):
        try:
            key = parseDateString(key)
        except ValueError:
            pass
    if isinstance(key, number_types):
        totime = fromtime
        fromtime = key
        key = 'value'
    if key not in ('value', 'status') and fromtime is None:
        fromtime = -24
    # Device.history() accepts number of hours only when negative (anything
    # > 10000 is taken to be a Unix timestamp)
    if isinstance(fromtime, number_types) and 0 < fromtime < 10000:
        fromtime = -fromtime
    if isinstance(totime, number_types) and 0 < totime < 10000:
        totime = -totime
    # history() already accepts strings as fromtime and totime arguments
    hist = session.getDevice(dev, Device).history(key, fromtime, totime)
    entries = []
    ltime = time.localtime
    ftime = time.strftime
    for t, v in hist:
        entries.append((ftime('%Y-%m-%d %H:%M:%S', ltime(t)), repr(v)))
    printTable(('timestamp', 'value'), entries, session.log.info)
Esempio n. 16
0
def version(*devlist):
    """List version info of the device(s).

    If no device is given, the version of nicos-core is printed.
    """
    if devlist:
        for dev in devlist:
            dev = session.getDevice(dev, Device)
            versions = dev.version()
            dev.log.info('relevant versions for this device:')
            printTable(('module/component', 'version'), versions,
                       session.log.info)
    else:
        session.log.info('NICOS version: %s (rev %s)', nicos_version,
                         nicos_revision)
Esempio n. 17
0
def fit(fitclass, *columns, **kwargs):
    xs, ys, dys, _, ds = _getData(columns, dataset=kwargs.pop('dataset', None))
    fit = fitclass(**kwargs)
    res = fit.run(xs, ys, dys)
    if res._failed:
        session.log.info('Fit failed.')
        return CommandLineFitResult((None, None))
    session.notifyFitCurve(ds, fit.fit_title, res.curve_x, res.curve_y)
    descrs = fit.fit_p_descr
    vals = []
    for par, err, descr in zip(res._pars[1], res._pars[2], descrs):
        vals.append((descr, '%.5g' % par, '+/- %.5g' % err,
                     '+/- {:.1%}'.format(err / par) if par else '-'))
    printTable(('parameter', 'value', 'error', 'rel. error'), vals,
               session.log.info)
    return CommandLineFitResult((tuple(res._pars[1]), tuple(res._pars[2])))
Esempio n. 18
0
def ListMailReceivers():
    """List all mail addresses for notifications.

    Example:

    >>> ListMailReceivers()

    see also: `SetMailReceivers`
    """
    session.log.info('Email addresses for notifications:')
    items = []
    for notifier, recipients in iteritems(_listReceivers(Mailer)):
        for rec in recipients:
            items.append((notifier,) + rec)
    printTable(('mailer', 'email address', 'info'), sorted(items),
               session.log.info)
Esempio n. 19
0
    def _generate_protocol(self, first=None, last=None, with_ts=False):
        data = []
        senv = set()

        for fname in os.listdir(self.datapath):
            if not fname.endswith(self.DATA_SUFFIX):
                continue
            parts = fname.split('_')
            if not (len(parts) > 1 and parts[self.RUNNO_INDEX].isdigit()):
                continue
            runno = int(parts[self.RUNNO_INDEX])
            if first is not None and runno < first:
                continue
            if last is not None and runno > last:
                continue
            try:
                data.append(
                    self._read_data_file(runno, senv,
                                         path.join(self.datapath, fname)))
            except Exception as err:
                self.log.warning('could not read %s: %s', fname, err)
                continue
        data.sort(key=lambda x: x['#'])

        headers = self.PROTO_HEADERS[:]
        if with_ts:
            headers.insert(1, 'Started')
        for sename in sorted(senv):
            if sename not in self.IGNORE_SENV:
                headers.append(sename)
        items = []
        day = ''
        for info in data:
            if 'Sample' not in info:
                continue
            if with_ts and info['Day'] != day:
                day = info['Day']
                items.append([''] * len(headers))
                items.append(['', day] + [''] * (len(headers) - 2))
            items.append([info.get(key, '') for key in headers])

        lines = [
            'Protocol for proposal %s, generated on %s' %
            (self.proposal, time.strftime('%Y-%m-%d %H:%M')), ''
        ]
        printTable(headers, items, lines.append)
        return '\n'.join(lines) + '\n'
Esempio n. 20
0
def ListDataReceivers():
    """List email addresses to which experimental data will be sent.

    Data is sent once when a proposal is finished.  See also
    `SetDataReceivers`.

    Example:

    >>> ListDataReceivers()

    see also: `SetDataReceivers`
    """
    session.log.info('Email addresses the data will be sent to:')
    propinfo = dict(session.experiment.propinfo)
    items = set()
    for addr in propinfo.get('user_email', ()):
        items.add((addr,))
    printTable(('email address', ), sorted(items), session.log.info)
Esempio n. 21
0
 def printtuning(self):
     """Print out the current tuning table."""
     data = []
     valueidx = {}
     all_values = set()
     for setting in self.curtable:
         all_values.update(key for key in setting if not key.startswith('_'))
     all_values = sorted(all_values)
     valueidx = {val: idx for idx, val in enumerate(all_values)}
     for idx, setting in enumerate(self.curtable):
         values = ['---'] * len(all_values)
         for devname, devvalue in setting.items():
             if not devname.startswith('_'):
                 values[valueidx[devname]] = str(devvalue)[:15]
         data.append((str(idx), setting['_name_'], '%.3f' %
                      setting['_tau_']) + tuple(values))
     self.log.info('current MIEZE settings (%s):', self.tuning)
     printTable(('#', 'name', 'tau (ps)') + tuple(all_values), data,
                session.log.info)
Esempio n. 22
0
def getall(*names):
    """List the given parameters for all existing devices that have them.

    Example:

    >>> getall('offset')

    lists the offset for all devices with an "offset" parameter.
    """
    items = []
    for name, dev in sorted(iteritems(session.devices),
                            key=lambda nd: nd[0].lower()):
        pvalues = []
        for param in names:
            if param in dev.parameters:
                pvalues.append(repr(getattr(dev, param)))
            else:
                pvalues.append(None)
        if any(v is not None for v in pvalues):
            items.append([name] + list(map(str, pvalues)))
    printTable(('device', ) + names, items, session.log.info)
Esempio n. 23
0
def PosListShow(listname='default'):
    """Show the positions in the given position list.

    Examples:

    >>> PosListShow()         # show default position list
    >>> PosListShow('other')  # show named position list
    """
    sample = session.experiment.sample
    instr = session.instrument
    lists = dict(sample.poslists)
    if listname not in lists:
        session.log.warning('Position list %r does not exist', listname)
        return
    if not lists[listname]:
        session.log.info('<empty>')
        return
    items = []
    R2D = math.degrees
    for i, item in enumerate(lists[listname]):
        calcpos = instr._calcPos(item[1]) if item[1] is not None else None
        items.append((
            '%d' % (i + 1),
            '%.3f' % R2D(item[0].gamma),
            '%.3f' % R2D(item[0].omega),
            '%.3f' % R2D(item[0].nu),
            '%.1f' % item[2][0],
            '%.1f' % item[2][1],
            '' if item[1] is None else '%.4g' % item[1][0],
            '' if item[1] is None else '%.4g' % item[1][1],
            '' if item[1] is None else '%.4g' % item[1][2],
            '' if calcpos is None else '%.3f' % R2D(calcpos.gamma),
            '' if calcpos is None else '%.3f' % R2D(calcpos.omega),
            '' if calcpos is None else '%.3f' % R2D(calcpos.nu),
        ))
    printTable(('pos#', 'γ', 'ω', 'ν', 'I', 'σ(I)', 'h', 'k', 'l', 'γ_calc',
                'ω_calc', 'ν_calc'),
               items,
               session.log.info,
               rjust=True)
Esempio n. 24
0
def ListParams(dev):
    """List all parameters of the device.

    Example:

    >>> ListParams(phi)
    """
    dev = session.getDevice(dev, Device)
    dev.log.info('Device parameters:')
    items = []
    aliasdev = getattr(dev, 'alias', None)
    if aliasdev is not None:
        aliasdev = session.getDevice(aliasdev, Device)
        items.append(
            (dev.name + '.alias', '-> ' + aliasdev.name, '', 'yes', 'string',
             'Aliased device, parameters of that device follow'))
        dev = aliasdev
    devunit = getattr(dev, 'unit', '')
    for name, info in sorted(iteritems(dev.parameters)):
        if not info.userparam:
            continue
        try:
            value = getattr(dev, name)
        except Exception:
            value = '<could not get value>'
        unit = (info.unit or '').replace('main', devunit)
        vstr = repr(value)
        if len(vstr) > 40:
            vstr = vstr[:37] + '...'
        settable = info.settable and 'yes' or 'no'
        name = dev.name + '.' + name
        if isinstance(info.type, type):
            ptype = info.type.__name__
        else:
            ptype = info.type.__doc__ or '?'
        items.append((name, vstr, unit, settable, ptype, info.description))
    printTable(('name', 'value', 'unit', 'r/w?', 'value type', 'description'),
               items, session.log.info)
Esempio n. 25
0
def ListSamples():
    """List all information about defined samples.

    Example:

    >>> ListSamples()

    see also: `NewSample`, `SetSample`, `SelectSample`, `ClearSamples`
    """
    rows = []
    all_cols = set()
    index = {}
    for info in session.experiment.sample.samples.values():
        all_cols.update(info)
    all_cols.discard('name')
    index = {key: i for (i, key) in enumerate(sorted(all_cols), start=2)}
    index['name'] = 1
    for number, info in session.experiment.sample.samples.items():
        rows.append([str(number), info['name']] + [''] * len(all_cols))
        for key in info:
            rows[-1][index[key]] = str(info[key])
    printTable(['number', 'sample name'] + sorted(all_cols), rows,
               session.log.info)
Esempio n. 26
0
def activation(formula=None,
               instrument=None,
               flux=None,
               cdratio=0,
               fastratio=0,
               mass=None,
               exposure=24,
               getdata=False):
    """Calculate sample activation using the FRM II activation web services.

        ``formula``:
           the chemical formula,  see below for possible formats

        The *flux* can be specified either by:

            ``instrument``:
                the instrument name to select flux data

        or:

            ``flux``:
                The thermal flux (for cold instruments use the equivalent
                thermal flux)
            ``cdratio``:
                The ratio between full flux and flux with 1mm Cd in the beam,
                0 to deactivate
            ``fastratio``:
                Thermal/fast neutron ratio, 0 to deactivate

        ``mass``:
            the sample mass in g

        ``exposure``:
            exposure time in h, default 24h

        ``getdata``:
            In addition to printing the result table,
            return a dict with the full results for  further
            processing

        **Formula input format**

        Formula:
              ``CaCO3``

        Formula with fragments:
             ``CaCO3+6H2O``

        Formula with parentheses:
            ``HO ((CH2)2O)6 H``

        Formula with isotope:
            ``CaCO[18]3+6H2O``

        Counts can be integer or decimal:
            ``CaCO3+(3HO1.5)2``

        Mass fractions use %wt, with the final portion adding to 100%:
            ``10%wt Fe // 15% Co // Ni``

        Volume fractions use %vol, with the final portion adding to 100%:
            ``10%vol [email protected] // [email protected]``

            For volume fractions you have to specify the density using
            ``@<density>``!

        Mixtures can nest. The following is a 10% salt solution by weight \
        mixed 20:80 by volume with D2O:
            ``20%vol (10%wt [email protected] // H2O@1) // D2O@1``
    """

    if formula is None:
        try:
            #  preparation for a future enhanced sample class
            formula = session.experiment.sample.formula
        except (ConfigurationError, AttributeError):
            # ConfigurationError is raised if no experiment is in session
            pass
    if formula is None:
        raise UsageError('Please give a formula')
    if flux:
        instrument = 'Manual'
    if instrument is None:
        try:
            instrument = session.instrument.instrument or None
        except ConfigurationError:
            pass
    if instrument is None:
        raise UsageError('Please specifiy an instrument or flux')
    if mass is None:
        try:
            formula = session.experiment.sample.mass
        except (ConfigurationError, AttributeError):
            pass
    if mass is None:
        raise UsageError('Please specify the sample mass')

    qs = '?json=1&formula=%(formula)s&instrument=%(instrument)s&mass=%(mass)g' \
        % locals()
    if flux:
        qs += '&fluence=%(flux)f&cdratio=%(cdratio)f&fastratio=%(fastratio)f' \
            % locals()
    qs = ACTIVATIONURL + qs
    try:
        response = urllib.request.urlopen(qs)
    except urllib.error.HTTPError as e:
        session.log.warning('Error opening: %s', qs)
        session.log.warning(e)
        return None
    data = json.load(response)
    if data['ecode'] == 'unknown instrument' and flux is None:
        session.log.warning(
            'Instrument %s unknown to calculator, '
            'specify flux manually', instrument)
        session.log.info('Known instruments')
        printTable(['instrument'], [(d, ) for d in data['instruments']],
                   session.log.info)

    if data['result']['activation']:
        h = data['result']['activation']['headers']
        th = [h['isotope'], h['daughter'], h['reaction'], h['Thalf_str']]
        for ha in h['activities']:
            th.append(ha)
        rows = []
        for r in data['result']['activation']['rows']:
            rd = [r['isotope'], r['daughter'], r['reaction'], r['Thalf_str']]
            for a in r['activities']:
                rd.append('%.3g' % a if a > 1e-6 else '<1e-6')
            rows.append(rd)
        dr = ['', '', '', 'Dose (uSv/h)']
        for d in data['result']['activation']['doses']:
            dr.append('%.3g' % d)
        rows.append(dr)

        printTable(th, rows, session.log.info)
    else:
        session.log.info('No activation')
    if getdata:
        return data
    return
Esempio n. 27
0
def IndexPeaks(max_deviation=0.2, listname='default'):
    """Index crystal reflections using Indexus.

    Uses the positions and intensities either the default position list, or the
    given name.

    You can also select the maximum deviation of HKL indices from integers
    using the *max_deviation* parameter (default is 0.2).

    After indexing is complete, the calculated HKLs and matrix are shown.
    You can accept the indexing with the `AcceptIndexing()` command.

    Examples:

    >>> IndexPeaks()            # use default position list
    >>> IndexPeaks(0.1)         # use different maximum deviation
    >>> IndexPeaks('other')     # use other position list

    This command will generate input files for Indexus (indexus.txt and
    angles.txt) in the experiment data directory and run Indexus.  Then it will
    read the output files and show the calculated matrix and positions, as well
    as the gamma/nu offsets.

    If you want to manually run Indexus, you can use the generated input files
    as a template.
    """
    if isinstance(max_deviation, str):
        listname = max_deviation
        max_deviation = 0.2
    sample = session.experiment.sample
    wavelength = session.getDevice('wavelength').read()
    lists = dict(sample.poslists)
    if listname not in lists:
        session.log.warning('Position list %r does not exist', listname)
        return
    posl = lists[listname]
    if len(posl) < 2:
        session.log.warning(
            'Cannot calculate: need at least two positions in list')
        return
    params = (
        len(posl),
        1.0,
        1.0,
        max_deviation,
        wavelength,
        sample.bravais,
        sample.a,
        sample.b,
        sample.c,
        sample.alpha,
        sample.beta,
        sample.gamma,
    )

    # write input file for Indexus
    root = session.experiment.samplepath
    with open(path.join(root, 'indexus.txt'), 'w', encoding='utf-8') as fp:
        fp.write('''\
poli                             ! instrument
n                                ! extended output
%d                               ! num of spots
%f %f                            ! delta theta, delta angle
%f                               ! max deviation
%f                               ! wavelength
%s                               ! lattice type
%.4f %.4f %.4f  %.3f %.3f %.3f   ! lattice parameters
.0 .1 -1.0 1.0                   ! offset gamma, step, low and high limits
.0 .1 -1.0 1.0                   ! offset nu, step, low and high limits
''' % params)
    R2D = math.degrees
    with open(path.join(root, 'angles.txt'), 'w', encoding='utf-8') as fp:
        fp.write('   gamma    omega       nu        I     sigI\n')
        for pos, _hkl, (intensity, sigma) in posl:
            fp.write('%8.3f %8.3f %8.3f %8.2f %8.2f\n' % (R2D(
                pos.gamma), R2D(pos.omega), R2D(pos.nu), intensity, sigma))

    session.log.info('Running Indexus...')
    proc = createSubprocess(['indexus'],
                            cwd=root,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)
    output = proc.communicate()[0]
    if 'unable to find solution' in output:
        session.log.warning('Indexus could not find a solution.')
        IndexPeaks._last_result = None
        return

    # read output from Indexus
    p1, p2 = None, None
    table = []
    peaks = []
    dgamma = 0.
    dnu = 0.
    chi2 = 0.
    with open(path.join(root, 'indexus.lis'), 'r', encoding='utf-8') as fp:
        lines = iter(fp)
        for line in lines:
            if line.startswith(' best combination:'):
                parts = line.split()
                p1, p2 = int(parts[2]) - 1, int(parts[4]) - 1
            elif line.startswith(' offset gamma:'):
                dgamma = float(line.split()[2])
            elif line.startswith(' offset nu:'):
                dnu = float(line.split()[2])
            elif line.startswith(' chi2:'):
                chi2 = float(line.split()[1])
            elif line.startswith(' list:'):
                session.log.info('Indexed reflections:')
                for i, line in enumerate(lines):
                    if not line.strip():  # empty line after table
                        break
                    cols = line.strip().strip('*').split()
                    if cols[0] == 'H':  # header
                        continue
                    peaks.append([float(ix) for ix in cols[:3]])
                    table.append([str(i)] + cols)
                break
    printTable(('pos#', 'h', 'k', 'l', 'γ', 'ω', 'ν', 'I', 'σ(I)'),
               table,
               session.log.info,
               rjust=True)

    # calculate UB matrix from "best combination" of two peaks
    or_calc = orient(*sample.cell.cellparams())
    pos1 = posl[p1][0]
    pos2 = posl[p2][0]
    hkl1 = [int(round(ix)) for ix in peaks[p1]]
    hkl2 = [int(round(ix)) for ix in peaks[p2]]
    new_cell = or_calc.Reorient(hkl1, pos1, hkl2, pos2)
    IndexPeaks._last_result = (new_cell.rmat.T, (dgamma, dnu), listname, peaks)
    session.log.info(
        'Using (%.4g %.4g %.4g) and (%.4g %.4g %.4g) to calculate'
        ' UB matrix:', *(tuple(hkl1) + tuple(hkl2)))
    for row in new_cell.rmat.T:  # pylint: disable=not-an-iterable
        session.log.info(' %8.4f %8.4f %8.4f', *row)
    session.log.info('')
    session.log.info('Fit quality χ²: %8.4f', chi2)
    session.log.info('')
    session.log.info('Offsets:')
    session.log.info('  delta gamma = %8.4f   delta nu = %8.4f', dgamma, dnu)
    session.log.info('')
    session.log.info('Use AcceptIndexing() to use this indexing.')