Skip to content

시계열 어노테이터 스키마

Raw
용어의미예시
트랙 (track)하나의 차트에 함께 표시되는 채널들의 그룹. 서로 다른 센서의 채널을 자유롭게 조합할 수 있다.GPS 고도 + 기압 고도 + EKF 고도를 하나의 차트에 표시
채널 (channel)하나의 값 배열을 가진 개별 데이터 시리즈. timestamps와 동일한 길이의 숫자 배열이다.battery_status__voltage_v
채널 메타 (channelMeta)채널의 표시 정보 (이름, 단위, 색상, 차트 타입).{ name: "전압", unit: "V", color: "#ab47bc", chartType: "line" }

project.category
time_series_annotation
project.annotation_types[]설명
time_range시간 범위 어노테이션

라벨링 결과는 공통 어노테이터 스키마annotatorData 최상위 구조를 그대로 따릅니다.

다른 어노테이터와 다른 점은 AssetId(에셋 키) 입니다. 이미지는 image_1, 오디오는 audio_1 같은 논리적 키를 쓰지만, 시계열은 task.data_unit.files에서 is_primary: true인 파일의 키를 그대로 사용합니다 (예: data_1).

최상위 키시계열에서의 사용
extra트랙 순서·숨김 상태 저장 (아래 시계열 전용 extra 참고)
annotationstime_range 어노테이션의 메타 정보
annotationsData각 어노테이션의 시간 구간 (section)
relations사용하지 않음 (항상 빈 배열)
annotationGroups조건부 사용 — 아래 설명 참고
assignmentId작업 식별자 (숫자)

relations — 시계열 어노테이터에는 관계(relation) 도구도, 관계 패널도 없습니다. 최상위 키는 공통 스키마 호환을 위해 유지되지만 항상 빈 배열입니다.

annotationGroups — 시계열 어노테이터도 공통 어노테이션 그룹 패널을 사용합니다. 프로젝트 분류 스키마에 annotationGroup이 정의된 경우에만 그룹 추가 탭이 노출되며, 이때 생성된 그룹은 공통 스키마의 AnnotationGroupItem 형태로 저장됩니다. 정의되지 않았다면 빈 배열로 유지됩니다.

각 키는 작업 로드 시 { "<AssetId>": [] } 형태로 초기화되므로, 어노테이션이 하나도 없어도 키 자체는 존재합니다.

필드타입설명
idstring어노테이션 고유 ID
tool"time_range"시계열은 항상 time_range
isLockedboolean편집 잠금 여부 (생성 시 false)
isVisibleboolean화면 표시 여부 (생성 시 true)
isValidboolean유효성 여부 (생성 시 false, 분류 입력 후 검증 결과로 갱신)
isDrawCompletedboolean그리기 완료 여부. 시계열은 구간 생성 즉시 완료되므로 항상 true
classificationClassification | null분류 정보 (관리자 정의 스키마를 flatten한 key-value)
labelobject[]classification에서 파생된 표시용 라벨 (아래 참고)
{
  "id": "Yx8pQ2mLk3",
  "section": {
    "start": 1613693139891,
    "end": 1613693157891
  },
  "excludedTracks": ["visual-odometry-latency"]
}
필드타입필수설명
idstringOannotations[].id와 1:1 매칭
section.startnumberO구간 시작 시각
section.endnumberO구간 종료 시각
excludedTracksstring[]X이 구간을 적용하지 않을 트랙 ID 목록 (미지정 시 전체 트랙 적용)

시간 구간 어노테이션은 기본적으로 모든 트랙에 걸쳐 표시됩니다. 특정 트랙에서만 “이 구간은 해당 없음”으로 처리하려면 해당 트랙 ID를 excludedTracks에 넣습니다.

  • 빈 배열이거나 필드가 없으면 전체 트랙에 적용됩니다.
  • 모든 트랙을 제외할 수는 없습니다 (최소 1개 트랙은 포함).
  • 현재 트랙 목록에 없는 ID는 편집 시 무시됩니다.

트랙 ID는 데이터 유닛 구성 방식에 따라 다릅니다.

