示例#1
0
def propanglesEngine(config):
    '''
	Calculate the propagation angles and distances between elements and a
	set of reflectors.
	'''
    psec = 'propangles'
    msec = 'measurement'

    try:
        # Grab the reflector positions
        rflfile = config.get(psec, 'reflectors')
    except Exception as e:
        err = 'Configuration must specify reflectors in [%s]' % psec
        raise HabisConfigError.fromException(err, e)

    try:
        # Grab the element positions by facet
        eltfiles = config.getlist(psec, 'elements')
        if len(eltfiles) < 1:
            err = 'Key elements must contain at least one entry'
            raise HabisConfigError(err)
    except Exception as e:
        err = 'Configuration must specify elements in [%s]' % psec
        raise HabisConfigError.fromException(err, e)

    try:
        # Determine whether to use surface or center distances
        useradius = not config.get(psec, 'centers', mapper=bool, default=True)
    except Exception as e:
        err = 'Invalid specification of optional centers in [%s]' % psec
        raise HabisConfigError.fromException(err, e)

    if useradius:
        # Grab the reflector radius if necessary
        try:
            radius = config.get(msec, 'radius', mapper=float)
        except Exception as e:
            err = 'Configuration must specify radius in [%s]' % msec
            raise HabisConfigError.fromException(err, e)

    try:
        # Grab the output distance file
        distfile = config.get(psec, 'distances')
    except Exception as e:
        err = 'Configuration must specify distances in [%s]' % psec
        raise HabisConfigError.fromException(err, e)

    try:
        # Grab the output angle file
        angfile = config.get(psec, 'angles')
    except Exception as e:
        err = 'Configuration must specify angles in [%s]' % psec
        raise HabisConfigError.fromException(err, e)

    # Load the element and reflector positions, then compute distances and angles
    elements = [np.loadtxt(efile) for efile in eltfiles]
    nedim = elements[0].shape[1]
    for el in elements[1:]:
        if el.shape[1] != nedim:
            raise ValueError('Dimensionality of all element files must agree')
    # Ignore an optional sound-speed column in the reflector coordinates
    reflectors = np.loadtxt(rflfile)[:, :nedim]
    distances, thetas = wavepaths(elements, reflectors)

    # Concatenate the distance and angle lists
    distances = np.concatenate(distances, axis=0)
    thetas = np.concatenate(thetas, axis=0)

    # Offset distances by radius, if desired
    if useradius: distances -= radius

    # Save the outputs
    np.savetxt(distfile, distances, fmt='%16.8f')
    np.savetxt(angfile, thetas, fmt='%16.8f')
