Options
All
  • Public
  • Public/Protected
  • All
Menu

Module @pixano/graphics-2d

@pixano/graphics-2d

Set of web components for image and video annotations.

Installation

npm install @pixano/graphics-2d

Include with a module bundler like rollup or webpack using ES6 modules:

// import all 2d elements
import "@pixano/graphics-2d";
// or a specific element
import "@pixano/graphics-2d/lib/pxn-rectangle";

The UMD build is also available on unpkg:

<script src="https://unpkg.com/@pixano/graphics-2d/dist/graphics-2d.min.js"></script>

Example: Rectangle annotation

Example usage:

import { css, html, LitElement} from 'lit-element';
import '@pixano/graphics-2d';

const colors = [
  'blue', 'green', 'purple',
  'yellow', 'pink', 'orange', 'tan'
];

class MyDemoRectangle extends LitElement {

  onCreate(evt) {
    // listening to the create event dispatched
    // by the element to assign a nice color to
    // the new rectangle.
    const newObj = evt.detail;
    newObj.color = colors[this.element.shapes.size % colors.length];
    this.element.mode = 'edit';
  }

  get element() {
    // Utility getter of the element
    return this.shadowRoot.querySelector('pxn-rectangle');
  }

  render() {
    // Render the template with the rectangle element
    // enriched with some buttons to interact with it.
    return html`
    <pxn-rectangle image="image.jpg" @create=${this.onCreate}></pxn-rectangle>
    <div>
        <button @click=${() => this.element.mode = 'create'}>Add</button>
        <button @click=${() => this.element.zoomIn()}>+</button>
        <button @click=${() => this.element.zoomOut()}>-</button>
    </div>`;
  }
}

customElements.define('my-demo-rectangle', MyDemoRectangle);

API

Properties/Attributes

pxn-canvas

Name Type Default Description
image `string null` null
input `string string[]` null
hideLabels boolean false When true, hides the label layer.
color string #f3f3f5 Background color
zoom number 0.95(readonly) Zoom value

pxn-canvas-2d

Note: pxn-canvas-2d inherits from pxn-canvas.

Name Type Default Description
mode InteractionMode* edit Sets the canvas interaction mode. Use none for no interactions at all.
shapes ShapeData**[] [] Sets the canvas shapes to be displayed.
selectedShapeIds string[] [] List of selected shape ids
enableOutsideDrawing boolean false Enable 2d shape drawing outside of image bounds

*InteractionMode depends on the element:

// pxn-rectangle | pxn-polygon | pxn-keypoints
type InteractiveMode =  'edit' | 'create' | 'none';

// pxn-segmentation
type InteractiveMode =  'edit' | 'create' | 'create-brush' | 'none';

// pxn-smart-rectangle
type InteractiveMode =  'edit' | 'create' | 'smart-create' | 'none';


// pxn-smart-segmentation
type InteractiveMode =  'edit' | 'create' | 'create-brush' | 'smart-create' | 'none';

**The 2d shapes have the following format:

// 2d shape
interface ShapeData {
  // unique id
  id: string;
  // geometry of the shape
  geometry: Geometry;
  // optional color to be displayed
  color?: string;
  // category string for smart elements
  // that automatically assign category
  category?: string;
}

// 2d shape generic geometry format
interface Geometry {
  // flatten array of geometry normalized vertices
  // e.g. rectangle: [x_left, y_top, x_right, y_bottom]
  // e.g. polygon: [x1, y1, x2, y2, ...]
  // e.g. multi_polygon: []
  vertices: number[];
  // edges: [[0,1],[0,2]...]
  edges?: [number, number][];
  // edges: [true,false...]
  visibles?: boolean[];
  // geometry type: rectangle | polygon | multi_polygon
  type: GeometryType;
  // dimension
  dim?: number;
  // in case of multi polygon
  // array of array of vertices
  // e.g. multi_polygon: [[x1, y1, x2, y2...], [x'1, y'1, ...]]
  mvertices?: number[][];
}

type GeometryType = 'rectangle' | 'polygon' | 'multi_polygon';

pxn-rectangle

Note: pxn-rectangle inherits from pxn-canvas-2d so all properties in pxn-canvas-2d will be available on pxn-rectangle.

pxn-polygon

Note: pxn-polygon inherits from pxn-canvas-2d so all properties in pxn-canvas-2d will be available on pxn-polygon.