데이터 유닛 구성트랙 ID
통합 JSONtracks[].id 값을 그대로 사용 (예: altitude-estimate)
CSV + 메타 JSONview.subplots 순서대로 자동 생성track-0, track-1, …

extra[AssetId]에는 작업자의 트랙 레이아웃 상태가 저장되어 세션 간 유지됩니다.

타입설명
trackOrderstring[]작업자가 재정렬한 트랙 ID 순서
hiddenTracksstring[]숨김 처리한 트랙 ID 목록

PX4 비행 로그(통합 JSON, origin: "absolute" → epoch_ms)를 라벨링한 결과 예시입니다.

label은 분류 스키마에서 파생되는 값이므로, 아래 샘플은 클래스 6종에 representativeCodes: ["class"]가 정의되고 vibration_anomaly에만 하위 속성(severity / axis)이 붙은 프로젝트를 가정한 결과입니다 (label 필드 설명 참고).

{
  "assignmentId": 12345,
  "extra": {
    "data_1": {
      "trackOrder": [
        "altitude-estimate",
        "actuator-controls",
        "motor-outputs",
        "roll-angle",
        "roll-angular-rate",
        "pitch-angle",
        "pitch-angular-rate",
        "yaw-angle",
        "yaw-angular-rate",
        "raw-acceleration",
        "vibration-metrics",
        "local-position-x",
        "local-position-y",
        "local-position-z",
        "velocity",
        "visual-odometry-latency"
      ],
      "hiddenTracks": ["visual-odometry-latency"]
    }
  },
  "annotations": {
    "data_1": [
      {
        "id": "Yx8pQ2mLk3",
        "tool": "time_range",
        "isLocked": false,
        "isVisible": true,
        "isValid": true,
        "isDrawCompleted": true,
        "classification": {
          "class": "takeoff"
        },
        "label": [
          { "code": "class", "text": "", "color": "#00B894", "display": { "ko": "이륙구간", "en": "takeoff" }, "widget": "single_choice", "isRepresentative": true, "customFields": null },
          { "code": "class", "text": "이륙구간", "color": "#00B894", "display": { "ko": "이륙구간", "en": "takeoff" }, "widget": "single_choice", "isRepresentative": true, "customFields": null }
        ]
      },
      {
        "id": "Bn4vR7tWq1",
        "tool": "time_range",
        "isLocked": false,
        "isVisible": true,
        "isValid": true,
        "isDrawCompleted": true,
        "classification": {
          "class": "hover"
        },
        "label": [
          { "code": "class", "text": "", "color": "#8B5CF6", "display": { "ko": "호버링구간", "en": "hover" }, "widget": "single_choice", "isRepresentative": true, "customFields": null },
          { "code": "class", "text": "호버링구간", "color": "#8B5CF6", "display": { "ko": "호버링구간", "en": "hover" }, "widget": "single_choice", "isRepresentative": true, "customFields": null }
        ]
      },
      {
        "id": "Hs9cE2yZd6",
        "tool": "time_range",
        "isLocked": false,
        "isVisible": true,
        "isValid": true,
        "isDrawCompleted": true,
        "classification": {
          "class": "cruise"
        },
        "label": [
          { "code": "class", "text": "", "color": "#3B82F6", "display": { "ko": "순항구간", "en": "cruise" }, "widget": "single_choice", "isRepresentative": true, "customFields": null },
          { "code": "class", "text": "순항구간", "color": "#3B82F6", "display": { "ko": "순항구간", "en": "cruise" }, "widget": "single_choice", "isRepresentative": true, "customFields": null }
        ]
      },
      {
        "id": "Kt3mA8bXf5",
        "tool": "time_range",
        "isLocked": true,
        "isVisible": true,
        "isValid": true,
        "isDrawCompleted": true,
        "classification": {
          "class": "vibration_anomaly",
          "severity": "high",
          "axis": "z"
        },
        "label": [
          { "code": "class", "text": "", "color": "#FF6B6B", "display": { "ko": "진동 이상구간", "en": "vibration_anomaly" }, "widget": "single_choice", "isRepresentative": true, "customFields": null },
          { "code": "class", "text": "진동 이상구간", "color": "#FF6B6B", "display": { "ko": "진동 이상구간", "en": "vibration_anomaly" }, "widget": "single_choice", "isRepresentative": true, "customFields": null },
          { "code": "severity", "text": "높음", "color": "#E53935", "display": { "ko": "높음", "en": "high" }, "widget": "single_choice", "isRepresentative": false, "customFields": null },
          { "code": "axis", "text": "Z축", "color": "#78909C", "display": { "ko": "Z축", "en": "z" }, "widget": "single_choice", "isRepresentative": false, "customFields": null }
        ]
      },
      {
        "id": "Pw6dU1nGj0",
        "tool": "time_range",
        "isLocked": false,
        "isVisible": true,
        "isValid": true,
        "isDrawCompleted": true,
        "classification": {
          "class": "landing"
        },
        "label": [
          { "code": "class", "text": "", "color": "#F59E0B", "display": { "ko": "착륙구간", "en": "landing" }, "widget": "single_choice", "isRepresentative": true, "customFields": null },
          { "code": "class", "text": "착륙구간", "color": "#F59E0B", "display": { "ko": "착륙구간", "en": "landing" }, "widget": "single_choice", "isRepresentative": true, "customFields": null }
        ]
      }
    ]
  },
  "annotationsData": {
    "data_1": [
      {
        "id": "Yx8pQ2mLk3",
        "section": {
          "start": 1613693139891,
          "end": 1613693157891
        }
      },
      {
        "id": "Bn4vR7tWq1",
        "section": {
          "start": 1613693157891,
          "end": 1613693189891
        }
      },
      {
        "id": "Hs9cE2yZd6",
        "section": {
          "start": 1613693189891,
          "end": 1613693352891
        }
      },
      {
        "id": "Kt3mA8bXf5",
        "section": {
          "start": 1613693271491,
          "end": 1613693285891
        },
        "excludedTracks": [
          "local-position-x",
          "local-position-y",
          "local-position-z",
          "yaw-angle"
        ]
      },
      {
        "id": "Pw6dU1nGj0",
        "section": {
          "start": 1613693402891,
          "end": 1613693426291
        }
      }
    ]
  },
  "relations": {
    "data_1": []
  },
  "annotationGroups": {
    "data_1": []
  }
}