示例#2
0
def wavesfcEngine(config):
    '''
	Use positions of elements and targets specified in the provided config,
	combined with a specified sound speed (to override per-reflector sound
	speeds), to compute control points that define a reflector surface.
	'''
    msection = 'measurement'
    wsection = 'wavesfc'
    try:
        # Try to grab the input and output files
        eltfiles = matchfiles(config.getlist(wsection, 'elements'))
        rflfile = config.get(wsection, 'reflectors')
        outfile = config.get(wsection, 'output')
    except Exception as e:
        err = 'Configuration must specify elements, reflectors and output in [%s]' % wsection
        raise HabisConfigError.fromException(err, e)

    try:
        # Try to read the input time files
        timefiles = matchfiles(config.getlist(wsection, 'timefile'))
    except Exception as e:
        err = 'Configuration must specify timefile in [%s]' % wsection
        raise HabisConfigError.fromException(err, e)

    try:
        # Grab the outlier range and group size
        olrange = config.get(wsection, 'olrange', mapper=float, default=None)
        olgroup = config.get(wsection, 'olgroup', mapper=int, default=64)
    except Exception as e:
        err = 'Invalid specification of optionals outrange or outgroup in [%s]' % wsection
        raise HabisConfigError.fromException(err, e)

    try:
        # Grab the sound speed
        c = config.get(msection, 'c', mapper=float)
        rad = config.get(msection, 'radius', mapper=float)
    except Exception as e:
        err = 'Configuration must specify c and radius in [%s]' % msection
        raise HabisConfigError.fromException(err, e)

    try:
        # Use the reflector radius for missing arrival-time values
        usemiss = config.get(wsection, 'usemiss', mapper=bool, default=False)
    except Exception as e:
        err = 'Invalid specification of optional usemiss in [%s]' % wsection
        raise HabisConfigError.fromException(err, e)

    try:
        # Use the reflector radius if it is larger than time-imputed radius
        maxrad = config.get(wsection, 'maxrad', mapper=bool, default=False)
    except Exception as e:
        err = 'Invalid specification of optional maxrad in [%s]' % wsection
        raise HabisConfigError.fromException(err, e)

    # Read the element positions and backscatter arrival times
    elements = loadmatlist(eltfiles, nkeys=1)
    times = {
        k[0]: v
        for k, v in loadmatlist(timefiles, scalar=False, nkeys=2).items()
        if k[0] == k[1]
    }

    reflectors = np.loadtxt(rflfile, ndmin=2)
    nrefl, nrdim = reflectors.shape

    # Make sure the reflector specification includes speed and radius
    if nrdim == 3:
        reflectors = np.concatenate([reflectors, [[c, rad]] * nrefl], axis=1)
    elif nrdim == 4:
        reflectors = np.concatenate([reflectors, [[rad]] * nrefl], axis=1)
    elif nrdim != 5:
        raise ValueError('Reflector file must contain 3, 4 or 5 columns')

    # Split the file name and extension
    fbase, fext = os.path.splitext(outfile)

    # Add a target specifier to outputs for multiple targets
    nrefl = len(reflectors)
    if nrefl == 1: idfmt = ''
    else: idfmt = '.Target{{0:0{width}d}}'.format(width=cutil.numdigits(nrefl))

    for ridx, refl in enumerate(reflectors):
        pos = refl[:3]
        lc, rad = refl[3:]

        # Convert all times to distances
        pdists = {}
        for el, elpos in elements.items():
            # Ray from reflector center to element
            dl = elpos - pos
            # Find distance to element and normalize ray
            ll = norm(dl)
            dl /= ll

            try:
                # Convert arrival time to distance from center
                ds = ll - times[el][ridx] * lc / 2.0
            except (KeyError, IndexError, TypeError):
                # Use default radius or skip missing value
                if usemiss: ds = rad
                else: continue
            else:
                if maxrad: ds = max(rad, ds)

            # Valid points will be inside segment or opposite reflector
            if ds <= ll:
                # Record distance and ray
                pdists[el] = (ds, dl)

        if olrange is not None:
            # Group distances according to olgroup
            pgrps = defaultdict(dict)
            for el, (ds, _) in pdists.items():
                pgrps[int(el / olgroup)][el] = ds
            # Filter outliers from each group and flatten map
            pdists = {
                el: pdists[el]
                for pg in pgrps.values()
                for el in stats.mask_outliers(pg, olrange)
            }

        # Sort remaining values, separate indices and control points
        cpel = sorted(pdists.keys())
        cpts = np.array([pos + pdists[el][0] * pdists[el][1] for el in cpel])

        fname = fbase + idfmt.format(ridx) + fext

        # Project control points for tesselation
        zmin = np.min(cpts[:, -1])
        zmax = np.max(cpts[:, -1])

        if np.allclose(zmin, zmax):
            # For flat structures, just use x-y
            tris = Delaunay(cpts[:, :2])
        else:
            # Otherwise, project radially
            zdiff = zmax - zmin
            prctr = np.mean(cpts, axis=0)
            prctr[-1] = zmax + zdiff
            prref = np.array(prctr)
            prref[-1] = zmin

            l = cpts - prctr[np.newaxis, :]
            d = (prref - prctr)[-1] / l[:, -1]

            if np.any(np.isinf(d)) or np.any(np.isnan(d)):
                raise ValueError('Failure in projection for tesselation')

            ppts = prctr[np.newaxis, :] + d[:, np.newaxis] * l
            tris = Delaunay(ppts[:, :2])

        # Save the control points and triangles
        np.savez(fname, nodes=cpts, triangles=tris.simplices, elements=cpel)
