Пример #1
0
def add_header_section(
        report: reporting.Report,
        settings: dict,
        activity_phases: limb.Property
):
    """

    :param report:
    :param settings:
    :param activity_phases:
    :return:
    """

    activity_phases = activity_phases.values()
    support_phases = settings['support_phases']

    activity_phases = ['{}%'.format(round(100 * x)) for x in activity_phases]
    support_phases = ['{}%'.format(round(100 * x)) for x in support_phases]

    report.add_template(
        paths.resource('trial', 'header.html'),
        title=settings.get('name'),
        summary=settings.get('summary'),
        duty_cycle=round(100.0 * settings['duty_cycle']),
        activity_phases=activity_phases,
        support_phases=support_phases,
        date=datetime.utcnow().strftime("%m-%d-%Y %H:%M")
    )
def strides(
        foot_positions: limb.Property,
        times: dict
) -> typing.Dict[str, list]:

    out = dict()

    for key, positions in foot_positions.items():
        distances = []
        out[key + '_strides'] = distances
        last_fixed = None

        for index, time in enumerate(times['cycles']):
            p = positions[index]

            if last_fixed is None:
                # Skip entries in motion until the first fixed position is
                # found
                if p.annotation == 'F':
                    last_fixed = p

                continue

            if p.annotation == 'F' and not last_fixed.compare(p):
                distances.append(events.Event(
                    time=time,
                    index=index,
                    value=last_fixed.distance_between(p)
                ))
                last_fixed = p

    return out
Пример #3
0
def make_cycle_data(
        foot_positions: limb.Property,
        times: dict
) -> limb.Property:
    """
    Trial report pages contain a duty-cycle diagram, which is created using the
    data generated in this method. The returned limb.Property contains a list
    for each limb of the duty cycle regions for that limb where a region is a
    list with elements:
        - 0: The time at which this cycle ends
        - 1: The enumerated annotation for this cycle

    :param foot_positions:
        Simulation results
    :param times:
        Time step information
    """

    gait_cycles = limb.Property().assign([], [], [], [])

    for i in range(times['count']):
        for key in limb.KEYS:
            cycles = gait_cycles.get(key)
            pos = foot_positions.get(key)[i]
            if not cycles or cycles[-1][1] != pos.annotation:
                cycles.append([1, pos.annotation])
            else:
                cycles[-1][0] += 1

    return gait_cycles
Пример #4
0
def unused_foot_prints(
        print_positions: limb.Property,
        foot_positions: limb.Property
):
    """
    Trims the print positions lists to include only those positions found in
    the foot positions lists and the positions just before and after to provide
    context

    :param print_positions:
    :param foot_positions:
    :return:
    """

    was_pruned = False

    def is_in(uid: str, items: list) -> bool:
        for item in items:
            if item.uid == uid:
                return True
        return False

    for limb_key, foot_prints in print_positions.items():

        index = len(foot_prints) - 1
        while index > 0:
            index -= 1
            if is_in(foot_prints[index].uid, foot_positions.get(limb_key)):
                break
            foot_prints.pop()
            was_pruned = True

        while len(foot_prints) > 1:
            if is_in(foot_prints[1].uid, foot_positions.get(limb_key)):
                break
            foot_prints.pop(0)
            was_pruned = True

    return was_pruned
Пример #5
0
def invalid_positions(
        settings: dict,
        time_steps: list,
        foot_positions: limb.Property
):
    """
    Iterates through the time_steps and foot_positions lists and removes values
    at the beginning and end where any invalid data is found. Such invalid data
    exists when some amount of time at the beginning or end of the simulation
    is valid for 1 or more of the limbs in the trackway, but not all 4.

    :param settings:
        Configuration for the simulation trial
    :param time_steps:
        A list of times at which the simulation calculated foot positions
    :param foot_positions:
        The calculated positions of each foot for each time step in the
        time_steps list
    """

    was_culled = False

    start_time = settings.get('start_time', 0)
    end_time = settings.get('end_time', 1e8)

    values = list(foot_positions.values())
    values.append(time_steps)
    index = 0

    while index < len(values[0]):
        entries = []
        for v in values:
            entries.append(v[index])

        cull = (
            None in entries or
            entries[-1] < start_time or
            entries[-1] > end_time
        )

        if cull:
            was_culled = True
            for v in values:
                v[index:index + 1] = []
        else:
            index += 1

    return was_culled