위 샘플에서 확인할 점:

  • data_1task.data_unit.filesis_primary 파일 키입니다. CSV 방식이라도 실데이터 파일 키(data_1)를 쓰며, 메타 JSON 키(data_meta_1)는 쓰지 않습니다. 에셋 키는 파일 키를 그대로 따르므로, 파일 키가 data_1이 아닌 프로젝트에서는 그 키가 들어갑니다.
  • section 값은 epoch_ms입니다. 같은 데이터를 CSV + timeAxis.origin: "relative" / unit: "us"로 로드했다면 191000000 같은 마이크로초 값이 저장됩니다.
  • 트랙 ID와 시간 범위는 PX4 예제 데이터의 실제 값이고, 클래스 코드(takeoff / hover / cruise / turn / landing / vibration_anomaly)도 해당 프로젝트의 분류 스키마와 동일합니다.
  • vibration_anomaly 구간은 위치 관련 트랙 4개를 excludedTracks로 제외했고, isLocked: true로 편집이 잠겨 있습니다.
  • classificationseverity / axis하위 속성(attributes)까지 정의한 프로젝트를 가정한 확장 예시입니다. 하위 속성은 특정 클래스가 선택됐을 때만 노출되도록 정의되므로, 위처럼 vibration_anomaly에만 붙는 형태가 됩니다. 클래스만 정의한 프로젝트라면 classification{ "class": "..." } 하나뿐입니다.
  • label첫 번째 요소는 text만 비운 중복 항목입니다. 클래스만 있는 어노테이션은 2개, 하위 속성 2개가 붙은 vibration_anomaly는 4개가 됩니다. 배열을 순회해 텍스트를 이어 붙일 때는 첫 요소를 건너뛰어야 합니다.
  • labelclassification에서 파생된 값이며, 작업을 다시 열 때 분류 스키마 기준으로 재계산되어 덮어써집니다. 저장된 값을 신뢰해 읽는 소비자는 없으므로, 외부에서 결과를 해석할 때는 label이 아니라 classification을 기준으로 삼아야 합니다.