示例#3
0
 def _throw(e, msg, sec=tsec):
     raise HabisConfigError.fromException(f'{msg} in section [{sec}]', e)
示例#4
0
    parser.add_argument('slowness',
                        type=str,
                        help='Numpy npy file containing the slowness image')
    parser.add_argument(
        'output',
        type=str,
        help='Integral output, keymap or binary matrix (with -c)')

    args = parser.parse_args(sys.argv[1:])

    # Load the tracer configuration
    try:
        config = HabisConfigParser(args.tracer)
    except Exception as e:
        err = f'Unable to load configuration {args.tracer}'
        raise HabisConfigError.fromException(err, e)

    tracer = PathTracer.fromconf(config)

    # Load the element coordinates and target list
    elements = loadkeymat(args.elements)

    if args.trlist:
        # Load the r-[t] keymap and flatten to a trlist
        targets = loadkeymat(args.targets)
        targets = [(t, r) for r, tl in targets.items() for t in tl]
    else:
        targets = mio.readbmat(args.targets)

    s = np.load(args.slowness)
示例#5
0
def exdelayEngine(config):
    '''
	Use positions of elements and reflectors specified in the provided
	config, combined with a specified sound speed and reflector radius, to
	estimate the round-trip arrival times from every element to the
	reflector and back.
	'''
    msection = 'measurement'
    esection = 'exdelays'
    try:
        # Try to grab the input and output files
        eltfiles = config.getlist(esection, 'elements')
        rflfile = config.get(esection, 'reflectors')
        timefile = config.get(esection, 'timefile')
    except Exception as e:
        err = 'Configuration must specify elements, reflectors and timefile in [%s]' % esection
        raise HabisConfigError.fromException(err, e)

    # Grab the sound speed and reflector radius
    try:
        c = config.get(msection, 'c', mapper=float)
        r = config.get(msection, 'radius', mapper=float)
    except Exception as e:
        err = 'Configuration must specify c and radius in [%s]' % msection
        raise HabisConfigError.fromException(err, e)

    try:
        # Read an optional global time offset
        offset = config.get(esection, 'offset', mapper=float, default=0.)
    except Exception as e:
        err = 'Invalid optional offset in [%s]' % esection
        raise HabisConfigError.fromException(err, e)

    # Read the element and reflector positions
    eltspos = dict(kp for efile in eltfiles
                   for kp in loadkeymat(efile).items())
    reflpos = np.loadtxt(rflfile, ndmin=2)
    nrefl, nrdim = reflpos.shape

    times = {}
    for elt, epos in eltspos.items():
        nedim = len(epos)
        if not nedim <= nrdim <= nedim + 2:
            raise ValueError(
                'Incompatible reflector and element dimensionalities')
        # Determine one-way distances between element and reflector centers
        dx = norm(epos[np.newaxis, :] - reflpos[:, :nedim], axis=-1)
        # Use encoded wave speed if possible, otherwise use global speed
        try:
            lc = reflpos[:, nedim]
        except IndexError:
            lc = c
        # Use encoded radius if possible, otherwise use global radius
        try:
            lr = reflpos[:, nedim + 1]
        except IndexError:
            lr = r
        # Convert distances to round-trip arrival times
        times[elt, elt] = 2 * (dx - lr) / lc + offset

    # Save the estimated arrival times
    savez_keymat(timefile, times)
示例#6
0
 def _throw(msg, e, sec=None):
     if not sec: sec = asec
     raise HabisConfigError.fromException(f'{msg} in [{sec}]', e)
