Source code for shapedata.io

# functions adopted from menpo.io.input.landmark.py

import numpy as np
import json
import itertools
from collections import OrderedDict


def _ljson_parse_null_values(points_list):
    filtered_points = [np.nan if x is None else x
                       for x in itertools.chain(*points_list)]
    return np.array(filtered_points,
                    dtype=np.float).reshape([-1, len(points_list[0])])


def _parse_ljson_v1(lms_dict):
    all_points = []
    labels = []  # label per group
    labels_slices = []  # slices into the full pointcloud per label
    offset = 0
    connectivity = []
    for group in lms_dict['groups']:
        lms = group['landmarks']
        labels.append(group['label'])
        labels_slices.append(slice(offset, len(lms) + offset))
        # Create the connectivity if it exists
        conn = group.get('connectivity', [])
        if conn:
            # Offset relative connectivity according to the current index
            conn = offset + np.asarray(conn)
            connectivity += conn.tolist()
        for p in lms:
            all_points.append(p['point'])
        offset += len(lms)

    # Don't create a PointUndirectedGraph with no connectivity
    points = _ljson_parse_null_values(all_points)
    return points


def _parse_ljson_v2(lms_dict):

    points = _ljson_parse_null_values(lms_dict['landmarks']['points'])

    return points


_ljson_parser_for_version = {
    1: _parse_ljson_v1,
    2: _parse_ljson_v2
}


[docs]def ljson_importer(filepath): """ Importer for the Menpo JSON format. This is an n-dimensional landmark type for both images and meshes that encodes semantic labels in the format. Landmark set label: JSON Landmark labels: decided by file Parameters ---------- filepath : str Absolute filepath of the file. Returns ------- np.ndarray loaded landmarks """ with open(str(filepath), 'r') as f: # lms_dict is now a dict rep of the JSON lms_dict = json.load(f, object_pairs_hook=OrderedDict) v = lms_dict.get('version') parser = _ljson_parser_for_version.get(v) if parser is None: raise ValueError("{} has unknown version {} must be " "1, or 2".format(filepath, v)) return parser(lms_dict)
[docs]def pts_importer(filepath, image_origin=True, z=False, **kwargs): """ Importer for the PTS file format. Assumes version 1 of the format. Implementations of this class should override the :meth:`_build_points` which determines the ordering of axes. For example, for images, the `x` and `y` axes are flipped such that the first axis is `y` (height in the image domain). Note that PTS has a very loose format definition. Here we make the assumption (as is common) that PTS landmarks are 1-based. That is, landmarks on a 480x480 image are in the range [1-480]. As Menpo is consistently 0-based, we *subtract 1* off each landmark value automatically. If you want to use PTS landmarks that are 0-based, you will have to manually add one back on to landmarks post importing. Landmark set label: PTS Parameters ---------- filepath : str Absolute filepath of the file. image_origin : `bool`, optional If ``True``, assume that the landmarks exist within an image and thus the origin is the image origin. **kwargs : `dict`, optional Any other keyword arguments. Returns ------- np.ndarray imported points """ with open(filepath, 'r') as f: lines = [l.strip() for l in f.readlines()] line = lines[0] while not line.startswith('{'): line = lines.pop(0) if not z: xs = [] ys = [] for line in lines: if not line.strip().startswith('}'): xpos, ypos = line.split()[:2] xs.append(xpos) ys.append(ypos) xs = np.array(xs, dtype=np.float).reshape((-1, 1)) ys = np.array(ys, dtype=np.float).reshape((-1, 1)) # PTS landmarks are 1-based, need to convert to 0-based (subtract 1) if image_origin: points = np.hstack([ys - 1, xs - 1]) else: points = np.hstack([xs - 1, ys - 1]) else: xs = [] ys = [] zs = [] for line in lines: if not line.strip().startswith('}'): xpos, ypos, zpos = line.split()[:3] xs.append(xpos) ys.append(ypos) zs.append(zpos) xs = np.array(xs, dtype=np.float).reshape((-1, 1)) ys = np.array(ys, dtype=np.float).reshape((-1, 1)) zs = np.array(zs, dtype=np.float).reshape((-1, 1)) # PTS landmarks are 1-based, need to convert to 0-based (subtract 1) if image_origin: points = np.hstack([zs -1, ys - 1, xs - 1]) else: points = np.hstack([xs - 1, ys - 1, zs-1]) return points
# adopted from menpo.io.output.landmark.py
[docs]def ljson_exporter(lmk_points, filepath, **kwargs): """ Given a file handle to write in to (which should act like a Python `file` object), write out the landmark data. No value is returned. Writes out the LJSON format which is a verbose format that closely resembles the labelled point graph format. It describes semantic labels and connectivity between labels. The first axis of the format represents the image y-axis and is consistent with ordering within Menpo. Parameters ---------- lmk_points : np.ndarray The shape to write out. filepath : str The file to write in to """ lmk_points[np.isnan(lmk_points)] = None lmk_points = [list(_tmp) for _tmp in lmk_points] ljson = { 'version': 2, 'labels': [], 'landmarks': { 'points': lmk_points } } with open(filepath, "w") as file_handle: return json.dump(ljson, file_handle, indent=4, separators=(',', ': '), sort_keys=True, allow_nan=False, ensure_ascii=False)
[docs]def pts_exporter(pts, file_handle, **kwargs): """ Given a file handle to write in to (which should act like a Python `file` object), write out the landmark data. No value is returned. Writes out the PTS format which is a very simple format that does not contain any semantic labels. We assume that the PTS format has been created using Matlab and so use 1-based indexing and put the image x-axis as the first coordinate (which is the second axis within Menpo). Note that the PTS file format is only powerful enough to represent a basic pointcloud. Any further specialization is lost. Parameters ---------- pts : np.ndarray points to save file_handle : `file`-like object The file to write in to """ # Swap the x and y axis and add 1 to undo our processing # We are assuming (as on import) that the landmark file was created using # Matlab which is 1 based if len(pts.shape) == 2: pts = pts[:, [1, 0]] + 1 else: pts = pts[:, [2, 1, 0]] + 1 header = 'version: 1\nn_points: {}\n{{'.format(pts.shape[0]) np.savetxt(file_handle, pts, delimiter=' ', header=header, footer='}', fmt='%.3f', comments='')