초안

⬇️ ⬇️ 아래 데이터 유닛 섹션부터 문서 끝까지는 작성 중인 내용으로, 확정되지 않았습니다.


시계열 데이터 유닛은 동일한 시간 범위를 공유하는 센서 채널들의 집합입니다. task.data_unit.files에 실제 파일들이 담기며, 다음 두 가지 구성을 지원합니다.

구성files 구성실데이터뷰 정의
통합 JSONdata_N (.json, is_primary)JSON 내 channels같은 JSON의 tracks / channelMeta
CSV + 메타 JSONdata_N (.csv, is_primary) + data_meta_N (.json)CSV 파일메타 JSON의 view.subplots

is_primary인 파일이 실데이터이며, 로더는 그 URL의 확장자로 통합 JSON(.json) / CSV(.csv) 경로를 판별합니다. (file_type 필드는 사용하지 않습니다.)

각 파일 항목은 다음과 같은 형태입니다. meta에는 파일 크기 등 스토리지 메타데이터가 담기며, 뷰 정의는 이 안에 들어가지 않습니다.

{
  "data_1": {                        // 실데이터 (is_primary)
    "url": "https://cdn.example.com/....csv?X-Amz-...", // CSV 또는 JSON
    "is_primary": true,
    "file_type": "data",             // 로더는 미사용 (URL 확장자로 판별)
    "meta": {                        // 스토리지 메타 (뷰 정의 아님)
      "file_size": 33694099,
      "original_file_key": "tenants/1/files/.../....csv"
    }
  },
  "data_meta_1": {                   // CSV 방식일 때만: 뷰 정의 메타 JSON
    "url": "https://cdn.example.com/....json?X-Amz-...",
    "is_primary": false,
    "file_type": "data",
    "meta": { "file_size": 3701, "original_file_key": "tenants/1/files/.../....json" }
  }
}

통합 JSON 방식에서는 data_meta_N 없이 data_N(.json) 하나만 존재합니다.

ULG(ULog) 형식을 차용합니다. data_N(.json) 파일 하나에 데이터와 뷰 정의가 모두 담깁니다.

timestampsmeta.startTime / endTime은 기본적으로 Unix Epoch 기준 밀리초(epoch_ms) 를 사용합니다. timeAxis.origin으로 timestamps의 기준(epoch/absolute/relative/index)을 명시하면 별도 사전 변환 없이 그대로 렌더링됩니다. origin: "relative"인 데이터(예: 부팅 후 마이크로초)는 timeAxis.unit으로 원시 단위를 지정합니다. 화면 표시 형식은 timeAxis.format으로 지정합니다.

{
  "meta": {
    "startTime": 1613693139891, // epoch_ms
    "endTime": 1613693426291,   // epoch_ms
    "duration": 286.4,          // 초(s)
    "sampleRate": 10,           // Hz
    "nSamples": 2865,
    "timeAxis": {
      "origin": "absolute", // → TimeAxisOrigin
      "format": "HH:mm"     // → TimeAxisFormat
    }
  },
  "timestamps": [1613693139891, 1613693139991, "..."], // epoch_ms 배열
  "tracks": [
    {
      "id": "altitude-estimate", // 임의의 문자열 (kebab-case 권장)
      "name": "트랙 이름",
      "chartType": "line", // → ChartType
      "channels": ["채널ID_1", "채널ID_2"],
      "yRange": { "min": -30, "max": 30 },           // Y축 클리핑 (선택)
      "thresholds": [                                  // 배경색 구간 (선택)
        { "max": 4.905, "color": "rgba(34,197,94,0.15)", "label": "Good" },
        { "min": 4.905, "max": 9.81, "color": "rgba(253,203,110,0.15)", "label": "Warning" }
      ]
    }
  ],
  "channels": {
    "채널ID_1": [0.1, 0.2, "..."], // timestamps와 길이 동일
    "채널ID_2": [1.0, 1.1, "..."]
  },
  "channelMeta": {
    "채널ID_1": {
      "name": "채널 표시 이름",
      "unit": "m/s²",
      "color": "#cc6600",
      "chartType": "line" // → ChartType
    }
  }
}