def create_tangents(foot_positions: limb.Property) -> limb.Property:
    """

    :param foot_positions:
    :return:
    """

    def find_next_position(_positions, _i):
        reference_pos = _positions[_i]
        for pos in _positions[(i + 1) :]:
            identical = (
                mstats.value.equivalent(pos.x.raw, reference_pos.x.raw, 0.1),
                mstats.value.equivalent(pos.y.raw, reference_pos.y.raw, 0.1),
            )
            if not identical[0] or not identical[1]:
                return pos
        return None

    tangents = limb.Property().assign([], [], [], [])

    for key in limb.KEYS:
        positions = foot_positions.get(key)

        for i in range(len(positions)):
            p = positions[i]
            next_pos = find_next_position(positions, i)

            if not next_pos or i >= (len(positions) - 1):
                try:
                    tan = tangents.get(key)[-1]
                except IndexError as err:
                    print("Index:", i)
                    print("Positions:", len(positions))
                    print("Next:", next_pos)
                    raise err
            else:
                tan = geometry.LineSegment2D(p, next_pos)
                tan.post_extend_line(4)
                tan.pre_extend_line(4)

            tangents.get(key).append(tan)

    return tangents
Пример #7
0
def write_data(
        path: str,
        settings: dict,
        trackway_definition: trackway.TrackwayDefinition,
        foot_positions: limb.Property,
        times: dict,
        coupling_data: dict,
        advancement_data: dict,
        tangent_data: dict
):
    """
    Writes a JSON serialized data file containing the results of the trial for
    later analysis

    :param path:
    :param settings:
    :param trackway_definition:
    :param foot_positions:
    :param times:
    :param coupling_data:
    :param advancement_data:
    :param tangent_data:
    :return:
    """

    position_data = dict()
    for limb_id, positions in foot_positions.items():
        position_data[limb_id] = [x.to_dict() for x in positions]

    track_data = dict()
    for limb_id, positions in trackway_definition.limb_positions.items():
        track_data[limb_id] = [x.to_dict() for x in positions]

    reporting.write_json_results(path, dict(
        settings=settings,
        times=times,
        foot_positions=position_data,
        track_positions=track_data,
        couplings=coupling.serialize(coupling_data),
        advancement=advancement.serialize(advancement_data),
        tangent=tangent.serialize(tangent_data)
    ))
Пример #8
0
def save_positions_file(trackway_positions: limb.Property, path):
    """
    Saves a limb positions property object to the specified path as a CSV
    file with columns:

        lp_x, lp_dx, lp_y, lp_dy, [lp_assumed], [lp_name], [lp_uid]
        rp_x, rp_dx, rp_y, rp_dy, [rp_assumed], [rp_name], [rp_uid]
        lm_x, lm_dx, lm_y, lm_dy, [lm_assumed], [lm_name], [lm_uid]
        rm_x, rm_dx, rm_y, rm_dy, [rm_assumed], [rm_name], [rm_uid]

    :param trackway_positions:
        The trackway positions to be saved
    :param path:
        The path to the positions file to be saved
    """

    df = []

    for prefix, limb_key in limb.LIMB_KEY_LOOKUP.items():
        for index, position in enumerate(trackway_positions.get(limb_key)):
            while index >= len(df):
                df.append(dict())

            row = df[index]
            row['{}_x'.format(prefix)] = position.x.value
            row['{}_dx'.format(prefix)] = position.x.uncertainty
            row['{}_y'.format(prefix)] = position.y.value
            row['{}_dy'.format(prefix)] = position.y.uncertainty
            row['{}_name'.format(prefix)] = position.name
            row['{}_uid'.format(prefix)] = position.uid
            row['{}_assumed'.format(prefix)] = 'x' if position.assumed else None

    df = pd.DataFrame(df)
    df.to_csv(path)

    return df