Name Type Default Description
isOpenedPolygon Boolean false Whether to open polygon into polylines

pxn-segmentation

Note: pxn-segmentation inherits from pxn-canvas so all properties in pxn-canvas will be available on pxn-segmentation.

Name Type Default Description
mask `ImageData* null` null
mode `select update create
maskVisuMode `SEMANTIC INSTANCE` SEMANTIC
showroi boolean false Show ROI helper when creating a new mask instance.

*The mask is stored as an ImageData:

interface ImageData {
  // Data contains the ImageData object's pixel data. it is stored as a one-dimensional array in the RGBA order, with integer values between 0 and 255 (inclusive).
  // Here [R, G, B, A] correspond to:
  // R: instance index from 1 to 255 (0 is for background or semantic classes)
  // G: additional instance index if #instances > 255 (often equals to 0)
  // B: class index
  // E.g.: Person class corresponds to idx 2 / Car of idx 3
  // All the pixels of a new person A will have a [1, 0, 2] value
  // All the pixels of a new car B will have a [2, 0, 3] value
  // All the pixels of a new person C will have a [3, 0, 2] value
  data: Uint8ClampedArray;
  height: number;
  width: number;
}

It can be read using the following python script:

import json
import base64
import cv2
import numpy as np

def readb64(uri):
  encoded_data = uri.split(',')[1]
  nparr = np.fromstring(base64.b64decode(encoded_data), np.uint8)
  img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED)
  return img

def writeb64(img):
    retval, buffer = cv2.imencode('.png', img)
    pic_str = base64.b64encode(buffer)
    pic_str = pic_str.decode()
    return pic_str

# assuming you stored the mask in a json file of the following structure
# { annotations: [{mask: "data:image/png;base64,iVBORw0KGgoAA..."}]}
with open(filename, 'r') as f:
  annotations = json.load(f)["annotations"]
for ann in annotations:
  mask = readb64(ann["mask"])
  print(mask.shape)
  # should be (height,width,3)
  # corresponding to [id1, id2, classIdx]
  # id1 and id2 are zeros in case of semantic segmentation

pxn-smart-rectangle

Note: pxn-smart-rectangle inherits from pxn-rectangle so all properties in pxn-rectangle will be available on pxn-smart-rectangle. Its additional interaction mode consists in clicking on a pixel in the image cropping its context of given size and automatically generate the best fitted box in the area. Detector used is SSD mobilenet trained on MsCOCO. Generated boxes are assigned MsCOCO's categories.

Name Type Default Description
scale number 1 Scaling factor from the base ROI used by the detector (256) to crop the image from

pxn-keypoints

Note: pxn-keypoints inherits from pxn-canvas-2d so all properties in pxn-canvas-2d will be available on pxn-keypoints. Additional properties are available: | Name | Type | Default | Description | -------------------- | -------------- | -------- |------------ | graphType | GraphSettings| defaultSkeleton | Skeleton structure

export interface IGraphSettings {
  radius: number;
  // Set list of node colors or empty if all take the shape color
  nodeColors: number[];
  // Set one color for all edges or same as nodes
  edgeColorType: "default" | "node";
  // Set skeleton #keypoints with their names
  vertexNames: string[];
  // Set skeleton links between its vertices
  edges: [number, number][];
  // Display node names during creation and edition
  showVertexName: boolean;
}

const defaultSkeletonSettings = {
  radius: 4,
  nodeColors: [
    0Xe6194b, 0X3cb44b, 0Xffe119, 0X4363d8, 0Xf58231, 0X911eb4,
    0X46f0f0, 0Xf032e6, 0Xbcf60c, 0Xfabebe, 0X008080, 0Xe6beff,
    0X9a6324, 0Xfffac8, 0X800000, 0Xaaffc3, 0X808000, 0Xffd8b1,
    0X000075, 0X808080, 0Xffffff, 0X000000
  ],
  edgeColorType: "node",
  edges: [[0,1], [0,2]],
  vertexNames: ['header', 'RFoot', 'LFoot'],
  showVertexName: true
};

Methods

pxn-canvas

Name Description
zoomIn() => void Zoom in
zoomOut() => void Zoom out
fullScreen() => void Fullscreen

pxn-canvas-2d

Note: pxn-canvas-2d inherits from pxn-canvas.

pxn-rectangle

Note: pxn-rectangle inherits from pxn-canvas-2d so all methods in pxn-canvas-2d will be available on pxn-rectangle.

pxn-polygon

Note: pxn-polygon inherits from pxn-canvas-2d so all methods in pxn-canvas-2d will be available on pxn-polygon.

Name Description
merge() => void Merge selected shapes
split() => void Split selected shape

pxn-segmentation

Note: pxn-segmentation inherits from pxn-canvas so all methods in pxn-canvas will be available on pxn-segmentation.

Name Description
setOpacity() => void Set mask opacity [0,1]
filterLittle(numPixels: number = 10) => void Filter isolated regions containing less than given number of pixels

pxn-smart-rectangle

Note: pxn-smart-rectangle inherits from pxn-rectangle so all methods in pxn-rectangle will be available on pxn-smart-rectangle.

Name Description
roiDown() => void Scale up ROI
roiUp() => void Scale down ROI

Events

pxn-canvas

None

pxn-canvas-2d

Event Name Detail Description
create ShapeDataDetail Fired when a shape has been created.
update ShapeDataIdxDetail Fired when a shapes update has been made.
delete ShapeDataIdxDetail Fired when shapes are deleted. Detail is the list of the deleted shape ids.
selection ShapeDatasDetail Fired when shapes are selected.
mode ModeDetail Fired when user interaction mode changed
interface ShapeDataDetail {
  detail: ShapeData;
}

interface ShapeDatasDetail {
  detail: ShapeData[];
}

interface ShapeDataIdxDetail {
  detail: string[];
}

interface ModeDetail {
  detail: InteractionMode;
}

Shortcuts

pxn-canvas

Key Description
m Darken image
p Brighten image
Ctrl+C Copy in clipboard currently selected shapes/instance
Ctrl+V Create new shapes/instance (with new ids) from the clipboard content
Ctrl+Space Toggle labels (hide / show)
Tab Loop throught the scene shapes/instances

pxn-canvas-2d

Key Description
Escape Unselect shapes
Delete Delete selected shapes

segmentation / controller-mask

Key Description
Escape Unselect instance
Delete Delete selected instance

Index

Variables

Const CONTROL_POINTS

CONTROL_POINTS: { cursor: string; x: number; y: number }[] = [{ cursor: "ns-resize", x: 0.5, y: 0 }, // n{ cursor: "ns-resize", x: 0.5, y: 1 }, // s{ cursor: "ew-resize", x: 1, y: 0.5 }, // e{ cursor: "ew-resize", x: 0, y: 0.5 }, // w{ cursor: "nwse-resize", x: 0, y: 0 }, // nw{ cursor: "nesw-resize", x: 1, y: 0 }, // ne{ cursor: "nesw-resize", x: 0, y: 1 }, // sw{ cursor: "nwse-resize", x: 1, y: 1 } // se]

Const distinctColors

distinctColors: [number, number, number][] = [[230, 25, 75], // 0 red-pink[60, 180, 75], // 1 green[255, 225, 25], // 2 yellow[0, 130, 200], // 3 blue[245, 130, 48], // 4 orange[145, 30, 180], // 5 purple[70, 240, 240], // 6 cyan[240, 50, 230], // 7 pink-purple[210, 245, 60], // 8 yellow-green[250, 190, 190], // 9 light pink[0, 128, 128], // 10 blue green[230, 190, 255], // 11 light purple[170, 110, 40], // 12 brown[255, 250, 200], // 13 light yellow green[128, 0, 0], // 14 dark red[170, 255, 195], // 15 green fluo[128, 128, 0], // 16 dark green-brown[255, 215, 180], // 17 beige[0, 0, 128], // 18 dark blue[128, 128, 128] // 19 grey]

Const style2d

style2d: CSSResult[] = [genericStyles, css`:host {width: 100%;height: 100%;min-width: 100px;position: relative;display: block;}.canvas-container {height: 100%;width: 100%;position: relative;background-repeat: no-repeat;margin: 0px;overflow: hidden;}.corner {-webkit-touch-callout: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;right: 0px;margin-bottom: 10px;margin-left: 10px;margin-right: 10px;display: flex;position: absolute;margin-top: 10px;height: 24px;width: 24px;z-index: 1;color: black;background: white;fill: #79005D;padding: 10px;border-radius: 50%;cursor: pointer;font-size: 18px;-webkit-transition: all 0.5s ease;-moz-transition: all 0.5s ease;-o-transition: all 0.5s ease;-ms-transition: all 0.5s ease;transition: all 0.5s ease;}.corner:hover {background: #79005D;fill: white;color: white;}#snackbar {visibility: hidden;min-width: 250px;margin-left: -125px;background-color: #333;color: #fff;text-align: center;border-radius: 2px;padding: 16px;position: fixed;z-index: 1;left: 50%;bottom: 30px;font-size: 17px;-webkit-touch-callout: none; /* iOS Safari */-webkit-user-select: none; /* Safari */-khtml-user-select: none; /* Konqueror HTML */-moz-user-select: none; /* Old versions of Firefox */-ms-user-select: none; /* Internet Explorer/Edge */user-select: none; /* Non-prefixed version, currentlysupported by Chrome, Opera and Firefox */}#snackbar.show {visibility: visible;-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;animation: fadein 0.5s, fadeout 0.5s 2.5s;}@-webkit-keyframes fadein {from {bottom: 0; opacity: 0;}to {bottom: 30px; opacity: 1;}}@keyframes fadein {from {bottom: 0; opacity: 0;}to {bottom: 30px; opacity: 1;}}@-webkit-keyframes fadeout {from {bottom: 30px; opacity: 1;}to {bottom: 0; opacity: 0;}}@keyframes fadeout {from {bottom: 30px; opacity: 1;}to {bottom: 0; opacity: 0;}}#zoom {right: 70px;font-size: 15px;font-weight: bold;text-align: center;background: #79005D;color: white;display: flex;align-items: center;justify-content: center;}.hidden {opacity: 0;}`]

Const trackColors

trackColors: string[] = ['#ff1100', '#ff867d', '#ffe0de', '#a89594', '#ad514c', '#ad0900', '#610500', '#59302e', '#61504f','#ff9500', '#ffc778', '#ffe0ad', '#b36f00', '#b38d50', '#5e3e0b', '#fffb00', '#8f8d00', '#f2f188','#6fff00', '#ceffa8', '#3c8a00', '#173600', '#5f8544', '#00ffcc', '#00997a', '#004a3b', '#409483','#00b7ff', '#0077a6', '#004b69', '#96e1ff', '#3c7f99', '#002aff', '#001891', '#2a387d', '#7a91ff','#4400ff', '#230085', '#15014d', '#533b96', '#b296ff', '#895eff', '#ff00fb', '#850083', '#360035','#fc95fb', '#8f278e', '#a86da8', '#873587', '#ff0062', '#bf2e66', '#73002c']

Functions

Const arraysMatch

  • arraysMatch(arr1: any[], arr2: any[]): boolean
  • Parameters

    • arr1: any[]
    • arr2: any[]

    Returns boolean

bounds

  • bounds(vertices: number[]): number[]
  • Get normalized bounding box encompassing the shape. Return [xmin,ymin,xmax,ymax]

    Parameters

    • vertices: number[]

    Returns number[]

calcStraightLine

  • calcStraightLine(p1: Point, p2: Point): Point[]
  • Returns all pixels of the straight line between p1 and p2

    Parameters

    • p1: Point

      pixel format : {x:x_value, y:y_value}

    • p2: Point

      pixel format : {x:x_value, y:y_value}

    Returns Point[]

chunk

  • chunk(arr: number[], chunkSize: number): number[][]
  • Parameters

    • arr: number[]
    • chunkSize: number

    Returns number[][]

convertIndexToDict

  • convertIndexToDict(indexes: number[], width: number): Point[]
  • Convert an array of points stored using row order into an array of pixels (pixel format : {x:x_value, y:y_value})

    Parameters

    • indexes: number[]

      an array of points stored row order, indexes[0] => x=0,y=0, indexes[1] => x=1,y=0, ...

    • width: number

      the width of the image

    Returns Point[]

convertShapes

  • convertShapes(tracks: {}, fIdx: number): ShapeData[]
  • Parameters

    • tracks: {}
    • fIdx: number

    Returns ShapeData[]

copyShape

  • Cpoy shape from closest key frames bounding boxes

    Parameters

    • track: TrackData

      the track processed

    • timestamp: number

    Returns ShapeData | null

Const dataToShape

  • Parameters

    Returns Graphic

deleteShape

  • deleteShape(track: TrackData, fIdx: number): boolean
  • Parameters

    Returns boolean

densifyPolygon

  • densifyPolygon(polygon: Point[], ctType?: string): DensePolygon
  • Returns all border pixels from a polygon defined from some vertices (3 vertices for a triangle for example)

    Parameters

    • polygon: Point[]

      an array of vertices with vertex format {x:x_value, y:y_value}

    • Default value ctType: string = "external"

      a string, whether 'external' or 'internal' to indicated the contour type

    Returns DensePolygon

drawDashedPolygon

  • drawDashedPolygon(polygon: Point[], contours: PIXIGraphics): void
  • Draw dashed polygons to contour graphics

    Parameters

    • polygon: Point[]
    • contours: PIXIGraphics

    Returns void

extremaUnion

  • extremaUnion(extrema: number[], extrema2: number[]): number[]
  • Parameters

    • extrema: number[]
    • extrema2: number[]

    Returns number[]

fuseId

  • fuseId(id: [number, number, number]): number
  • Fused id is idx1 + 256idx2 + 256256*cls

    Parameters

    • id: [number, number, number]

    Returns number

generateKey

  • generateKey(): string
  • Key generator

    Returns string

    a random key

getClosestFrames

  • getClosestFrames(track: TrackData, timestamp: number, key?: boolean): number[]
  • Get closest key frames ids (max and min bounds) for a specific frame index

    Parameters

    • track: TrackData

      the track where the search happens

    • timestamp: number

      the frame timestamp

    • Default value key: boolean = true

      if the shape is a key shape (by default true)

    Returns number[]

    Previous (-1 if does not exist) and next (infinity) frame index

getDensePolysExtrema

  • getDensePolysExtrema(densePolygons: DensePolygon[]): number[]
  • Parameters

    Returns number[]

getKeyShape

  • Get key shape at a given frame index

    Parameters

    • track: TrackData

      the track

    • fIdx: number

      the frame index

    Returns ShapeData | undefined

    the key shape if exist, undefined otherwise

getNewTrackId

  • getNewTrackId(tracks: {}): string
  • Parameters

    Returns string

getNumShapes

  • Get length of a track

    Parameters

    Returns number

    the number of shapes for this track

getPixelId

  • getPixelId(data: ImageData, idx: number): [number, number, number]
  • Parameters

    • data: ImageData
    • idx: number

    Returns [number, number, number]

getPolygonExtrema

  • getPolygonExtrema(polygon: Point[]): number[]
  • Parameters

    • polygon: Point[]

    Returns number[]

getPolygons

  • Returns all blobs contours of gmask whose id is equal to targetId. Basically the same function a BlobExtraction but for fixed mask (gmask's one)

    Parameters

    • mask: GraphicMask
    • targetId: [number, number, number]

      the id of the blobs to be found

    • Optional extrema: number[]

      (optional) the box research zone [xMin, yMin, xMax, yMax]

    Returns DensePolygon[]

getShape

  • Get shape at a given frame index

    Parameters

    • track: TrackData

      the track

    • timestamp: number

      the frame index

    Returns ShapeData | null

    the shape if exist, undefined otherwise

Const hexToRgb255

  • hexToRgb255(hex: number): number[]
  • Convert a hex color number into an [R, G, B] array

    Parameters

    • hex: number

      hexadecimal color number

    Returns number[]

insertMidNode

  • insertMidNode(vertices: number[], idx: number): number[]
  • Insert a node into a flatten array of vertices

    Parameters

    • vertices: number[]

      flatten array of vertices

    • idx: number

      index of insertion

    Returns number[]

interpolate

  • interpolate(track: TrackData, timestamp: number): { isLast?: undefined | false | true; shape: ShapeData | undefined }
  • Interpolate shape from closest key frames bounding boxes

    Parameters

    • track: TrackData

      the track processed

    • timestamp: number

    Returns { isLast?: undefined | false | true; shape: ShapeData | undefined }

    • Optional isLast?: undefined | false | true
    • shape: ShapeData | undefined

invertColor

  • invertColor(rgb: string): string
  • Parameters

    • rgb: string

    Returns string

isClockwise

  • isClockwise(polygon: Point[]): boolean
  • Check whether an array of vertices (defining a polygon) is ordered clockwise

    Parameters

    • polygon: Point[]

      an array of vertices with vertex format {x:x_value, y:y_value}

    Returns boolean

    true if the array is ordered clockwise, false otherwise

isIncludedRect

  • isIncludedRect(g: number[], s: number[]): boolean
  • Parameters

    • g: number[]
    • s: number[]

    Returns boolean

isInside

  • isInside(pt: Point, vs: Point[]): boolean
  • Parameters

    • pt: Point
    • vs: Point[]

    Returns boolean

isKeyShape

  • isKeyShape(track: TrackData, fIdx: number): boolean
  • Check if track has a key shape at given frame index

    Parameters

    • track: TrackData

      the track

    • fIdx: number

      the frame index

    Returns boolean

    True if the track ahs a key shpe at this frame

Const isValid

  • isValid(inputVertices: number[]): boolean
  • Check if polygon self intersects TODO: implement Shamos-Hoey (faster)

    Parameters

    • inputVertices: number[]

      flatten array of 2d vertices

    Returns boolean

mergeTracks

  • mergeTracks(tracks: {}, t1Id: string, t2Id: string): { keysIntersection: string[]; trackId: string }
  • Merge two tracks. Do the concatenation of keyshapes if the two tracks do not overlap.

    Parameters

    • tracks: {}

      the set of tracks

    • t1Id: string

      the id of the first track

    • t2Id: string

      the id of the second track

    Returns { keysIntersection: string[]; trackId: string }

    a object containing the id trackId of the merged track and the list keysIntersection of the frames at which the tracks overlaps. If the tracks do not overlap, keysIntersection is empty. If the tracks overlap, an empty string is returned instead of the id of the merged track.

    • keysIntersection: string[]
    • trackId: string

removeOrAddKeyShape

  • removeOrAddKeyShape(t: TrackData, fIdx: number): void
  • Remove keyshape from track

    Parameters

    Returns void

rgb2hex

  • rgb2hex(rgb: number[]): number
  • Convert an [R, G, B] array to hex color number

    Parameters

    • rgb: number[]

      array

    Returns number

Const rgbToHex

  • rgbToHex(r: number, g: number, b: number): number
  • Parameters

    • r: number
    • g: number
    • b: number

    Returns number

setShape

  • Set key shape at a given frame index

    Parameters

    • track: TrackData

      the track

    • fIdx: number

      the frame index

    • shape: ShapeData

      the shape to add

    • Default value key: boolean = true

      if the shape is a key shape (by default true)

    Returns void

sortDictByKey

  • sortDictByKey(dict: {}): {}
  • Sort dictionary key-value by key. Key is a string to cast in int.

    Parameters

    • dict: {}

      object

      • [key: string]: any

    Returns {}

    • [key: string]: any

splitTrack

  • splitTrack(tId: string, fIdx: number, tracks: {}): TrackData
  • Split track into two tracks

    Parameters

    • tId: string
    • fIdx: number
    • tracks: {}

    Returns TrackData

switchTrack

  • switchTrack(tracks: {}, t1Id: string, t2Id: string, fIdx: number): void
  • Switch two tracks at given timestamp.

    Parameters

    • tracks: {}

      tracks to be switched

    • t1Id: string
    • t2Id: string
    • fIdx: number

    Returns void

unfuseId

  • unfuseId(fId: number): [number, number, number]
  • Parameters

    • fId: number

    Returns [number, number, number]

updateDisplayedSelection

  • updateDisplayedSelection(contours: PIXIGraphics, densePolygons: DensePolygon[]): void
  • Parameters

    Returns void

Object literals

Const settings

settings: object

edgeColorType

edgeColorType: "node" = "node"

edges

edges: [number, number][] = [[0, 1], [0, 2]]

nodeColors

nodeColors: number[] = [0Xe6194b, 0X3cb44b, 0Xffe119, 0X4363d8, 0Xf58231, 0X911eb4,0X46f0f0, 0Xf032e6, 0Xbcf60c, 0Xfabebe, 0X008080, 0Xe6beff,0X9a6324, 0Xfffac8, 0X800000, 0Xaaffc3, 0X808000, 0Xffd8b1,0X000075, 0X808080, 0Xffffff, 0X000000]

radius

radius: number = 4

showVertexName

showVertexName: true = true

vertexNames

vertexNames: string[] = ['header', 'RFoot', 'LFoot']