示例#7
0
def atimesEngine(config):
    '''
	Use habis.trilateration.ArrivalTimeFinder to determine a set of
	round-trip arrival times from a set of one-to-many multipath arrival
	times. Multipath arrival times are computed as the maximum of
	cross-correlation with a reference pulse, plus some constant offset.
	'''
    asec = 'atimes'
    msec = 'measurement'
    ssec = 'sampling'

    kwargs = {}

    def _throw(msg, e, sec=None):
        if not sec: sec = asec
        raise HabisConfigError.fromException(f'{msg} in [{sec}]', e)

    try:
        # Read all target input lists
        targets = sorted(k for k in config.options(asec)
                         if k.startswith('target'))
        targetfiles = OrderedDict()
        for target in targets:
            targetfiles[target] = matchfiles(config.getlist(asec, target))
            if len(targetfiles[target]) < 1:
                raise HabisConfigError(f'Key {target} matches no inputs')
    except Exception as e:
        _throw('Configuration must specify at least one unique "target" key',
               e)

    try:
        efiles = config.getlist(asec, 'elements', default=None)
        if efiles:
            efiles = matchfiles(efiles)
            kwargs['elements'] = loadmatlist(efiles, nkeys=1)
    except Exception as e:
        _throw('Invalid optional elements', e)

    # Grab the reference file
    try:
        reffile = config.get(msec, 'reference', default=None)
    except Exception as e:
        _throw('Invalid optional reference', e, msec)

    # Grab the output file
    try:
        outfile = config.get(asec, 'outfile')
    except Exception as e:
        _throw('Configuration must specify outfile', e)

    try:
        # Grab the number of processes to use (optional)
        nproc = config.get('general',
                           'nproc',
                           mapper=int,
                           failfunc=process.preferred_process_count)
    except Exception as e:
        _throw('Invalid optional nproc', e, 'general')

    try:
        # Determine the sampling period and a global temporal offset
        dt = config.get(ssec, 'period', mapper=float)
        t0 = config.get(ssec, 'offset', mapper=float)
    except Exception as e:
        _throw('Configuration must specify period and offset', e, ssec)

    # Override the number of samples in WaveformMap
    try:
        kwargs['nsamp'] = config.get(ssec, 'nsamp', mapper=int)
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional nsamp', e, ssec)

    # Determine the oversampling rate to use when cross-correlating
    try:
        osamp = config.get(ssec, 'osamp', mapper=int, default=1)
    except Exception as e:
        _throw('Invalid optional osamp', e, ssec)

    try:
        neighbors = config.get(asec, 'neighbors', default=None)
        if neighbors:
            kwargs['neighbors'] = loadkeymat(neighbors, dtype=int)
    except Exception as e:
        _throw('Invalid optional neighbors', e)

    # Determine the range of elements to use; default to all (as None)
    try:
        kwargs['minsnr'] = config.getlist(asec, 'minsnr', mapper=int)
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional minsnr', e)

    # Determine a temporal window to apply before finding delays
    try:
        kwargs['window'] = config.get(asec, 'window')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional window', e)

    # Determine an energy leakage threshold
    try:
        kwargs['eleak'] = config.get(asec, 'eleak', mapper=float)
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional eleak', e)

    # Determine a temporal window to apply before finding delays
    try:
        kwargs['bandpass'] = config.get(asec, 'bandpass')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional bandpass', e)

    # Determine peak-selection criteria
    try:
        kwargs['peaks'] = config.get(asec, 'peaks')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional peaks', e)

    # Determine IMER criteria
    try:
        kwargs['imer'] = config.get(asec, 'imer')
    except HabisNoOptionError:
        pass
    except Exception as e:
        _throw('Invalid optional imer', e)

    maskoutliers = config.get(asec, 'maskoutliers', mapper=bool, default=False)
    optimize = config.get(asec, 'optimize', mapper=bool, default=False)
    kwargs['negcorr'] = config.get(asec, 'negcorr', mapper=bool, default=False)
    kwargs['signsquare'] = config.get(asec,
                                      'signsquare',
                                      mapper=bool,
                                      default=False)
    kwargs['flipref'] = config.get(asec, 'flipref', mapper=bool, default=False)

    # Check for delay cache specifications as boolean or file suffix
    cachedelay = config.get(asec, 'cachedelay', default=True)
    if isinstance(cachedelay, bool) and cachedelay: cachedelay = 'delays.npz'

    try:
        # Remove the nearmap file key
        guesses = shsplit(kwargs['peaks'].pop('nearmap'))
        guesses = loadmatlist(guesses, nkeys=2, scalar=False)
    except IOError as e:
        guesses = None
        print(f'WARNING - Ignoring nearmap: {e}', file=sys.stderr)
    except (KeyError, TypeError, AttributeError):
        guesses = None
    else:
        # Adjust delay time scales
        guesses = {k: (v - t0) / dt for k, v in guesses.items()}

    # Adjust the delay time scales for the neardefault, if provided
    try:
        v = kwargs['peaks']['neardefault']
    except KeyError:
        pass
    else:
        kwargs['peaks']['neardefault'] = (v - t0) / dt

    try:
        # Load the window map, if provided
        winmap = shsplit(kwargs['window'].pop('map'))
        winmap = loadmatlist(winmap, nkeys=2, scalar=False)
    except IOError as e:
        winmap = None
        print(f'WARNING - Ignoring window map: {e}', file=sys.stderr)
    except (KeyError, TypeError, AttributeError):
        winmap = None
    else:
        # Replace the map argument with the loaded array
        kwargs['window']['map'] = winmap

    times = OrderedDict()

    # Process each target in turn
    for i, (target, datafiles) in enumerate(targetfiles.items()):
        if guesses:
            # Pull the column of the nearmap for this target
            nearmap = {k: v[i] for k, v in guesses.items()}
            kwargs['peaks']['nearmap'] = nearmap

        if cachedelay:
            delayfiles = buildpaths(datafiles, extension=cachedelay)
        else:
            delayfiles = [None] * len(datafiles)

        times[target] = dict()

        dltype = 'IMER' if kwargs.get('imer', None) else 'cross-correlation'
        ftext = 'files' if len(datafiles) != 1 else 'file'
        print(
            f'Finding {dltype} delays for {target} ({len(datafiles)} {ftext})')

        for (dfile, dlayfile) in zip(datafiles, delayfiles):
            kwargs['cachefile'] = dlayfile

            delays = finddelays(nproc, dfile, reffile, osamp, **kwargs)

            # Note the receive channels in this data file
            lrx = set(k[1] for k in delays.keys())

            # Convert delays to arrival times
            delays = {k: v * dt + t0 for k, v in delays.items()}

            if any(dv < 0 for dv in delays.values()):
                raise ValueError('Non-physical, negative delays exist')

            if maskoutliers:
                # Remove outlying values from the delay dictionary
                delays = stats.mask_outliers(delays)

            if optimize:
                # Prepare the arrival-time finder
                atf = trilateration.ArrivalTimeFinder(delays)
                # Compute the optimized times for this data file
                optimes = {(k, k): v for k, v in atf.lsmr() if k in lrx}
            else:
                # Just pass through the desired times
                optimes = delays

            times[target].update(optimes)

    # Build the combined times list
    for tmap in times.values():
        try:
            rxset.intersection_update(tmap.keys())
        except NameError:
            rxset = set(tmap.keys())

    if not len(rxset):
        raise ValueError(
            'Different targets have no common receive-channel indices')

    # Cast to Python float to avoid numpy dependencies in pickled output
    ctimes = {i: [float(t[i]) for t in times.values()] for i in sorted(rxset)}

    # Save the output as a pickled map
    savez_keymat(outfile, ctimes)
示例#8
0
 def _throw(msg, e):
     errmsg = msg + ' in [' + tsec + ']'
     raise HabisConfigError.fromException(errmsg, e)
示例#9
0
 def _throw(msg, e, sec=None):
     if not sec: sec = tsec
     raise HabisConfigError.fromException(msg + ' in [%s]' % (sec, ), e)
示例#10
0
 def _throw(msg, e, sec=tsec):
     raise HabisConfigError.fromException(msg + ' in [%s]' % (sec, ), e)