Пример #9
0
def make_animation_frame_data(
        foot_positions: limb.Property,
        times: dict,
        coupling_data: dict,
        tangent_data: dict
) -> typing.List[dict]:
    """
    Creates a list of animation frame data from the results, which is used by
    the JavaScript report to animate the feet within the trackway. Each frame
    in the returned list contains:

    - time: The simulation time for the frame
    - positions: An ordered list of position dictionaries for each limb, where
        the order is defined by the limb.KEYS order. Each dictionary contains:
            - x: A list where x[0] is the position and x[1] is the uncertainty
            - y: A list where y[0] is the position and y[1] is the uncertainty
            - f: The enumerated annotation for the position

    :param coupling_data:
    :param tangent_data:
    :param foot_positions:
        The simulation results
    :param times:
        Time step information
    """

    frames = []

    for i in range(times['count']):
        positions = []
        tangents = []
        for key in limb.KEYS:
            pos = foot_positions.get(key)[i]
            tan = tangent_data['tangents'].get(key)[i]
            positions.append({
                'x': [pos.x.value, pos.x.uncertainty, pos.x.raw],
                'y': [pos.y.value, pos.y.uncertainty, pos.y.raw],
                'f': pos.annotation,
                'tx0': [tan.start.x.raw, tan.start.x.uncertainty],
                'ty0': [tan.start.y.raw, tan.start.y.uncertainty],
                'tx1': [tan.end.x.raw, tan.end.x.uncertainty],
                'ty1': [tan.end.y.raw, tan.end.y.uncertainty],
            })

        rear = coupling_data['rear'][i]
        forward = coupling_data['forward'][i]
        midpoint = coupling_data['midpoints'][i]

        rear_box = tangent_data['rear_boxes'][i]
        forward_box = tangent_data['forward_boxes'][i]

        frames.append(dict(
            time=times['cycles'][i],
            support_time=times['support_cycles'][i],
            positions=positions,
            rear_coupler={
                'x': [rear.x.value, rear.x.uncertainty, rear.x.raw],
                'y': [rear.y.value, rear.y.uncertainty, rear.y.raw]
            },
            forward_coupler={
                'x': [forward.x.value, forward.x.uncertainty, forward.x.raw],
                'y': [forward.y.value, forward.y.uncertainty, forward.y.raw]
            },
            midpoint={
                'x': [midpoint.x.value, midpoint.x.uncertainty, midpoint.x.raw],
                'y': [midpoint.y.value, midpoint.y.uncertainty, midpoint.y.raw]
            },
            rear_support_box=[
                { 'x': rear_box[0].x.raw, 'y': rear_box[0].y.raw },
                { 'x': rear_box[1].x.raw, 'y': rear_box[1].y.raw },
                { 'x': rear_box[2].x.raw, 'y': rear_box[2].y.raw },
                { 'x': rear_box[3].x.raw, 'y': rear_box[3].y.raw }
            ],
            forward_support_box=[
                { 'x': forward_box[0].x.raw, 'y': forward_box[0].y.raw },
                { 'x': forward_box[1].x.raw, 'y': forward_box[1].y.raw },
                { 'x': forward_box[2].x.raw, 'y': forward_box[2].y.raw },
                { 'x': forward_box[3].x.raw, 'y': forward_box[3].y.raw }
            ]
        ))

    return frames
Пример #10
0
def load_trackway_positions(
        settings: dict,
        existing: limb.Property = None,
        save_as: str = None
) -> limb.Property:
    """
    Loads the trackway positions for the trial from the information provided in
    the settings object, unless an existing trackway positions object has been
    specified

    :param settings:
        Configuration for the simulation trial
    :param existing:
        Optionally the already loaded trackway positions, which will be cloned
        and returned if present
    :param save_as:
        An optional path where the loaded trackway positions shold be saved
    """

    if existing:
        # Ignore if already specified
        return existing.clone()

    data = settings.get('data')

    if isinstance(data, str):
        # Load from a specified file
        if not data.startswith('/'):
            data = os.path.join(settings['path'], data)
        if not os.path.exists(data):
            system.log(
                """
                [ERROR]: No CSV source data exists at the path:

                    {}
                """.format(data)
            )
            raise FileNotFoundError('No CSV source file found')

        return trackway.load_positions_file(data)

    # Generate from configuration settings
    track_offsets = limb.Property().assign(*data['offsets'])
    out = generate.trackway_positions(
        cycle_count=data['count'],
        step_size=data['step_size'],
        track_offsets=track_offsets,
        lateral_displacement=data['lateral_displacement'],
        positional_uncertainty=data.get('uncertainty')
    )

    if not save_as and data.get('save'):
        save_as = data.get('save')

    if save_as:
        trackway.save_positions_file(
            trackway_positions=out,
            path=os.path.join(settings['directory'], save_as)
        )

    return out