data_N이 CSV(실측 데이터), data_meta_N이 뷰/시간축 정의를 담은 메타 JSON인 구성입니다. 컬럼이 많은 CSV에서 어떤 컬럼을 어떤 트랙으로 그릴지는 메타 JSON의 view.subplots가 결정합니다.

  • x축(timestamps): CSV의 timestamp 컬럼 값 (컬럼이 없으면 행 인덱스)
  • 채널: view.subplots[].columns에 명시된 컬럼만 로드 (라인 차트)
  • 트랙: subplot 하나당 트랙 하나

메타 JSON 예시:

{
  "format_version": "2.0",
  "total_rows": 48213,
  "filename": "AIPilot_SIM_FLT01.csv",
  "classes": ["takeoff", "hover", "landing"], // 어노테이션 클래스 (선택)
  "view": {
    "timeAxis": {         // 시간축 (선택, 미지정 시 absolute/epoch_ms)
      "origin": "relative",
      "unit": "us"        // PX4 timestamp = 부팅 후 마이크로초
    },
    "subplots": [
      { "name": "고도/자세", "columns": ["altitude", "roll", "pitch"] },
      { "name": "가속도",    "columns": ["accel_x", "accel_y", "accel_z"] }
    ]
  }
}
필드타입필수설명
view.subplots{ name?, columns[] }[]O트랙별로 로드/표시할 CSV 컬럼 (name 생략 시 Track N)
view.timeAxis{ origin, unit?, format? }X시간축 기준/단위 (미지정 시 absolute/epoch_ms)
format_version 또는 total_rowsstring / numberO메타 포맷 식별 필드 (둘 중 하나 이상 필요)
classesstring[]X어노테이션 클래스 목록
filename / csv_hashstringX원본 CSV 식별용
설명
line라인 차트
scatter산점도
area영역 차트
stacked_area누적 영역 차트
step계단 차트
bar막대 차트
band범위(min/max) 밴드 차트
box_plot박스 플롯(통계 범위 요약)
waterfall워터폴(증감) 차트

timeAxis.origin은 timestamps 값의 기준을 지정하며, ECharts 축 타입을 결정합니다. 지정하지 않으면 absolute(epoch_ms)로 간주합니다.

각 origin은 별도 사전 변환 없이 그대로 렌더링됩니다. 사용자가 표시 기준만 바꾸고 싶을 때는 displayOrigin을 사용합니다(데이터 origin은 유지, 포맷터에만 영향).

설명
absolutetimestamps가 epoch_ms. 벽시계 시각(format)으로 표시 (기본값)
epochtimestamps가 epoch_ms. 원시 숫자 그대로 표시
relative시작(0) 기준 경과 시간. 단위는 unit으로 지정 (예: PX4 부팅 후 마이크로초 → unit: "us")
indextimestamps를 샘플 인덱스 기준으로 표시

origin: "relative"일 때 timestamps의 원시 단위를 지정합니다. 지정하지 않으면 ms이며, 표시 시 자동으로 밀리초로 환산됩니다.

단위
us마이크로초 (PX4 로그 등)
ms밀리초 (기본값)
s
min
h

트랙의 Y축에 표시되는 배경색 구간입니다. tracks[].thresholds 배열의 각 요소입니다.

필드타입필수설명
colorstringO배경색 (rgba 권장)
labelstringX구간 라벨
minnumberXY축 최소값 (미지정 시 -∞)
maxnumberXY축 최대값 (미지정 시 +∞)

트랙의 Y축 표시 범위를 고정합니다. tracks[].yRange 객체입니다.

필드타입필수설명
minnumberOY축 하한
maxnumberOY축 상한

화면에 표시할 시간 형식을 지정합니다. Day.js 포맷 문자열을 사용합니다.

예시출력
HH:mm:ss14:05:32
HH:mm14:05
s.SSS5.320
MM-DD HH:mm02-18 14:05