Пример #11
0
def trackway_positions(
        limb_positions: limb.Property,
        drawer,
        positions=None
):
    """

    :param limb_positions:
    :param drawer:
    :param positions:
    :return:
    """

    limb_positions = create_positions(limb_positions, positions)

    bounds = [1e12, 1e12, -1e12, -1e12]
    for positions in limb_positions.values():
        for pos in positions:
            bounds[0] = min(bounds[0], pos.x.raw, pos.x.value)
            bounds[1] = min(bounds[1], pos.y.raw, pos.y.value)
            bounds[2] = max(bounds[2], pos.x.raw, pos.x.value)
            bounds[3] = max(bounds[3], pos.y.raw, pos.y.value)

    scale = 2048.0 / max(
        abs(bounds[2] - bounds[0]),
        abs(bounds[3] - bounds[1])
    )

    drawer.add_style_definition('.left_pes', {
        'fill': svg.SvgWriter.LIMB_COLORS.left_pes,
        'opacity': '0.5'
    })

    drawer.add_style_definition('.right_pes', {
        'fill': svg.SvgWriter.LIMB_COLORS.right_pes,
        'opacity': '0.5'
    })

    drawer.add_style_definition('.left_manus', {
        'fill': svg.SvgWriter.LIMB_COLORS.left_manus,
        'opacity': '0.5'
    })

    drawer.add_style_definition('.right_manus', {
        'fill': svg.SvgWriter.LIMB_COLORS.right_manus,
        'opacity': '0.5'
    })

    for key, positions in limb_positions.items():
        is_pes = key in [limb.LEFT_PES, limb.RIGHT_PES]

        for pos in positions:
            classes = [key, 'track-pos']

            html_data = {
                'x': '{}'.format(pos.x.value),
                'x-unc': '{}'.format(pos.x.uncertainty),
                'y': '{}'.format(pos.y.value),
                'y-unc': '{}'.format(pos.y.uncertainty),
                'color': svg.SvgWriter.LIMB_COLORS.get(key)
            }

            if pos.annotation:
                html_data['annotation'] = pos.annotation
            if pos.uid:
                html_data['uid'] = pos.uid
            if pos.name:
                html_data['name'] = pos.name
            if pos.assumed:
                html_data['assumed'] = '1'
                classes.append('assumed')

            drawer.draw_circle(
                x=scale*pos.x.raw,
                y=-scale*pos.y.raw,
                radius=PES_RADIUS if is_pes else RADIUS,
                classes=classes,
                data=html_data
            )

        color = svg.SvgWriter.LIMB_COLORS.get(key)

        print_style_name = '.{}'.format(key)
        print_style = {
            'fill': color,
            'opacity': '0.5',
            'stroke-width': '{}px'.format(STROKE),
            'stroke': color
        }

        assumed_style_name = '.{}.assumed'.format(key)
        assumed_style = {
            'fill': 'white',
            'opacity': '0.33'
        }

        marker_style_name = '.{}-marker'.format(key)
        marker_style = {
            'fill': 'transparent',
            'stroke-width': '{}px'.format(STROKE),
            'stroke': color
        }

        if not is_pes:
            dash_array = '{},{}'.format(4, 4)
            marker_style['stroke-dasharray'] = '{},{}'.format(8,4)
            print_style['stroke-dasharray'] = dash_array
            assumed_style['stroke-dasharray'] = dash_array

        drawer.add_style_definition(print_style_name, print_style)
        drawer.add_style_definition(assumed_style_name, assumed_style)
        drawer.add_style_definition(marker_style_name, marker_style)

        p0 = positions[0]
        drawer.draw_circle(
            x=scale*p0.x.raw,
            y=-scale*p0.y.raw,
            radius=(PES_RADIUS if is_pes else RADIUS) + 0.5 * STROKE,
            classes=marker_style_name[1:],
            name=key
        )

    return {
        'scale': scale,
        'offset': (0, 0)
    }