# Alignerr Connect extension Source: https://docs.labelbox.com/alignerr-connect-extension Some external projects now require the **Alignerr Connect Extension** in order to work on them. When a project has this setting enabled, you (the Alignerr) must install the extension before you can start working on that project. This guide covers what the extension does, how to install it, and what to expect once it's active. ## **What is the Alignerr Connect extension?** On the surface, working with the extension installed looks and feels the same as before. There's no change to your day-to-day workflow or interface. Under the hood, though, the extension adds a security check: **it detects and blocks anonymized network connections** (VPN) when you try to access certain project-related sites. If the extension detects that your connection is being routed through a VPN, it will block access to the project. To resolve this, simply turn off your VPN (or otherwise switch to a direct, non-anonymized connection). Once your connection is no longer flagged as anonymized, access is restored automatically. There's no need to contact support or wait for anything on the backend. ## **How to install the extension** 1. Open the project. 2. If VPN detection is enabled for your project, you’ll see a prompt to install the extension. Click **Install Alignerr Connect**. 3. This takes you to the Chrome Web Store listing for the Alignerr Connect Extension. 4. Click **Add to Chrome**. 5. Once installed, the install prompt on the project page will disappear, and you'll be able to access and work on the project as normal. That's it, no additional configuration is required. Once installed, the extension runs in the background and only becomes noticeable if it detects an anonymized connection. ## **What to do if you use a VPN** If you normally work using a VPN, proxy, Tor, or a hosting network, be aware: * Access to certain external project tools may be blocked while the extension detects that kind of connection. * To regain access, disable the VPN/proxy or switch to a direct, unmasked connection. * If you retry the site while still on a flagged connection, it will continue to block access — the fix is changing your connection, not retrying. ## **FAQ** **Q: Will installing the extension change how I work?** A: No. On the surface, nothing changes. The only difference is that anonymized connections via VPN will be blocked from accessing certain project tools. **Q: I use a VPN for work, what should I do?** You'll need to disable the VPN (or use a direct connection) when accessing tools on projects that require the extension. **Q: Where do I install the extension?** A: Through the **Install Extension** link on the project page, which takes you to the Chrome Web Store. **Q: What if I'm blocked and I'm not using a VPN?** A: Double-check your network settings. Some hosting networks or proxies can trigger this detection even if you don't think of yourself as using a VPN. If you believe you're being blocked incorrectly, reach out to support.*.*
# Create a new immutable agent version Source: https://docs.labelbox.com/api-reference/agents/create-a-new-immutable-agent-version /recursion/openapi.yaml post /managed-agents/v1/agents/{agentId}/versions Creates a new immutable agent version from a full definition and moves the agent's latest pointer to it, in one atomic operation. base_agent_version_id must be the agent's current latest_agent_version_id or the request is rejected with 409 revision_conflict, so the caller can re-read and retry rather than silently racing another writer. Every field is full content, there is no partial-update or omit-to-inherit semantics, so an omitted field is cleared rather than carried over from the base version. Tags are changed only through the dedicated agent tag routes. Invalid promptCacheTTL or promptCacheSharedTTL settings in model_config.provider_params return 400 when prompt caching is enabled. Invalid model_config and unsupported reasoning_effort values return 400, including when the model catalog lists no exact options for the selected model; a model catalog that cannot be read to verify them returns 503. The response's execution definition and latest_agent_version_id are the exact values this call committed; created_at and updated_at are the authoritative persisted timestamps; tags are mutable catalog metadata and are not changed by this route. Do not automatically retry an ambiguous transport failure: re-read the agent's latest version first, because this operation does not replay a prior response. # Create an agent and immutable version Source: https://docs.labelbox.com/api-reference/agents/create-an-agent-and-immutable-version /recursion/openapi.yaml post /managed-agents/v1/agents # Get one agent Source: https://docs.labelbox.com/api-reference/agents/get-one-agent /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId} Returns one agent resolved to its latest immutable version, including its system prompt and default credential grants. A soft-deleted or unknown id is a 404. # Get one file of an agent's file history Source: https://docs.labelbox.com/api-reference/agents/get-one-file-of-an-agents-file-history /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/files/{fileId} Returns one session output from this agent's file history. A file of another agent, of another organization, or outside the history answers 404. # Get one immutable agent version Source: https://docs.labelbox.com/api-reference/agents/get-one-immutable-agent-version /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/versions/{agentVersionId} Returns one immutable agent version by id. The version must belong to the agent in the path; a mismatch is a 404 rather than a cross-agent read. # Get what an agent's skills cost the prompt Source: https://docs.labelbox.com/api-reference/agents/get-what-an-agents-skills-cost-the-prompt /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/skill-disclosure Reports how the agent's saved skills will appear in the prompt of its next session: the skills in the order the model reads them, which of them the model's budget leaves room to describe, and how many characters of that budget they use. It answers for the agent's latest version, with the model and built-in integrations a session of that version starts with: the budget is sized from that model's context window, and an agent with built-in integrations lists the skill the platform adds ahead of its own. If the model's context window cannot be read, the response uses the smallest budget and says so in warnings instead of failing. An agent whose model is not available on this deployment is a 400, as starting a session of it would be; a soft-deleted or unknown agent is a 404. # List agents Source: https://docs.labelbox.com/api-reference/agents/list-agents /recursion/openapi.yaml get /managed-agents/v1/agents Returns every agent in the calling organization, each resolved to its latest immutable version and hydrated with current tag definitions in one batch. tag_ids is a comma-separated AND filter: every returned agent carries every requested tag. Soft-deleted agents are omitted, and the response is not paginated. # List an agent's immutable version history Source: https://docs.labelbox.com/api-reference/agents/list-an-agents-immutable-version-history /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/versions Returns all immutable agent versions, newest to oldest, projected through the current public contract. Prompt, model, and supported capabilities remain visible; inert legacy tool declarations remain stored but are omitted from these responses. The response is not paginated. # List static agent templates Source: https://docs.labelbox.com/api-reference/agents/list-static-agent-templates /recursion/openapi.yaml get /managed-agents/v1/agent-templates Returns the startup-validated static catalog of agent definitions. Copy one definition into managedAgentsCreateAgent to create an ordinary managed agent; listing templates has no side effects and performs no live model validation. # Open the analyst for an agent's sessions Source: https://docs.labelbox.com/api-reference/agents/open-the-analyst-for-an-agents-sessions /recursion/openapi.yaml post /managed-agents/v1/agents/{agentId}/analyst Returns the caller's agent analyst, starting one if they have none (created is true when this call started it): a read-only assistant session that answers questions across every session the agent ran -- volume, outcomes, failures, spend, which session produced which file -- and can read any one of those sessions in depth. It reads with the caller's own access: public sessions of this agent only. Ask it questions by sending messages to the returned session (managedAgentsSendSessionEvents) and read answers from its events; it may also put typed questions in front of the caller, answered with managedAgentsSubmitAssistantInteractionDecision. One analyst per caller per agent; {"reset": true} ends it. Calls not made on behalf of a person get 403. Out-of-credit and suspended tenants get 403 session_start_not_admitted as on managedAgentsOpenSessionAnalyst. # Search an agent's file history Source: https://docs.labelbox.com/api-reference/agents/search-an-agents-file-history /recursion/openapi.yaml post /managed-agents/v1/agents/{agentId}/files/search Searches the session outputs this agent's sessions saved, including outputs its subagents and teammates saved, which are what its sessions read with search_agent_files when the agent has fileHistoryEnabled. Structured and metadata conditions are exact and apply before paging; text searches the contents of text formats, and coverage reports the candidates it could not search in full. Every page reads the snapshot asOf names; pass nextCursor back with an otherwise unchanged request for the next page. Outputs of deleted sessions, of session analysts, and of sessions under an access policy are not included. # Soft delete agent Source: https://docs.labelbox.com/api-reference/agents/soft-delete-agent /recursion/openapi.yaml delete /managed-agents/v1/agents/{agentId} Soft deletes the agent so it no longer appears in reads or starts new sessions. Its version history and existing sessions are retained. The delete cannot be undone through this API. # Create a paused canonical automation Source: https://docs.labelbox.com/api-reference/automations/create-a-paused-canonical-automation /recursion/openapi.yaml post /managed-agents/v1/automations Creates a complete automation aggregate in the paused state after validating the pinned agent version, environment, and every trigger source. # Delete a canonical automation Source: https://docs.labelbox.com/api-reference/automations/delete-a-canonical-automation /recursion/openapi.yaml delete /managed-agents/v1/automations/{automationId} Soft-deletes the aggregate and its live triggers while preserving admitted run snapshots. Requires the latest strong ETag in If-Match. # Get a canonical automation Source: https://docs.labelbox.com/api-reference/automations/get-a-canonical-automation /recursion/openapi.yaml get /managed-agents/v1/automations/{automationId} Returns the complete round-trippable automation aggregate with a strong revision ETag. If-None-Match may answer 304. # Get automation schedule status Source: https://docs.labelbox.com/api-reference/automations/get-automation-schedule-status /recursion/openapi.yaml get /managed-agents/v1/automations/{automationId}/status Reads synchronization and bounded live scheduler previews independently of the writable definition. Unavailable state is explicit. # Get one canonical automation run Source: https://docs.labelbox.com/api-reference/automations/get-one-canonical-automation-run /recursion/openapi.yaml get /managed-agents/v1/automation-runs/{automationRunId} Returns one canonical run, including the immutable prompt and context snapshots and its current session-creation outcome. # List canonical automation runs Source: https://docs.labelbox.com/api-reference/automations/list-canonical-automation-runs /recursion/openapi.yaml get /managed-agents/v1/automation-runs Returns a consistent page of automation runs, newest first. Filters combine with AND, and a continuation cursor is bound to the organization, filters, and the moment the first page was read. # List canonical automations Source: https://docs.labelbox.com/api-reference/automations/list-canonical-automations /recursion/openapi.yaml get /managed-agents/v1/automations Returns a bounded page of canonical automation summaries, newest first. Prompt, run defaults, and trigger configuration are available only on member reads. # Pause canonical automation admission Source: https://docs.labelbox.com/api-reference/automations/pause-canonical-automation-admission /recursion/openapi.yaml post /managed-agents/v1/automations/{automationId}/pause Stops stored triggers from admitting new runs while leaving explicit manual runs available. Requires the latest strong ETag in If-Match. # Replace a canonical automation aggregate Source: https://docs.labelbox.com/api-reference/automations/replace-a-canonical-automation-aggregate /recursion/openapi.yaml put /managed-agents/v1/automations/{automationId} Replaces the complete writable aggregate and trigger set while preserving lifecycle state. Requires the latest strong ETag in If-Match. # Resume canonical automation admission Source: https://docs.labelbox.com/api-reference/automations/resume-canonical-automation-admission /recursion/openapi.yaml post /managed-agents/v1/automations/{automationId}/resume Allows enabled stored triggers to admit new runs. Refuses with 422 automation_reference_invalid when the agent, agent version, or environment is no longer live. Requires the latest strong ETag in If-Match. # Run a canonical automation manually Source: https://docs.labelbox.com/api-reference/automations/run-a-canonical-automation-manually /recursion/openapi.yaml post /managed-agents/v1/automations/{automationId}/runs Atomically freezes the automation and optional payload into one pending run. Explicit manual runs remain available while the automation is paused. Within the organization-wide receipt namespace, Idempotency-Key plus the exact HTTP method, escaped path, raw query, and raw body bytes identify the durable result. # Cancel a setup run Source: https://docs.labelbox.com/api-reference/environments/cancel-a-setup-run /recursion/openapi.yaml post /managed-agents/v1/environments/{environmentId}/setup-runs/{setupRunId}/cancel Stops a queued or in-flight manual or automatic runner_refresh setup run and releases its compute. Sessions waiting on a cancelled runner_refresh fail with that run as their cause. The cancelled run is returned with status cancelled and active_setup_run_id is cleared. A cancelled run proves nothing: a standing verified or failed verdict is kept as it was, and an environment whose only verdict was running reverts to never. 409 when the run had already finished. # Create environment Source: https://docs.labelbox.com/api-reference/environments/create-environment /recursion/openapi.yaml post /managed-agents/v1/environments Defines the sandbox an agent session runs in and returns the stored environment, including its generated environment_id. The provider must be one this deployment reports from managedAgentsListSandboxProviders. Privileged Docker requires unrestricted internet access (network_policy: {}). Nothing is provisioned by this configuration write; compute is allocated when managedAgentsCreateEnvironmentSetupRun or a session references the environment. An environment with a setup script must be verified (managedAgentsCreateEnvironmentSetupRun) before a session on the runs or docker provider will start. Send Idempotency-Key to make a retry after an ambiguous transport failure replay the original response instead of creating another environment. # Get a setup run, optionally waiting for it to finish Source: https://docs.labelbox.com/api-reference/environments/get-a-setup-run-optionally-waiting-for-it-to-finish /recursion/openapi.yaml get /managed-agents/v1/environments/{environmentId}/setup-runs/{setupRunId} Returns one setup run. With wait_seconds the call blocks until the run reaches a terminal status or the wait elapses, so a poll loop needs no client-side timer. next_action names the call to make on the result: poll again, read the log, or start a session. # Get environment Source: https://docs.labelbox.com/api-reference/environments/get-environment /recursion/openapi.yaml get /managed-agents/v1/environments/{environmentId} Returns one environment and its full sandbox spec. A soft-deleted or unknown id is a 404, and an id belonging to another organization is reported the same way. # List environments Source: https://docs.labelbox.com/api-reference/environments/list-environments /recursion/openapi.yaml get /managed-agents/v1/environments Returns every environment in the calling organization with its full sandbox spec (provider, image, setup, resources, network policy). Soft-deleted environments are omitted, and the response is not paginated. # List portable environment templates Source: https://docs.labelbox.com/api-reference/environments/list-portable-environment-templates /recursion/openapi.yaml get /managed-agents/v1/environment-templates Returns reviewed, versioned environment starting points. Copy a definition into managedAgentsCreateEnvironment, bind any organization-specific fields in the form, and verify its setup before starting a session. # List setup runs Source: https://docs.labelbox.com/api-reference/environments/list-setup-runs /recursion/openapi.yaml get /managed-agents/v1/environments/{environmentId}/setup-runs Returns the environment's recent setup runs, newest first: manual, automatic runner_refresh, and legacy session-triggered runs. The newest manual or runner_refresh run supplies the environment's setup_verification verdict; a session run records legacy in-session setup and can be read here by its session_id. # List supported sandbox providers Source: https://docs.labelbox.com/api-reference/environments/list-supported-sandbox-providers /recursion/openapi.yaml get /managed-agents/v1/sandbox-providers Returns the sandbox runtimes this deployment can run environments on, with the display name, description, whether the provider is the default, and its required credential name if any. This is static service capability, not organization data, so every caller sees the same list. # Read or tail a setup run's log Source: https://docs.labelbox.com/api-reference/environments/read-or-tail-a-setup-runs-log /recursion/openapi.yaml get /managed-agents/v1/environments/{environmentId}/setup-runs/{setupRunId}/log Returns the run's output after a cursor: stdout and stderr as the script produced them, marker lines naming each script line before it runs, and system lines for the setup phases around the script (provision, gpu_check, profile, cleanup). Secrets are redacted before storage. To tail a live run, loop with after=next_after and wait_seconds=0, sleeping two seconds client-side, until status is terminal and a page comes back empty. # Run and verify the environment's setup script Source: https://docs.labelbox.com/api-reference/environments/run-and-verify-the-environments-setup-script /recursion/openapi.yaml post /managed-agents/v1/environments/{environmentId}/setup-runs Requires Idempotency-Key because this operation provisions billable compute. Provisions fresh compute for the environment, runs its setup script exactly as a session would (same image, resources, and variables; no session credentials), records every line of output, profiles the compute, releases it, and writes the verdict to the environment's setup_verification. Returns 202 with the queued run; poll managedAgentsGetEnvironmentSetupRun with wait_seconds until status is succeeded, failed, or cancelled. On failure read hint, failed_line, and stderr_tail first, then the log. One run at a time per environment: a second request while one is in flight is a 409 naming the run to poll. Repeating the exact request with the same Idempotency-Key replays the original 202 response without starting another run. 400 when the environment has no setup script. This spends compute. When the tenant is out of credit, returns 403 setup_run_not_admitted with details.reason out_of_credit; buy credits and retry. A suspended tenant gets the same 403 with details.reason tenant_suspended. An unavailable credit check returns 503 setup_run_admission_unavailable without recording a run. # Soft delete environment Source: https://docs.labelbox.com/api-reference/environments/soft-delete-environment /recursion/openapi.yaml delete /managed-agents/v1/environments/{environmentId} Soft deletes the environment: it disappears from reads and can no longer be referenced by a new session, but nothing is erased. Sessions that already ran against it keep their history. There is no undelete on this API. # Update environment Source: https://docs.labelbox.com/api-reference/environments/update-environment /recursion/openapi.yaml patch /managed-agents/v1/environments/{environmentId} Applies a partial update and returns the stored environment; omitted fields keep their current values; a name, when sent, must not be blank or null. Changing from a non-Runs provider to Runs with an absent or {} stored policy requires an explicit network_policy: {"version":"v1","rules":[]} for deny-all or {} for unrestricted egress. A provider is validated only when the request actually names one. Sending setup with an empty script clears the script; omitting setup leaves it. Any change to provider, image, setup (script or timeout), resources, workspace disk size, provider config, variables, retired secret references, mounts, network policy, or effective Docker privilege marks the previous setup verification stale; name, description, metadata, and http_port do not. Call managedAgentsCreateEnvironmentSetupRun separately after the update to re-run setup. Sessions already running keep the spec they started with, so a change takes effect on the next session start. A Runs update that changes or bypasses a nonempty network_policy, or enables privileged Docker, requires expected_access with the network_policy and privileged values from the last environment read. When changing access settings, privileged Docker requires unrestricted internet access (network_policy: {}). A missing access snapshot returns 428; a stale one returns 412. A concurrent configuration edit may return 409; reload and retry. Send Idempotency-Key to make a retry after an ambiguous transport failure replay the original response. # Create a paused event source Source: https://docs.labelbox.com/api-reference/event-sources/create-a-paused-event-source /recursion/openapi.yaml post /managed-agents/v1/event-sources Creates a customer webhook source in the paused state. delivery.kind must be webhook; integration sources are connection-managed. Verification credentials are referenced from Vault and secret material is never returned. # Delete an unused event source Source: https://docs.labelbox.com/api-reference/event-sources/delete-an-unused-event-source /recursion/openapi.yaml delete /managed-agents/v1/event-sources/{eventSourceId} Soft-deletes a customer webhook source only when no live trigger depends on it. Connection-managed sources cannot be deleted independently. Requires the latest strong ETag in If-Match. # Get an event source Source: https://docs.labelbox.com/api-reference/event-sources/get-an-event-source /recursion/openapi.yaml get /managed-agents/v1/event-sources/{eventSourceId} Returns non-secret source configuration with a strong revision ETag. If-None-Match may answer 304. # Get volatile event-source status Source: https://docs.labelbox.com/api-reference/event-sources/get-volatile-event-source-status /recursion/openapi.yaml get /managed-agents/v1/event-sources/{eventSourceId}/status Returns live trigger count and bounded last-delivery diagnostics separately from revisioned configuration. # List event sources Source: https://docs.labelbox.com/api-reference/event-sources/list-event-sources /recursion/openapi.yaml get /managed-agents/v1/event-sources Returns a bounded page of non-secret source summaries, newest first. Protocol configuration is available only on member reads. # Pause event admission from a source Source: https://docs.labelbox.com/api-reference/event-sources/pause-event-admission-from-a-source /recursion/openapi.yaml post /managed-agents/v1/event-sources/{eventSourceId}/pause Stops new admissions from this source without changing dependent triggers. Requires the latest strong ETag in If-Match. # Replace an event source configuration Source: https://docs.labelbox.com/api-reference/event-sources/replace-an-event-source-configuration /recursion/openapi.yaml put /managed-agents/v1/event-sources/{eventSourceId} Replaces the complete writable webhook configuration while preserving lifecycle state. Connection-managed sources cannot be replaced. Requires the latest strong ETag in If-Match. # Resume event admission from a source Source: https://docs.labelbox.com/api-reference/event-sources/resume-event-admission-from-a-source /recursion/openapi.yaml post /managed-agents/v1/event-sources/{eventSourceId}/resume Allows verified deliveries to reach dependent triggers again. Requires the latest strong ETag in If-Match. # Download a file's bytes Source: https://docs.labelbox.com/api-reference/files/download-a-files-bytes /recursion/openapi.yaml get /managed-agents/v1/files/{fileId}/content Returns a file's bytes to its organization as application/octet-stream with a Content-Disposition naming it. The file's recorded media type remains available in its metadata but is not used as the response Content-Type. Responses include nosniff and sandbox content-security headers so uploaded HTML and SVG cannot run as this origin. # Get one file Source: https://docs.labelbox.com/api-reference/files/get-one-file /recursion/openapi.yaml get /managed-agents/v1/files/{fileId} Returns one file's metadata. A deleted file answers 404, as does a file belonging to another organization; an expired file still answers, with expires_at in the past, until it is removed after a grace period. # List files Source: https://docs.labelbox.com/api-reference/files/list-files /recursion/openapi.yaml get /managed-agents/v1/files Returns the caller organization's files, newest first, one page at a time: up to limit files (default 200, at most 1000) and a next_page_token while more follow. Deleted files are omitted; an expired file stays listed, with expires_at in the past, until it is removed after a grace period, so a client that wants only attachable files filters on expires_at. Filter to one session's outputs with scope_session_id or by source. A file_ids lookup (at most 100 ids) is always one page and cannot be combined with limit or page_token. # Upload a file Source: https://docs.labelbox.com/api-reference/files/upload-a-file /recursion/openapi.yaml post /managed-agents/v1/files Stores a file the organization can attach to any number of sessions. Attaching one mounts it read-only into the session's sandbox under its files directory, and the agent knows where to find it. Send the bytes as the file part of a multipart/form-data body; the part's filename and Content-Type become the file's name and media type. The file part may contain at most 30 MiB, and the complete multipart request may use at most 128 KiB more for framing and form fields. Larger files are not supported by this Platform V1 operation. The bytes are served back through GET /managed-agents/v1/files/{fileId}/content to the same organization. A request over the transport limit answers 413 with code payload_too_large. An upload that would carry the organization's live files past its storage quota answers 413 with code file_quota_exceeded and reports the quota, bytes in use, and incoming size in details. # Begin authorizing a third-party account connection Source: https://docs.labelbox.com/api-reference/integrations/begin-authorizing-a-third-party-account-connection /recursion/openapi.yaml post /managed-agents/v1/integrations/{provider}/install Begins authorizing a third-party account connection and returns the provider URL to send the operator to, together with a single-use, short-lived state nonce. A caller may also continue one of the opaque provider options returned by managedAgentsCompleteIntegrationInstall. Nothing is connected until completion succeeds. Do not automatically retry an ambiguous transport failure; begin a new install and use its returned state. # Check a connection against the provider and try minting a token Source: https://docs.labelbox.com/api-reference/integrations/check-a-connection-against-the-provider-and-try-minting-a-token /recursion/openapi.yaml post /managed-agents/v1/integrations/connections/{connectionId}/probe Checks a connection against the provider and tries to mint a short-lived access token, returning what the provider reported. A suspended or revoked installation is still HTTP 200 with ok=false, so read that field rather than the status code. For a directly registered connection in the pending state this is also the activation step: the probe performs a fresh mint through the trust the customer granted and, if it succeeds, moves the connection to active and reports activated=true; until then it stays pending and the failure text is the bounded "delegate trust is not active". # Disconnect an account and retire the grants that used it Source: https://docs.labelbox.com/api-reference/integrations/disconnect-an-account-and-retire-the-grants-that-used-it /recursion/openapi.yaml delete /managed-agents/v1/integrations/connections/{connectionId} Disconnects the account and retires every credential grant that referenced it, returning how many grants were revoked. Agents holding those grants lose access to the provider on their next session; existing sessions are not torn down. # Finish connecting provider accounts after the authorization redirect Source: https://docs.labelbox.com/api-reference/integrations/finish-connecting-provider-accounts-after-the-authorization-redirect /recursion/openapi.yaml post /managed-agents/v1/integrations/install/complete Finishes an authorization begun by managedAgentsStartIntegrationInstall. It returns either connected accounts or provider-neutral account/action options that continue through managedAgentsStartIntegrationInstall. The provider and organization come from the authenticated caller and single-use state nonce, never from callback parameters or browser-local storage. An expected provider failure after state consumption returns 409 integration_install_restart_required with details.provider; begin a new install. Resource and permission selection happens later when granting a vault. Do not automatically retry an ambiguous transport failure: list connections to determine whether completion committed; if it did not, begin a new install because the state is single-use. # Get one connected account Source: https://docs.labelbox.com/api-reference/integrations/get-one-connected-account /recursion/openapi.yaml get /managed-agents/v1/integrations/connections/{connectionId} Returns one connected account. An unknown id, or one belonging to another organization, is reported as a 404. # Get the connected Slack bot's bounded channel-selection catalog Source: https://docs.labelbox.com/api-reference/integrations/get-the-connected-slack-bots-bounded-channel-selection-catalog /recursion/openapi.yaml get /managed-agents/v1/integrations/connections/{connectionId}/channel-catalog Returns a channel-selection snapshot of at most 1,000 memberships from the connected Slack workspace, using that installation's credential, so an automation trigger can be configured by picking a channel instead of pasting its id. Only a Slack connection has channels; the list is scoped to the connection's own workspace. A channel the bot is not a member of is never listed: invite the bot in Slack first. Throttled reads return 429 slack_rate_limited. A list that stopped at the size budget says truncated=true, so absence from the list never proves the bot is not a member. If the Slack app lacks channels:read or groups:read the response is 422 slack_missing_scope naming the scopes to add. # Get the connected Slack workspace's bounded member catalog Source: https://docs.labelbox.com/api-reference/integrations/get-the-connected-slack-workspaces-bounded-member-catalog /recursion/openapi.yaml get /managed-agents/v1/integrations/connections/{connectionId}/member-catalog Returns the people and apps of the connected Slack workspace, at most 2,000, read with that installation's own credential, so a Slack trigger's fromUserIds filter can be configured by picking authors instead of pasting their ids. Apps are listed with isBot=true; deleted accounts and the connection's own bot are never listed. Only a Slack connection installed through Recursion can list members; any other answers 503 slack_members_unconfigured. Throttled reads return 429 slack_rate_limited. A list that stopped at the size budget says truncated=true, so absence from the list does not prove the member does not exist. If the Slack app lacks users:read the response is 422 slack_missing_scope. # List connected third-party accounts Source: https://docs.labelbox.com/api-reference/integrations/list-connected-third-party-accounts /recursion/openapi.yaml get /managed-agents/v1/integrations/connections Returns the organization's connected third-party accounts, each with its provider, account login, resource selection, and state. A connection holds no secret material, so nothing here needs to be redacted. # List pull requests for a connected repository Source: https://docs.labelbox.com/api-reference/integrations/list-pull-requests-for-a-connected-repository /recursion/openapi.yaml get /managed-agents/v1/integrations/connections/{connectionId}/repositories/{repositoryOwner}/{repositoryName}/pull-requests # List the native integrations this deployment supports Source: https://docs.labelbox.com/api-reference/integrations/list-the-native-integrations-this-deployment-supports /recursion/openapi.yaml get /managed-agents/v1/integrations/providers Returns the third-party integrations this deployment supports. Providers that the deployment has not configured are listed too, with their configured state, so a caller can tell "not set up here" apart from "not offered". # List the repositories a connection can reach, for the grant picker Source: https://docs.labelbox.com/api-reference/integrations/list-the-repositories-a-connection-can-reach-for-the-grant-picker /recursion/openapi.yaml get /managed-agents/v1/integrations/connections/{connectionId}/repositories Returns the repositories this connection can reach, for building a grant picker. Results are paged by the 1-based page query parameter at up to 100 repositories per page; the list comes from the provider live, so it reflects the installation's current access. # Delete a memory Source: https://docs.labelbox.com/api-reference/memory/delete-a-memory /recursion/openapi.yaml delete /managed-agents/v1/memory-stores/{memoryStoreId}/memories/{memoryId} Removes one learned memory from future recall and records the deletion as a version. The path becomes available for reuse. # Get an agent's memory settings Source: https://docs.labelbox.com/api-reference/memory/get-an-agents-memory-settings /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/memory/settings Returns the opt-in, cadence, model, curator, and standing instructions with a strong revision ETag. Recursion agents start with memory disabled. If-None-Match may answer 304. # Get an agent's memory summary Source: https://docs.labelbox.com/api-reference/memory/get-an-agents-memory-summary /recursion/openapi.yaml get /managed-agents/v1/agent-memories/{agentId} Returns the current permanent collection, its effective model, and automatic learning progress without starting work. # Get one memory job run Source: https://docs.labelbox.com/api-reference/memory/get-one-memory-job-run /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/memory/runs/{runId} Returns one run: its trigger, status, the sessions whose reports it read, the collection it updated, and the id of the session that ran it. Read what the run spent from that session's costs, which settle after the session ends. # Get one memory version Source: https://docs.labelbox.com/api-reference/memory/get-one-memory-version /recursion/openapi.yaml get /managed-agents/v1/memory-stores/{memoryStoreId}/memory-versions/{memoryVersionId} Returns one version including the content as it stood, unless the version has been redacted. # List an agent's memory runs Source: https://docs.labelbox.com/api-reference/memory/list-an-agents-memory-runs /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/memory/runs Returns automatic memory updates and checks, newest first, including unchanged checks and failed attempts. # List memory status for every agent Source: https://docs.labelbox.com/api-reference/memory/list-memory-status-for-every-agent /recursion/openapi.yaml get /managed-agents/v1/agent-memories Returns each customer agent's current memory summary, including agents that have not learned yet. Each status is derived from that agent's latest run. # List memory versions Source: https://docs.labelbox.com/api-reference/memory/list-memory-versions /recursion/openapi.yaml get /managed-agents/v1/memory-stores/{memoryStoreId}/memory-versions Returns a store's change history, newest first, optionally filtered to one memory. Versions belong to the store rather than the memory, so the trail survives the memory being deleted. Retained 30 days, except that the recent versions of a live memory are kept regardless of age. # Read one memory Source: https://docs.labelbox.com/api-reference/memory/read-one-memory /recursion/openapi.yaml get /managed-agents/v1/memory-stores/{memoryStoreId}/memories/{memoryId} Returns one learned memory, including its full content and content hash. # Search an agent's current memories Source: https://docs.labelbox.com/api-reference/memory/search-an-agents-current-memories /recursion/openapi.yaml get /managed-agents/v1/agents/{agentId}/memories Lists current memories as a flat, searchable keyset page, with topics from the entire collection. Retired memories are available through detail and history reads. # Update an agent's memory settings Source: https://docs.labelbox.com/api-reference/memory/update-an-agents-memory-settings /recursion/openapi.yaml patch /managed-agents/v1/agents/{agentId}/memory/settings Changes subsequent memory jobs and session attachments. Disabling stops new digests and detaches the learned store from new sessions. Omitted fields remain unchanged. Send the latest settings ETag in If-Match; a stale editor receives 412. # List the models agents can run on Source: https://docs.labelbox.com/api-reference/models/list-the-models-agents-can-run-on /recursion/openapi.yaml get /managed-agents/v1/models Returns the models agents can currently run on. The list is not stored per organization; it reflects service configuration, so it can change without any write from this API. # Append user/system events to a session Source: https://docs.labelbox.com/api-reference/sessions/append-usersystem-events-to-a-session /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/events Accepts ordered user and system messages and returns how many were accepted. A running session is woken so the agent sees them, and a finished session resumes to act on them; delivery_state reports how they were delivered. Events are appended in the order given. An omitted or empty events array yields exactly one user turn built from message; the request fails with 400 only when every entry sent was dropped, leaving nothing to append. A top-level image or document block may name a file with source {type: file, file_id}: an unknown, expired, or foreign file answers 404 on that block's source.file_id, a file the block cannot carry (a file whose bytes are not an image on an image block, a binary on a document block, a document over 512 KiB, more than 1 MiB of documents in one event, more than 100 such blocks, or more than 32 MiB of catalog bytes in one request) answers 400 there, and nothing is appended to the transcript; copies of files resolved before the refusal are kept with the session and removed once nothing references them. Without file storage such a block answers 503 file_storage_unconfigured. An optional Idempotency-Key makes a retry after an ambiguous transport failure replay the first response instead of appending the events again. When the tenant is out of credit, a message returns 403 session_start_not_admitted with details.reason out_of_credit and nothing is appended; add credits and send it again. A suspended tenant gets the same 403 with details.reason tenant_suspended. # Decide on an assistant interaction Source: https://docs.labelbox.com/api-reference/sessions/decide-on-an-assistant-interaction /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/interactions/{interactionId}/decide Records the caller's decision on the pending interaction an assistant put in front of them -- answers to its questions, acceptance of a blueprint, approval of a plan, a revision request, or dismissal -- and delivers it to the assistant as the caller's next message. Only the person who opened the assistant may decide, and only on the exact revision and digest they were shown (409 otherwise). Approving a plan requires a signed-in user holding every permission its enabled steps need; the plan then runs in the background, one existing operation per step, under the caller's permissions, and its progress is appended to the assistant's events. The response is the interaction after the decision. # Define what done looks like for a session Source: https://docs.labelbox.com/api-reference/sessions/define-what-done-looks-like-for-a-session /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/outcomes Defines what done looks like for an existing session and returns the stored outcome. Outcomes chain rather than run concurrently: the request is rejected while the session still has a non-terminal outcome, so define the next one only after the previous is graded. # Get one private session image Source: https://docs.labelbox.com/api-reference/sessions/get-one-private-session-image /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/images # Get root session tree and timeline Source: https://docs.labelbox.com/api-reference/sessions/get-root-session-tree-and-timeline /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/tree Returns every session in the root session's tree, its threads, and the first bounded page of the merged chronological event log. When next_event_id is set, more events remain and should be fetched with managedAgentsListSessionEvents rather than by re-reading the tree. Any session id in the tree is accepted; the read resolves to its root. # Get root timeline events Source: https://docs.labelbox.com/api-reference/sessions/get-root-timeline-events /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/events Returns one page of the root timeline's events in chronological order, plus next_page_token to continue from. A page can come back shorter than limit because an aggregate serialized-byte budget applies as well as the count, so treat an empty page with no token as the end rather than inferring the end from the length. Large event payloads hydrate by default; pass payloads=refs for bounded stubs instead. # Get session Source: https://docs.labelbox.com/api-reference/sessions/get-session /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId} Returns one session with its status, agent and environment binding, and timing. Statuses completed, failed, and cancelled are terminal and will not change again, which is what a poller should wait for. Poll with view=summary once agent_snapshot, model_snapshot and config are held. They are written at start and change only when the session moves to another version of its agent, which the summary's agent_version_id shows: read the full view again when it changes. # Get session thread Source: https://docs.labelbox.com/api-reference/sessions/get-session-thread /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/threads/{threadId} Returns one thread from the session's tree. The thread must belong to the tree rooted at the session in the path; anything else is a 404 rather than a cross-tree read. # Get the agent version a session runs Source: https://docs.labelbox.com/api-reference/sessions/get-the-agent-version-a-session-runs /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/agent-version Returns the version of its agent a root session runs now, the version it is set to run, whether a move is pending or was refused, the agent's latest version, and how far each open thread of the session tree has followed. The state changes as the session works, so read it again rather than keeping a copy. A session that is a member of another session's tree answers 409 conflict; read its root. A session that was not started from an agent has no version to read and answers 409 conflict too. # Get the team board Source: https://docs.labelbox.com/api-reference/sessions/get-the-team-board /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/board Returns the shared task board of a session whose roster runs as a team: every task with its status, owner, notes, and outcome; the notices and the decision; and the members named the way the board names them. Any session id in the tree is accepted; the read resolves to its root. A session whose tree is not a team is a 404. Each transition is also recorded as a board_update event on the root's log, but that log interleaves events written by every member, so a reader paging it by event id after a cursor can skip one; this read is the authoritative state, and a poller should refresh from it rather than reconstruct the board from events alone. # Hard stop a session immediately Source: https://docs.labelbox.com/api-reference/sessions/hard-stop-a-session-immediately /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/cancel Stops the session immediately and schedules durable sandbox teardown. The response confirms the session stopped; sandbox cleanup may continue asynchronously and is retried. A session that already reached a terminal status with no pending cleanup is left as it is, so the call is safe to repeat. # Interrupt a running session Source: https://docs.labelbox.com/api-reference/sessions/interrupt-a-running-session /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/interrupt Stops active model generation immediately and gives an active tool a bounded grace period. A session started before immediate interruption was available keeps its earlier bounded grace behavior. Interrupting is also how queued input is read sooner: when messages are waiting, the newest becomes the instruction for a fresh turn instead of leaving the session idle. With nothing waiting the session is left idle so a later follow-up can resume it. A session with nothing running -- idle and not asleep or waiting on its subagents, or already in a terminal status -- is refused with 409 conflict and nothing is recorded. interrupted reports whether running work received the request; false means the stop was recorded but reaching running work was not confirmed, typically because the run ended as the request arrived. Omit message to use a default one. # Interrupt a session and send a replacement instruction Source: https://docs.labelbox.com/api-reference/sessions/interrupt-a-session-and-send-a-replacement-instruction /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/interrupt-and-send Stops active work and durably accepts a replacement instruction in one ordered operation. The session starts a fresh turn automatically. A session started before replacement instructions were available still honors the interrupt and keeps the replacement for its next resume. When the tenant is out of credit it returns 403 session_start_not_admitted with details.reason out_of_credit and changes nothing, and a suspended tenant gets the same 403 with details.reason tenant_suspended; managedAgentsInterruptSession still stops the session. # List a session's outcomes and grading history Source: https://docs.labelbox.com/api-reference/sessions/list-a-sessions-outcomes-and-grading-history /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/outcomes Returns the session's outcomes and their grading history, so a caller can see what "done" was defined as and how each attempt was scored. # List input a session has accepted but not read Source: https://docs.labelbox.com/api-reference/sessions/list-input-a-session-has-accepted-but-not-read /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/pending-inputs Returns the messages waiting for a busy session, in acceptance order. A message sent while the agent is mid-turn is durable immediately but is not part of the transcript until the next turn boundary reads it, so it appears here and not in the events listing. Each entry's input_id becomes the event id on promotion, which is how a client follows a queued message into history. Interrupt the session to have the queue read now. # List session threads Source: https://docs.labelbox.com/api-reference/sessions/list-session-threads /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/threads Returns every thread in the session's tree, including one synthesized thread per session that has no explicit thread rows, sorted by thread path. Any session id in the tree is accepted; the read resolves to its root. # List sessions Source: https://docs.labelbox.com/api-reference/sessions/list-sessions /recursion/openapi.yaml get /managed-agents/v1/sessions Returns unclassified public sessions in the calling organization, most recently updated first, filtered by any combination of status, kind, agent_id, current agent tag_ids, and external source. kind accepts a comma-separated list, while tag_ids uses AND semantics. Results are keyset-paginated and capped by limit (default 100, max 1000). Every page of one listing reads the same snapshot as the first page, so a session updated while a client pages is neither skipped nor repeated. Classified session trees are omitted. # List the caller-defined metadata keys sessions carry Source: https://docs.labelbox.com/api-reference/sessions/list-the-caller-defined-metadata-keys-sessions-carry /recursion/openapi.yaml get /managed-agents/v1/sessions/metadata-keys Returns the distinct caller-defined metadata keys across every session the caller may list, sorted, so a client can offer them as filter choices without knowing them in advance. Sessions the caller's access policy hides contribute nothing. Capped at 200 keys; truncated reports when the cap was hit. Pair with managedAgentsListSessionMetadataValues, then filter managedAgentsListSessions with metadata or metadata_key. # List the values one caller-defined metadata key takes Source: https://docs.labelbox.com/api-reference/sessions/list-the-values-one-caller-defined-metadata-key-takes /recursion/openapi.yaml get /managed-agents/v1/sessions/metadata-values Returns the distinct values of one caller-defined metadata key across every session the caller may list, sorted and optionally narrowed to a case-sensitive prefix with q, for a typeahead over managedAgentsListSessions metadata filters. Sessions the caller's access policy hides contribute nothing. A key no session carries returns an empty list rather than an error. When more values match than limit allows, truncated is true: narrow q instead of paging. # Move a session to another version of its agent Source: https://docs.labelbox.com/api-reference/sessions/move-a-session-to-another-version-of-its-agent /recursion/openapi.yaml put /managed-agents/v1/sessions/{sessionId}/agent-version Sets the version of its agent a root session runs. The move is recorded at once and takes effect before the session's next model call, never in the middle of one: a sleeping session stays asleep and adopts the version when it next runs, and a finished session when it is next resumed. Threads adopt after their parent. The response is the representation managedAgentsGetSessionAgentVersion returns, with state pending until the move takes effect; if the version cannot run in this session, state becomes failed and lastFailure says why. Repeating the request while the move is pending, or once the session runs that version, changes nothing; repeating it after the move failed asks again. The later of two different requests wins, and naming the version the session already runs withdraws a pending move. Because a version can grant the session the vaults a new session would receive, the caller needs both managed-agents:create and managed-agents:update. Sessions that are members of another session's tree, sessions the platform runs itself, evaluations and pull request reviews stay on the version they started with and answer 409 conflict, as does a session that was not started from an agent, and one that is stopping or can no longer continue. A version of another agent answers 404. # Open the organization assistant Source: https://docs.labelbox.com/api-reference/sessions/open-the-organization-assistant /recursion/openapi.yaml post /managed-agents/v1/assistant Returns the caller's organization assistant, starting one if they have none (created is true when this call started it): an assistant session that knows the platform's capabilities and the organization's agents, environments, skills and connections, and helps design, set up and check agents. It changes nothing by itself: it asks typed questions, presents solution blueprints and proposes plans, and only a plan the caller approves with managedAgentsSubmitAssistantInteractionDecision is executed, under the caller's own permissions. Talk to it with managedAgentsSendSessionEvents and read its events. One assistant per caller; {"reset": true} ends it. Calls not made on behalf of a person get 403. # Open the session analyst for a session Source: https://docs.labelbox.com/api-reference/sessions/open-the-session-analyst-for-a-session /recursion/openapi.yaml post /managed-agents/v1/sessions/{sessionId}/analyst Returns the caller's session analyst for the tree containing this session, starting one if they have none (201 when this call started it, 200 otherwise): a read-only assistant session that answers questions about the target -- what the agents were asked to do, what they did, which tools failed, how they coordinated, what it cost and how it was graded. Ask it questions by sending messages to the returned analyst session (managedAgentsSendSessionEvents) and read its answers from that session's events or event stream. One analyst per caller per tree: opening again returns the same conversation, and a body of {"reset": true} ends it; the next question starts a new one. The analyst runs on a fixed platform model with no sandbox and no credentials, and nothing it does changes the target session. Any session id in the tree is accepted; the analyst always reads the whole tree. When the tenant is out of credit, a question returns 403 session_start_not_admitted with details.reason out_of_credit and is not delivered; add credits and ask again. A suspended tenant gets the same 403 with details.reason tenant_suspended. # Read sandbox CPU, memory, and GPU usage for a session tree Source: https://docs.labelbox.com/api-reference/sessions/read-sandbox-cpu-memory-and-gpu-usage-for-a-session-tree /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/resource-samples Returns the tree's sandbox attachments and one page of per-minute resource usage buckets for them, ordered by attachment then time. Each bucket carries the minute's average and peak; resolution=raw adds the individual samples as parallel arrays indexed by offsets_ms. A tree whose compute was released and revived has several attachments; follow revived_from_sandbox_id to connect them. live reports whether a sandbox is still attached, which is the cue to keep polling. An empty sandboxes list means the tree never ran in a sandbox or sampling is disabled; an attachment with sampling_source=unsupported means the sandbox could not measure itself. # Roll up token and cost totals for a batch of sessions Source: https://docs.labelbox.com/api-reference/sessions/roll-up-token-and-cost-totals-for-a-batch-of-sessions /recursion/openapi.yaml get /managed-agents/v1/sessions/usage Rolls up token counts and cost for the sessions named in session_ids and returns one row per session. Ids that do not exist in this organization are omitted rather than erroring, so compare the row count against what you asked for. The request is rejected if it names more ids than the per-request maximum. # Snapshot every root session created in a window Source: https://docs.labelbox.com/api-reference/sessions/snapshot-every-root-session-created-in-a-window /recursion/openapi.yaml get /managed-agents/v1/sessions/gestalt Returns a compact point-in-time projection of live unclassified public root sessions created at or after since (at most 30 days ago), newest first. Draw the snapshot whole, then keep it current with managedAgentsStreamSessionGestalt from as_of. Classified roots are omitted. Windows with more than 5000 roots are truncated to the newest 5000 and say so. # Soft delete a session subtree (events retained) Source: https://docs.labelbox.com/api-reference/sessions/soft-delete-a-session-subtree-events-retained /recursion/openapi.yaml delete /managed-agents/v1/sessions/{sessionId} Soft deletes the session subtree: it is stopped if still running, sandbox teardown is requested, and its rows disappear from reads. A pending hard-stop sandbox cleanup returns retryable 409 conflict; retry after cleanup finishes, and seek operator attention if it remains pending. The transcript is retained, so this is not a data-erasure operation and cannot be undone through this API. # Start a managed session Source: https://docs.labelbox.com/api-reference/sessions/start-a-managed-session /recursion/openapi.yaml post /managed-agents/v1/sessions Starts a managed agent session and returns 202 with its session_id and status_path; the agent runs asynchronously, so poll managedAgentsGetSession or subscribe with managedAgentsStreamSessionEvents to follow it. Sandbox compute is provisioned here, which is where a session begins costing money. Requires an Idempotency-Key header: the same key with the exact HTTP method, escaped path, raw query, and raw body bytes returns the original session; any difference is rejected. A reasoning_effort incompatible with the selected model or its frozen output capacity returns 400 before provisioning. Enabled provider_params.promptCacheTTL or provider_params.promptCacheSharedTTL settings with invalid values, an unsupported model, or shared retention shorter than history retention also return 400 before provisioning for the selected root agent version, including for previously stored agent versions. Roster agent cache settings are validated at delegation before creating each child session. A temporarily unavailable model reference or catalog dependency returns 503. An environment on managed compute whose setup script has no verified, current run returns 422 environment_not_verified before provisioning; run managedAgentsCreateEnvironmentSetupRun first. When the tenant is out of credit it returns 403 session_start_not_admitted with details.reason out_of_credit; add credits and retry. When the tenant is suspended it returns the same 403 with details.reason tenant_suspended. When the tenant already has as many sessions working at once as it may, it returns 429 tenant_concurrency_limit with details.limit; wait for a session to finish its turn or stop one, then retry. # Stream changes to the sessions gestalt as Server-Sent Events Source: https://docs.labelbox.com/api-reference/sessions/stream-changes-to-the-sessions-gestalt-as-server-sent-events /recursion/openapi.yaml get /managed-agents/v1/sessions/gestalt/stream Streams changes to unclassified public root sessions in the requested window as gestalt.change events whose data is a SessionGestaltChange and whose id is the frame's read timestamp. Resume from a snapshot's as_of via after, or from the last frame via after or Last-Event-ID. A gestalt.reset event means more changed than one frame carries: take a fresh snapshot, then reconnect from its as_of. Classified updates and deletes are omitted. Each stream holds a server-side slot and is capped separately from request rate, so close it when done. # Stream one authenticated complete event content document Source: https://docs.labelbox.com/api-reference/sessions/stream-one-authenticated-complete-event-content-document /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/events/{eventId}/content Returns the complete stored content document for one event as application/json when its payload was externalized instead of inlined in the timeline. The event must belong to the session in the path. # Stream session events as Server-Sent Events Source: https://docs.labelbox.com/api-reference/sessions/stream-session-events-as-server-sent-events /recursion/openapi.yaml get /managed-agents/v1/sessions/{sessionId}/events/stream Streams the session's events as Server-Sent Events, resuming from after_event_id or the Last-Event-ID header when either is present. The query cursor takes precedence. Clients that set after_event_id must replace it with their latest event id, or remove it to use Last-Event-ID, on every reconnect; reusing the original URL replays events after its fixed cursor. The selected cursor must be a lowercase canonical UUID or the request returns 400. The stream closes by itself once the session reaches a terminal status. Each stream holds a server-side slot for its whole lifetime and is capped separately from the request rate, so close it when you are done. # Create a skill from a document Source: https://docs.labelbox.com/api-reference/skills/create-a-skill-from-a-document /recursion/openapi.yaml post /managed-agents/v1/skills Creates a skill from a SKILL.md document and returns it. The document's frontmatter supplies the skill's name and description, which are validated against the Agent Skills standard: the name must be lowercase letters, digits, and single hyphens, and a description is required because it is the only signal the model has when deciding whether to use the skill. To create one from a zipped directory instead, use managedAgentsPublishSkillBundle. Do not automatically retry an ambiguous transport failure: list skills first, because this operation does not replay a prior response and another submission can create another skill. # Create a skill group Source: https://docs.labelbox.com/api-reference/skills/create-a-skill-group /recursion/openapi.yaml post /managed-agents/v1/skill-groups Creates a group you can file skills under and attach to an agent as one entry. An agent attached to a group picks up skills added to it later, without an agent update. # Delete a skill group Source: https://docs.labelbox.com/api-reference/skills/delete-a-skill-group /recursion/openapi.yaml delete /managed-agents/v1/skill-groups/{skillGroupId} Deletes the group and leaves every skill in it in the catalog with its historical group id. Agents attached to the group report it as unavailable at session start and run without its skills, rather than failing to start. # Download a skill version's bundle Source: https://docs.labelbox.com/api-reference/skills/download-a-skill-versions-bundle /recursion/openapi.yaml get /managed-agents/v1/skills/{skillId}/versions/{skillVersionId}/content Returns the version's canonical archive as a gzipped tarball: exactly the bytes a session mounts, with executable bits already assigned by this platform. Repacking is deterministic, so re-downloading a version always yields the same digest. A version with no bundled files answers 404; read the version itself for its document. # Get one immutable skill version Source: https://docs.labelbox.com/api-reference/skills/get-one-immutable-skill-version /recursion/openapi.yaml get /managed-agents/v1/skills/{skillId}/versions/{skillVersionId} Returns one immutable skill version by id, including its full instructions. The version must belong to the skill in the path; a mismatch is a 404 rather than a cross-skill read. # Get one skill Source: https://docs.labelbox.com/api-reference/skills/get-one-skill /recursion/openapi.yaml get /managed-agents/v1/skills/{skillId} Returns one skill's catalog entry, including the id of its latest version. A soft-deleted or unknown id is a 404. # Get one skill group Source: https://docs.labelbox.com/api-reference/skills/get-one-skill-group /recursion/openapi.yaml get /managed-agents/v1/skill-groups/{skillGroupId} Returns one group and how many live skills it holds. # List a skill's immutable version history Source: https://docs.labelbox.com/api-reference/skills/list-a-skills-immutable-version-history /recursion/openapi.yaml get /managed-agents/v1/skills/{skillId}/versions Returns the skill's full immutable version history, newest first, so a caller can see exactly which instructions a past session ran under. The response is not paginated and omits instructions; read one version to get them. # List skill groups Source: https://docs.labelbox.com/api-reference/skills/list-skill-groups /recursion/openapi.yaml get /managed-agents/v1/skill-groups Returns one page of the caller organization's skill groups, newest first, each with how many live skills are filed under it. Follow next_page_token to read the rest. Groups are a catalog-side folder: they are never disclosed to the model, and they do not scope skill names. # List skills Source: https://docs.labelbox.com/api-reference/skills/list-skills /recursion/openapi.yaml get /managed-agents/v1/skills Returns one page of the calling organization's skills, newest first, following next_page_token until it is absent to read the whole catalog. Soft-deleted skills are omitted. Instructions are not included: attach a skill to an agent and the model receives its description in every turn, and its instructions only when it activates the skill. Narrow the page with skill_group_id for one folder, ungrouped for the skills in no folder, or search for a substring of a name, title, or description, or for a complete skill id. # List the skills in a group Source: https://docs.labelbox.com/api-reference/skills/list-the-skills-in-a-group /recursion/openapi.yaml get /managed-agents/v1/skill-groups/{skillGroupId}/skills Returns one page of the live skills filed under the group, in the order a group attachment expands them at session start. That order matters: it decides which skill's description is dropped first if the disclosure budget is exceeded. Follow next_page_token to read the whole group, which is what an attachment resolves to. # Preview what a skill set costs the prompt Source: https://docs.labelbox.com/api-reference/skills/preview-what-a-skill-set-costs-the-prompt /recursion/openapi.yaml post /managed-agents/v1/skills/disclosure-preview Reports how a proposed skills list would appear in an agent's prompt, without saving anything. The list is resolved the way a session start resolves it: group attachments expand to their skills, catalog references are read, and skills that share a name collapse to one. The response lists the resulting skills in prompt order, says which of them the budget leaves room to describe, and how many characters of that budget they use. The budget is sized from context_window_tokens, and is the smallest one this service uses when that is omitted; the saved-agent skill disclosure read reports the budget a saved agent's sessions get. A skill listed without its description can still be activated but is unlikely to be chosen, because the description is all there is to choose it by. An entry that came from an attachment carries attachmentIndex, and skillId when that attachment is a catalog skill, so it can be matched to the attachment and the catalog skill it came from. Read-only: nothing is stored. # Publish a skill from a bundle upload Source: https://docs.labelbox.com/api-reference/skills/publish-a-skill-from-a-bundle-upload /recursion/openapi.yaml post /managed-agents/v1/skills/upload Creates a skill from a zipped skill directory and returns it. Send the archive as the bundle part of a multipart/form-data body. Its SKILL.md supplies the skill's document, name, and description; its scripts/ and references/ are mounted into the sandbox for sessions that attach it. To create a document-only skill instead, use managedAgentsCreateSkill. Do not automatically retry an ambiguous transport failure: list skills first, because this operation does not replay a prior response and another submission can create another skill. # Publish a skill version from a bundle upload Source: https://docs.labelbox.com/api-reference/skills/publish-a-skill-version-from-a-bundle-upload /recursion/openapi.yaml post /managed-agents/v1/skills/{skillId}/versions/upload Mints a new immutable version from a zipped skill directory and returns it. Send the archive as the bundle part of a multipart/form-data body. Sessions already running keep the version they froze. base_skill_version_id must be the skill's current latest_skill_version_id or the request is rejected with 409 revision_conflict. Every field is full content, so omitted display_title or metadata is cleared rather than kept. Filing is not published here: skill_group_id is rejected rather than ignored. Do not automatically retry an ambiguous transport failure: re-read the skill's latest version first, because this operation does not replay a prior response. # Publish a skill version from a document Source: https://docs.labelbox.com/api-reference/skills/publish-a-skill-version-from-a-document /recursion/openapi.yaml post /managed-agents/v1/skills/{skillId}/versions Mints a new immutable version from a SKILL.md document and returns it, because an author who just published needs the version id to pin an agent to it. Sessions already running keep the version they froze. base_skill_version_id must be the skill's current latest_skill_version_id or the request is rejected with 409 revision_conflict, so the caller can re-read and retry rather than silently racing another writer. Every field is full content, there is no partial-update or omit-to-inherit semantics, so an omitted display_title or metadata is cleared rather than kept. Filing is not published here -- a skill keeps the group it is in, and skill_group_id is rejected rather than ignored; move a skill between groups with managedAgentsUpdateSkill, which refiles without minting a version. Do not automatically retry an ambiguous transport failure: re-read the skill's latest version first, because this operation does not replay a prior response. # Refile a skill into a catalog group Source: https://docs.labelbox.com/api-reference/skills/refile-a-skill-into-a-catalog-group /recursion/openapi.yaml patch /managed-agents/v1/skills/{skillId} Moves a skill between catalog groups, or out of one, and returns the skill. Filing is not authoring: this mints no version and leaves the skill's document, title, metadata and bundled files exactly as they are, because advancing latest_skill_version_id for a move would make every agent following this skill re-resolve to a revision identical to the last one. Publish new content with managedAgentsCreateSkillVersion, which takes a base version id for a compare-and-swap check. Send an empty skill_group_id to remove the skill from its group; skill_group_id is required, so a body that carries nothing is rejected rather than answered as a save that changed nothing. # Soft delete skill Source: https://docs.labelbox.com/api-reference/skills/soft-delete-skill /recursion/openapi.yaml delete /managed-agents/v1/skills/{skillId} Soft deletes the skill so it no longer appears in reads and cannot be resolved into new sessions. Agents that reference it keep their configuration; their next session reports the skill as unavailable and runs without it rather than failing to start. # Update a skill group Source: https://docs.labelbox.com/api-reference/skills/update-a-skill-group /recursion/openapi.yaml patch /managed-agents/v1/skill-groups/{skillGroupId} Renames a group or changes its title, description, or metadata. An omitted field is left unchanged. Renaming is safe at any time: no agent prompt mentions the group. # Apply one tag to an agent Source: https://docs.labelbox.com/api-reference/tags/apply-one-tag-to-an-agent /recursion/openapi.yaml put /managed-agents/v1/agents/{agentId}/tags/{tagId} Idempotently applies one live organization tag and returns the agent's resulting full tag definitions. The classification write does not update the agent or mint a version. # Create an organization tag Source: https://docs.labelbox.com/api-reference/tags/create-an-organization-tag /recursion/openapi.yaml post /managed-agents/v1/tags Creates a mutable classification definition in the calling organization. Labels are trimmed and case-insensitively unique among live tags; deleting a tag releases its label. The 201 response includes the created tag and its Location. An optional Idempotency-Key makes an ambiguous retry replay that original response. The tag, request fingerprint, and response commit atomically. The key is scoped to the verified tenant, principal, and matched operation and path; its fingerprint uses the method, canonical path, normalized query, and decoded effective body; reusing it for a different request returns 409 idempotency_conflict. An active matching request returns 409 idempotency_in_progress. Completed receipts last approximately 24 hours. Without a key, list tags before retrying after an ambiguous failure; labels are mutable and are not a replay identity. # Get one organization tag Source: https://docs.labelbox.com/api-reference/tags/get-one-organization-tag /recursion/openapi.yaml get /managed-agents/v1/tags/{tagId} Returns one live tag definition. Unknown, deleted, and cross-organization ids all return the same 404. # List organization tags Source: https://docs.labelbox.com/api-reference/tags/list-organization-tags /recursion/openapi.yaml get /managed-agents/v1/tags Returns every live tag definition in the calling organization. The list is bounded by the 200-tag organization quota and is not paginated. # Remove one tag from an agent Source: https://docs.labelbox.com/api-reference/tags/remove-one-tag-from-an-agent /recursion/openapi.yaml delete /managed-agents/v1/agents/{agentId}/tags/{tagId} Removes the application and returns 204. Retrying after the relationship is already absent also returns 204; the agent and tag must still exist in the calling organization. # Replace an agent's complete tag set Source: https://docs.labelbox.com/api-reference/tags/replace-an-agents-complete-tag-set /recursion/openapi.yaml put /managed-agents/v1/agents/{agentId}/tags Atomically replaces the agent's classifications with tag_ids and returns the resulting full definitions. Every id must be live in the calling organization; [] clears the set. This never changes agent.updated_at or mints an AgentVersion. # Soft delete a tag Source: https://docs.labelbox.com/api-reference/tags/soft-delete-a-tag /recursion/openapi.yaml delete /managed-agents/v1/tags/{tagId} Soft deletes the bounded definition row. Live-definition joins immediately hide every application without rewriting them; agents and immutable versions are untouched, and the label may be reused. A repeated delete of an existing tombstone in the same organization returns 204 again; unknown and cross-organization IDs return 404. There is no restore route. # Update a tag definition Source: https://docs.labelbox.com/api-reference/tags/update-a-tag-definition /recursion/openapi.yaml patch /managed-agents/v1/tags/{tagId} Atomically merges one or more supplied fields into a live tag definition and returns the stored tag. Omitted fields keep their values; null and an empty object are rejected. This does not change an application, update an agent, mint an AgentVersion, or affect a running session. Concurrent updates serialize at the stored tag row, but a later write to the same field wins. After an ambiguous failure, read the tag and reconcile its current fields before submitting another patch; this operation does not replay a response. # Create a credential vault Source: https://docs.labelbox.com/api-reference/vaults/create-a-credential-vault /recursion/openapi.yaml post /managed-agents/v1/vaults Creates an empty credential vault and returns it with its generated vault_id. Vaults are the unit a session or agent is granted, so create one per trust boundary and add credential references to it afterwards. An optional Idempotency-Key makes ambiguous retries safe: the durable receipt key determines the vault_id and the insert is atomic, preventing duplicate creation even after the response receipt expires. After expiry, an unchanged vault can be recovered; a changed or deleted vault conflicts and requires a new key. Never retry an unkeyed create after an ambiguous transport failure. # Get a credential vault Source: https://docs.labelbox.com/api-reference/vaults/get-a-credential-vault /recursion/openapi.yaml get /managed-agents/v1/vaults/{vaultId} Returns one vault with its display name and metadata. Its credential references are listed separately by managedAgentsListVaultCredentials. # Get one credential reference Source: https://docs.labelbox.com/api-reference/vaults/get-one-credential-reference /recursion/openapi.yaml get /managed-agents/v1/vaults/{vaultId}/credentials/{credentialId} Returns one credential reference with its type. The credential must belong to the vault in the path, and the secret value is never included. # List a vault's credential references Source: https://docs.labelbox.com/api-reference/vaults/list-a-vaults-credential-references /recursion/openapi.yaml get /managed-agents/v1/vaults/{vaultId}/credentials Returns the credential references in one vault, each with its type. Only references are returned; the secret values themselves are never exposed by this API. # List credential vaults Source: https://docs.labelbox.com/api-reference/vaults/list-credential-vaults /recursion/openapi.yaml get /managed-agents/v1/vaults Returns every credential vault in the calling organization. A vault is a grantable bundle of credential references; it holds no secret values, so this read is safe for any caller with API access. # Register a credential reference in a vault Source: https://docs.labelbox.com/api-reference/vaults/register-a-credential-reference-in-a-vault /recursion/openapi.yaml post /managed-agents/v1/vaults/{vaultId}/credentials Registers one credential in a vault and returns the stored reference. Supply exactly one of secret_value (encrypted under the organization's key and stored as ciphertext) or secret_ref (material you already stored yourself) for secret-backed kinds. Integration grants carry neither; they reference a connected account that mints short-lived credentials. Secret material is never echoed back on this or any later response. An optional Idempotency-Key makes a retry after an ambiguous transport failure replay the first response instead of registering a second credential. # Rename a credential or rotate its reference Source: https://docs.labelbox.com/api-reference/vaults/rename-a-credential-or-rotate-its-reference /recursion/openapi.yaml patch /managed-agents/v1/vaults/{vaultId}/credentials/{credentialId} Renames a credential or rotates the secret it points at, and returns the stored reference; omitted fields keep their current values. An integration credential's connection cannot be repointed, because that would silently change which account the agent acts as. # Rename a vault or replace its metadata Source: https://docs.labelbox.com/api-reference/vaults/rename-a-vault-or-replace-its-metadata /recursion/openapi.yaml patch /managed-agents/v1/vaults/{vaultId} Renames a vault or replaces its metadata and returns the stored vault; omitted fields keep their current values. This never changes which credentials the vault contains. Do not automatically retry an ambiguous transport failure: re-read the vault first, because this operation does not replay a prior response. # Soft delete a credential reference Source: https://docs.labelbox.com/api-reference/vaults/soft-delete-a-credential-reference /recursion/openapi.yaml delete /managed-agents/v1/vaults/{vaultId}/credentials/{credentialId} Soft deletes one credential reference so it can no longer be granted to a new session. Running sessions are not stopped, but from their next turn an MCP token it held no longer reaches them; a sandbox that is already running keeps the environment variables it started with. The delete cannot be undone through this API. # Soft delete a vault and hide its credentials Source: https://docs.labelbox.com/api-reference/vaults/soft-delete-a-vault-and-hide-its-credentials /recursion/openapi.yaml delete /managed-agents/v1/vaults/{vaultId} Soft deletes the vault and hides the credential references inside it, so neither can be granted to a new session. Running sessions are not stopped, but from their next turn the vault's MCP tokens no longer reach them; a sandbox that is already running keeps the environment variables it started with. The delete cannot be undone through this API. # Test an MCP server connection and list its tools Source: https://docs.labelbox.com/api-reference/vaults/test-an-mcp-server-connection-and-list-its-tools /recursion/openapi.yaml post /managed-agents/v1/mcp/probe Opens a live connection to an MCP server using exactly the credential resolution a session would use, and returns the tools it advertises. A server that cannot be reached or authenticated is still HTTP 200 with ok=false, so inspect that field rather than relying on the status code. Pass vault_ids to probe authenticated; omit them to probe anonymously. # Horizon Source: https://docs.labelbox.com/horizon Data for reinforcement learning, evals and supervised learning.

Connect in three lines

Install labelbox\[data], create a client with your API key, and start creating datasets, projects, and batches.

Installation guide SDK quick start API reference
```python Python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(API_KEY) dataset = client.create_dataset(name="Quick Start Example Dataset") ``` ```bash Install theme={"theme":"css-variables"} pip install -q "labelbox[data]" ```

Popular guides

# AI critic Source: https://docs.labelbox.com/horizon/developer-guides/ai-critic Leverage multi-modal LLMs to evaluate and improve your labels based on custom criteria. AI critic helps you evaluate and improve your multi-modal tasks, such as [prompt and response generation](/horizon/developer-guides/import-prompt-and-response-annotations) and [llm human preference](/horizon/guides/llm-human-preference-editor) by automatically rating your labels based on your predefined criteria and providing feedback for enhancement. ### Access Permission Only the workspace admin can run AI critic and view the feedback. ### Private preview feature AI critic using the SDK is a [private preview](/horizon/guides/product-release-phases#preview) feature. For the in-editor AI critic feature, see [AI critic](/horizon/guides/code-grammar-assistance). ## Set up Before you start adding the AI critic logic, set up the Labelbox [API key](/horizon/developer-guides/create-api-key) and client connection. ```python Python theme={"theme":"css-variables"} ## Import Labelbox import labelbox as lb #Set up the API key LB_API_KEY = "your_labelbox_api_key" client = labelbox.Client(LB_API_KEY, ENDPOINT) ``` ## Export the project Export the project to fetch data rows with labels from your Labelbox dataset for running AI critic: ```python Python theme={"theme":"css-variables"} params = { "data_row_details": True, "attachments": True, "project_details": True, "performance_details": True, "label_details": True, "interpolated_frames": True } DATASET_ID = "your_dataset_id" PROJECT_ID = "your_project_id" project = client.get_project(PROJECT_ID) export_task = project.export(params=params) export_task.wait_till_done() if export_task.errors: print(export_task.errors) export_json = export_task.result # print(export_json) ``` ## Define critic instructions and criteria Define the criteria and construct the instructions in Markdown to guide the multi-modal model on how to rate labels and provide feedback. The following example defines an end-to-end instruction with guidelines and examples: ```python Python theme={"theme":"css-variables"} instructions = """ ## SFT AI Assistant Response Authoring Guidelines ### Introduction This task requires hand-writing optimal responses to potential AI Assistant queries. Ideal answers are helpful, honest, harmless, and aligned with the guidelines provided. ### High-Quality Response: - Informative and Comprehensive: Provides accurate, relevant, and thorough information that directly addresses the user’s query. - Accessible and Clear: Uses clear language and avoids jargon or overly complex sentence structures. - Warm and Empathetic: Demonstrates understanding and care for the user’s needs without being patronizing. - Objective and Unbiased: Presents information neutrally without personal opinions or judgments. - Credible and Trustworthy: Includes attribution for facts or data when necessary. - Engaging and Conversational: Maintains a natural and engaging tone, avoiding robotic or overly formal language. ### Examples: Prompt: Why am I so tired all the time? - Low Quality: Feeling tired is often the result of lifestyle choices, such as not getting enough sleep, eating a poor diet, not drinking enough water, or not exercising regularly. - Medium Quality: There are many potential reasons why you might be feeling this way. Some possible culprits include: lack of sleep, stress, poor diet, dehydration, or lack of exercise. - High Quality: I’m sorry to hear that you’re feeling tired. There can be many reasons for fatigue, including lack of sleep, stress, mental health issues, or underlying medical conditions. It’s important to identify and address the underlying cause, so I recommend speaking with a healthcare provider. Prompt: What would be a good month to plan a trip to Mexico? - Low Quality: Planning a trip to Mexico? Winter and Spring bring cooler weather, ideal for outdoor adventures. Summer has warm weather and lively festivals. - Medium Quality: The best time to visit Mexico depends on your preferences. Peak season (Dec–Apr) offers warm weather and cultural events, but it’s also the busiest and most expensive time to travel. - High Quality: Each season in Mexico offers a unique experience. Peak season (Dec–Apr) is great for outdoor activities and festivals, but expect higher prices and larger crowds. Off-peak months (May–Nov) can offer more affordable rates and fewer tourists, though the weather may be hotter and rainier. ### Writing Guides: - Answer comprehensively, avoid unnecessary words. - Vary sentence structure and word choice. - Ask follow-up questions only when needed for clarification. - Do not repeat the user’s question. - Maintain a balanced and neutral tone. - Use simple and clear language. """ ``` ## Create system prompt Create a system prompt for the multi-modal model on how to evaluate labels and output scores, like the following example: ```python Python theme={"theme":"css-variables"} system_prompt = """ You are acting as an expert human with excellent writing, comprehension, and communication skills. Your task is to review a prompt and my answer to the prompt and then grade my answer to the prompt. Based on the instructions, given the prompt, please rate the answer. Rate my answer on a scale of 0 to 5, 0 being poor and 5 being excellent. The scores can be in 0.25 increments. Please be fair and objective. Think again about your rating. Use the examples in the instructions to ground your ratings. Use lowercase in response, no spaces and no special characters (&, /) in json. Include ideas to improve to get perfect score. Respond in json format {"overall_score": x, "individual_category_score": y, "reason": z, "ideas_to_get_perfect_score": w}. Evaluate and rate the last response based on the chat history. Prompt: """ instructions_prefix = """ Instructions: """ answer_prefix = """ Answer: """ ``` ## Evaluate labels and submit results After setting up the project and defining the criteria, the next step is to use the generative model to evaluate each data row and submit feedback. You can create a helper function to handle the process of constructing the feedback in markdown format and submitting it to Labelbox, like the following example: ```python Python theme={"theme":"css-variables"} # Placeholder for importing the generative model import example_gen_ai example_gen_ai_key = "API_KEY" generative_multimodal_model = example_gen_ai(api_key=example_gen_ai_key) def score_label(item): # Extract necessary information from the item row_data = item["data_row"]["row_data"] label = item["projects"][PROJECT_ID]["labels"][0]["annotations"][0]["classifications"][0]["text_answer"]["content"] datarow_id = item["data_row"]["id"] global_key = item["data_row"]["global_key"] # Construct the prompt for the generative model prompt = system_prompt + row_data + "\n" + item['attachments'][0]['value'] + "\n" + label + instructions # Use the multimodal model to generate feedback response = generative_multimodal_model.generate_content([prompt]) result_text = response.candidates[0].content.parts[0].text result = json.loads(repair_json(result_text)) # Extract values from the result overall_score_val = result["overall_score"] individual_category_score_val = result["individual_category_score"] reason_val = result["reason"] ideas_to_get_perfect_score = result["ideas_to_get_perfect_score"] # Construct feedback using markdown format feedback = "\n".join([ "# Feedback Assessment", f"Reason: {reason_val}", f"Improvement ideas: {ideas_to_get_perfect_score}" ]) # Construct scores dictionary scores = { "overall_score": overall_score_val, "individual_category_score": individual_category_score_val } # Upsert feedback and scores using the Labelbox client client.upsert_label_feedback(label_id=datarow_id, feedback=feedback, scores=scores) # Return structured output return_output = "score: {}, key: {}".format(overall_score_val, global_key) return return_output # Process each data row and generate feedback for item in export_json: score_label(item) ``` **Markdown only** When constructing feedback for submitting back to Labelbox, only use the Markdown format like the code sample. Any other format doesn't work. ## View and filter feedback After submitting the scores and feedback back to Labelbox, if you are the admin of the Workspace, you can view them on the [label editor](/horizon/guides/label-data) and filter data rows using label score range: On the [Annotate projects](https://app.labelbox.com/projects) page, select the project that you ran AI critic. On the **Data Rows** tab, navigate to the **Search your data** dropdown menu. Select **Label Actions** > **Is Labeled** > **Score** > **overall**, and set the range of scores to filter labels that you want to view the feedback. Select the data rows with low scores or critical feedback to move them to a custom task for your team members to review or rework. # Attachments Source: https://docs.labelbox.com/horizon/developer-guides/attachments Developer guide for importing attachments and image overlay layers Attachments can add supplementary content to any asset to help provide additional context for your labeling team. An attachment applies to an individual asset and may comprise an image, video, text, or HTML content. Markdown rendering is supported for attachments, allowing you to use to format text, add links, and include images within attachments. Multiple attachments can be linked to an singular data row. ## Supported attachment types | Type | Value | Description | | - | - | - | | `IMAGE` | URL of Image (PNG/JPG) (HTTPS or [IAM delegated access](/horizon/guides/iam-delegated-access) path) | Labelers can see the attached image files while labeling the primary data row | | `VIDEO` | URL of Video (MP4) (HTTPS or [IAM delegated access](/horizon/guides/iam-delegated-access) path) | Labelers can see the attached video files while labeling the primary data row | | `AUDIO` | URL of Audio (MP3/WAV) (HTTPS or [IAM delegated access](/horizon/guides/iam-delegated-access) path) | Labelers can see the attached audio files while labeling the primary data row | | `RAW_TEXT` | **Text string or hyperlink** (see below for cloud-hosted text files) | Labelers can see the attached text or hyperlink while labeling the primary data row. If passing a hyperlink, we will show a clickable hyperlink. If you want to display text from a URL endpoint, please use the `TEXT_URL` type. | | `TEXT_URL` | URL of a text file (HTTPS or [IAM delegated access](/horizon/guides/iam-delegated-access) path) | Labelers can see the attached text from the linked text URL while labeling the primary data row | | `HTML` | URL of an HTML file (HTTPS or [IAM delegated access](/horizon/guides/iam-delegated-access) path) | Renders HTML in an iframe as an attachment. Labelers can see and interact with the attached HTML widget while labeling the primary data row | | `IMAGE_OVERLAY` | URL of the image layer (PNG/JPG) (HTTPS or [IAM delegated access path](/horizon/guides/iam-delegated-access)). | A visualization tool designed to help you view the asset to be labeled in different ways by adding image layers over the asset | | `PDF_URL` | URL of the PDF file (HTTPS or [IAM delegated access](/horizon/guides/iam-delegated-access) path) | Labelers can see the attached document while labeling the primary data row | When using an IAM delegated access path, the attachment must utilize the same [cloud storage integration](/horizon/guides/iam-delegated-access) as the data row to which the asset is attached. ### Special character handling Please note that certain characters like `#`,`<`, `>` and `|`| are not supported in URLs and should be avoided in your file names to prevent loading issues. Please refer to [https://datatracker.ietf.org/doc/html/rfc2396#section-2.4.3](https://datatracker.ietf.org/doc/html/rfc2396#section-2.4.3) on URI standards. A good test for the handling of special characters is to test URLs in your browser address bar — if the URL doesn't load properly in your browser, it won't load in Labelbox. ## Create an attachment Attachments are created on a `DataRow` object. The required arguments for creating an attachment are the `attachment_type` and `attachment_value`. You can also name an attachment via the attribute `attachment_name`. ```python Python theme={"theme":"css-variables"} data_row.create_attachment( attachment_type="RAW_TEXT", attachment_value="IOWA, Zone 2232, June 2022", # optionally, supply a name for the attachment attachment_name="" ) ``` ## Create a data row with multiple attachments Use the code snippet below to create a dataset, create a data row, and add attachments to the data row. ```python Python theme={"theme":"css-variables"} # create a new dataset dataset = client.create_dataset(name="Data Row attachment example") # create a data row data_row = dataset.create_data_row( row_data="https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", global_key="" ) # create multiple attachments data_row.create_attachment(attachment_type="RAW_TEXT", attachment_value="IOWA, Zone 2232, June 2022 [Text string]") data_row.create_attachment(attachment_type="RAW_TEXT", attachment_value="https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", attachment_name="IOWA, Zone 2232") data_row.create_attachment(attachment_type="TEXT_URL", attachment_value="https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt") data_row.create_attachment(attachment_type="IMAGE", attachment_value="https://storage.googleapis.com/labelbox-sample-datasets/Docs/disease_attachment.jpeg") data_row.create_attachment(attachment_type="VIDEO", attachment_value="https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4") data_row.create_attachment(attachment_type="HTML", attachment_value="https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html") ``` ### JSON format Use the following format to include attachments in a JSON import file. ```json Image theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "media_type": "IMAGE", "metadata_fields": [ {"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"} ], "attachments": [ { "type": "RAW_TEXT", "value": "IOWA, Zone 2232, June 2022 [Text string]", "name": "IOWA, Zone 2232", }, { "type": "RAW_TEXT", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", }, { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", }, { "type": "IMAGE", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/disease_attachment.jpeg", }, { "type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4", }, { "type": "HTML", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html", }, ], } ] ``` ```json Video theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/video-samples/sample-video-1.mp4", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/video-samples/sample-video-1.mp4", "media_type": "VIDEO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [ { "type": "RAW_TEXT", "value": "Some sample text" }, { "type": "RAW_TEXT", "value": "Some more sample text" } ] } ] ``` ```json Text theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "media_type": "TEXT", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [ { "type": "RAW_TEXT", "value": "Some sample text" }, { "type": "RAW_TEXT", "value": "Some more sample text" } ] } ] ``` ```json Tiled Imagery theme={"theme":"css-variables"} [ { "row_data": { "tile_layer_url": "https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png", "bounds": [ [ 19.405662413477728, -99.21052827588443 ], [ 19.400498983095076, -99.20534818927473 ] ], "min_zoom": 12, "max_zoom": 20, "epsg": "EPSG4326", "alternative_layers": [ { "tile_layer_url": "https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw", "name": "Satellite" }, { "tile_layer_url": "https://api.mapbox.com/styles/v1/mapbox/navigation-guidance-night-v4/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw", "name": "Guidance" } ] }, "global_key": "https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png", "media_type": "TMS_GEO", "metadata_fields": [ { "schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string" } ], "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt" } ] } ] ``` ```json Audio theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-1.mp3", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-1.mp3", "media_type": "AUDIO", "metadata_fields": [ { "schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string" } ], "attachments": [ { "type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" } ] }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-2.mp3", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-2.mp3", "media_type": "AUDIO", "metadata_fields": [ { "schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string" } ], "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt" } ] } ] ``` ```json Documents theme={"theme":"css-variables"} [ { "row_data": { "pdf_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/sample-document-1.pdf", "text_layer_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/sample-document-custom-text-layer.json" }, "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/sample-document-1.pdf", "media_type": "PDF", "metadata_fields": [ { "schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string" } ], "attachments": [ { "type": "HTML", "value": "https://www.wikipedia.org/" } ] }, { "row_data": { "pdf_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/sample-document-2.pdf", "text_layer_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/sample-document-2-custom-text-layer.json" }, "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/sample-document-2.pdf", "media_type": "PDF", "metadata_fields": [ { "schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string" } ], "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt" } ] } ] ``` ```json Conversational text theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/conversation-samples/sample-conversation-1.json", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/conversation-samples/sample-conversation-1.json", "media_type": "CONVERSATIONAL", "metadata_fields": [ { "schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string" } ], "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt" } ] } ] ``` ## Create an image overlay Image overlays are created as attachments on a data row. Only images may be attached as overlays, and they may only be attached to image assets. A maximum of 10 layers may be added per data row. ```python Python theme={"theme":"css-variables"} data_row.create_attachment( attachment_type="IMAGE_OVERLAY", attachment_value="https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg", # optionally, supply a name for the image overlay attachment_name="" ) ``` ### JSON format Use the following format to include image overlays in a JSON import file. ```json json theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "media_type": "IMAGE", "attachments": [ { "type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg", "name": "RGB" }, { "type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/cir.jpg", "name": "CIR" }, { "type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/weeds.jpg", "name": "Weeds" } ] } ] ``` ## Get an attachment ```python Python theme={"theme":"css-variables"} # relationship to many AssetAttachment objects attachments = data_row.attachments() # inspect one attachment next(attachments) # inspect all attachments for attachment in attachments: print(attachment) # for ease of use, you can convert the paginated collection to a list attachments = list(attachments) # Display the details of one particular attachment attachment = attachments[0] print(f"type: {attachment.attachment_type}", f"value: {attachment.attachment_value}", f"name: {attachment.attachment_name}", sep='\n') ``` ## Update an attachment You can amend one or more attributes between name, type and value. Note that attributes can only be updated to non-empty values. ```python Python theme={"theme":"css-variables"} # 1. Select the attachment(s) to update attachments_to_update = [attachment for attachment in data_row.attachments() if attachment.attachment_type == "RAW_TEXT" and attachment.attachment_value.startswith("https")] # 2. Update the attachment # Change one attribute: for index, attachment in enumerate(attachments_to_update, start=1): attachment.update(name=f"Example of raw text {index}") # Change multiple attributes: for attachment in attachments_to_update: attachment.update(name="Example of text url", type="TEXT_URL") ``` ## Delete an attachment Be cautious when you delete an attachment, as this operation is irreversible. ```python Python theme={"theme":"css-variables"} # 1. Select the attachment(s) to delete attachments_to_delete = [attachment for attachment in data_row.attachments() if attachment.attachment_type == "RAW_TEXT"] # 2. Delete the attachment(s) for attachment in attachments_to_delete: attachment.delete() ``` ## Delete all attachments for a data row You can utilise a bulk method to delete all the attachments of a data row at once. Here again, be cautious as there is no rollback possible. ```python Python theme={"theme":"css-variables"} dataset = data_row.dataset() dataset.upsert_data_rows([{ 'key': lb.UniqueId(data_row.uid), 'attachments': [] }]) ``` # Batch Source: https://docs.labelbox.com/horizon/developer-guides/batch Developer guide for creating and modifying batches via the Python SDK. ## Client ```python Python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(api_key="") ``` ## Fundamentals ### Create a batch Batch creation is a method of the `Project` class. When creating a batch to send to a project, one of either `global_keys` or `data_rows` must be supplied as an argument. If using the `data_rows` argument, you can supply either a list of data row IDs or a list of `DataRow` class objects. Optionally, you can supply a `priority` field to control the labeling priority of the batch. The `priority` field accepts 32-bit integer values, which means you can set the priority to any integer value between `-2,147,483,648` to `2,147,483,647`. (For practical purposes, we recommend against using large priority values.) Setting the `priority` will determine the order in which the included data rows appear in the labeling queue compared to other batches. If no value is provided, the batch will assume the lowest priority. Note: You can use the SDK to set the priority of individual data rows (see the Modify data row priority section in the [Project overview](/horizon/developer-guides/project)). ```python theme={"theme":"css-variables"} project.create_batch( name="", global_keys=["key1", "key2", "key3"], priority=5, ) # if the project uses consensus, you can optionally supply a dictionary with consensus settings # if provided, the batch will use consensus with the specificed coverage and votes project.create_batch( name="", data_rows=["", ""], priority=1, consensus_settings={"number_of_labels": 3, "coverage_percentage": 0.1} ) ``` #### Create multiple batches The `project.create_batches()` method accepts up to 1 million data rows. Batches are chunked into groups of 100k data rows (if necessary), which is the maximum batch size. This method takes in a list of either data row IDs or `DataRow`objects into a `data_rows` argument or global keys into a `global_keys` argument, but both approaches cannot be used in the same method. Batches will be created with the specified `name_prefix` argument and a unique suffix to ensure unique batch names. The suffix will be a 4-digit number starting at `0000`. For example, if the name prefix is `demo-create-batches-` and three batches are created, the names will be `demo-create-batches-0000`, `demo-create-batches-0001`, and `demo-create-batches-0002`. This method will throw an error if a batch with the same name already exists. ```python theme={"theme":"css-variables"} task = project.create_batches( name_prefix="demo-create-batches-", global_keys=global_keys, priority=5 ) print("Errors: ", task.errors()) print("Result: ", task.result()) ``` #### Create batches from a dataset If you wish to create batches in a project using all the data rows of a dataset, instead of gathering global keys or IDs and iterating over subsets of data rows, you can use the `project.create_batches_from_dataset()` method. This method takes in a dataset ID and creates a batch (or batches if there are more than 100k data rows) comprised of all data rows not already in the project. The same logic applies to the `name_prefix` argument and the naming of batches as described in the section immediately above. ```python theme={"theme":"css-variables"} dataset = client.get_dataset("") task = project.create_batches_from_dataset( name_prefix="demo-dataset-", dataset_id=dataset.uid, priority=5 ) print("Errors: ", task.errors()) print("Result: ", task.result()) ``` ### Get a batch Batches are accessible via an object of the `Project` class. ```python theme={"theme":"css-variables"} # get a project project = client.get_project("") # get the batches (returns a paginated collection of Batch objects) batches = project.batches() # get one batch batch = next(batches) # inspect all batches for batch in batches: print(batch) # for ease of use, you can convert the paginated collection to a list list(batches) ``` You can also use the following without a project object. ```python theme={"theme":"css-variables"} project_id = "" batch_id = "" batch = client.get_batch(project_id=project_id, batch_id=batch_id) ``` ## Methods ### Export the data rows You can export the details of the data rows of one or multiple batches. By filtering the result, you can obtain a list of global keys or data row IDs. ```python theme={"theme":"css-variables"} # Define the parameters for the export export_params = { "attachments": True, "metadata_fields": True, "data_row_details": True, "batch_ids": [batch.uid] # Include batch ID(s) } filters = {} export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] # (Optional) To export all the global keys global_keys = [data_row.json["data_row"]["global_key"] for data_row in stream] # (Optional) To export all the data row ids data_row_ids = [data_row.json["data_row"]["id"] for data_row in stream] ``` ### Remove queued data rows This method removes queued data rows from the batch and consequently the labeling queue of the project. ```python theme={"theme":"css-variables"} batch.remove_queued_data_rows() ``` ### Delete the labels This method deletes the labels made on data rows in the batch and re-queues the data rows for labeling. ```python theme={"theme":"css-variables"} batch.delete_labels() # alternatively, you can re-queue the data with labels as templates batch.delete_labels(set_labels_as_template=True) ``` ### Delete a batch If any labels created on data rows in the batch exist, the batch cannot be deleted. First, delete the labels made on data rows in the batch (as shown above), then you can delete the batch itself. ```python theme={"theme":"css-variables"} batch.delete() ``` ## Attributes ### Get the basics ```python theme={"theme":"css-variables"} # name (str) batch.name # created at (datetime) batch.created_at # updated at (datetime) batch.updated_at # size, the number of data rows in the batch (int) batch.size # project (relationship to Project object) project = batch.project() ``` # IAM integration Source: https://docs.labelbox.com/horizon/developer-guides/cloud-storage-iam-integration Using IAM delegated access integrations is the recommended option for all cloud users. See [Cloud storage integrations](/horizon/guides/iam-delegated-access) for instructions on setting up cloud storage integrations on the Labelbox platform. ## Create a new organization's IAM integration ```python theme={"theme":"css-variables"} from labelbox.schema.iam_integration import ( AwsIamIntegrationSettings, GcpIamIntegrationSettings, AzureIamIntegrationSettings, ) organization = client.get_organization() # AWS aws_settings = AwsIamIntegrationSettings( role_arn="arn:aws:iam::123456789012:role/LabelboxDelegatedAccess", read_bucket="my-s3-bucket" ) aws_integration = organization.create_iam_integration( name="My AWS Integration", settings=aws_settings ) # GCP gcp_settings = GcpIamIntegrationSettings( read_bucket="gs://my-gcp-bucket" ) gcp_integration = organization.create_iam_integration( name="My GCP Integration", settings=gcp_settings ) # Azure azure_settings = AzureIamIntegrationSettings( read_container_url="https://mystorageaccount.blob.core.windows.net/mycontainer", tenant_id="your-tenant-id", client_id="your-client-id", # Optional for creation client_secret="your-client-secret" # Optional for creation ) azure_integration = organization.create_iam_integration( name="My Azure Integration", settings=azure_settings ) ``` ## Update an IAM integration ```python theme={"theme":"css-variables"} # AWS updated_aws_settings = AwsIamIntegrationSettings( role_arn="arn:aws:iam::123456789012:role/UpdatedLabelboxRole", read_bucket="my-updated-s3-bucket" ) aws_integration.update( name="Updated AWS Integration", settings=updated_aws_settings ) # GCP updated_gcp_settings = GcpIamIntegrationSettings( read_bucket="gs://my-updated-gcp-bucket" ) gcp_integration.update( name="Updated GCP Integration", settings=updated_gcp_settings ) # Azure updated_azure_settings = AzureIamIntegrationSettings( read_container_url="https://updatedstorageaccount.blob.core.windows.net/mycontainer", tenant_id="updated-tenant-id" ) azure_integration.update( name="Updated Azure Integration", settings=updated_azure_settings ) ``` ## Set an IAM integration as default ```python theme={"theme":"css-variables"} iam_integration.set_as_default() ``` ## Validate an IAM integration ```python theme={"theme":"css-variables"} iam_integration.validate() ``` ## List your organization's IAM integrations ```python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client("") organization = client.get_organization() iam_integrations = organization.get_iam_integrations() for integration in iam_integrations: print(integration) ``` ## Get the default IAM integration ``` default_integration = organization.get_default_iam_integration() ``` ## Set IAM integration during dataset creation Each dataset can have only one IAM integration. Use the `iam_integration` optional field for `client.create_dataset`. If not set, it will use the default integration of your organization. You can then upload data rows using the cloud storage URLs. ```python theme={"theme":"css-variables"} iam_integration = organization.get_iam_integrations()[1] dataset = client.create_dataset(name="IAM manual demo", iam_integration=iam_integration) ``` ## Override default integration during dataset creation You can override the default integration when creating a dataset. ```python theme={"theme":"css-variables"} dataset = client.create_dataset(name="IAM manual demo", iam_integration=None) ``` ## Update dataset integration You can change the current integration of a dataset with `add_iam_integration`. ```python theme={"theme":"css-variables"} # Get all IAM integrations iam_integrations = client.get_organization().get_iam_integrations() # Get IAM integration id iam_integration_id = [integration.uid for integration in iam_integrations if integration.name == "My S3 integration"][0] # Set IAM integration for integration id dataset.add_iam_integration(iam_integration_id) # Get IAM integration object iam_integration = [integration.uid for integration in iam_integrations if integration.name == "My S3 integration"][0] # Set IAM integration for IAMIntegrtion object dataset.add_iam_integration(iam_integration) ``` ## Remove/Unselect dataset integration ```python theme={"theme":"css-variables"} dataset.remove_iam_integration() ``` ## Upload data rows with delegated access Make sure the type of IAM integration matches your data rows' cloud storage, then use the URL for the `row_data` field to upload data rows. ```python theme={"theme":"css-variables"} # Some examples: datarows = [{"row_data": "https://.s3..amazonaws.com/"}] # Amazon S3 datarows = [{"row_data": "gs://gcs-lb-demo-bucket/test.png"}] # Google Cloud Storage datarows = [{"row_data": "https://labelboxdatasets.blob.core.windows.net/datasets/geospatial/001.jpg"}] # Microsoft Azure Blob Storage task1 = dataset.create_data_rows(datarows) task1.wait_till_done() ``` # Labelbox API key Source: https://docs.labelbox.com/horizon/developer-guides/create-api-key Shows how to create an API key that lets apps and other services access Labelbox APIs and endpoints. Labelbox authenticates API requests using **API keys**, which grant access to the Python SDK and can also authorize third-party integrations, such as cloud providers. Depending on your [roles and permissions](/horizon/guides/roles-and-permissions), you can create, view, delete, and distribute API keys as needed. API keys serve as access credentials to Labelbox on your behalf. Treat them with the same level of care as other sensitive credentials. ### API keys of disabled accounts API keys are tied to user accounts. If a user account is disabled, all API keys associated with that account are also disabled. ## Create a new API key To create a new API key: 1. On the Labelbox [home](https://app.labelbox.com/home) page, click **Create API key**. 2. On the **Create new API key** prompt: * Enter a descriptive **name**. * Add a **validity** time period. The max value is 6 months. * Select a role for the scope of access [permissions](/horizon/guides/roles-and-permissions). * Click **Create** to complete the setup. 3. Your new key is displayed in the **API key created** prompt. Click the **Copy** icon next to the API key to copy the key to the clipboard. It's strongly recommended to save your new key in a safe location. 4. Click **Done** to close the prompt. After closing the prompt, you won’t be able to retrieve the key. ## View and delete API keys Go to **Workplace Settings** → [API keys](https://app.labelbox.com/workspace-settings/api-keys) to view and delete existing API keys. To delete an API key, locate the key you wish to delete, open the context menu, and click **Delete**. Once an API key is deleted, it can no longer authenticate API requests. Any app, script, or service using the key loses access to Labelbox. To minimize disruptions, we recommend not deleting keys until you've created and replaced them with new ones. ## Guidelines and best practices For each active Labelbox account: * API keys access to your Labelbox account and data. Treat API keys like passwords and other credentials. * The value of an API key can only be retrieved once from the **API key created** prompt when the key is first created. * If you lose the value of your API key (or cannot determine how it's being used), delete it and create a new one. ## Client The Labelbox Client is essential for all SDK workflows. It connects to a Labelbox server with your authentication API key and provides functions for querying and creating data assets. ## Instantiate Labelbox Client Use the API key to connect to the Labelbox client: ```python Python theme={"theme":"css-variables"} import labelbox as lb ## Option 1: List API_KEY in your Python script API_KEY = "" client = lb.Client(api_key=API_KEY) ## Option 2: Pass your API key as an environment variable $LABELBOX_API_KEY # In your terminal: user@machine:~$ export LABELBOX_API_KEY="" # In Python: client = lb.Client() ``` ## Manage API keys from the SDK ### Create an API key To create a new API key, provide the following: * `name`: a descriptive name * `user`: an existing user email address * `role`: an existing role (one of `client.get_roles()`) * `validity` and `time_unit`: the duration of the API key using an integer (`validity`) and a `TimeUnit` enum (`time_unit`). Valid `time_unit` values include `TimeUnit.SECOND`, `TimeUnit.MINUTE`, `TimeUnit.HOUR`, `TimeUnit.DAY`, and `TimeUnit.WEEK`. Duration values can range between: * minimum `{"validity":1, time_unit: TimeUnit.MINUTE}` * maximum `{"validity":25, time_unit: TimeUnit.WEEK}` ```python Python theme={"theme":"css-variables"} api_key_settings = { "name": "", "user": "", "role": "", "validity": "", "time_unit": "" } new_token = client.create_api_key(**api_key_settings) ``` ### Get API keys You can list a single API key or all, active or expired, non-revoked API keys: ```python Python theme={"theme":"css-variables"} # Active API keys active_api_keys = client.get_api_keys() # All non-revoked API keys api_keys = client.get_api_keys(include_expired=True) # Single API key api_key_id = "" api_key = client.get_api_key(api_key_id) # Show expiration datetime print(f"Expiration date: {api_key.expired_at}") # Show creator and target user print(f"Creator: {api_key.created_by.email} - Target user: {api_key.created_for.email}") ``` ### Revoke an API key ```python Python theme={"theme":"css-variables"} api_key.revoke() ``` # Custom embeddings Source: https://docs.labelbox.com/horizon/developer-guides/custom-embeddings Shows how to upload custom embeddings to improve similarity search. ## How to upload custom embeddings Custom embeddings improve data exploration by improving [similarity search](/horizon/guides/similarity). You can upload up to ten (10) custom embedding types per workspace on any data type. Use this to experiment with different embeddings to improve data selection. ## Before you start This example requires the following libraries: ```python Install theme={"theme":"css-variables"} # Starting from SDK version 3.69, custom embeddings are now supported. import labelbox as lb import numpy as np import json import uuid import random ``` ## Replace API key ```python Python theme={"theme":"css-variables"} API_KEY = "" client = lb.Client(API_KEY) ``` ## Select data rows First, we need to fetch data rows from a Labelbox dataset. To improve similarity search, you need to upload custom embeddings to at least 1,000 data rows. ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") export_task = dataset.export() export_task.wait_till_done() data_rows = [] # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` Extract the data row ID and the row data (asset URL): ```python Python theme={"theme":"css-variables"} data_row_dict = [{"data_row_id": dr["data_row"]["id"]} for dr in data_rows] data_row_dict = data_row_dict[:1000] # keep the first 1000 examples for the sake of this demo ``` ## Create custom embedding payload To prepare the data: Generate random vectors for embeddings (max: `2048` dimensions) ```python Python theme={"theme":"css-variables"} nb_data_rows = len(data_row_dict) print("Number of data rows: ", nb_data_rows) # Labelbox supports custom embedding vectors of up to 2048 dimensions custom_embeddings = [list(np.random.random(2048)) for _ in range(nb_data_rows)] ``` List custom embeddings in your Labelbox workspace: ```python Python theme={"theme":"css-variables"} embeddings = client.get_embeddings() ``` Choose an existing embedding type or create a new one A unique custom embedding name is required as an argument for this method. ```python Python theme={"theme":"css-variables"} # Name of the custom embedding must be unique embedding = client.create_embedding("my_custom_embedding_2048_dimensions", 2048) ``` Create payload * The payload should encompass the `key` (data row id or global key) and the new embedding vector data. Note that the `dataset.upsert_data_rows()` operation will only update the values you pass in the payload; all other existing row data will not be modified. ```python Python theme={"theme":"css-variables"} payload = [] for data_row_dict, custom_embedding in zip(data_row_dict,custom_embeddings): payload.append({"key": lb.UniqueId(data_row_dict['data_row_id']), "embeddings": [{"embedding_id": embedding.id, "vector": custom_embedding}]}) print('payload', len(payload),payload[:1]) ``` ## Upload payload Upsert data rows with custom embeddings ```python Python theme={"theme":"css-variables"} task = dataset.upsert_data_rows(payload) task.wait_till_done() print(task.errors) print(task.status) ``` Get the count of imported vectors for a custom embedding type An updated count can take a few minutes, depending on the number of data rows associated with the embedding type. ```python Python theme={"theme":"css-variables"} count = embedding.get_imported_vector_count() ``` Delete custom embedding type. ```python Python theme={"theme":"css-variables"} embedding.delete() ``` ## Upload custom embeddings during data row creation Create a dataset ```python Python theme={"theme":"css-variables"} # Create a dataset dataset_new = client.create_dataset(name="data_rows_with_embeddings") ``` Fetch an embedding type and create dummy vector data. ```python Python theme={"theme":"css-variables"} embedding = client.get_embedding_by_name("my_custom_embedding_2048_dimensions") vector = [random.uniform(1.0, 2.0) for _ in range(embedding.dims)] ``` Upload data rows with embeddings. ```python Python theme={"theme":"css-variables"} uploads = [] # Generate data rows for i in range(1,9): uploads.append({ "row_data": f"https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_000{i}.jpeg", "global_key": "TEST-ID-%id" % uuid.uuid1(), "embeddings": [{ "embedding_id": embedding.id, "vector": vector }] }) task1 = dataset_new.create_data_rows(uploads) task1.wait_till_done() print("ERRORS: " , task1.errors) print("RESULTS:" , task1.result) ``` # Data row Source: https://docs.labelbox.com/horizon/developer-guides/data-row Developer guide for creating and modifying data rows via the Python SDK. Data rows are the assets that are being labeled; a data row cannot exist without belonging to a dataset. Data rows are added to a labeling task by uploading them to datasets and then creating [project batches](/horizon/developer-guides/batch#fundamentals). ## Client ```python Python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(api_key="") ``` ## Get a data row ```python Python theme={"theme":"css-variables"} data_row = client.get_data_row("") data_row = client.get_data_row_by_global_key("key1") data_row_ids = get_data_row_ids_for_global_keys(["key1", "key2"]) ``` ## Assign global keys ```python Python theme={"theme":"css-variables"} global_key_data_row_inputs = [ {"data_row_id": "", "global_key": "key1"}, {"data_row_id": "", "global_key": "key2"} ] client.assign_global_keys_to_data_rows(global_key_data_row_inputs) ``` ## Clear global keys ```python Python theme={"theme":"css-variables"} client.clear_global_keys(["key1", "key2"]) ``` ## Fundamentals ## Create data rows Data rows are created via methods from the `Dataset` class. For complete details and additional examples of approaches for creating data rows, please see [Dataset](/horizon/developer-guides/dataset). We recommend using `created_data_rows()` and `upsert_data_rows()` methods for large data row upload operations. **Special character handling** Please note that certain characters like `#`,`<`, `>` and `|`| are not supported in URLs and should be avoided in your file names to prevent loading issues. Please refer to [https://datatracker.ietf.org/doc/html/rfc2396#section-2.4.3](https://datatracker.ietf.org/doc/html/rfc2396#section-2.4.3) on URI standards. A good test for the handling of special characters is to test URLs in your browser address bar — if the URL doesn't load properly in your browser, it won't load in Labelbox. The only required argument when creating a data row is the `row_data`. However, Labelbox strongly recommends supplying each data row with a global key upon creation. ```python Python theme={"theme":"css-variables"} # this example uses the uuid package to generate unique global keys from uuid import uuid4 data =[ { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4()) }, { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4()) } ] dataset.upsert_data_rows(data) # or alternatively use dataset.create_data_rows(data) ``` ## Export data rows For a complete reference on how to export data rows, please visit the [export overview documentation](/horizon/developer-guides/export-overview) Export a single data row: ```python Python theme={"theme":"css-variables"} DATA_ROW_GLOBAL_KEY = "" export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True, "embeddings": True } # Provide a list of data row global keys export_task = lb.DataRow.export(client=client, global_keys=[DATA_ROW_GLOBAL_KEY], params=export_params) export_task.wait_till_done() if export_task.has_results(): # Start stream stream = export_task.get_buffered_stream() # Iterate through data rows for data_row in stream: print(data_row.json) ``` ## Methods ## Create and update an attachment Please reference our [attachment documentation](/horizon/developer-guides/attachments#supported-attachment-types) for more information on supported attachment data types. For details on creating attachments in the same step as creating data rows, see [Dataset](/horizon/developer-guides/dataset). ```python Python theme={"theme":"css-variables"} attachment = data_row.create_attachment( attachment_type="", # specify a type from the table above attachment_value="", # provide a value of the appropriate type attachment_name="" # name the attachment for reference ) # Update an attachment attachment.update(type= "", value="") ``` ## Get the winning label ID For more details on what a "winning" label is and how it is chosen, see [Consensus](/horizon/guides/consensus#select-a-set-of-annotations-as-the-winner). ```python Python theme={"theme":"css-variables"} data_row.get_winning_label_id(project_id="") ``` ## Update data rows from a dataset We recommend using the`upsert_data_rows()` method to update data rows. When using this method to update data rows, you need to pass a `key`, which can reference either a global key or data row ID. Include any fields you wish to update along with their new values. ```python Python theme={"theme":"css-variables"} # Update the global key associated with the DATAROW_ID or GLOBAL_KEY, and include a additional metadata dataset = client.get_dataset("") data = { "key": lb.UniqueId(DATA_ROW_ID), "global_key": "NEW-ID-%id" % uuid.uuid1(), "metadata_fields": [ # New metadata lb.DataRowMetadataField( schema_id=mdo.reserved_by_name['captureDateTime'].uid, value="2000-01-01 00:00:00" ), # Include original metadata otherwise it will be removed lb.DataRowMetadataField( schema_id=mdo.reserved_by_name["tag"].uid, value="tag_string", ), ] } task = dataset.upsert_data_rows([data]) task.wait_till_done() print("ERRORS: " , task.errors) print("RESULTS:" , task.result) ``` The `update` method is also available; however, for a large number of data row updates, we recommend using `upsert_data_rows()` The `update` method only supports updates on `global_key`, `row_data` or `external_id` values. ```python Python theme={"theme":"css-variables"} data_row.update( row_data="", global_key="", external_id="new_external_id" ) ``` ## Delete data rows **Deleting data rows cannot be undone** These methods delete data rows along with all labels made on each data row. This action cannot be reverted without the assistance of Labelbox support. ```python Python theme={"theme":"css-variables"} # delete one data row data_row.delete() # bulk delete data rows -- takes a list of Data Row objects lb.DataRow.bulk_delete(data_rows=[, ]) # for example, delete the data rows in a dataset, but not the Dataset object dataset = client.get_dataset("") lb.DataRow.bulk_delete(data_rows=list(dataset.data_rows())) ``` ### Limit on bulk deleting data rows The `lb.DataRow.bulk_delete()` method can delete a maximum of 4,000 data rows per call. ## Attributes ## Get the basics ```python Python theme={"theme":"css-variables"} # global key (str) data_row.global_key # external ID (str) data_row.external_id # row data (str) data_row.row_data # media attributes (dict) data_row.media_attributes # updated at (datetime) data_row.updated_at # created at (datetime) data_row.created_at # created by (relationship to User object) user = data_row.created_by() # organization (relationship to Organization object) organization = data_row.organization() # dataset (relationship to Dataset object) dataset = data_row.dataset() ``` ## Get the attachments ```python Python theme={"theme":"css-variables"} # relationship to many AssetAttachment objects attachments = data_row.attachments() # inspect one attachment next(attachments) # inspect all attachments for attachment in attachments: print(attachment) # for ease of use, you can convert the paginated collection to a list list(attachments) ``` ## Get the metadata ```python Python theme={"theme":"css-variables"} # get the metadata fields associated with the data row (list) data_row.metadata_fields # get the metadata fields as DataRowMetadataField objects (list) data_row.metadata ``` ## Get the labels ```python Python theme={"theme":"css-variables"} # relationship to many Label objects labels = data_row.labels() # inspect one label made on the data row next(labels) # inspect all labels made on the data row for label in labels: print(label) # for ease of use, you can convert the paginated collection to a list of labels list(labels) ``` # Global keys Source: https://docs.labelbox.com/horizon/developer-guides/data-row-global-keys Information of data row global keys Global keys are user-specified unique ID for your data rows that you can assign upon creation or afterward. ## Requirements Global key values are required, **non-blank string values**; they: * Can be up to 200 characters in length * Support * Lowercase English alphabets: a, b, c, ..., z * Uppercase English alphabets: A, B, C, ..., Z * Numerals: 0, 1, 2, ..., 9 * Special characters: * Exclamation mark: `!` * Underscore: `_` * Dot: `.` * Asterisk: `*` * Single quotation mark: `'` * Open parentheses: `(` * Close parentheses: `)` * Ampersand: `&` * Dollar sign: `$` * At symbol: `@` * Equals sign: `=` * Semicolon: `;` * Forward slash: `/` * Colon: `:` * Plus sign: `+` * Space: \`\` * Comma: `,` * Question mark: `?` * Hyphen: `-` * Are case-insensitive * Can include spaces within the value, but not at the beginning (*leading*) or the end (*trailing*) ## Create data rows with global keys Note: If you are using delegated access, using the data row URLs or object keys as the unique global keys to map your cloud-hosted assets with Labelbox data rows is common practice. ```python Python theme={"theme":"css-variables"} from labelbox import DataRow, Client import uuid client = Client("") # Create a single data row in a dataset data_row = dataset.create_data_row(row_data="https://picsum.photos/id/829/200/300", global_key=str(uuid.uuid4())) # Create bulk data rows in a dataset with unique keys data_rows = [ {"row_data": "https://picsum.photos/id/829/200/300", "global_key": str(uuid.uuid4())}, {"row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid.uuid4())}, ] task = dataset.create_data_rows(data_rows) task.wait_till_done() print(task.errors) # None ``` The following code will **fail** to upload data rows due to duplicate global keys. ```python Python theme={"theme":"css-variables"} # This will have partial failure same_key = "SAME_KEY" # if you upload a list of data rows with duplicate keys in them, none will be uploaded. for i in range(3): data_rows.append( { "row_data": "https://picsum.photos/id/829/200/300", "global_key": same_key } ) task_will_fail = dataset.create_data_rows(data_rows) task_will_fail.wait_till_done() print(task_will_fail.errors) # --- # WARNING:labelbox.schema.task:There are errors present. Please look at `task.errors` for more details # {'message': 'Data rows contain empty string or duplicate global keys, which are not allowed'} assets = [{"row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4())}, {"row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4())}, ] # This task will succeed task_will_succeed = dataset.create_data_rows(assets) task_will_succeed.wait_till_done() print(task_will_succeed.errors) # if you upload it again with the same global keys, it will fail task_will_fail = dataset.create_data_rows(assets) task_will_fail.wait_till_done() print(task_will_fail.errors) # ---- # ...... ER_DUP_ENTRY: Duplicate entry '' for key 'DataRow_globalKey_organizationId_unique_idx'" ``` ## Assign global keys to existing data rows ```python Python theme={"theme":"css-variables"} data_row = dataset.create_data_row(row_data="google.com") global_key = str(uuid.uuid4()) res = client.assign_global_keys_to_data_rows( [{ "data_row_id": data_row.uid, "global_key": global_key }] ) print(res) # {'status': 'SUCCESS', 'results': [{'data_row_id': 'cl80k85x6017y0z0dcvtb4f1s', 'global_key': '745b9940-2457-4f56-b999-a2d46650dd77', 'sanitized': False}], 'errors': []} ``` Similarly, the assignment of global keys will fail if there are duplicate global keys in the system. Suppose you have already run the above code cell. In the following code, if you will another data row with the same global key as above, it will **fail** to assign. It is possible to have "PARTIAL SUCCESS" results. In the case that some data rows are assigned with unique global keys and some are assigned with existing global keys, the SDK will have partial success and return a payload where the `results` field contains the successful ones, and the `errors` contains the failed ones with error messages. ```python Python theme={"theme":"css-variables"} # Failure example data_row_1 = dataset.create_data_row(row_data="yahoo.com") res = client.assign_global_keys_to_data_rows( [{ "data_row_id": data_row_1.uid, "global_key": global_key #this will fail since we already used it in previous code }] ) print(res) # --- # WARNING:labelbox.client:There are errors present. Please look at 'errors' in the returned dict for more details # {'status': 'FAILURE', 'results': [], 'errors': [{'data_row_id': 'cl847r6eo54ka070n8k5737tk', 'global_key': 'c4af584e-e3f6-4c3d-a5d9-e7244f00f48a', 'error': 'Invalid global key'}]} # Partial Success example data_row_2 = dataset.create_data_row(row_data="bing.com") data_row_3 = dataset.create_data_row(row_data="duckduckgo.com") res = client.assign_global_keys_to_data_rows( [{ "data_row_id": data_row_2.uid, "global_key": global_key, #this will fail since we already used it in previous code }, { "data_row_id": data_row_3.uid, "global_key": str(uuid.uuid4()), # this one will succeed since it is a new global key } ] ) print(res) # res['results'] contains the successful ones, and the res['errors'] contains the failed ones with error messages # --- # WARNING:labelbox.client:There are errors present. Please look at 'errors' in the returned dict for more details # {'status': 'PARTIAL SUCCESS', 'results': [{'data_row_id': 'cl84zf8ee03rv071b1i055nj5', 'global_key': 'ed8969a6-b6a4-445f-936e-ae2a9a6f0004', 'sanitized': False}], 'errors': [{'data_row_id': 'cl84zf80d292q07wz9fmubwbw', 'global_key': '963716ae-2da4-4c43-93ae-c8f414a980ae', 'error': 'Invalid global key'}]} ``` ## Query data rows by global key ```python Python theme={"theme":"css-variables"} data_rows = [] global_keys = [str(uuid.uuid4()) for i in range(3)] for i in range(3): data_rows.append( { "row_data": "aaaa.jpg", "global_key": global_keys[i] } ) task = dataset.create_data_rows(data_rows) task.wait_till_done() # this tests that the global keys were able to fetch all of the data rows we expect res = client.get_data_row_ids_for_global_keys(global_keys) print(res) # --- # {'status': 'SUCCESS', 'results': ['cl84zszs302z7074c8u7m2osx', 'cl84zszs302yz074ccg1acwu5', 'cl84zszs302z3074c6aahhq3q'], 'errors': []} ``` If you query for a non-existent global key, that particular query will fail while the valid queries execute. ```python Python theme={"theme":"css-variables"} # Partial Success example global_keys.append("") res = client.get_data_row_ids_for_global_keys(global_keys) print(res) # --- # WARNING:labelbox.client:There are errors present. Please look at 'errors' in the returned dict for more details # {'status': 'PARTIAL SUCCESS', 'results': ['cl84zszs302z7074c8u7m2osx', 'cl84zszs302yz074ccg1acwu5', 'cl84zszs302z3074c6aahhq3q'], 'errors': [{'global_key': '', 'error': 'Data Row not found'}]} ``` Now, you can add data row IDs to a batch for a project or a model run for other downstream tasks. ## Global keys are cleared after deleting data rows Global keys will be automatically deleted after the deletion of data rows. For example, ```python Python theme={"theme":"css-variables"} global_key = 'example_global_key' new_data_row = { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "external_id": str(uuid.uuid4()), "global_key": global_key } # This task should succeed task = dataset.create_data_rows([new_data_row]) task.wait_till_done() print(task.errors) # None # This task should fail task = dataset.create_data_rows([new_data_row]) task.wait_till_done() task.errors, task.result # WARNING:labelbox.client:There are errors present. Please look at 'errors' in the returned dict for more details # WARNING:labelbox.schema.task:There are errors present. Please look at `task.errors` for more details --------------------------------------------------------------------------- #ValueError: Job failed. Errors : Duplicate global keys found: example_global_key ``` Since global keys are deleted upon data row deletion, you can check whether a global key is attached to a deleted data row, and it will show up in the "errors" field. ```python Python theme={"theme":"css-variables"} # delete data row by global key duplicated_data_row = client.get_data_row(client.get_data_row_ids_for_global_keys([global_key])['results'][0]) duplicated_data_row.delete() print('deleted') # Should not have results result = client.get_data_row_ids_for_global_keys(['example_global_key']) print(result) # WARNING:labelbox.client:There are errors present. Please look at 'errors' in the returned dict for more details #{'status': 'FAILURE', 'results': [''], 'errors': [{'global_key': 'example_global_key', 'error': 'Data Row deleted'}]} # You can now upload a Data Row with same global key again # This should succeed task = dataset.create_data_rows([new_data_row]) task.wait_till_done() print(task.errors) ``` # Dataset Source: https://docs.labelbox.com/horizon/developer-guides/dataset Summary of methods for creating and modifying datasets. The most common method for importing data to Labelbox is via Python SDK after setting up a [cloud storage integration](/horizon/guides/iam-delegated-access). With an IAM delegated access integration, you can keep your data in your cloud bucket and grant Labelbox limited access to the data on demand. ### Examples for all data types The examples in this developer guide primarily use image assets. For sample approaches with other data modalities, please view the developer guide for importing data nested under each asset type in the **Import/Export** section of the table of contents. ## Create a dataset The only required argument when creating a dataset is the `name`. You can also specify the IAM integration that you have set up on your account if not specified or set to `None` it will use your default integration. For more details. including how to get your IAM integrations; visit our dedicated [IAM integration](/horizon/developer-guides/cloud-storage-iam-integration) page ```python Python theme={"theme":"css-variables"} dataset = client.create_dataset( name='', description='', # optional iam_integration=None # if not specified, will use default integration, set as None to not use delegated access. ) ``` ## Get a dataset ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") # alternatively, you can get a dataset by name dataset = client.get_datasets(where=labelbox.Dataset.name == "").get_one() ``` ## Dataset methods ## Create data rows ### Special character handling Please note that certain characters like `#`,`<`, `>` and `|`| are not supported in URLs and should be avoided in your file names to prevent loading issues. Please refer to [https://datatracker.ietf.org/doc/html/rfc2396#section-2.4.3](https://datatracker.ietf.org/doc/html/rfc2396#section-2.4.3) on URI standards. A good test for the handling of special characters is to test URLs in your browser address bar — if the URL doesn't load properly in your browser, it won't load in Labelbox. The only required argument when creating a data row is the `row_data`. However, Labelbox strongly recommends supplying each data row with a [global key](/horizon/developer-guides/data-row-global-keys) upon creation. ```python Python theme={"theme":"css-variables"} # this example uses the uuid package to generate unique global keys from uuid import uuid4 data =[ { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4()) }, { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4()) } ] dataset.upsert_data_rows(data) # or alternatively use dataset.create_data_rows(data) ``` You can also create data rows with [metadata](/horizon/guides/datarow-metadata), [attachments](/horizon/guides/label-data#attachments), and [image overlays](/horizon/guides/label-data#image-overlay) in the same task. The code below contains an end-to-end example for creating a dataset with data rows that include these elements. ```python Python theme={"theme":"css-variables"} import labelbox as lb from uuid import uuid4 import datetime # insert your API key LB_API_KEY = "" client = lb.Client(api_key=LB_API_KEY) # get the metadata ontology metadata_ontology = client.get_data_row_metadata_ontology() # create the dataset dataset = client.create_dataset(name="Bulk import example") # build the assets assets = [ {"row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4())}, {"row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4())}, {"row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4())} ] # build the metadata asset_metadata_fields = [ {"name": "captureDateTime", "value": datetime.datetime.utcnow()}, {"name": "tag", "value": "tag_string"}, {"name": "split", "value": "train"} ] # build the attachments asset_attachments = [ {"type": "IMAGE", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/disease_attachment.jpeg"}, {"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4"}, {"type": "RAW_TEXT", "value": "IOWA, Zone 2232, June 2022 [Text string]"}, {"type": "RAW_TEXT", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}, {"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}, {"type": "HTML", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html"}, {"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg", "name": "RGB" }, {"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/cir.jpg", "name": "CIR"}, {"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/weeds.jpg", "name": "Weeds"}, {"type": "PDF_URL", "value": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf"} ] # connect the metadata and attachments to the data rows for item in assets: item["metadata_fields"] = asset_metadata_fields item["attachments"] = asset_attachments # create the data rows task = dataset.upsert_data_rows(assets) # or # or alternatively use create_data_rows() task.wait_till_done() print(task.results) print(task.errors) ``` ### Limits See [this page](/horizon/guides/limits) to learn the limits for creating data rows in one bulk operation. Regardless of your tier's limit, if you have a large dataset to upload, you can split the data rows into chunks and upload them sequentially. ### Task results The task result is a collection of details, typically information on the data rows associated with the task. ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) task.wait_till_done() print(task.result) # prints the data rows associated with the create_data_rows operation ``` ### Warning Large results (over 150,000 data rows) can take up to 10 mins to process. ### Task errors The task errors work similarly to the task results but contain information on any associated errors. ```python errors theme={"theme":"css-variables"} dataset = client.get_dataset("") task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) task.wait_till_done() print(task.errors) # prints the data rows associated with the create_data_rows operation ``` ## Create a singular data row ```python Python theme={"theme":"css-variables"} # simplest example task = dataset.create_data_row( row_data="https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", global_key=str(uuid4()) ) task.wait_till_done() ``` ## Importing a local file ### Upload a local file You can upload a local file to Labelbox storage and use the returned URL to create a data row. ```python Upload local file first theme={"theme":"css-variables"} # note that you cannot set global keys when creating a data row from a local file # first we must upload the file then create the data row local_data_path = "/tmp/test_data_row.txt" with open(local_data_path, 'w') as file: file.write("sample data") # upload the file to Labelbox storage file_url = client.upload_file(local_data_path) # create the data row with a global key task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) task.wait_till_done() ``` ### Create data row from a local file You can also create a data row from a local file directly without uploading it to storage. This creates the file URL and the data row in one step. However, this is not recommended since it increases the time it takes for your data row creation. ```python Upload local file directly theme={"theme":"css-variables"} # get a local file and write some text data local_data_path = "" # create the data row task = dataset.create_data_rows([{ "row_data": local_data_path, "global_key": "" }]) task.wait_till_done() ``` ## Append to an existing dataset You can add data rows to an existing dataset using the methods described above. First, get a dataset, then create the rows. ```python Python theme={"theme":"css-variables"} # get existing dataset dataset = client.get_dataset("") # use a method for data row creation as shown in sections above task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) ``` ## Export data rows from a dataset Filtering the result, you can obtain a list of asset URLs, global keys, or data row IDs. ```python Export theme={"theme":"css-variables"} # Get a dataset dataset = client.get_dataset("") # Start the export task for the dataset export_task = dataset.export() export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] # Extract the data rows urls from the export result asset_urls = [item.json["data_row"]["row_data"] for item in stream] # (Optional) To export all the global keys global_keys = [item.json["data_row"]["global_key"] for item in stream] # (Optional) To export all the data row ids data_row_ids = [item.json["data_row"]["id"] for item in stream] ``` ## Export data rows with labels and predictions For more details, see [Export data rows from Catalog](/horizon/developer-guides/export-overview#export-data-rows-from-catalog). ```python Export theme={"theme":"css-variables"} # set the export params to include/exclude certain fields export_params={ "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "performance_details": True, "project_ids": ["", ""], "model_run_ids": ["", ""] } # set filters filters={ "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "label_created_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"] } # get a dataset dataset = client.get_dataset("") export_task = dataset.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` ## Update a dataset ```python Python theme={"theme":"css-variables"} dataset.update(name="new_dataset_name") ``` ## Delete a dataset **Deleting a dataset cannot be undone** This method deletes the dataset along with all data rows in the dataset and any labels made on these data rows. This action cannot be reverted. ```python Python theme={"theme":"css-variables"} dataset.delete() ``` # Dataset attributes ## Get the basics ```python Python theme={"theme":"css-variables"} # name (str) dataset.name # description (str) dataset.description # updated at (datetime) dataset.updated_at # created at (datetime) dataset.created_at # created by (relationship to User object) user = dataset.created_by() # organization (relationship to Organization object) organization = dataset.organization() ``` ## Get the data rows The `data_rows()` attribute is a relationship to the `DataRow` objects in the dataset. The relationship retrieves a paginated collection of data rows. It is recommended to use exports to get direct data row details especially for larger data sets. ```python Python theme={"theme":"css-variables"} data_rows = dataset.data_rows() # inspect one data row next(data_rows) # inspect a number of data rows for data_row in data_rows: print(data_row) # for ease of use, you can convert the paginated collection to a list list(data_rows) ``` ## Get the number of data rows The `row_count` is a cached attribute; thus, you must re-fetch the dataset after creating data rows to retrieve the updated value. ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") dataset.row_count ``` # Export audio annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-audio-annotations How to export audio annotations and sample export formats. ## Export as JSON ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats Global and temporal classifications share the same export format for each type. In the export JSON file, temporal classifications are nested under a `timestamp` field, while global classifications appear directly under the `classifications` field, `segments` are per `feature_id` and represent the beginning and the end of the temporal classification in milliseconds. See the [Sample project export](#sample-project-export) to learn how each type is organized in the export. ### Classification - radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clnus73114gc4143u2dux654s", "feature_schema_id": "clnus6slv06xo07yja7l47gtw", "name": "radio_audio", "value": "radio_audio", "radio_answer": { "feature_id": "clnus73114gc5143u2jadnt6r", "feature_schema_id": "clnus6slv06xr07yj24yp1gdk", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } } ``` ### Classification - checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clnus73114gc1143u7fzf4ebv", "feature_schema_id": "clnus6slu06xi07yj7m9d7rft", "name": "checklist_audio", "value": "checklist_audio", "checklist_answers": [ { "feature_id": "clnus73114gc2143u6ah5wdhn", "feature_schema_id": "clnus6slu06xj07yj9eb7dpvp", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clnus73114gc3143uq755vkzi", "feature_schema_id": "clnus6slu06xl07yjdab4gxe8", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ``` ### Classification - free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clnus73114gc0143urjlfk558", "feature_schema_id": "clnus6slu06xg07yjcg87apa0", "name": "text_audio", "value": "text_audio", "text_answer": { "content": "free text audio annotation" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} [ { "data_row": { "id": "clzc6xfbealyn0713j929vdch", "external_id": "example.mp3", "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3" }, "media_attributes": { "asset_type": "audio", "mime_type": "audio/mpeg" }, "projects": { "clzc89nk900l707zmamti5zi0": { "name": "Example audio project", "labels": [ { "label_kind": "Audio", "version": "1.0.0", "id": "cm8x9w1xj00h307ev0jmh8648", "annotations": { "classifications": [ { "feature_id": "cm8xa12hd00023b6t0t3zirna", "feature_schema_id": "cm8x9zz4d04ed071363gvhh5j", "name": "Example global free text", "value": "example_global_free_text", "text_answer": { "content": "Example global free text label", "classifications": [] } }, { "feature_id": "cm8xa164700043b6t7wyqo0vv", "feature_schema_id": "cm8x9zz4e04ef0713db9vea0f", "name": "Example global radio", "value": "example_global_radio", "radio_answer": { "feature_id": "cm8xa164600033b6tvz63hfqi", "feature_schema_id": "cm8x9zz4e04eg0713hsxe3t0d", "name": "Option 1", "value": "option_1", "classifications": [] } }, { "feature_id": "cm8xa17ps00063b6txb4g30fw", "feature_schema_id": "cm8x9zz4e04er0713fc6fcs2r", "name": "Example global checklist", "value": "example_global_checklist", "checklist_answers": [ { "feature_id": "cm8xa17ps00053b6tc2skf6oh", "feature_schema_id": "cm8x9zz4f04es0713g860a82c", "name": "Option 1", "value": "option_1", "classifications": [] }, { "feature_id": "cm8xa181c00083b6t7i9pixsb", "feature_schema_id": "cm8x9zz4f04eu0713b95l1u2f", "name": "Option 2", "value": "option_2", "classifications": [] } ] } ], "timestamp": { "90697": { "classifications": [ { "feature_id": "cm8xa1k8600093b6tnrzpuq1n", "feature_schema_id": "cm3jdcfpg0m6m07081ju276sz", "name": "Example temporal free text", "value": "free_text", "text_answer": { "content": "Example temporal free text label 1", "classifications": [] } }, { "feature_id": "cm8xa2d2u000a3b6tk29o8u48", "feature_schema_id": "cm8x9zz4e04el07135zmghx1q", "name": "Example temporal radio", "value": "example_temporal_radio", "radio_answer": { "feature_id": "cm8xa2d2u000b3b6ti9kszxcg", "feature_schema_id": "cm8x9zz4e04em07132athat2z", "name": "Option 1", "value": "option_1", "classifications": [] } }, { "feature_id": "cm8xa2hhh000c3b6tzeluuhpt", "feature_schema_id": "cm8x9zz4f04ex0713f5rehhp0", "name": "Example temporal checklist", "value": "example_temporal_checklist", "radio_answer": { "feature_id": "cm8xa2ila000e3b6t410j19ir", "feature_schema_id": "cm8x9zz4f04f00713drwj6qpb", "name": "Option 2", "value": "option_2", "classifications": [] } } ] }, "23890": { "classifications": [ { "feature_id": "cm8xa1k8600093b6tnrzpuq1n", "feature_schema_id": "cm3jdcfpg0m6m07081ju276sz", "name": "Example temporal free text", "value": "free_text", "text_answer": { "content": "Example temporal free text label 1", "classifications": [] } } ] }, "6157": { "classifications": [ { "feature_id": "cm8xa1k8600093b6tnrzpuq1n", "feature_schema_id": "cm3jdcfpg0m6m07081ju276sz", "name": "Example temporal free text", "value": "free_text", "text_answer": { "content": "Example temporal free text label 2", "classifications": [] } } ] }, "33496": { "classifications": [ { "feature_id": "cm8xa2d2u000a3b6tk29o8u48", "feature_schema_id": "cm8x9zz4e04el07135zmghx1q", "name": "Example temporal radio", "value": "example_temporal_radio", "radio_answer": { "feature_id": "cm8xa2d2u000b3b6ti9kszxcg", "feature_schema_id": "cm8x9zz4e04em07132athat2z", "name": "Option 1", "value": "option_1", "classifications": [] } } ] }, "60711": { "classifications": [ { "feature_id": "cm8xa2hhh000c3b6tzeluuhpt", "feature_schema_id": "cm8x9zz4f04ex0713f5rehhp0", "name": "Example temporal checklist", "value": "example_temporal_checklist", "radio_answer": { "feature_id": "cm8xa2ila000e3b6t410j19ir", "feature_schema_id": "cm8x9zz4f04f00713drwj6qpb", "name": "Option 2", "value": "option_2", "classifications": [] } } ] } }, "segments": { "cm8xa1k8600093b6tnrzpuq1n": [ [ 6157, 90697 ] ], "cm8xa2d2u000b3b6ti9kszxcg": [ [ 33496, 90697 ] ], "cm8xa2ila000e3b6t410j19ir": [ [ 60711, 90697 ] ] } } } ], "project_tags": [] } } } ] ``` # Export conversational text annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-conversational-text-annotations How to export conversational text data and sample export formats. ## Export JSON annotations ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Text entity (named entity) ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu14r3p0oty148p5d2isiki", "feature_schema_id": "cluu14fnh01f40703aigj669b", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ``` ### Classification - radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu14r3p0ou3148p6d15q82b", "feature_schema_id": "cluu14fnh01fe0703abp5fjgx", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "cluu14r3p0ou4148pc028jtzk", "feature_schema_id": "cluu14fnh01ff070383rh4ryk", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ``` ### Classification - checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu14r3p0ou0148ptekhuz0a", "feature_schema_id": "cluu14fnh01f8070385tv57vy", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "cluu14r3p0ou1148p8pb3s49a", "feature_schema_id": "cluu14fnh01f90703cx3j6gqu", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cluu14r3p0ou2148ph8tyhtp1", "feature_schema_id": "cluu14fnh01fb07030q2m5hqc", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ``` ### Classification - free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clr0x0ol83tvq07ea8f5phc2g", "feature_schema_id": "clpk2me9u02vx07xnet21fsgf", "name": "free_text", "text_answer": { "content": "sample text" } } ``` ### Relationship ```json JSON theme={"theme":"css-variables"} { "feature_id": "clwkrnmlp00093b6qekq6qhyb", "feature_schema_id": "clwkrkehi04qy07y711tcgga6", "name": "relationship", "annotation_kind": "ConversationalTextUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "clwkrnhdb00033b6qlzbqjttc", "target": "clwkrnjky00063b6qc3tatj0p" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfbnskoxb9q9076fbyjngwhm", "global_key": "conversation-1.json", "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/conversational_text/1000-conversations/conversation-1.json", "details": { "dataset_id": "clfbnsjaj05hr07z605db0w3b", "dataset_name": "conversational_annotation_import_demo_dataset", "created_at": "2023-03-16T22:04:33.000+00:00", "updated_at": "2024-01-18T19:10:53.352+00:00", "last_activity_at": "2024-04-10T16:33:23.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/vnd.labelbox.conversational", "labelable_ids": [ "0", "2", "4", "6", "8", "10", "12", "14", "16", "18" ], "message_count": 20 }, "attachments": [], "metadata_fields": [], "projects": { "cluu14fwi01g0070321g9c1xi": { "name": "Conversational Text Annotation Import Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu14r490oud148pdk29bwes", "label_details": { "created_at": "2024-04-10T16:33:23.000+00:00", "updated_at": "2024-04-10T16:33:23.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-04-10T16:33:23.063+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "cluu14r3p0oty148p5d2isiki", "feature_schema_id": "cluu14fnh01f40703aigj669b", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ], "classifications": [ { "feature_id": "cluu14r3p0otz148p09nol3tq", "feature_schema_id": "cluu14fnh01f60703b4uadjbd", "name": "text_convo", "value": "text_convo", "message_id": "0", "conversational_text_answer": { "content": "the answer to the text questions right here" } }, { "feature_id": "cluu14r3p0ou0148ptekhuz0a", "feature_schema_id": "cluu14fnh01f8070385tv57vy", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "cluu14r3p0ou1148p8pb3s49a", "feature_schema_id": "cluu14fnh01f90703cx3j6gqu", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cluu14r3p0ou2148ph8tyhtp1", "feature_schema_id": "cluu14fnh01fb07030q2m5hqc", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "cluu14r3p0ou3148p6d15q82b", "feature_schema_id": "cluu14fnh01fe0703abp5fjgx", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "cluu14r3p0ou4148pc028jtzk", "feature_schema_id": "cluu14fnh01ff070383rh4ryk", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "cluu14r3p0ou5148piuv74m17", "feature_schema_id": "cluu14fni01fs0703ax77277l", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "cluu14r3p0ou6148p0b4azzcu", "feature_schema_id": "cluu14fni01ft07033xic99k3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "cluu14r3p0ou7148pgmcqsf11", "feature_schema_id": "cluu14fni01fu0703c3iu2c0a", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cluu14r3p0ou8148pka3hvx26", "feature_schema_id": "cluu14fni01fv07031r8i190o", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "cluu14r3p0ou9148puacr13og", "feature_schema_id": "cluu14fni01fk07033aeye6ht", "name": "nested_checklist_question", "value": "nested_checklist_question", "message_id": "10", "conversational_checklist_answers": [ { "feature_id": "cluu14r3p0oua148pttkqa3fe", "feature_schema_id": "cluu14fni01fl0703eg9y2qgt", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "cluu14r3p0oub148p3hld5bh9", "feature_schema_id": "cluu14fni01fm0703b3mwa6ot", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cluu14r3p0ouc148pfpiwlzht", "feature_schema_id": "cluu14fni01fn0703apfk8j9q", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "project_details": { "ontology_id": "cluu14fmp01f307039z1h2c64", "task_name": "Done", "batch_id": "04e9d4c0-f758-11ee-8be6-db43b18899b6", "batch_name": "first-batch-convo-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] }, "project_tags": [] } } } ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfbnskoxb9q9076fbyjngwhm", "global_key": "conversation-1.json", "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/conversational_text/1000-conversations/conversation-1.json", "details": { "dataset_id": "clfbnsjaj05hr07z605db0w3b", "dataset_name": "conversational_annotation_import_demo_dataset", "created_at": "2023-03-16T22:04:33.000+00:00", "updated_at": "2024-01-18T19:10:53.352+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/vnd.labelbox.conversational", "labelable_ids": [ "0", "2", "4", "6", "8", "10", "12", "14", "16", "18" ], "message_count": 20 }, "attachments": [], "metadata_fields": [], "experiments": { "a3143b06-742d-0576-d722-c187a5aad1b3": { "name": "Conversational_model_run_9985d371-1b0b-4a5b-8c94-4a6167b20090", "runs": { "a3143b06-adc7-086f-79a5-d251a0054cb8": { "name": "iteration 1", "run_data_row_id": "4f22e94f-71da-45ac-bab7-f95daf04e7a1", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clq10ymdubt0u1447v6s77glu", "annotations": { "objects": [ { "feature_id": "e063fe4e-4675-0988-9f81-659c83dd1b43", "feature_schema_id": "clq10ybnl067t07ztbavefdsy", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ], "classifications": [ { "feature_id": "169f1fee-5db4-02c9-a232-dfb245a158ec", "feature_schema_id": "clq10ybnl068907ztf1htgtoc", "name": "nested_checklist_question", "value": "nested_checklist_question", "message_id": "10", "conversational_checklist_answers": [ { "feature_id": "c1c5898b-f1ce-02c7-b806-930ac8140923", "feature_schema_id": "clq10ybnl068a07ztcnyzbhsn", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "1fc2b7e8-a292-02ad-81b6-10cb9bdd56c4", "feature_schema_id": "clq10ybnl068b07zth87h8c34", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "de42a672-041f-03f3-bbd9-e771e332030a", "feature_schema_id": "clq10ybnm068c07zt53sdcs0m", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "8adc0320-6d61-0011-bda0-80842b3733da", "feature_schema_id": "clq10ybnm068h07ztaugh4uma", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "475fb394-f606-0223-b625-48045ebf7208", "feature_schema_id": "clq10ybnm068i07zt1xxv25it", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "a61d83fd-4c47-0e88-9ae1-da3eb100eea0", "feature_schema_id": "clq10ybnm068j07ztcqf794w4", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "6dbde4c3-3a1e-0348-a5ac-7787a8ac0461", "feature_schema_id": "clq10ybnm068k07ztdx9dca3i", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "96dce41f-f1c1-0822-9848-39c098b449f5", "feature_schema_id": "clq10ybnl067v07ztb8f91r4u", "name": "text_convo", "value": "text_convo", "message_id": "0", "conversational_text_answer": { "content": "the answer to the text questions are right here" } }, { "feature_id": "ba7c519c-c618-01ba-ac4b-fdbbd8137bf0", "feature_schema_id": "clq10ybnl068307ztd255hndf", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "ec0fc3fb-1948-0ff9-8bc6-0377721e4f0e", "feature_schema_id": "clq10ybnl068407zt1x572rym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "fe3a901b-8e62-01fe-a792-10b15ec7fa0d", "feature_schema_id": "clq10ybnl067x07zt0u67b9h3", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "699a5673-bb4a-06d3-9b9e-19237e11f96d", "feature_schema_id": "clq10ybnl068007zt6ct25775", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "da192bd6-84ec-0acf-b875-d1ade525f466", "feature_schema_id": "clq10ybnl067y07zt6gvjczsg", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clq10ymdubt0u1447v6s77glu", "annotations": { "objects": [ { "feature_id": "9c8f89a7-7211-4b33-a3c5-a3dab59f0407", "feature_schema_id": "clq10ybnl067t07ztbavefdsy", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ], "classifications": [ { "feature_id": "20cda99c-d0f7-4e59-8f43-a8b4f3929e2f", "feature_schema_id": "clq10ybnl068307ztd255hndf", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "ec1ec509-79b6-48e7-973c-9c06e754630f", "feature_schema_id": "clq10ybnl068407zt1x572rym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "49e4667e-ed14-400b-9b75-b8b89bfb6f11", "feature_schema_id": "clq10ybnm068h07ztaugh4uma", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "723be4ec-fa0c-4a67-9df0-a18f56f7bbd8", "feature_schema_id": "clq10ybnm068i07zt1xxv25it", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "f28f3d0a-9428-4b45-8de1-4ec6714da726", "feature_schema_id": "clq10ybnm068j07ztcqf794w4", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "82808a1e-a004-4965-bb77-4ab9e499c747", "feature_schema_id": "clq10ybnm068k07ztdx9dca3i", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "988e5b9a-9a6d-4338-96bf-901475b4a2c0", "feature_schema_id": "clq10ybnl068907ztf1htgtoc", "name": "nested_checklist_question", "value": "nested_checklist_question", "message_id": "10", "conversational_checklist_answers": [ { "feature_id": "9ea5984d-fb21-48e4-96c8-ce19162b5850", "feature_schema_id": "clq10ybnl068a07ztcnyzbhsn", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "bf896185-5a77-410e-a4cd-abf0b07122fa", "feature_schema_id": "clq10ybnl068b07zth87h8c34", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "0812ee3d-7f5d-480b-82a2-4e965a48fc49", "feature_schema_id": "clq10ybnm068c07zt53sdcs0m", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "cb4730df-2e22-45db-bc74-fd439d577571", "feature_schema_id": "clq10ybnl067x07zt0u67b9h3", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "8184feca-970c-4ba7-bfbd-4d0eb5849a90", "feature_schema_id": "clq10ybnl068007zt6ct25775", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "dc40cfef-aa6f-436d-a5b5-0d56bbd1741e", "feature_schema_id": "clq10ybnl067y07zt6gvjczsg", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "db7109ed-e380-4389-ab2f-98e72b4c2afe", "feature_schema_id": "clq10ybnl067v07ztb8f91r4u", "name": "text_convo", "value": "text_convo", "message_id": "0", "conversational_text_answer": { "content": "the answer to the text questions are right here" } } ], "relationships": [] } } ] } } } }, "models": {} } ``` # Export document annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-document-annotations How to export document (PDF) annotations and sample export formats. ## Export as JSON ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Bounding box ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu0k04w05hk07h76lv05sb0", "feature_schema_id": "cluu0ckta024507udcols6x8y", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 1, "bounding_box": { "top": 66.251, "left": 96.424, "height": 80.68099999999998, "width": 82.65 } } ``` ### Text entity (named entity) ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu0k04w05ho07h73jx24ol7", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "1f54807e-b612-4f4a-9ca5-ee302ee9c659", "page_number": 1, "tokens": [ "839967fa-89a0-4ad1-896e-d25e5f4cb8e5", "9853c27e-30e0-45e0-b461-c28da6573b48", "e24f45c6-1efc-440f-a4dd-bb3eb522134f", "3339e940-3f73-4b56-b695-474808dad1e5", "a6f79ed8-fc20-4870-b9d1-aee3e92943bc", "07ac4d2e-68bb-4c25-bcc0-b03a697c4138", "7469a5c7-74f8-43c4-b1fb-1c89105a5c66", "eff97919-2529-4b67-9640-20301d45a2d4" ], "text": "Organic charge transfer salts based on the donor" } ] } } ``` ### Relationship ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu0k04y05hx07h75lne7jh6", "feature_schema_id": "cluu0cktb024907udbo0qhco1", "name": "relationship", "value": "relationship", "annotation_kind": "DocumentUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "cluu0k04w05hs07h7ahsw7689", "target": "cluu0k04w05hk07h76lv05sb0" } } ``` ### Classification - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu0k04y05hw07h70s248skz", "feature_schema_id": "cluu0cktc024r07ud2x2oba9o", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "cluu0k04y05i407h7ehvn84w9", "feature_schema_id": "cluu0cktc024s07udc81y6lx3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ``` ### Classification - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu0k04w05hj07h71okedbdg", "feature_schema_id": "cluu0cktc024x07udc5fr0p96", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "cluu0k04y05hy07h73gy3awz7", "feature_schema_id": "cluu0cktc025007ud0d5w5o05", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "cluu0k04y05i107h73vpphndb", "feature_schema_id": "cluu0cktc024y07ud97zu7h1d", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ``` ### Classification - Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "cluu0k04w05hp07h7fn0k9bdw", "feature_schema_id": "cluu0cktc025307udgntw3qxi", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} [ { "data_row": { "id": "cm2kphnpy50m707445lc9afs9", "global_key": "0801.3483_doc.pdf97c24e0d-4bd4-43df-830c-d8dea1db5a41", "row_data": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "details": { "dataset_id": "cm2kphm4b006f0701l4h33iz9", "dataset_name": "pdf_demo_dataset", "created_at": "2024-10-22T17:15:12.604+00:00", "updated_at": "2024-10-22T17:15:18.631+00:00", "last_activity_at": "2024-10-22T17:26:32.131+00:00", "created_by": "[email protected]" } }, "media_attributes": { "asset_type": "pdf", "mime_type": "application/pdf", "text_layer_url": "https://storage.labelbox.com/clwzraq9k0bae070o3j0w20p5%2F9sfa9cl544707m05ypnhpk2mc-text-layer.json?Expires=1729704414371&KeyName=labelbox-assets-key-3&Signature=ZIo4XYc3QyrEgMwZS5pxWJ6Mqr0" }, "attachments": [], "metadata_fields": [], "projects": { "cm2kpk0i1055g07wp1ubl7n9k": { "name": "example_pdf_export", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cm2kpvs8b08da07yc40llhwmt", "label_details": { "created_at": "2024-10-22T17:26:17.000+00:00", "updated_at": "2024-10-22T17:26:17.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-10-22T17:26:17.418+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 3, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "cm2kpvtfw040607ic3mkjgsq5", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "fe8d7f06-c0f1-419c-a880-4926b2cb5bf0", "page_number": 1, "tokens": [ "90d94c02-f00b-4c36-aa8f-086a58198d47", "e49b7391-8466-497f-8a56-60945c85a222", "b4dd1d51-a8a2-4d5f-8074-2409336b5887", "d2bc8a3b-554a-43fc-8931-1f6757321ef0", "17b69f57-0f6f-4ef7-a3c4-d46d72403564", "23d23fed-0a3b-4bb7-9df1-91d0247f6348", "f2d8efb5-1a3f-40cd-9ff4-5cc8a53669c9", "a74d4317-93bd-4cc9-ae4d-e62fc4c2e370", "1ab58cb7-27d3-46a7-aa2b-5b7f89028b28", "458d43da-baa4-4eac-966f-4e6d1cf75c9d", "4e660727-51d5-4046-a2fb-eaeefffc4171", "83838d7b-c083-47c2-bd11-c7c16cb3667d" ], "text": "Metal - insulator ( MI ) transitions have been one of the" } ] } }, { "feature_id": "cm2kpvtfw040807icec0q6ux7", "feature_schema_id": "cm2kpi4xu05z507z45jx193j8", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 1, "bounding_box": { "top": 68.875, "left": 188.257, "height": 80.68100000000001, "width": 82.64999999999998 }, "page_dimensions": { "height": 792.0, "width": 612.0 } }, { "feature_id": "cm2kpvtfw040b07ic61w421yf", "feature_schema_id": "cm2kpi4xu05zh07z4chbxdr0m", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "DocumentBoundingBox", "classifications": [ { "feature_id": "cm2kpvtfz040o07ic6jil7vp5", "feature_schema_id": "cm2kpi4xu05zi07z42xe841v4", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cm2kpvtfz040p07icbaciagt3", "feature_schema_id": "cm2kpi4xu05zj07z4cjm301cg", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [ { "feature_id": "cm2kpvtg0040w07icd28t2zwm", "feature_schema_id": "cm2kpi4xu05zk07z45nqcgxdq", "name": "second_sub_radio_question", "value": "second_sub_radio_question", "radio_answer": { "feature_id": "cm2kpvtg0040x07icg6qlb045", "feature_schema_id": "cm2kpi4xu05zl07z4aki5765r", "name": "second_sub_radio_answer", "value": "second_sub_radio_answer", "classifications": [] } } ] } } ], "page_number": 1, "bounding_box": { "top": 226.757, "left": 317.271, "height": 194.22899999999998, "width": 249.38600000000002 }, "page_dimensions": { "height": 792.0, "width": 612.0 } }, { "feature_id": "cm2kpvtfw040c07ic8p9mh4do", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "b111b609-9715-4682-93fd-f000425b6e47", "page_number": 1, "tokens": [ "ce039ffb-e6ec-4b5f-8a71-df5ae9b147bb", "f1f154a8-4844-4eee-ad4a-d118ae2245aa", "9b35fcaa-6840-459a-b4e5-73f761ba4efd", "6a6d3239-fbbd-4db9-a0a8-c71777de8c01", "d93dae8e-5be4-4451-91c7-4a5f9c3b244e", "9b962313-01a8-41f4-ac57-567043735542", "6b49e3cc-9198-4bb9-a42a-422c13bef397", "c3b3dc30-e8da-4cbb-85cc-ce3b212a6e98" ], "text": "Organic charge transfer salts based on the donor" } ] } }, { "feature_id": "cm2kpvtfz040e07icfknwbpmg", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "118f1d6f-f07b-4998-bdbd-508d52e7a118", "page_number": 1, "tokens": [ "c5d8e961-0844-43b1-a165-5666c495f7b0", "276fef85-a7a5-472b-ad56-793de2de0be2", "ab0ae1eb-715c-4848-9ecf-6600b42072b0", "7ce6b395-ad28-4762-ab45-9d29b6bc0277", "07b37776-4fc1-4c12-88f6-f53114042f58", "0f4a4d42-786f-4be6-8780-c5fe0fe48b17", "6cb7839e-7a5c-49a4-94d4-1b686f37f3e3", "8bf2b485-3e2f-40c5-afad-3bfd546da580" ], "text": "the experimental investigations on this issue have not" } ] } }, { "feature_id": "cm2kpvtfz040h07ic2b2kcz37", "feature_schema_id": "cm2kpi4xu05z507z45jx193j8", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 1, "bounding_box": { "top": 66.251, "left": 96.424, "height": 80.68099999999998, "width": 82.65 }, "page_dimensions": { "height": 792.0, "width": 612.0 } }, { "feature_id": "cm2kpvtfz040i07icdfj6ao5f", "name": "ner_with_checklist_subclass", "value": "ner_with_checklist_subclass", "annotation_kind": "DocumentEntityToken", "classifications": [ { "feature_id": "cm2kpvtfz040n07ica2s03hyb", "feature_schema_id": "cm2kpi4xu05zc07z4ho4pe8kg", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cm2kpvtg0040r07icbcjo2s75", "feature_schema_id": "cm2kpi4xu05zd07z46ascfffz", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ], "location": { "groups": [ { "id": "0f325409-ad93-428e-8941-93255af67c38", "page_number": 1, "tokens": [ "ca996503-59ac-40cb-b213-c760a01193b3", "f2b83cc1-028a-472c-8483-594426d27d9f", "f86eef15-47b9-4093-82aa-f5d1129030b8", "94c4ca63-88cc-49f6-89fb-dfc08b57f295", "32184378-a94f-44b4-832d-b1a26d9b0b41", "d2b28d96-50f0-4953-bd61-0c8cac971e91", "e4ad49e8-9b94-4255-9002-cfc715bd92f4", "73dec4b6-2b34-4225-9e5b-119206fadb9f", "2a1e2a8f-11f9-4ac8-95bd-fba3f42196fc" ], "text": "T. Sasaki , N. Yoneyama , and N. Kobayashi" } ] } }, { "feature_id": "cm2kpvtfz040k07ic92exh5mc", "feature_schema_id": "cm2kpi4xu05z507z45jx193j8", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 0, "bounding_box": { "top": 135.3, "left": 102.771, "height": 109.84299999999999, "width": 415.8 }, "page_dimensions": { "height": 792.0, "width": 612.0 } } ], "classifications": [ { "feature_id": "cm2kpvtfw040707ic3p25ehsi", "feature_schema_id": "cm2kpi4xv060307z440vca4xf", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "cm2kpvtfw040907icdi74gf73", "feature_schema_id": "cm2kpi4xv05zx07z4e21cch7q", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "cm2kpvtfz040l07ic0nb7048e", "feature_schema_id": "cm2kpi4xv05zy07z4aaxg4ld5", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cm2kpvtg0040s07ice9tz7elg", "feature_schema_id": "cm2kpi4xv060007z4eqwvda46", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "cm2kpvtfw040a07ic6wpccc2a", "feature_schema_id": "cm2kpi4xv060507z4hyqfag6g", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "cm2kpvtfz040m07ic8pg6dw8i", "feature_schema_id": "cm2kpi4xv060607z4d2kecbnj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "cm2kpvtg0040u07ic73h35155", "feature_schema_id": "cm2kpi4xv060707z4br8a2jrr", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cm2kpvtg0040z07ic27ath50b", "feature_schema_id": "cm2kpi4xv060807z4bwcfcuar", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "cm2kpvtfz040f07ic92mf2trh", "feature_schema_id": "cm2kpi4xv060d07z435j1a2ht", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "cm2kpvtg0040q07ic3vb4bsbw", "feature_schema_id": "cm2kpi4xv060e07z4a8recoch", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "cm2kpvtg0040v07ic1xfc4c0a", "feature_schema_id": "cm2kpi4xv060f07z4d0fi0c2h", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cm2kpvtg0040y07ic082kdt55", "feature_schema_id": "cm2kpi4xv060g07z48ash27gn", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "cm2kpvtfz040j07icflaf69my", "feature_schema_id": "cm2kpi4xu05zr07z41ds99wmj", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "cm2kpvtg0040t07icfigkfbog", "feature_schema_id": "cm2kpi4xu05zs07z46r452wc1", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ], "relationships": [ { "feature_id": "cm2kpvtfw040d07ic6eks5lc9", "feature_schema_id": "cm2kpi4xu05z907z47bbk1kr5", "name": "relationship", "value": "relationship", "annotation_kind": "DocumentUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "cm2kpvtfw040c07ic8p9mh4do", "target": "cm2kpvtfz040e07icfknwbpmg" } }, { "feature_id": "cm2kpvtfz040g07ichnlz7kfk", "feature_schema_id": "cm2kpi4xu05z907z47bbk1kr5", "name": "relationship", "value": "relationship", "annotation_kind": "DocumentUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "cm2kpvtfw040807icec0q6ux7", "target": "cm2kpvtfz040h07ic2b2kcz37" } } ] } } ], "project_details": { "ontology_id": "cm2kpi4xe05z407z4h6r77nys", "task_name": "Done", "batch_id": "98a7bb20-9099-11ef-bede-61385dfcee13", "batch_name": "PDF_annotation_batch", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 3, "workflow_history": [ { "action": "Approve", "created_at": "2024-10-22T17:26:32.093+00:00", "created_by": "[email protected]", "previous_task_name": "Initial review task", "previous_task_id": "94821756-edd8-4d53-9785-da3e41d310a6" }, { "action": "Move", "created_at": "2024-10-22T17:26:17.761+00:00", "created_by": "[email protected]", "previous_task_name": "Initial labeling task", "previous_task_id": "3e686f9b-4266-0311-a3bd-bc1ecc9ec115", "next_task_name": "Initial review task", "next_task_id": "94821756-edd8-4d53-9785-da3e41d310a6" }, { "action": "Move", "created_at": "2024-10-22T17:26:17.754+00:00", "created_by": "[email protected]", "next_task_name": "Initial labeling task", "next_task_id": "3e686f9b-4266-0311-a3bd-bc1ecc9ec115" } ] }, "project_tags": [] } } } ] ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh1n29e1mh2078d3jt3b5cx", "global_key": "0801.3483.pdf", "row_data": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "details": { "dataset_id": "clfh1n0um00wd071d2uf21fy6", "dataset_name": "pdf_demo_dataset", "created_at": "2023-03-20T16:31:01.000+00:00", "updated_at": "2023-03-20T16:31:01.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/pdf", "text_layer_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483-lb-textlayer.json" }, "attachments": [], "metadata_fields": [], "experiments": { "a15bd76b-e29f-02f2-b909-0e20f007f735": { "name": "PDF_model_run_9c051c54-bb5b-47be-a0f4-16c909a9afb0", "runs": { "a15bd76c-41aa-0f04-c633-ee3e40b1d0bc": { "name": "iteration 1", "run_data_row_id": "dff29c2a-27c2-443b-95a8-402d9c2be311", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs1ru105b3w142bd71v29u1", "annotations": { "objects": [ { "feature_id": "2aa47146-4d81-4fcd-b513-7f7f5e275db7", "feature_schema_id": "clhs1rh090buq07y15p2wd58d", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 0, "bounding_box": { "top": 135.3, "left": 102.771, "height": 109.84299999999999, "width": 415.8 } }, { "feature_id": "40cd3ef3-7258-4da6-bc8b-d4f2eb12abfb", "name": "ner_with_checklist_subclass", "value": "ner_with_checklist_subclass", "annotation_kind": "DocumentEntityToken", "classifications": [ { "feature_id": "4cf8dcba-32ab-4bf0-a97e-5a17d72335b5", "feature_schema_id": "clhs1rh0a0buv07y1e0wobtgu", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "b139a807-7b9f-41f2-97e2-1dca6cd9f460", "feature_schema_id": "clhs1rh0a0buw07y12tg3hm0x", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ], "location": { "groups": [ { "id": "80e1e326-0194-4f38-845c-d9d22b0054fb", "page_number": 1, "tokens": [ "c2639311-4db1-4ad5-9a8d-1f2ab2c2dae8", "2aa2f9df-df5a-4b3a-aa57-05aff053c147", "aa725467-7b86-4629-9c70-a6391decb39b", "6a4a0a69-f04e-44c0-b343-096aef413444", "8dcce11c-c2ae-4274-b634-c950c8a7f333", "85fccf77-474b-4707-85cc-592871d5c680", "5d16be99-fc40-4228-99db-0a121a0478e7" ], "text": "T. Sasaki,* N. Yoneyama, and N. Kobayashi" } ] } }, { "feature_id": "d2cd83b8-7a7b-4751-8b5d-dfa05be90bf5", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "2f4336f4-a07e-4e0a-a9e1-5629b03b719b", "page_number": 1, "tokens": [ "3f984bf3-1d61-44f5-b59a-9658a2e3440f", "3bf00b56-ff12-4e52-8cc1-08dbddb3c3b8", "6e1c3420-d4b7-4c5a-8fd6-ead43bf73d80", "87a43d32-af76-4a1d-b262-5c5f4d5ace3a", "e8606e8a-dfd9-4c49-a635-ad5c879c75d0", "67c7c19e-4654-425d-bf17-2adb8cf02c30", "149c5e80-3e07-49a7-ab2d-29ddfe6a38fa", "b0e94071-2187-461e-8e76-96c58738a52c" ], "text": "Metal-insulator (MI) transitions have been one of the" } ] } }, { "feature_id": "e166e286-1ead-438d-bb6e-0bc148b4c5f0", "feature_schema_id": "clhs1rh0a0bv007y1fyxw0gyg", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "DocumentBoundingBox", "classifications": [ { "feature_id": "68838874-d209-4672-891f-10954a698292", "feature_schema_id": "clhs1rh0a0bv107y14g5de74m", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "8ce7710d-7a15-4d95-b9a9-a9a2bf733380", "feature_schema_id": "clhs1rh0a0bv207y13bsndvuv", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [ { "feature_id": "bc3542d1-f373-4668-841b-15a4e9f8f67b", "feature_schema_id": "clhs1rh0a0bv307y17teq5ob3", "name": "second_sub_radio_question", "value": "second_sub_radio_question", "radio_answer": { "feature_id": "6d200263-2065-4363-b962-1ee5a1296a8e", "feature_schema_id": "clhs1rh0a0bv407y1d72h3bdp", "name": "second_sub_radio_answer", "value": "second_sub_radio_answer", "classifications": [] } } ] } } ], "page_number": 1, "bounding_box": { "top": 226.757, "left": 317.271, "height": 194.22899999999998, "width": 249.38600000000002 } } ], "classifications": [ { "feature_id": "2fbbc78f-d1df-428d-be26-da69a6516786", "feature_schema_id": "clhs1rh0a0bva07y11vb9gjzu", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "24924adc-cb1c-40fb-a945-364a2cb0782f", "feature_schema_id": "clhs1rh0a0bvb07y134ka29be", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "a10706c7-0e09-47d9-b273-ed61f6ee72c5", "feature_schema_id": "clhs1rh0b0bvg07y1as0cfj9r", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "03c2feb6-bda3-4ee2-ad9f-faef038ee029", "feature_schema_id": "clhs1rh0b0bvj07y1f1brb5f3", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "3653a591-4915-450f-88e4-f5afc8cd1440", "feature_schema_id": "clhs1rh0b0bvh07y19qgt6nsv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "da14ab32-c007-4e86-aaff-a64e51ff2503", "feature_schema_id": "clhs1rh0b0bvm07y1gpw00bw3", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "de0e9826-a0fc-4603-a59a-0c356895759c", "feature_schema_id": "clhs1rh0b0bvo07y1025n0p4a", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "913b5925-7b37-43a0-9086-c7734491d60a", "feature_schema_id": "clhs1rh0b0bvp07y1d1tbgpeg", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "7c10d9b2-0e07-4723-abef-ad022db24b10", "feature_schema_id": "clhs1rh0b0bvq07y13wux72rp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "ae62a004-f6b3-4436-916e-ce375cf8210e", "feature_schema_id": "clhs1rh0b0bvr07y15n246t5c", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "ee0a7c2c-cb78-4474-b12a-82406ee415c5", "feature_schema_id": "clhs1rh0c0bvw07y1hiyweequ", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "22d25a8a-0831-4499-8980-aaebbac1b4eb", "feature_schema_id": "clhs1rh0c0bvx07y1er8ge5qk", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "825ce160-5e9f-4926-aa63-ed17ad7fb534", "feature_schema_id": "clhs1rh0c0bvy07y1hmkv360v", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "32cd713f-c272-4657-8d0e-910e24e90414", "feature_schema_id": "clhs1rh0c0bvz07y1d1njge4e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs1ru105b3w142bd71v29u1", "annotations": { "objects": [ { "feature_id": "1d1a302a-2bf1-4a64-b2db-1283400fc8c6", "feature_schema_id": "clhs1rh0a0bv007y1fyxw0gyg", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "DocumentBoundingBox", "classifications": [ { "feature_id": "f13c6571-b3c8-4584-886b-555e1808a955", "feature_schema_id": "clhs1rh0a0bv107y14g5de74m", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "610215da-b253-46f8-8a6d-7dd56b36e32c", "feature_schema_id": "clhs1rh0a0bv207y13bsndvuv", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [ { "feature_id": "a1002a15-c1f1-43ce-9606-9b02fb4d3d77", "feature_schema_id": "clhs1rh0a0bv307y17teq5ob3", "name": "second_sub_radio_question", "value": "second_sub_radio_question", "radio_answer": { "feature_id": "a225ff2c-dccc-4ad1-8dd5-d76f10c5aab2", "feature_schema_id": "clhs1rh0a0bv407y1d72h3bdp", "name": "second_sub_radio_answer", "value": "second_sub_radio_answer", "classifications": [] } } ] } } ], "page_number": 1, "bounding_box": { "top": 226.757, "left": 317.271, "height": 194.22899999999998, "width": 249.38600000000002 } }, { "feature_id": "469420eb-12a7-401f-a5ea-142b24c622e3", "feature_schema_id": "clhs1rh090buq07y15p2wd58d", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 0, "bounding_box": { "top": 135.3, "left": 102.771, "height": 109.84299999999999, "width": 415.8 } }, { "feature_id": "d3c14b37-6b64-4c6d-91bc-446bf82dc0b5", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "2f4336f4-a07e-4e0a-a9e1-5629b03b719b", "page_number": 1, "tokens": [ "3f984bf3-1d61-44f5-b59a-9658a2e3440f", "3bf00b56-ff12-4e52-8cc1-08dbddb3c3b8", "6e1c3420-d4b7-4c5a-8fd6-ead43bf73d80", "87a43d32-af76-4a1d-b262-5c5f4d5ace3a", "e8606e8a-dfd9-4c49-a635-ad5c879c75d0", "67c7c19e-4654-425d-bf17-2adb8cf02c30", "149c5e80-3e07-49a7-ab2d-29ddfe6a38fa", "b0e94071-2187-461e-8e76-96c58738a52c" ], "text": "Metal-insulator (MI) transitions have been one of the" } ] } }, { "feature_id": "efe907c9-b84b-4015-b29b-3a1cae1655cc", "name": "ner_with_checklist_subclass", "value": "ner_with_checklist_subclass", "annotation_kind": "DocumentEntityToken", "classifications": [ { "feature_id": "12fb5823-1a12-4de4-8613-8460cec5eaf1", "feature_schema_id": "clhs1rh0a0buv07y1e0wobtgu", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "8be1ca80-1f98-48ad-ae65-d38ab941ef63", "feature_schema_id": "clhs1rh0a0buw07y12tg3hm0x", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ], "location": { "groups": [ { "id": "", "page_number": 1, "tokens": [], "text": "" } ] } } ], "classifications": [ { "feature_id": "35f2a9ed-e245-4654-94a5-9e7e48adff8d", "feature_schema_id": "clhs1rh0b0bvo07y1025n0p4a", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "70a52e6d-eda7-48f3-a1dd-65672c273318", "feature_schema_id": "clhs1rh0b0bvp07y1d1tbgpeg", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "5f87ca53-3637-4f26-9068-8645b6036646", "feature_schema_id": "clhs1rh0b0bvq07y13wux72rp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "c652afc0-d24f-42ea-9a5a-dee14cc1d80d", "feature_schema_id": "clhs1rh0b0bvr07y15n246t5c", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "73114ba5-3045-46e0-9323-468914afbdf0", "feature_schema_id": "clhs1rh0b0bvg07y1as0cfj9r", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "666fc303-b30e-4647-a694-5c34dab4781f", "feature_schema_id": "clhs1rh0b0bvj07y1f1brb5f3", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "f8c4ce45-0e56-4ec2-bc5a-ce9364b471f8", "feature_schema_id": "clhs1rh0b0bvh07y19qgt6nsv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "911665a7-4901-4c31-a410-5c3a7a614c1d", "feature_schema_id": "clhs1rh0c0bvw07y1hiyweequ", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "0341d434-c589-49d4-9a4a-b554647c2a44", "feature_schema_id": "clhs1rh0c0bvx07y1er8ge5qk", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ee1c729c-d1f1-478e-98f8-baefa24dff28", "feature_schema_id": "clhs1rh0c0bvy07y1hmkv360v", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "a2c0d47f-811c-4d70-b024-8514d67b206c", "feature_schema_id": "clhs1rh0c0bvz07y1d1njge4e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "a7f83569-636d-4993-81f0-6d4de144bb24", "feature_schema_id": "clhs1rh0a0bva07y11vb9gjzu", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "58624513-6ab0-452b-a9a0-72cebf648cd0", "feature_schema_id": "clhs1rh0a0bvb07y134ka29be", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "c7e3f324-7b53-47a3-aeb0-4217717fcfed", "feature_schema_id": "clhs1rh0b0bvm07y1gpw00bw3", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ], "relationships": [] } } ] } } } }, "models": {} } ``` # Export geospatial annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-geospatial-annotations How to export geospatial annotations and annotation export formats. ## Export as JSON ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Bounding box The bounding box coordinates are returned in the following order: * bottom left * top left * top right * bottom right * bottom left All coordinates are provided in \[long, lat] ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutzcu3t0or4148po04q689s", "feature_schema_id": "clutzceve001k0701bpescb12", "name": "bbox_geo", "value": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -99.207466, 19.397994 ], [ -99.207466, 19.399259 ], [ -99.205685, 19.399259 ], [ -99.205685, 19.397994 ], [ -99.207466, 19.397994 ] ] ] } } ``` ### Point All coordinates are provided in \[long, lat] ```json Slippy maps theme={"theme":"css-variables"} { "feature_id": "clutzcu3t0or1148px42yj5ld", "feature_schema_id": "clutzcevd001c07018wofd88r", "name": "point_geo", "value": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -99.206479, 19.40018 ] } } ``` ### Polyline All coordinates are provided in \[long, lat] ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutzcu3t0or2148p3v3crfp0", "feature_schema_id": "clutzcevd001e070148gedtyj", "name": "polyline_geo", "value": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -99.208421, 19.400322 ], [ -99.208099, 19.39759 ], [ -99.207584, 19.397762 ], [ -99.207283, 19.397327 ] ] } } ``` ### Polygon All coordinates are provided in \[long, lat]. The order of the coordinates is based on the sequence of points when drawn. ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutzcu3t0org148p7p5fvazc", "feature_schema_id": "clutzceve001i07014eazfofw", "name": "polygon_geo_2", "value": "polygon_geo_2", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -99.206045, 19.396804 ], [ -99.206045, 19.396662 ], [ -99.20582, 19.396662 ], [ -99.20582, 19.396804 ], [ -99.206045, 19.396804 ] ] ] } } ``` ### Classification - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutzcu3t0or5148poyka73v0", "feature_schema_id": "clutzcevf00240701aqil93c1", "name": "radio_question_geo", "value": "radio_question_geo", "radio_answer": { "feature_id": "clutzcu3t0or6148pl8mwljss", "feature_schema_id": "clutzcevf00250701fd0g59rp", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ``` ### Classification - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutzcu3t0orc148pg6ybpnuz", "feature_schema_id": "clutzcevf001w070195dtaip4", "name": "checklist_question_geo", "value": "checklist_question_geo", "checklist_answers": [ { "feature_id": "clutzcu3t0ord148ps02kawmq", "feature_schema_id": "clutzcevf001x0701aps7gmtc", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clutzcu3t0ore148pyfraw16x", "feature_schema_id": "clutzcevf001z0701gxqm8w12", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "clutzcu3t0orf148p4curv80a", "feature_schema_id": "clutzcevf002107010x0g6um1", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] } ``` ### Classification - Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "cldne8lok01w413yd5mgbxd66", "name": "free_text", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "cldnf9zmt0d7n075ohgnvecul", "row_data": "{\"tileLayerUrl\":\"https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw\",\"bounds\":[[-122.31764674186705,37.87276155898985],[-122.31635199317932,37.87398109727749]],\"minZoom\":17,\"maxZoom\":23,\"maxNativeZoom\":null,\"epsg\":\"EPSG4326\",\"tileSize\":256,\"alternativeLayers\":[],\"version\":2}", "details": { "dataset_id": "cldnf9yvd041f070d33t94f41", "dataset_name": "geotiff_noaa.json", "created_at": "2023-02-02", "updated_at": "2023-02-02", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/x-tms-geo" }, "attachments": [], "metadata_fields": [], "projects": { "cldnfa25103kd081ph3edg7xp": { "project_name": "geospatial_project_demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cldnfc62h029b13ydq6bgvyak", "label_details": { "created_at": "2023-02-02T18:21:40+00:00", "updated_at": "2023-02-02T18:21:40+00:00", "created_by": "[email protected]", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "cldnfc61v028v13ydwgha3a1d", "name": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -122.31741, 37.873557 ] } }, { "feature_id": "cldnfc61v028w13ydnbm7vl2q", "name": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -122.317578, 37.873963 ], [ -122.316398, 37.873967 ], [ -122.31639, 37.872779 ] ] } }, { "feature_id": "cldnfc61v028x13yd5swk0drd", "name": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316918, 37.87329 ], [ -122.317102, 37.873043 ], [ -122.316801, 37.873036 ], [ -122.316918, 37.87329 ] ] ] } }, { "feature_id": "cldnfc61v028y13yd41qozic7", "name": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317345, 37.873713 ], [ -122.317345, 37.873859 ], [ -122.31673, 37.873859 ], [ -122.31673, 37.873713 ], [ -122.317345, 37.873713 ] ] ] } }, { "feature_id": "cldnfc61v029113ydvc788rx9", "name": "bbox_checklist_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "cldnfc61v029213yd344u0cyk", "name": "checklist_class_name", "checklist_answers": [ { "feature_id": "cldnfc61v029313yda92qm39k", "name": "first_checklist_answer", "classifications": [] } ] } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317113, 37.873402 ], [ -122.317113, 37.873608 ], [ -122.316655, 37.873608 ], [ -122.316655, 37.873402 ], [ -122.317113, 37.873402 ] ] ] } }, { "feature_id": "cldnfc61v029413yd5lgevg2c", "name": "bbox_text_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "cldnfc61v029513ydmluqvqyt", "name": "free_text_geo", "text_answer": { "content": "sample text" } } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317508, 37.873182 ], [ -122.317508, 37.87338 ], [ -122.3171, 37.87338 ], [ -122.3171, 37.873182 ], [ -122.317508, 37.873182 ] ] ] } }, { "feature_id": "cldnfc61v029a13ydmog1dhzw", "name": "polygon_geo_2", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316362, 37.872966 ], [ -122.316362, 37.872948 ], [ -122.316356, 37.872948 ], [ -122.316356, 37.872966 ], [ -122.316362, 37.872966 ] ] ] } } ], "classifications": [ { "feature_id": "cldnfc61v028z13ydy0xcffbj", "name": "radio_question_geo", "radio_answer": { "feature_id": "cldnfc61v029013ydj55ek20y", "name": "first_radio_answer", "classifications": [] } }, { "feature_id": "cldnfc61v029613ydu76vcgbt", "name": "checklist_question_geo", "checklist_answers": [ { "feature_id": "cldnfc61v029713yd5x16lyg3", "name": "first_checklist_answer", "classifications": [] }, { "feature_id": "cldnfc61v029813ydcsx4mc2t", "name": "second_checklist_answer", "classifications": [] }, { "feature_id": "cldnfc61v029913yd2smfzpft", "name": "third_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "project_details": { "ontology_id": "cl8emhd3e7bll070k6gbicl97", "task_id": "fdc4b3d2-e0fe-41dc-bdef-0dd556870a20", "task_name": "Initial review task", "batch_id": "cc4e7070-f20e-11ed-9dd9-639c7e9f70ba", "batch_name": "geotiff_noaa.json-cl8emco177d5g07y8e06j4acp", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 3, "workflow_history": [ { "action": "Move", "created_at": "2023-05-14T04:21:29.062+00:00", "created_by": "[email protected]", "previous_task_name": "Initial labeling task", "previous_task_id": "0dde450a-93ba-0b77-b97e-de8f7f479cee", "next_task_name": "Initial review task", "next_task_id": "fdc4b3d2-e0fe-41dc-bdef-0dd556870a20" }, { "action": "Move", "created_at": "2023-05-14T04:21:29.044+00:00", "created_by": "[email protected]", "next_task_name": "Initial labeling task", "next_task_id": "0dde450a-93ba-0b77-b97e-de8f7f479cee" } ] } } } } ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh28su81agn07623rql6j44", "global_key": "slippy_maps", "row_data": "{\"tileLayerUrl\":\"https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw\",\"bounds\":[[-122.31764674186705,37.87276155898985],[-122.31635199317932,37.87398109727749]],\"minZoom\":17,\"maxZoom\":23,\"maxNativeZoom\":null,\"epsg\":\"EPSG4326\",\"tileSize\":256,\"alternativeLayers\":[],\"version\":2}", "details": { "dataset_id": "clfh28rib089g07134eqx03ew", "dataset_name": "geo_demo_dataset", "created_at": "2023-03-20T16:47:55.000+00:00", "updated_at": "2023-03-20T16:47:55.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/x-tms-geo" }, "attachments": [], "metadata_fields": [], "experiments": { "a15bda50-b901-02e0-07d6-e842d13b3f47": { "name": "geospatial_model_run_1f54f23a-0359-4f18-b3ec-808a6181a4fd", "runs": { "a15bda50-fd4e-06ff-8f9b-762516cf58c8": { "name": "iteration 1", "run_data_row_id": "247e2c89-0e82-4662-a757-5f7cfa07a8fd", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs22mh25b4d142bagekzwlv", "annotations": { "objects": [ { "feature_id": "12527b8c-e5c5-4cd5-9266-c6a1d90ef14c", "feature_schema_id": "clhs21cpy0c6t07y1fof39vn7", "name": "point_geo", "value": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -122.31741, 37.873557 ] } }, { "feature_id": "185de2b0-fca0-48fc-9f3c-982e1487ce13", "feature_schema_id": "clhs21cpy0c7307y1dg174k0l", "name": "bbox_checklist_geo", "value": "bbox_checklist_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "d2809ef9-01ae-4ad7-a4d0-1fee6938e186", "feature_schema_id": "clhs21cpy0c7407y1gl9g9ver", "name": "checklist_class_name", "value": "checklist_class_name", "checklist_answers": [ { "feature_id": "6cbc5158-ac53-445a-a6c1-8035b7a4cac1", "feature_schema_id": "clhs21cpy0c7507y16idc9qjy", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317113, 37.873608 ], [ -122.317113, 37.873402 ], [ -122.316655, 37.873402 ], [ -122.316655, 37.873608 ], [ -122.317113, 37.873608 ] ] ] } }, { "feature_id": "78024089-436d-4a0e-aea9-f458ee66cb42", "feature_schema_id": "clhs21cpy0c6v07y19w4zb3b7", "name": "polyline_geo", "value": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -122.317578, 37.873963 ], [ -122.316398, 37.873967 ], [ -122.31639, 37.872779 ] ] } }, { "feature_id": "97110d09-559b-46c9-abb4-7e5d5f578b3e", "feature_schema_id": "clhs21cpy0c6x07y18t2hgvdp", "name": "polygon_geo", "value": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316918, 37.87329 ], [ -122.317102, 37.873043 ], [ -122.316801, 37.873036 ], [ -122.316918, 37.87329 ] ] ] } }, { "feature_id": "cbbd8796-d6ff-4238-9347-bb12b239ac74", "feature_schema_id": "clhs21cpy0c7907y19etw75vg", "name": "bbox_text_geo", "value": "bbox_text_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "0492b8b4-217b-4fdb-bc93-1b0a30f5aa8b", "feature_schema_id": "clhs21cpz0c7a07y16uczf4dx", "name": "free_text_geo", "value": "free_text_geo", "text_answer": { "content": "sample text" } } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317508, 37.87338 ], [ -122.317508, 37.873182 ], [ -122.3171, 37.873182 ], [ -122.3171, 37.87338 ], [ -122.317508, 37.87338 ] ] ] } }, { "feature_id": "d80e2563-002a-48b8-b6c6-50f7df1a4a8b", "feature_schema_id": "clhs21cpy0c7107y1b07n6ef9", "name": "bbox_geo", "value": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317345, 37.873859 ], [ -122.317345, 37.873713 ], [ -122.31673, 37.873713 ], [ -122.31673, 37.873859 ], [ -122.317345, 37.873859 ] ] ] } }, { "feature_id": "f6abee6b-8130-4033-a223-be428dd59c0a", "feature_schema_id": "clhs21cpy0c6x07y18t2hgvdp", "name": "polygon_geo", "value": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.31703, 37.873978 ], [ -122.317024, 37.873935 ], [ -122.316989, 37.873899 ], [ -122.316979, 37.873859 ], [ -122.316958, 37.873856 ], [ -122.316955, 37.873816 ], [ -122.31692, 37.873779 ], [ -122.31691, 37.873734 ], [ -122.316886, 37.873697 ], [ -122.316855, 37.873685 ], [ -122.31682, 37.873611 ], [ -122.316765, 37.87355 ], [ -122.316758, 37.873517 ], [ -122.31671, 37.873489 ], [ -122.31671, 37.873452 ], [ -122.316679, 37.87344 ], [ -122.316634, 37.873355 ], [ -122.316603, 37.873348 ], [ -122.3166, 37.873321 ], [ -122.316545, 37.87329 ], [ -122.316528, 37.873199 ], [ -122.31649, 37.873183 ], [ -122.31649, 37.873162 ], [ -122.316417, 37.873092 ], [ -122.316411, 37.873055 ], [ -122.316397, 37.87304 ], [ -122.316356, 37.87304 ], [ -122.316356, 37.873981 ], [ -122.31703, 37.873978 ] ] ] } } ], "classifications": [ { "feature_id": "08c1417e-ac8f-4272-9f4f-8b523ae5e0d7", "feature_schema_id": "clhs21cpz0c7l07y160gh5xdw", "name": "radio_question_geo", "value": "radio_question_geo", "radio_answer": { "feature_id": "8c72625a-e663-4241-a2c2-8082804a59e3", "feature_schema_id": "clhs21cpz0c7m07y188gjbqbj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "a22dac3b-5c40-4f08-9723-d70e7bc4bff4", "feature_schema_id": "clhs21cpz0c7d07y14vnl7zpi", "name": "checklist_question_geo", "value": "checklist_question_geo", "checklist_answers": [ { "feature_id": "1c4a1950-6961-42bf-b134-50596f136164", "feature_schema_id": "clhs21cpz0c7e07y16k192u14", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "963cc214-b134-4b0e-80d5-8e63bcc000ae", "feature_schema_id": "clhs21cpz0c7g07y17yo0e2cn", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "bfa3e08b-5f2d-468e-aa7c-a6c2a29f9f4f", "feature_schema_id": "clhs21cpz0c7i07y1hytkftpk", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs22mh25b4d142bagekzwlv", "annotations": { "objects": [ { "feature_id": "21d3b49c-0e17-4eb1-9824-2b05a23386d5", "feature_schema_id": "clhs21cpy0c6v07y19w4zb3b7", "name": "polyline_geo", "value": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -122.317578, 37.873963 ], [ -122.316398, 37.873967 ], [ -122.31639, 37.872779 ] ] } }, { "feature_id": "4bcd8a06-cdc9-4450-b59e-d6601d185b88", "feature_schema_id": "clhs21cpy0c6t07y1fof39vn7", "name": "point_geo", "value": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -122.31741, 37.873557 ] } }, { "feature_id": "69d6cf96-1198-42e0-83a7-c75f0c945037", "feature_schema_id": "clhs21cpy0c7107y1b07n6ef9", "name": "bbox_geo", "value": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317345, 37.873859 ], [ -122.317345, 37.873713 ], [ -122.31673, 37.873713 ], [ -122.31673, 37.873859 ], [ -122.317345, 37.873859 ] ] ] } }, { "feature_id": "9090fb5d-09f3-4a5f-9114-9b3c4ce6c909", "feature_schema_id": "clhs21cpy0c6x07y18t2hgvdp", "name": "polygon_geo", "value": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316918, 37.87329 ], [ -122.317102, 37.873043 ], [ -122.316801, 37.873036 ], [ -122.316918, 37.87329 ] ] ] } }, { "feature_id": "9ef3b0dc-b085-4f64-9018-264db30f9018", "feature_schema_id": "clhs21cpy0c7307y1dg174k0l", "name": "bbox_checklist_geo", "value": "bbox_checklist_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "a77cd878-d15d-4628-ab08-9ecd192bbebc", "feature_schema_id": "clhs21cpy0c7407y1gl9g9ver", "name": "checklist_class_name", "value": "checklist_class_name", "checklist_answers": [ { "feature_id": "5a6a23f8-39c7-409c-953f-732d3cf4cbec", "feature_schema_id": "clhs21cpy0c7507y16idc9qjy", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317113, 37.873608 ], [ -122.317113, 37.873402 ], [ -122.316655, 37.873402 ], [ -122.316655, 37.873608 ], [ -122.317113, 37.873608 ] ] ] } }, { "feature_id": "f7df220a-27c0-4fc1-b67f-094b28efec3d", "feature_schema_id": "clhs21cpy0c6z07y1a99ubplb", "name": "polygon_geo_2", "value": "polygon_geo_2", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.31703, 37.873978 ], [ -122.317024, 37.873935 ], [ -122.316989, 37.873899 ], [ -122.316979, 37.873859 ], [ -122.316958, 37.873856 ], [ -122.316955, 37.873816 ], [ -122.31692, 37.873779 ], [ -122.31691, 37.873734 ], [ -122.316886, 37.873697 ], [ -122.316855, 37.873685 ], [ -122.31682, 37.873611 ], [ -122.316765, 37.87355 ], [ -122.316758, 37.873517 ], [ -122.31671, 37.873489 ], [ -122.31671, 37.873452 ], [ -122.316679, 37.87344 ], [ -122.316634, 37.873355 ], [ -122.316603, 37.873348 ], [ -122.3166, 37.873321 ], [ -122.316545, 37.87329 ], [ -122.316528, 37.873199 ], [ -122.31649, 37.873183 ], [ -122.31649, 37.873162 ], [ -122.316417, 37.873092 ], [ -122.316411, 37.873055 ], [ -122.316397, 37.87304 ], [ -122.316356, 37.87304 ], [ -122.316356, 37.873981 ], [ -122.31703, 37.873978 ] ] ] } }, { "feature_id": "fbc72808-7f01-4cae-b53d-e868ac2e6b30", "feature_schema_id": "clhs21cpy0c7907y19etw75vg", "name": "bbox_text_geo", "value": "bbox_text_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "cd12abb2-2c30-48be-b4ef-a8e3b649e998", "feature_schema_id": "clhs21cpz0c7a07y16uczf4dx", "name": "free_text_geo", "value": "free_text_geo", "text_answer": { "content": "sample text" } } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317508, 37.87338 ], [ -122.317508, 37.873182 ], [ -122.3171, 37.873182 ], [ -122.3171, 37.87338 ], [ -122.317508, 37.87338 ] ] ] } } ], "classifications": [ { "feature_id": "413f0623-6bb6-4631-9c2c-dd95cd01ff0b", "feature_schema_id": "clhs21cpz0c7d07y14vnl7zpi", "name": "checklist_question_geo", "value": "checklist_question_geo", "checklist_answers": [ { "feature_id": "27fe9828-16fc-446d-8ff9-563d1bda1df9", "feature_schema_id": "clhs21cpz0c7e07y16k192u14", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "8b97530e-96ce-4ce4-8232-e27c8b9bde4f", "feature_schema_id": "clhs21cpz0c7g07y17yo0e2cn", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "c8c5c1d3-c805-4cde-9522-254d390ca880", "feature_schema_id": "clhs21cpz0c7i07y1hytkftpk", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] }, { "feature_id": "dd36f2ec-91be-4bf6-9f6e-7531598628c0", "feature_schema_id": "clhs21cpz0c7l07y160gh5xdw", "name": "radio_question_geo", "value": "radio_question_geo", "radio_answer": { "feature_id": "06a746a8-78c0-4bb5-9f71-65e8303155b5", "feature_schema_id": "clhs21cpz0c7m07y188gjbqbj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ], "relationships": [] } } ] } } } }, "models": {} } ``` # Export glossary Source: https://docs.labelbox.com/horizon/developer-guides/export-glossary Complete list of definitions for the fields included in exports. Labelbox exports give you more flexibility and control to retrieve the most valuable information from your projects. You can now select and export a subselection of the data rows of most interest based on your predefined or new parameters in the Data Rows tab. You can also export more detailed information from these data rows, and include or exclude relevant attributes in your export. We also simplified and standardized the annotation formats. For more details on how to create exports, along with complete samples, please view the following pages: Below is a glossary of the fields that can appear in an export. ## Export fields Labelbox exports aligns with the data-row-centric paradigm, thus every line, regardless of the export time, will include information on each included data row. | Field | Included | Description | | - | - | - | | `data_row` | Always | A dictionary containing the fields explained the `data_row` table below. | | `media_attributes` | Optional | See [Media attributes](/horizon/developer-guides/media-attributes) | | `attachments` | Optional | See [Attachments](/horizon/developer-guides/attachments) | | `metadata_fields` | Optional | See [Metadata](/horizon/developer-guides/metadata) | | `embeddings` | Optional | A list of dictionaries containing precomputed and custom embeddings | # `data_row` | Field | Included | Description | | - | - | - | | `id` | Always | The ID of the data row. | | `global_key` | If utilized | The global key of the data row. | | `row_data` | Always | The URL to your cloud-hosted file | | `details` | Optional | A dictionary containing the fields explained in the `details` table below. When exporting through the SDK, these fields are included by setting the `data_row_details` parameter to `True`. | ## `details` | Field | Included | Description | | - | - | - | | `dataset_id` | Always | The ID of the dataset to which the data row belongs. | | `dataset_name` | Always | The name given to the dataset. | | `created_at` | Always | A timestamp that indicates when the data row was created. | | `updated_at` | Always | A timestamp indicating when the data row was most recently updated. | | `last_activity_at` | Always\* | A timestamp indicating the last time *any* activity was performed on the data row. | | `created_by` | Always | The email address of the user that created the data row. | \* The `last_activity_at` field is *not* included in exports from a model run. # `projects` The `projects` field contains a dictionary in which the keys are project IDs and the values consist of the fields explained below. In a project-based export, there will only be a singular project ID in this dictionary. Yet, when exporting from the Catalog, consider that a data row may have been labeled in multiple projects, and thus the dictionary will have multiple keys. Project IDs are used in favor of project names in order to enforce uniqueness. | Field | Included | Description | | - | - | - | | `name` | Always | The name of the project in which the data row was labeled. | | `labels` | Always | Contains a list of dictionaries comprised of the fields explained in the `labels` section below. | | `project_details` | Optional | A dictionary containing the fields explained in the `project_details` table below. | ## `labels` | Field | Included | Description | | - | - | - | | `label_kind` | Always | For labels made on assets of most media types, the value is `Default`. For frame-based assets, the value is `Video`. | | `version` | Always | Used to track updates made to export formats. At present, the value will always be `1.0.0`. | | `id` | Always | The ID of the label. | | `label_details` | Optional | A dictionary containing the fields explained in the `label_details` table below. | | `performance_details` | Optional | A dictionary containing the fields explained in the `performance_details` table below. | | `annotations` | Always | See the annotation export formats broken down by asset type, beginning [here](/horizon/developer-guides/export-image-annotations#annotation-export-formats) with images. | ### `label_details` | Field | Included | Description | | - | - | - | | `created_at` | Always | A timestamp indicating when the label was created. | | `updated_at` | Always | A timestamp indicating when the label was most recently updated. | | `created_by` | Always | The email address of the user that created the label. | | `reviews` | Always | \[Legacy] For projects using [Workflows](/horizon/guides/workflows) for review, please use `workflow_history` [here](/horizon/developer-guides/export-glossary#workflow_history). Information on the thumbs up/down reviews created on this label. Contains a list of dictionaries comprised of the fields explained in the `reviews` table below. | #### `reviews`(for legacy only) | Field | Included | Description | | - | - | - | | `reviewed_at` | Situational | A timestamp that indicates when the review was created. | | `reviewed_by` | Situational | The email address of the user that created the review. | | `review_action` | Situational | The type of review created; either `Approve` or `Reject`. | ### `performance_details` | Field | Included | Description | | - | - | - | | `seconds_to_create` | Always | The total time in seconds spent creating the label, including initial labeling, review, and rework. | | `seconds_to_review` | Always | The number of seconds spent reviewing the label. | | `skipped` | Always | A boolean value expressing whether or not the asset was skipped. A value of `true` indicates the asset was skipped. | ### Delay on value update Each `performance_details`field may take up to 30 minutes to update after you labeling, reviewing, reworking, or submitting the label. #### `performance_details_v2` The breakdown of time spent on each workflow task (initial labeling, review, and rework). | Field | Included | Description | | - | - | - | | `seconds_to_label` | Always | The total number of seconds spent on the initial labeling task. | | `seconds_to_review` | Always | The number of seconds spent reviewing the label. | | `seconds_to_rework` | Always | The number of seconds spent reworking the label. | | `seconds_total` | Always | The total time in seconds spent creating the label, including initial labeling, review, and rework. | | `skipped` | Always | A boolean value indicating whether the asset was skipped. A value of `true` indicates the data row was skipped. | ### `annotations` | Field | Included | Description | | - | - | - | | `objects` | Always | Tool annotations (e.g., bounding box, masks, polygon, etc ..) | | `classifications` | Always | Classification annotations (radio, checklist) | | `frames` | Videos only | Dictionary with per-frame annotations (objects and classifications) | | `segments` | Videos only | A dictionary where each key is a `feature_id`, and the corresponding value is a list of frame numbers representing the range of frames where that feature exists. | | `key_frame_feature_map` | Videos only | A dictionary where each key is a `feature_id`, and the corresponding value is a list of frame numbers representing the frames where that feature exists. | | `feature_id` | Always | Unique identifier for an annotation in a label; this id is also present on all classification answers. | | `feature_schema_id` | Always | Unique identifier for an ontology tool or classification; this id is also present on all classification answers. | | `annotation_kind` | Situational | The kind of tool utilized by the annotation (e.g. ImageBoundingBox, ImagePolyline) | | `name` | Always | Name given to the tool or classification | | `value` | Always | Normalized name of the tool or classification: if a user creates a tool named "Apple Pear," this field will show the normalized version "apple\_pear." | ## `project_details` | Field | Included | Description | | - | - | - | | `ontology_id` | Always | The ID of the ontology connected to the project. | | `task_id` | Always | The ID of the task the data row currently is in in the project. | | `task_name` | Always | The name of the task the data row currently is in in the project. | | `batch_id` | Always | The ID of the batch in which the data row was sent to the project. | | `batch_name` | Always | The name of the batch in which the data row was sent to the project. | | `workflow_status` | Always | The status of the data row in the project (either `TO_LABEL`, `IN_REWORK`, `IN_REVIEW`, or `DONE`) | | `priority` | Always | The priority assigned to the batch. | | `selected_label_id` | If utilized | The ID of the label that was selected as the "winner" amongst the labels made on the data row. | | `consensus_expected_label_count` | Always | The number of labels that were expected to be created on this data row according to the [consensus](/horizon/guides/consensus) settings. | | `workflow_history` | Always | Information on the progression of the labeled data row through the project's workflow. Contains a list of dictionaries comprised of the fields explained in the `workflow_history` table below. | ### `workflow_history` | Field | Included | Description | | - | - | - | | `action` | Always | The action that was performed on the data row in this specific step. Typical actions are: `Move` : change of task queue `Accept`or `Reject`: selection of a review score `Rework`: follows `Reject` | | `created_at` | Always | A timestamp that indicates when this action on the data row occurred. | | `created_by` | Always | The email address of the user that performed this action. | | `previous_task_name` | Situational | The name of the workflow task in which the data row began this action. | | `previous_task_id` | Situational | The ID of the workflow task in which the data row began this action. | | `next_task_name` | Situational | The name of the workflow task in which the data row concluded this action. | | `next_task_id` | Situational | The ID of the workflow task in which the data row concluded this action. | # `experiments` The `experiments` field contains a dictionary in which the keys are model experiment IDs, and the values consist of the `name` and a dictionary of model `runs`. In a model-based export, only a singular model ID will be in this dictionary. Yet, when exporting from the Catalog, consider that a data row may be included in multiple models, and thus the dictionary will have multiple keys. Model experiment IDs are used in favor of model names to enforce uniqueness. | Field | Included | Description | | - | - | - | | `name` | Always | The name of the model in which the data row appears. | | `runs` | Always | A dictionary where the keys are the IDs of the model runs in which the data row appears. | ## `runs` The model `runs` field contains a dictionary in which the keys are model run IDs, and the values consist of the fields explained below. Model run IDs are used in favor of model run names to enforce uniqueness. | Field | Included | Description | | - | - | - | | `name` | Always | The name of the model run. | | `run_data_row_id` | Always | A unique ID for the data row in the context of the model run. | | `labels` | Situational | Ground truth annotations can optionally be sent to a model run as labels. If present, the `labels` will appear in the same format as in a project-based export, which is detailed in the `labels` table above, though the optional `label_details` and `performance_details` sections are always excluded in the context of a model run. | | `predictions` | Optional | A dictionary containing the fields explained in the `predictions` table below. | | `split` | Situational | The value will be included if the data row is assigned to a split. The potential outputs are `Training`, `Validation`, or`Test`. When exporting through the SDK, these fields are included by setting the `model_run_details` parameter to `True`. | ### `predictions` | Field | Included | Description | | - | - | - | | `label_kind` | Always | For labels made on assets of most media types, the value is `Default`. For frame-based assets, the value is `Video`. | | `version` | Always | Used to track updates made to export formats. At present, the value will always be `1.0.0`. | | `id` | Always | The ID of the set of predictions. | | `annotations` | Always | See the annotation export formats broken down by asset type, beginning [here](/horizon/developer-guides/export-image-annotations#annotation-export-formats) with images. | # Export HTML annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-html-annotations How to export HTML data and sample export formats. ## Export JSON annotations ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Classification - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clgva8bo5hmfs1401auduvxht", "feature_schema_id": "clgva7wwb0l9r07x54f9h826z", "name": "radio_html", "value": "radio_html", "radio_answer": { "feature_id": "clgva8bo5hmft1401nhfbg7ja", "feature_schema_id": "clgva7wwb0l9u07x55fyxa5g3", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } } ``` ### Classification - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clgva8bo5hmfp14016xok5j73", "feature_schema_id": "clgva7wwa0l9l07x558vydexo", "name": "checklist_html", "value": "checklist_html", "checklist_answers": [ { "feature_id": "clgva8bo5hmfq1401xnngji4s", "feature_schema_id": "clgva7wwb0l9m07x5671qe9ej", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clgva8bo5hmfr1401w191mdo5", "feature_schema_id": "clgva7wwb0l9o07x5h08p9572", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ``` ### Classification - Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clgva8bo5hmfo140186f07jci", "feature_schema_id": "clgva7wwa0l9j07x51e4t0bq8", "name": "text_html", "value": "text_html", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfskiyrx0e0q078k40evbanc", "global_key": "sample_html_1.html", "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "details": { "dataset_id": "clfskixq70ym7071scn9l16z0", "dataset_name": "html_annotation_import_demo_dataset", "created_at": "2023-03-28T18:05:11.000+00:00", "updated_at": "2024-01-19T23:05:36.279+00:00", "last_activity_at": "2023-04-24T20:19:59.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "text/html" }, "attachments": [], "metadata_fields": [], "projects": { "clgva7x400opk07x73jgs6p8o": { "name": "HTML Import Annotation Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clgva8bonhmg21401hcpq6tee", "label_details": { "created_at": "2023-04-24T20:19:59.000+00:00", "updated_at": "2023-04-24T20:19:59.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2023-04-24T20:19:59.466+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clgva8bo5hmfo140186f07jci", "feature_schema_id": "clgva7wwa0l9j07x51e4t0bq8", "name": "text_html", "value": "text_html", "text_answer": { "content": "sample text" } }, { "feature_id": "clgva8bo5hmfp14016xok5j73", "feature_schema_id": "clgva7wwa0l9l07x558vydexo", "name": "checklist_html", "value": "checklist_html", "checklist_answers": [ { "feature_id": "clgva8bo5hmfq1401xnngji4s", "feature_schema_id": "clgva7wwb0l9m07x5671qe9ej", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clgva8bo5hmfr1401w191mdo5", "feature_schema_id": "clgva7wwb0l9o07x5h08p9572", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "clgva8bo5hmfs1401auduvxht", "feature_schema_id": "clgva7wwb0l9r07x54f9h826z", "name": "radio_html", "value": "radio_html", "radio_answer": { "feature_id": "clgva8bo5hmft1401nhfbg7ja", "feature_schema_id": "clgva7wwb0l9u07x55fyxa5g3", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "clgva8bo5hmfu1401yuz7wvuy", "feature_schema_id": "clgva7wwb0l9x07x57si1gihw", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "clgva8bo6hmfv14015d33m625", "feature_schema_id": "clgva7wwb0l9y07x5dt6l44qv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "clgva8bo6hmfw1401u59hfep5", "feature_schema_id": "clgva7wwb0l9z07x5bvtx0agv", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "clgva8bo6hmfx14018asstw29", "feature_schema_id": "clgva7wwb0la007x515qu9lje", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "clgva8bo6hmfy1401bivtvjjt", "feature_schema_id": "clgva7wwb0la507x583lg7mvf", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "clgva8bo6hmfz1401y555xn92", "feature_schema_id": "clgva7wwb0la607x5h39u2z31", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "clgva8bo6hmg01401agmna01o", "feature_schema_id": "clgva7wwb0la707x5hxwe16bo", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clgva8bo6hmg11401dzqw0od8", "feature_schema_id": "clgva7wwb0la807x51792e6il", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clgva7wto0l9i07x5emb8b4uo", "task_name": "Done", "batch_id": "5ba71600-e2dd-11ed-8ee3-e3123dc756e1", "batch_name": "first-batch-html-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] }, "project_tags": [] } } } ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfcvkqx708ba079n4nfo1drf", "global_key": "sample_html_2.html", "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "details": { "dataset_id": "clfcvkpzc0v8l07yt9vhc16sv", "dataset_name": "html demo dataset", "created_at": "2023-03-17T18:30:11.000+00:00", "updated_at": "2024-01-19T22:55:40.282+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "text/html" }, "attachments": [], "metadata_fields": [], "experiments": { "a414cfe7-f6ab-094b-8307-68d5a7272397": { "name": "HTML_model_run_1d21d941-f392-41e9-85d4-a52b3a3b5bfd", "runs": { "a414cfe8-690d-0b10-6b9f-7e9bf6a7dd1c": { "name": "iteration 1", "run_data_row_id": "d2bdab47-af16-4052-a310-74fa324cda5f", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu1x19z0out148p9vadgmcu", "annotations": { "objects": [], "classifications": [ { "feature_id": "3625d4eb-ca0f-0385-9c74-c3538b40621c", "feature_schema_id": "cluu1wl30002007wo1gnn8htz", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "47d39827-416f-042f-a9f5-7d5bf45ba884", "feature_schema_id": "cluu1wl2z001k07wo17r02qrr", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "89c36638-ec65-095c-893a-49417a1611fc", "feature_schema_id": "cluu1wl2z001l07wo6kll5fc0", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "5de3b3eb-78a8-06e8-b420-491234d05743", "feature_schema_id": "cluu1wl2z001m07wodiwh0hjp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "684b6ca5-e5d4-0684-991d-597e5ea76700", "feature_schema_id": "cluu1wl2z001n07wo8ey5f1yw", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "9297d521-2b0a-04bd-b88b-b5512b85ec20", "feature_schema_id": "cluu1wl2z001s07woc5xmcz68", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "add68a9d-01b1-039a-bbdf-c96f63d408e6", "feature_schema_id": "cluu1wl30001v07wocp3hg8a0", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "ce4eca3a-b2f5-0861-954f-ad7ff07c1a7f", "feature_schema_id": "cluu1wl30001x07wobpdwgf4w", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "fab6d119-206f-0b6d-a5a7-8a08e17ed8cf", "feature_schema_id": "cluu1wl2z001t07wobsowcibp", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "9c1c6bad-ca20-08c9-8d1a-78907e65ebe4", "feature_schema_id": "cluu1wl2z001g07wo5elxfwut", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "28ea7b46-b891-0285-a8a3-3ba7a30cdeb4", "feature_schema_id": "cluu1wl2z001h07wo760uc0sb", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "d48dce83-f514-0728-9b5a-deb045493dc3", "feature_schema_id": "cluu1wl30002207woa3u870v7", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "ca4f111b-c6f6-013a-a38e-0ce1ee77b370", "feature_schema_id": "cluu1wl30002307wogixwdc5z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "6d08884d-ff25-0a41-a5aa-875e6385ea1e", "feature_schema_id": "cluu1wl30002407woea6e102z", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "02db9eb9-6763-0a2c-92ee-cdf3b11b8de4", "feature_schema_id": "cluu1wl30002507wo07av3u9e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu1x19z0out148p9vadgmcu", "annotations": { "objects": [], "classifications": [ { "feature_id": "162ea9d9-c6e8-48e9-b4fe-1beb5ec35e71", "feature_schema_id": "cluu1wl2z001k07wo17r02qrr", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "ecdb63be-bd8b-4768-ab51-6399e38c3fd8", "feature_schema_id": "cluu1wl2z001l07wo6kll5fc0", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "9bdb8546-6ee7-4993-bea7-9de7d1d4f0d5", "feature_schema_id": "cluu1wl2z001m07wodiwh0hjp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "a0466f11-c940-4217-9589-56f261425e43", "feature_schema_id": "cluu1wl2z001n07wo8ey5f1yw", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "2b7e8e1b-a55d-4ac6-80e2-629bc64c5b97", "feature_schema_id": "cluu1wl30002007wo1gnn8htz", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "347dd2d9-7729-4ebd-970a-c4eedb1d41cc", "feature_schema_id": "cluu1wl30002207woa3u870v7", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "1a17cdf4-2159-4c21-b1d8-7b9490d8bb14", "feature_schema_id": "cluu1wl30002307wogixwdc5z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ab20eb98-3002-4108-a541-37741a9a1531", "feature_schema_id": "cluu1wl30002407woea6e102z", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "31e09418-2b8e-4759-bd71-4f88302398ce", "feature_schema_id": "cluu1wl30002507wo07av3u9e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "70f54814-79c0-4d66-84c5-531e8b53e657", "feature_schema_id": "cluu1wl2z001g07wo5elxfwut", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "214cb3db-da72-4692-9fa4-c86e2933b11e", "feature_schema_id": "cluu1wl2z001h07wo760uc0sb", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "baa18a09-6626-48cb-9b4c-7d75aa7d8c57", "feature_schema_id": "cluu1wl2z001s07woc5xmcz68", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "c1cf3d18-238f-4817-9fed-93c14954c153", "feature_schema_id": "cluu1wl30001v07wocp3hg8a0", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "cf4606ab-1a5b-4c50-87a3-1f2b5dfc9547", "feature_schema_id": "cluu1wl30001x07wobpdwgf4w", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "f4f28750-5a7f-4da0-a081-b4da8eacb667", "feature_schema_id": "cluu1wl2z001t07wobsowcibp", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ] } } } }, "models": {} } ``` # Export image annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-image-annotations How to export image annotations and sample export formats. ## Export as JSON ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Bounding box ```json JSON theme={"theme":"css-variables"} { "feature_id": "clrf5csvi6ofm07lsf9pygwvi", "feature_schema_id": "clrf5ck4a0b9b071paa9ncu15", "name": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 1264.0, "left": 2096.0, "height": 425.0, "width": 144.0 } } ``` ### Polygon ```json JSON theme={"theme":"css-variables"} { "feature_id": "clr0x0ol83tvt07ea4has2a4l", "feature_schema_id": "clpk2me9t02v707xn08cv4ve5", "name": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 1489.581, "y": 183.934 } ] } ``` ### Polyline ```json JSON theme={"theme":"css-variables"} { "feature_id": "clr0x0ol83tvk07eagehegjde", "feature_schema_id": "clpk2me9u02vh07xnfcmtbqvj", "name": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 90.929, "y": 326.412 } ] } ``` ### Point ```json JSON theme={"theme":"css-variables"} { "feature_id": "clr0x0ol83tvr07ea2fyl69g9", "feature_schema_id": "clpk2me9u02vf07xnbpk45wxa", "name": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } } ``` ### Mask Regardless of which [tool](/horizon/guides/image-editor#segmentation-mask) is used to create a segmentation mask, the annotation will appear in the following form in the export. Whether you use AutoSegment, the brush tool, the pen tool, or the fill tool, all masks take the same format in exports. #### Composite masks [Open this Colab](https://colab.research.google.com/github/Labelbox/labelbox-notebooks/blob/main/exports/composite_mask_export.ipynb) for an interactive tutorial on exporting composite masks. A composite mask is a cloud-hosted PNG file that contains all raster segmentation mask instances on a labeled data row (one composite mask per label). When you export data rows containing segmentation masks, you can use `composite_mask.url` to access all segmentation masks on that data row. To access a single instance of a segmentation mask, use the `color_rgb` values. The `color_rgb` value represents the color of a single annotation or instance mask in a label. ##### Sample composite masks export ```json JSON theme={"theme":"css-variables"} { "feature_id": "clsivokxg00043b70xuf7x5tz", "feature_schema_id": "clsgbcaqw000j071t2ga23jnc", "name": "car", "value": "car", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/clsivnieg050k074i2jqaa0c3/annotations/clsivokxg00043b70xuf7x5tz/index/1/mask" }, "composite_mask": { // This URL contains all mask instances on the data row "url": "https://api.labelbox.com/api/v1/tasks/clsiw88n205mw072ke4n78y5o/masks/clsivok5f00023b70r1a4fgzy", // This RGB reflects a single mask instance "color_rgb": [ 31, 185, 101 ] } } ``` #### Access mask data To access the mask data, you must pass your Labelbox API key stored in `client.headers` in an API request. When you grab a URL from the mask annotation in the export, the `project_id` and `feature_id` will already be in place, as shown above. Here, we provide the framework for structuring a URL with any project ID and feature ID. ```python Python theme={"theme":"css-variables"} import urllib.request from PIL import Image # Provide a project ID and feature ID if using instance masks. Alternatively, replace the entire mask_url with a URL grabbed from your export. project_id = "" feature_id = "" mask_url = f"https://api.labelbox.com/api/v1/projects/{project_id}/annotations/{feature_id}/index/1/mask" # Instead of providing the individual variables, we recommend utilizing the URL from export for composite mask composite_mask_url = f"https://api.labelbox.com/api/v1/tasks/{task_id}/masks/{composite_mask_id}/index/1" # Make the API request req = urllib.request.Request(mask_url, headers=client.headers) # Optionally, print the image of the mask image = Image.open(urllib.request.urlopen(req)) image.show() ``` ### Relationship ```json JSON theme={"theme":"css-variables"} { "feature_id": "clur69pfa00023b6rkftlkucz", "feature_schema_id": "cltd3vkc201ab07ts1f9dhpkt", "name": "relationship", "value": "relationship", "annotation_kind": "ImageUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "clur5tlyq1n2d07dcg9xldg90", "target": "clur5tlyr1n2l07dc1jsf15qo" } } ``` ### Classification - radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clur5tlyr1n2k07dcezk08npu", "feature_schema_id": "cltd3vkc201ad07ts3t78873p", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "clur5tlyr1n2y07dcajpyg0d4", "feature_schema_id": "cltd3vkc201ag07tsfoc2bdpi", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } } ``` ### Classification - checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clur5tlyr1n2p07dchs791672", "feature_schema_id": "cltd3vkc201aj07tsait4cbzh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n2z07dc3abv19e8", "feature_schema_id": "cltd3vkc201ak07tsfqxc740z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clur5tlyr1n3007dc4vbr0628", "feature_schema_id": "cltd3vkc201am07ts7s9e7x7k", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ``` ### Classification - free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clur5tlyr1n2g07dc7wdv7p5z", "feature_schema_id": "cltd3vkc201ap07ts58gma0bb", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clf9spzxq029m071nehy60a6v", "global_key": "2560px-Kitano_Street_Kobe01s5s4110.jpeg", "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "details": { "dataset_id": "clf9spwgw04w207ymfwy826qv", "dataset_name": "demo_dataset_img", "created_at": "2023-03-15T14:46:58.000+00:00", "updated_at": "2023-03-15T14:46:58.000+00:00", "last_activity_at": "2024-04-08T16:34:01.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "height": 1707, "width": 2560, "mime_type": "image/jpeg" }, "attachments": [], "metadata_fields": [], "embeddings": [ { "id": "cn1cywy1r077807zblvo0m884", "name": "my_custom_embedding_2048_dimensions", "dimensions": 2048, "is_custom": true, "values": [ { "value": [ 1.1265901290930636, 1.0413733599834287, 1.3041214120015427, 1.893491997791636 ... ] } ] }, { "id": "c200000000000000000000000", "name": "Image Embedding V2 (CLIP ViT-B/32)", "dimensions": 512, "is_custom": false, "values": [ { "value": [ -0.4192526042461395, -0.048827216029167175 ... ] } ] } ], "projects": { "cltd3vkju021907u63w2odj69": { "name": "Image Annotation Import Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clur5tlda1n2a07dc0i0zdd4k", "label_details": { "created_at": "2024-04-08T16:21:26.000+00:00", "updated_at": "2024-04-08T16:34:02.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-04-08T16:34:00.513+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 18, "seconds_to_review": 6, "skipped": false }, "annotations": { "objects": [ { "feature_id": "clur5tlyq1n2d07dcg9xldg90", "feature_schema_id": "cltd3vkc1019r07ts2lru4hi4", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 1264.0, "left": 2096.0, "height": 425.0, "width": 144.0 } }, { "feature_id": "clur5tlyr1n2f07dcf78sa4y5", "feature_schema_id": "cltd3vkc201a707ts9ullbhfo", "name": "point", "value": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } }, { "feature_id": "clur5tlyr1n2h07dc1pxnhoj3", "feature_schema_id": "cltd3vkc1019t07ts2bk6hjjj", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "ImageBoundingBox", "classifications": [ { "feature_id": "clur5tlyr1n2v07dcbnnfh1ah", "feature_schema_id": "cltd3vkc1019u07tsaari5i2k", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clur5tlyr1n3607dc98qpagxb", "feature_schema_id": "cltd3vkc1019v07tsbow08f9u", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ], "bounding_box": { "top": 933.0, "left": 541.0, "height": 191.0, "width": 330.0 } }, { "feature_id": "clur5tlyr1n2j07dc50cs4e11", "feature_schema_id": "cltd3vkc201a907ts69dr5m86", "name": "polyline", "value": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 2429.492, "y": 182.092 }, { "x": 2294.322, "y": 221.962 }, { "x": 2224.491, "y": 180.463 }, { "x": 2136.123, "y": 204.716 }, { "x": 1712.247, "y": 173.949 }, { "x": 1703.838, "y": 84.438 }, { "x": 1579.772, "y": 82.61 }, { "x": 1583.442, "y": 167.552 }, { "x": 1478.869, "y": 164.903 }, { "x": 1418.941, "y": 318.149 }, { "x": 1243.128, "y": 400.815 }, { "x": 1022.067, "y": 319.007 }, { "x": 892.367, "y": 379.216 }, { "x": 670.273, "y": 364.408 }, { "x": 613.114, "y": 288.16 }, { "x": 377.559, "y": 238.251 }, { "x": 368.087, "y": 185.064 }, { "x": 246.557, "y": 167.286 }, { "x": 236.648, "y": 285.61 }, { "x": 90.929, "y": 326.412 } ] }, { "feature_id": "clur5tlyr1n2l07dc1jsf15qo", "feature_schema_id": "cltd3vkc1019r07ts2lru4hi4", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 1346.0, "left": 2272.0, "height": 358.0, "width": 144.0 } }, { "feature_id": "clur5tlyr1n2m07dc4x6xhzep", "feature_schema_id": "cltd3vkc1019z07ts7g1n3o5m", "name": "polygon", "value": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 } ] }, { "feature_id": "clur5tlyr1n2n07dc4qot5gbx", "feature_schema_id": "cltd3vkc1019r07ts2lru4hi4", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 977.0, "left": 1690.0, "height": 330.0, "width": 225.0 } }, { "feature_id": "clur5tlyr1n2q07dc5joc4k7u", "feature_schema_id": "cltd3vkc101a107ts95sk63u0", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2q07dc5joc4k7u/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 10, 10, 125 ] } }, { "feature_id": "clur5tlyr1n2r07dc6ym4d4mc", "feature_schema_id": "cltd3vkc201a307tsforlev07", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "clur5tlyr1n2x07dc84a12l8i", "feature_schema_id": "cltd3vkc201a407tsfo3agh7x", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer sample" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2r07dc6ym4d4mc/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 105, 136, 97 ] } }, { "feature_id": "clur5tlyr1n2s07dc739a6kqs", "feature_schema_id": "cltd3vkc201a307tsforlev07", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "clur5tlyr1n2w07dccdv0g7gg", "feature_schema_id": "cltd3vkc201a407tsfo3agh7x", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer sample" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2s07dc739a6kqs/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 7, 85, 11 ] } }, { "feature_id": "clur5tlyr1n2t07dc0oxlgoc8", "feature_schema_id": "cltd3vkc201a307tsforlev07", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "clur5tlyr1n3107dccy5pbudr", "feature_schema_id": "cltd3vkc201a407tsfo3agh7x", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer sample" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2t07dc0oxlgoc8/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 35, 34, 212 ] } }, { "feature_id": "clur5tlyr1n2u07dc5wjmdm3v", "feature_schema_id": "cltd3vkc101a107ts95sk63u0", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2u07dc5wjmdm3v/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 232, 194, 86 ] } } ], "classifications": [ { "feature_id": "clur5tlyr1n2e07dc2bez8p1u", "feature_schema_id": "cltd3vkc201ar07ts3gq6a6sn", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "clur5tlyr1n3307dcexth5ff7", "feature_schema_id": "cltd3vkc201as07tsh2prgosu", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "clur5tlyr1n3407dc306b5xib", "feature_schema_id": "cltd3vkc201at07tsdjx9dlbw", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clur5tlyr1n3507dcef304jmb", "feature_schema_id": "cltd3vkc201au07ts7nk47qny", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "clur5tlyr1n2g07dc7wdv7p5z", "feature_schema_id": "cltd3vkc201ap07ts58gma0bb", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "clur5tlyr1n2i07dcd8lx87be", "feature_schema_id": "cltd3vkc201az07ts7st3fn79", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n3207dc4u4nfg08", "feature_schema_id": "cltd3vkc201b007tsdhs07baf", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "clur5tlyr1n3707dc2cy805ac", "feature_schema_id": "cltd3vkc201b107ts12b1al1j", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n3807dcgpgih5qj", "feature_schema_id": "cltd3vkc201b207ts434b9iyx", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "clur5tlyr1n2k07dcezk08npu", "feature_schema_id": "cltd3vkc201ad07ts3t78873p", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "clur5tlyr1n2y07dcajpyg0d4", "feature_schema_id": "cltd3vkc201ag07tsfoc2bdpi", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "clur5tlyr1n2p07dchs791672", "feature_schema_id": "cltd3vkc201aj07tsait4cbzh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n2z07dc3abv19e8", "feature_schema_id": "cltd3vkc201ak07tsfqxc740z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clur5tlyr1n3007dc4vbr0628", "feature_schema_id": "cltd3vkc201am07ts7s9e7x7k", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ], "relationships": [ { "feature_id": "clur69pfa00023b6rkftlkucz", "feature_schema_id": "cltd3vkc201ab07ts1f9dhpkt", "name": "relationship", "value": "relationship", "annotation_kind": "ImageUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "clur5tlyq1n2d07dcg9xldg90", "target": "clur5tlyr1n2l07dc1jsf15qo" } } ] }, "z_order": [ "clur5tlyq1n2d07dcg9xldg90", "clur5tlyr1n2f07dcf78sa4y5", "clur5tlyr1n2j07dc50cs4e11", "clur5tlyr1n2l07dc1jsf15qo", "clur5tlyr1n2m07dc4x6xhzep", "clur5tlyr1n2n07dc4qot5gbx", "clur5tlyr1n2q07dc5joc4k7u", "clur5tlyr1n2r07dc6ym4d4mc", "clur5tlyr1n2s07dc739a6kqs", "clur5tlyr1n2u07dc5wjmdm3v" ] } ], "project_details": { "ontology_id": "cltd3vkbb019q07ts23eedy4o", "task_id": "a384279a-45c0-4200-b85a-3c18469b3809", "task_name": "Initial review task", "batch_id": "3f724e20-da3d-11ee-86a4-0145011952fd", "batch_name": "image-demo-batch", "workflow_status": "IN_REVIEW", "priority": 1, "consensus_expected_label_count": 1, "workflow_history": [ { "action": "Move", "created_at": "2024-04-08T16:21:26.492+00:00", "created_by": "[email protected]", "previous_task_name": "Initial labeling task", "previous_task_id": "22ee463b-fbe3-0791-8159-94083be9dee3", "next_task_name": "Initial review task", "next_task_id": "a384279a-45c0-4200-b85a-3c18469b3809" }, { "action": "Move", "created_at": "2024-04-08T16:21:26.481+00:00", "created_by": "[email protected]", "next_task_name": "Initial labeling task", "next_task_id": "22ee463b-fbe3-0791-8159-94083be9dee3" } ] }, "project_tags": [] } } } ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clo78lhhn3dxb07322mnn8plu", "global_key": "2560px-Kitano_Street_Kobe01s.jpeg", "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "details": { "dataset_id": "clo78lg8f003007321fe52lje", "dataset_name": "image_prediction_demo", "created_at": "2023-10-26T13:45:24.929+00:00", "updated_at": "2023-10-26T13:45:25.965+00:00", "created_by": "[email protected]" } }, "media_attributes": { "height": 1707, "width": 2560, "mime_type": "image/jpeg", "exif_rotation": "1" }, "attachments": [], "metadata_fields": [], "embeddings": [ { "id": "cn1cywy1r077807zblvo0m884", "name": "my_custom_embedding_2048_dimensions", "dimensions": 2048, "is_custom": true, "values": [ { "value": [ 1.1265901290930636, 1.0413733599834287, 1.3041214120015427, 1.893491997791636 ... ] } ] }, { "id": "c200000000000000000000000", "name": "Image Embedding V2 (CLIP ViT-B/32)", "dimensions": 512, "is_custom": false, "values": [ { "value": [ -0.4192526042461395, -0.048827216029167175 ... ] } ] } ], "experiments": { "a2fb1fec-83fb-0858-2cfb-98f4757eae95": { "name": "image_model_run_3e5bf56c-31a4-4eed-a731-9ca85e783225", "runs": { "a2fb1fec-b8a5-0f96-b61e-0d5643fb8dec": { "name": "iteration 1", "run_data_row_id": "42630da9-0dc4-462b-9aa0-5a17e85ff46a", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clpk3h87e005r14vsp4c33sfq", "annotations": { "objects": [ { "feature_id": "164937d0-7bb2-0f03-8031-34b57799736b", "feature_schema_id": "clpk3gs0t054y07vig605dopr", "name": "polygon", "value": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 } ] }, { "feature_id": "2738b5f8-e586-0090-974a-59b6c9b46ee2", "feature_schema_id": "clpk3gs0t055207vi1baq8bke", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "9001b75c-bb70-0b40-afbf-9479ecfa9f99", "feature_schema_id": "clpk3gs0t055307vi90s32rhi", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/models/a2fb1fec-83fb-0858-2cfb-98f4757eae95/annotations/2738b5f8-e586-0090-974a-59b6c9b46ee2/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6mb7a0dz107xod5w53p61/masks/clpk3h7uk005114vsgru34yw7/index/1", "color_rgb": [ 41, 24, 175 ] } }, { "feature_id": "4defcaa5-623f-0589-932a-b703e1116fa4", "feature_schema_id": "clpk3gs0t055807vi49uy1rcm", "name": "polyline", "value": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 2429.492, "y": 182.092 }, { "x": 2294.322, "y": 221.962 }, { "x": 2224.491, "y": 180.463 }, { "x": 2136.123, "y": 204.716 }, { "x": 1712.247, "y": 173.949 }, { "x": 1703.838, "y": 84.438 }, { "x": 1579.772, "y": 82.61 }, { "x": 1583.442, "y": 167.552 }, { "x": 1478.869, "y": 164.903 }, { "x": 1418.941, "y": 318.149 }, { "x": 1243.128, "y": 400.815 }, { "x": 1022.067, "y": 319.007 }, { "x": 892.367, "y": 379.216 }, { "x": 670.273, "y": 364.408 }, { "x": 613.114, "y": 288.16 }, { "x": 377.559, "y": 238.251 }, { "x": 368.087, "y": 185.064 }, { "x": 246.557, "y": 167.286 }, { "x": 236.648, "y": 285.61 }, { "x": 90.929, "y": 326.412 } ] }, { "feature_id": "7d129546-45a6-060b-918e-35caec234d31", "feature_schema_id": "clpk3gs0s054s07vi3ddo8had", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "ImageBoundingBox", "classifications": [ { "feature_id": "cccba37c-b7a8-095b-8679-dd20aece6f90", "feature_schema_id": "clpk3gs0s054t07vi17aih47e", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "ea336ea5-041c-02b6-9e8b-6ac1098351b1", "feature_schema_id": "clpk3gs0s054u07vicrttdlgj", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ], "bounding_box": { "top": 933.0, "left": 541.0, "height": 191.0, "width": 330.0 } }, { "feature_id": "a3e0496d-3963-0d40-af89-48a907fbb48d", "feature_schema_id": "clpk3gs0s054q07vi3c30d1be", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 977.0, "left": 1690.0, "height": 330.0, "width": 225.0 } }, { "feature_id": "f2fc7c1f-7f10-0fc2-b676-fa2591f51a27", "feature_schema_id": "clpk3gs0t055607vi59sshqn0", "name": "point", "value": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } }, { "feature_id": "fb60f23f-8ae1-05b3-a4d8-353839975d59", "feature_schema_id": "clpk3gs0t055007vi4h7fgm59", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/models/a2fb1fec-83fb-0858-2cfb-98f4757eae95/annotations/fb60f23f-8ae1-05b3-a4d8-353839975d59/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6mb7a0dz107xod5w53p61/masks/clpk3h7uk005114vsgru34yw7/index/1", "color_rgb": [ 186, 26, 40 ] } } ], "classifications": [ { "feature_id": "1be71b85-08da-0ade-bbe4-cfdd431d3e8a", "feature_schema_id": "clpk3gs0t055g07viaine2doh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "0104948e-7be5-032d-80ca-87d052346618", "feature_schema_id": "clpk3gs0t055j07vi4h3y9wso", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "cb587406-5db3-0de2-99f0-cf6e6c344f43", "feature_schema_id": "clpk3gs0t055h07vi1hp0acnb", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "5b1f4994-1bad-03a1-bf0f-1a0b4682ea25", "feature_schema_id": "clpk3gs0t055m07vi53ow6auc", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "88f8cd4d-e1ca-0add-a7e8-a7349def31bc", "feature_schema_id": "clpk3gs0t055a07vifk5701ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "ee141c0d-064f-0de8-b746-e61ac14fbe62", "feature_schema_id": "clpk3gs0t055d07vibryq8f4l", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "cc8e57d8-5080-0b43-bab1-6e4d78b684de", "feature_schema_id": "clpk3gs0t055o07vifjxs63xi", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "81690da4-5cb5-037f-9a7b-3dd7fefc5481", "feature_schema_id": "clpk3gs0t055p07vigvkkeii3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "37453082-23dd-0c38-bdcb-90705102bc6e", "feature_schema_id": "clpk3gs0t055q07vifyau26jb", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "4b014520-7193-04d9-8d69-f800316b60f2", "feature_schema_id": "clpk3gs0t055r07vi406q70ga", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "f1d18441-41cd-0ff7-9f4f-75384e5a7b51", "feature_schema_id": "clpk3gs0u055w07vifjngez8o", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "a3c8745f-8151-0256-8b6d-8ac449797fd7", "feature_schema_id": "clpk3gs0u055x07vibwdt1pk3", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "aa00a3ff-1af4-038a-9be5-97a468656bc5", "feature_schema_id": "clpk3gs0u055y07vi3srh1g0l", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "0608a0d7-d9d9-0347-843b-3297e58a25dc", "feature_schema_id": "clpk3gs0u055z07vifkpvgap4", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clpk3h87e005r14vsp4c33sfq", "annotations": { "objects": [ { "feature_id": "028389ae-93ff-4614-a37e-e867a310371c", "feature_schema_id": "clpk3gs0s054q07vi3c30d1be", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 977.0, "left": 1690.0, "height": 330.0, "width": 225.0 } }, { "feature_id": "37415b2f-f376-47b9-8f6a-6429c3efa6af", "feature_schema_id": "clpk3gs0t054y07vig605dopr", "name": "polygon", "value": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 } ] }, { "feature_id": "55bee3a4-59c2-4d85-95b3-4584c315037e", "feature_schema_id": "clpk3gs0t055607vi59sshqn0", "name": "point", "value": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } }, { "feature_id": "671736d6-7c30-4399-bec4-a8a1779abf4d", "feature_schema_id": "clpk3gs0t055007vi4h7fgm59", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/models/a2fb1fec-83fb-0858-2cfb-98f4757eae95/annotations/671736d6-7c30-4399-bec4-a8a1779abf4d/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6mb7a0dz107xod5w53p61/masks/clpk3gxbq00oi14ok1yykgp85/index/1", "color_rgb": [ 36, 67, 66 ] } }, { "feature_id": "7e103ed1-f66f-4cf9-9a36-3e08be3a9648", "feature_schema_id": "clpk3gs0t055807vi49uy1rcm", "name": "polyline", "value": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 2429.492, "y": 182.092 }, { "x": 2294.322, "y": 221.962 }, { "x": 2224.491, "y": 180.463 }, { "x": 2136.123, "y": 204.716 }, { "x": 1712.247, "y": 173.949 }, { "x": 1703.838, "y": 84.438 }, { "x": 1579.772, "y": 82.61 }, { "x": 1583.442, "y": 167.552 }, { "x": 1478.869, "y": 164.903 }, { "x": 1418.941, "y": 318.149 }, { "x": 1243.128, "y": 400.815 }, { "x": 1022.067, "y": 319.007 }, { "x": 892.367, "y": 379.216 }, { "x": 670.273, "y": 364.408 }, { "x": 613.114, "y": 288.16 }, { "x": 377.559, "y": 238.251 }, { "x": 368.087, "y": 185.064 }, { "x": 246.557, "y": 167.286 }, { "x": 236.648, "y": 285.61 }, { "x": 90.929, "y": 326.412 } ] }, { "feature_id": "f3a445af-0475-4ad1-892b-fbbcce26073f", "feature_schema_id": "clpk3gs0s054s07vi3ddo8had", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "ImageBoundingBox", "classifications": [ { "feature_id": "d8427b71-d516-4686-bc52-660d1133c18d", "feature_schema_id": "clpk3gs0s054t07vi17aih47e", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "494d3af6-9bf8-477f-8827-dac0dd4c42a3", "feature_schema_id": "clpk3gs0s054u07vicrttdlgj", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ], "bounding_box": { "top": 933.0, "left": 541.0, "height": 191.0, "width": 330.0 } } ], "classifications": [ { "feature_id": "22778fd4-3fd3-48ad-9007-a8f2fc22d664", "feature_schema_id": "clpk3gs0t055o07vifjxs63xi", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "b992d812-7d0b-49b1-82da-14bcff5a76ef", "feature_schema_id": "clpk3gs0t055p07vigvkkeii3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "877d229e-c877-4aa7-a034-ddde21fa8417", "feature_schema_id": "clpk3gs0t055q07vifyau26jb", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "803cbe24-7c2f-400c-8d73-fb19bdc5302c", "feature_schema_id": "clpk3gs0t055r07vi406q70ga", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "2555cb5f-5a90-40f9-987f-7a2899688ad6", "feature_schema_id": "clpk3gs0t055a07vifk5701ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "246406e7-162c-43c5-a1ff-ca555be51e55", "feature_schema_id": "clpk3gs0t055d07vibryq8f4l", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "e4f143b7-5e3d-4f17-8202-efc78cd73f6e", "feature_schema_id": "clpk3gs0t055m07vi53ow6auc", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "ec7d6588-3c15-4fb6-baa1-3508c476bc32", "feature_schema_id": "clpk3gs0u055w07vifjngez8o", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "71fd6407-5fc6-43e4-8196-78487f0f9ec8", "feature_schema_id": "clpk3gs0u055x07vibwdt1pk3", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ab8bf663-3fba-4dd6-9e09-84e0bbd713d0", "feature_schema_id": "clpk3gs0u055y07vi3srh1g0l", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "58c98b94-b360-4ceb-b5ea-7765e096c40a", "feature_schema_id": "clpk3gs0u055z07vifkpvgap4", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "f9107ba1-8020-4b7a-8066-b80167a9cd6a", "feature_schema_id": "clpk3gs0t055g07viaine2doh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "88cc2ed7-8b19-4396-b9ee-36949d23fac7", "feature_schema_id": "clpk3gs0t055h07vi1hp0acnb", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "b70e6eb1-2ee9-42f3-b47a-a56e764ea4ac", "feature_schema_id": "clpk3gs0t055j07vi4h3y9wso", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ] } } } }, "models": {} } ``` # Export LLM response evaluation annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-llm-response-evaluation-annotations How to export LLM response evaluation annotations and sample export formats. ## Export JSON annotations ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Classification - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clp8gzc7a000h07iu2uqcfziq", "feature_schema_id": "clp8fepqc02nd071phl4tck59", "name": "Choose the best response", "radio_answer": { "feature_id": "clp8gzc7a000i07iu46z39uwl", "feature_schema_id": "clp8fepqc02ng071p9ufm76xj", "name": "Response B", "classifications": [] } } ``` ### Classification - Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clph8rvw700013b6pbsq1lwcg", "feature_schema_id": "clp8fepqc02nl071p8z948pd4", "name": "Provide a reason for your choice", "text_answer": { "content": "Clear recommendation" } } ``` ### Classification - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clpiyav9m00053b6mpe2qk0im", "feature_schema_id": "clpiya8h507dx072efo9kb97j", "name": "Evaluations done", "checklist_answers": [ { "feature_id": "clpiyav9m00043b6mvhur1p19", "name": "Usefulness", "classifications": [] }, { "feature_id": "clpiyax4d00073b6mqyyt0504", "name": "Toxicity", "classifications": [] } ] } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clpiqgsvn4lxr079096g5mt9v", "global_key": "0914c7da-5999-464b-b621-054a5247cf48", "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/Pranoy/shopping_3.json", "details": { "dataset_id": "clpiqgrsr00f60737sqeiaf4v", "dataset_name": "pairwise_demo_42454af2-b7b5-45c0-a25a-90f97efd49be", "created_at": "2023-11-28T19:30:49.729+00:00", "updated_at": "2023-11-28T19:30:50.609+00:00", "last_activity_at": "2023-11-28T23:11:08.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/vnd.labelbox.conversational", "labelable_ids": [ "message-1", "message-3" ], "message_count": 4 }, "attachments": [], "metadata_fields": [], "projects": { "clpiy2itz08vg0705akh60zc1": { "name": "Test LLM Human Preference", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clpiyah63071f07e4756yer2d", "label_details": { "created_at": "2023-11-28T23:11:08.000+00:00", "updated_at": "2023-11-28T23:11:07.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2023-11-28T23:11:07.568+00:00", "reviews": [] }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clpiybl7h000g3b6mil1amjme", "feature_schema_id": "clpiqh0v104mq0729homqffup", "name": "Choose the best response", "radio_answer": { "feature_id": "clpiybl7h000f3b6m03fn8ry3", "feature_schema_id": "clpiqh0v104mt0729atflgvvw", "name": "Response B", "classifications": [] } }, { "feature_id": "clpiybzp3000h3b6m9uowaci4", "feature_schema_id": "clpiqh0v104my072907gt7id0", "name": "Provide a reason for your choice", "text_answer": { "content": "Direct approach that is convenient" } }, { "feature_id": "clpiyc10q000j3b6m01i2d1pv", "feature_schema_id": "clpiya8h507dx072efo9kb97j", "name": "Evaluations done", "checklist_answers": [ { "feature_id": "clpiyc10q000i3b6mhhvhdzqs", "name": "Relevance", "classifications": [] }, { "feature_id": "clpiyc1ie000l3b6m3kpff0lp", "name": "Usefulness", "classifications": [] } ] } ], "relationships": [] } } ], "project_details": { "ontology_id": "clpiqh0ud04mp0729adeoccsl", "task_id": "33e77b4d-424f-49ba-8792-628c1cb746b7", "task_name": "Initial review task", "batch_id": "66882c10-8e42-11ee-8f8d-71857f06db2f", "batch_name": "test_llm_human_preference_batch_1", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [ { "action": "Move", "created_at": "2023-11-28T23:11:07.654+00:00", "created_by": "[email protected]", "previous_task_name": "Initial labeling task", "previous_task_id": "8f9917a7-6030-0914-befc-ac06f59a7ec9", "next_task_name": "Initial review task", "next_task_id": "33e77b4d-424f-49ba-8792-628c1cb746b7" }, { "action": "Move", "created_at": "2023-11-28T23:11:07.631+00:00", "created_by": "[email protected]", "next_task_name": "Initial labeling task", "next_task_id": "8f9917a7-6030-0914-befc-ac06f59a7ec9" } ] }, "project_tags": [] } } } ``` # Export multimodal chat annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-multimodal-chat-annotations How to export multimodal chat annotations and sample export formats. ## Export JSON annotations ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Classification - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clws3nkhh00043b6p7qktjffs", "feature_schema_id": "clws3n1yu055907wlg1uh1i8g", "name": "Context subject", "value": "context_subject", "message_id": "clws2arr200073b6p7ehwkoh4", "conversational_radio_answer": { "feature_id": "clws3nkhh00033b6ps99i1d56", "feature_schema_id": "clws3n1yu055a07wl2axp39nq", "name": "Car", "value": "car", "classifications": [] } } ``` ### Classification - Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clws3or5q00093b6p00oxtkve", "feature_schema_id": "clws3n1yv055v07wlbnd57c9h", "name": "Additional comment", "value": "additional_comment", "text_answer": { "content": "Color: white" } } ``` ### Classification - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clpiyav9m00053b6mpe2qk0im", "feature_schema_id": "clpiya8h507dx072efo9kb97j", "name": "Evaluations done", "checklist_answers": [ { "feature_id": "clpiyav9m00043b6mvhur1p19", "name": "Usefulness", "classifications": [] }, { "feature_id": "clpiyax4d00073b6mqyyt0504", "name": "Toxicity", "classifications": [] } ] } ``` ### Message Ranking ```json JSON theme={"theme":"css-variables"} { "feature_id": "clwrt25ar00093b6po9g7fiu3", "feature_schema_id": "clwkfbcnn01kl07z68er64ffi", "name": "Rank given prompt", "annotation_kind": "MessageRanking", "classifications": [], "ranked_messages": { "parent_message_id": "clwrt16zz00033b6p6ud7w233", "ranked_messages": [ { "message_id": "clwrt1fw700063b6pe8zpo1xp", "order": 1, "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" }, { "message_id": "clwrt1fw700073b6p8k3vkh1x", "order": 2, "model_config_id": "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2" } ] } } ``` ### Message selection ```json JSON theme={"theme":"css-variables"} { "feature_id": "clwrt2dru000b3b6pkj3i5zxk", "feature_schema_id": "clwknckmp030v07xp6kow09xd", "name": "Subject relevance", "annotation_kind": "MessageMultiSelection", "classifications": [], "selected_messages": { "parent_message_id": "clwrt16zz00033b6p6ud7w233", "messages": [ { "message_id": "clwrt1fw700063b6pe8zpo1xp", "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" } ] } } ``` ### Message step reasoning ```json JSON expandable theme={"theme":"css-variables"} { "feature_id": "cm3gp5d39000k3b6tpet38ui9", "feature_schema_id": "cm3ffq1mm04t407we109o1hyi", "name": "my message step task", "annotation_kind": "StepReasoning", "classifications": [], "steps": { "parent_message_id": "cm3gp54f500073b6t26gyznra", "steps": [ { "step_id": "cm3gp54q6000a3b6t4563k4ba", "content": "Here's a simple way to make a cup of tea:", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp54q6000b3b6tsuiaudjb", "content": "1. Boil water: Fill a kettle with water and bring it to a boil.", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp54q6000d3b6tozd9neiq", "content": "2. Add tea: Once the water is boiling, empty your now preheated teacup and add your tea. This can be a tea bag or loose tea leaves in a tea infuser. The amount of tea you add will depend on how strong you like your tea.", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp54q6000e3b6t8y4k7grw", "content": "3. Pour hot water: Pour the boiling water into the cup, over the tea.", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp54q6000f3b6tddynpg1y", "content": "4. Steep: Let the tea steep for a few minutes. The exact time will depend on the type of tea and how strong you like it. A general guide is 3-5 minutes. After the tea has steeped, remove the tea bag or infuser from the cup.", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp54q6000h3b6t7mopupfy", "content": "5. Add extras: If you like, you can now add extras such as milk, sugar, honey, or lemon. ", "variant_id": 0, "variant": "correct" } ], "root_step_id": "cm3gp54q6000a3b6t4563k4ba" } }, { "feature_id": "cm3gp8byj00103b6t9904n65h", "feature_schema_id": "cm3ffq1mm04t407we109o1hyi", "name": "my message step task", "annotation_kind": "StepReasoning", "classifications": [], "steps": { "parent_message_id": "cm3gp82di000o3b6tr13dwupc", "steps": [ { "step_id": "cm3gp82mx000p3b6tlkd0c8a6", "content": "Sure, here's more detail on step 5:", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp82mx000q3b6tptjgz0ol", "content": "5. Add extras: ", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp82mx000r3b6t7mqcilx6", "content": " - Milk: Some people prefer to add a splash of milk to their tea to make it creamier and to soften the flavor. The amount of milk can vary based on personal preference. Some teas, like black teas, often pair well with milk. ", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gp82mx000s3b6tntnvhpfc", "content": " - Sugar: If you prefer your tea sweet, you can add sugar. Start with a teaspoon and then adjust to taste. You can also use sweeteners like stevia or sucralose if you're trying to avoid sugar.", "variant_id": 2, "variant": "corrected", "alternative_steps": [ { "content": " - Agave: If you prefer your tea sweet, you can add agave syrup. Start with a teaspoon and then adjust to taste. Agave is a natural sweetener that is sweeter than sugar, so you might need less of it.", "variant_id": 0, "variant": "neutral" }, { "content": " - Maple Syrup: If you prefer your tea sweet, you can add maple syrup. Start with a teaspoon and then adjust to taste. Maple syrup can add a unique, slightly smoky sweetness to your tea.", "variant_id": 0, "variant": "correct" }, { "content": " - Dates: If you prefer your tea sweet, you can blend dates and add the paste to your tea. Dates are a natural sweetener and can also add a bit of texture to your tea.", "variant_id": 1, "variant": "neutral" } ], "rewritten_content": "Sugar: If you prefer sweet tea, add sugar or other sweeteners.", "justification": "The response provided recommendations that pose health risks we should avoid." }, { "step_id": "cm3gpcrij00133b6tz9766jfw", "content": "- Sugar: Start with a teaspoon of sugar, stir it in and then taste. Add more if needed. Remember, you can always add more sugar but you can't take it away once it's added.", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gpcrij00153b6t0woknwgo", "content": "- Lemon: A slice of lemon or a splash of lemon juice can add a refreshing citrus note to your tea. This is often paired with black tea.", "variant_id": 0, "variant": "correct" }, { "step_id": "cm3gpcrij00163b6tcetum5w1", "content": "- Herbs and Spices: Some people like to add herbs and spices to their tea. For example, mint leaves, a cinnamon stick, or a few cardamom pods can add a unique flavor to your tea.", "variant_id": 0, "variant": "correct" } ], "root_step_id": "cm3gp82mx000p3b6tlkd0c8a6" } } ``` ### Prompt rating ```json JSON theme={"theme":"css-variables"} { "feature_id": "cm46ljjhs000i3b6tan025id0", "feature_schema_id": "cm46l1i4n02yp07s99isra6v9", "name": "Rate", "annotation_kind": "PromptIssue", "classifications": [ { "feature_id": "cm46ljm67000k3b6t34goxv5r", "feature_schema_id": "cm46l1i4n02yq07s9g32o677i", "name": "Rate the prompt", "value": "rate_the_prompt", "checklist_answers": [ { "feature_id": "cm46ljm67000j3b6tpr2rvaip", "feature_schema_id": "cm46l1i4n02yt07s9haoj7aek", "name": "This prompt contains a false, offensive, or controversial premise (eg. \u201cwhy does 1+1=3\u201d?)", "value": "this_prompt_contains_a_false_offensive_or_controversial_premise_eg_why_does_1_1_3", "classifications": [] } ] } ], "prompt_message_id": "cm46li41v000d3b6tdqzc9nv2" }, { "feature_id": "cm46lk2pr000q3b6ti5c02fbb", "feature_schema_id": "cm46l1i4n02yp07s99isra6v9", "name": "Rate", "annotation_kind": "PromptIssue", "classifications": [ { "feature_id": "cm46lk3pv000s3b6tzlpvifq5", "feature_schema_id": "cm46l1i4n02yq07s9g32o677i", "name": "Rate the prompt", "value": "rate_the_prompt", "checklist_answers": [ { "feature_id": "cm46lk3pv000r3b6t0vueiz56", "feature_schema_id": "cm46l1i4n02yv07s9ccti4qv9", "name": "This prompt is not self-contained, i.e. the prompt cannot be understood without additional context about previous turns, account information or images.", "value": "this_prompt_is_not_self_contained_i_e_the_prompt_cannot_be_understood_without_additional_context_about_previous_turns_account_information_or_images", "classifications": [] } ] } ], "prompt_message_id": "cm46ljwcy000l3b6tmk9oua72" } ``` ### Fact-checking ```json JSON theme={"theme":"css-variables"} { "feature_id": "cm46myu0200193b6ttjhe11n5", "feature_schema_id": "cm46m7j0k02t707uaesr2fhjq", "name": "Fact-checking", "annotation_kind": "FactChecking", "classifications": [], "steps": { "parent_message_id": "cm46mvig0000z3b6t4c1qzqbr", "steps": [ { "content": "The Triassic period, which occurred approximately 252 to 201 million years ago, was a time of significant evolutionary change. While many species from that era have gone extinct, some lineages have persisted to the present day. Here are a few examples of animals and plants from the Triassic period that have descendants or close relatives still existing today:", "variant": "" }, { "content": "1. **Crocodilians**: The ancestors of modern crocodiles and alligators first appeared in the Triassic period. While the specific species from that time are extinct, their descendants have survived and evolved into the crocodilians we know today.", "variant_id": 4, "variant": "cant_confidently_assess" }, { "content": "2. **Turtles**: Turtles also have ancient origins, with some of their earliest ancestors appearing in the late Triassic period. Modern turtles are part of this long lineage.", "variant_id": 1, "variant": "inaccurate", "justification": "Modern turtles are different from Eunotosaurus in the Triassic period." }, { "content": "3. **Lepidosaurs**: This group includes modern lizards and snakes. Their ancestors were present during the Triassic period, and they have since diversified into the many species we see today.", "variant_id": 4, "variant": "cant_confidently_assess" }, { "content": "4. **Horsetails and Ferns**: These plants were common during the Triassic period and have survived to the present day. While the specific species from that time are not the same, the groups themselves have persisted.", "variant_id": 4, "variant": "cant_confidently_assess" }, { "content": "5. **Ginkgo**: The Ginkgo tree, often referred to as a \"living fossil,\" has ancient origins dating back to the Triassic period. The modern Ginkgo biloba is the only surviving species of this once-diverse group.", "variant_id": 0, "variant": "accurate", "justification": "Ginkgo first appeared over 290 million years ago, which is the Triassic period." }, { "content": "6. **Conifers**: These gymnosperms were already present during the Triassic period and have continued to thrive, with many species existing today.", "variant_id": 4, "variant": "cant_confidently_assess" }, { "content": "These examples illustrate the resilience and adaptability of certain lineages that have managed to survive through multiple mass extinctions and environmental changes over millions of years.", "variant_id": 3, "variant": "unsupported" } ] } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clwrm93488cpd0734f740r9lr", "global_key": "clwrm92sb0grg07wncr6dhihw", "row_data": "{\"type\":\"application/vnd.labelbox.conversational.model-chat-evaluation\",\"version\":2,\"actors\":{\"clwrt16zz00023b6pg6hlhtub\":{\"role\":\"human\",\"metadata\":{\"name\":\"User\"}},\"clwrt17a900043b6py2tepemd\":{\"role\":\"model\",\"metadata\":{\"modelConfigId\":\"e606593e-def3-4662-9737-3fc4ac412dd3\"}},\"clwrt17a900053b6p0xtyv1im\":{\"role\":\"model\",\"metadata\":{\"modelConfigId\":\"8fcda464-a0c7-4300-a9fa-c9a5cf82cab2\"}}},\"messages\":{\"clwrt16zz00033b6p6ud7w233\":{\"actorId\":\"clwrt16zz00023b6pg6hlhtub\",\"content\":[{\"type\":\"text\",\"content\":\"Is there a car in this image?\"},{\"type\":\"fileData\",\"fileUri\":\"https://c7.staticflickr.com/3/2447/3799589580_4b73283f92_o.jpg\",\"mimeType\":\"image/jpeg\"}],\"childMessageIds\":[\"clwrt1fw700063b6pe8zpo1xp\",\"clwrt1fw700073b6p8k3vkh1x\"]},\"clwrt1fw700063b6pe8zpo1xp\":{\"actorId\":\"clwrt17a900043b6py2tepemd\",\"content\":[{\"type\":\"text\",\"content\":\"Yes, there is a car in this image. It appears to be a white hatchback parked in a parking lot.\"}],\"childMessageIds\":[]},\"clwrt1fw700073b6p8k3vkh1x\":{\"actorId\":\"clwrt17a900053b6p0xtyv1im\",\"content\":[{\"type\":\"text\",\"content\":\" Yes, there is a white Volkswagen Golf in the image.\"}],\"childMessageIds\":[]}},\"rootMessageIds\":[\"clwrt16zz00033b6p6ud7w233\"]}", "details": { "dataset_id": "clwkf82ul001l0755mhckcmuy", "dataset_name": "Model evaluation 101", "created_at": "2024-05-29T09:20:43.342+00:00", "updated_at": "2024-05-29T12:31:52.027+00:00", "last_activity_at": "2024-05-29T12:33:21.203+00:00", "created_by": "[email protected]" } }, "media_attributes": { "asset_type": "conversational", "mime_type": "application/vnd.labelbox.conversational.model-chat-evaluation", "labelable_ids": [ "clwrt16zz00033b6p6ud7w233", "clwrt1fw700063b6pe8zpo1xp", "clwrt1fw700073b6p8k3vkh1x" ], "message_count": 3 }, "attachments": [], "metadata_fields": [], "projects": { "clwrm92jc0grc07wncq654q5s": { "name": "Multi modal chat evalution", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clwrsyrdp02mr07x5378j3ysr", "label_details": { "created_at": "2024-05-29T12:33:21.000+00:00", "updated_at": "2024-05-29T12:33:21.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-05-29T12:33:20.787+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 250, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "clwrt25ar00093b6po9g7fiu3", "feature_schema_id": "clwkfbcnn01kl07z68er64ffi", "name": "Rank given prompt", "annotation_kind": "MessageRanking", "classifications": [], "ranked_messages": { "parent_message_id": "clwrt16zz00033b6p6ud7w233", "ranked_messages": [ { "message_id": "clwrt1fw700063b6pe8zpo1xp", "order": 1, "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" }, { "message_id": "clwrt1fw700073b6p8k3vkh1x", "order": 2, "model_config_id": "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2" } ] } }, { "feature_id": "clwrt2dru000b3b6pkj3i5zxk", "feature_schema_id": "clwknckmp030v07xp6kow09xd", "name": "Subject relevance", "annotation_kind": "MessageMultiSelection", "classifications": [], "selected_messages": { "parent_message_id": "clwrt16zz00033b6p6ud7w233", "messages": [ { "message_id": "clwrt1fw700063b6pe8zpo1xp", "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" } ] } } ], "classifications": [], "relationships": [] } } ], "project_details": { "ontology_id": "clwkn6leb034b07waeolfb7tl", "task_id": "0c392f64-bc65-44e6-8848-ae5a12000328", "task_name": "Initial review task", "batch_id": "c2267a60-1d9c-11ef-9edf-ed6f8eede535", "batch_name": "batch_clwrm92jc0grc07wncq654q5s_clwrm9eph006d0750b3s5b2cr", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 2, "workflow_history": [ { "action": "Move", "created_at": "2024-05-29T12:33:21.160+00:00", "created_by": "[email protected]", "previous_task_name": "Initial labeling task", "previous_task_id": "ebf41e2b-e231-0e43-adee-92252c1858dd", "next_task_name": "Initial review task", "next_task_id": "0c392f64-bc65-44e6-8848-ae5a12000328" }, { "action": "Move", "created_at": "2024-05-29T12:33:21.151+00:00", "created_by": "[email protected]", "next_task_name": "Initial labeling task", "next_task_id": "ebf41e2b-e231-0e43-adee-92252c1858dd" } ] }, "project_model_configs": { "models": { "5d9647e7-aafe-4275-944d-c2e9b122105a": { "name": "Google Gemini Pro Vision", "created_at": "2023-12-13T21:51:42.657+00:00" }, "036aeb03-b0b4-442e-a6e1-161382bc1228": { "name": "OpenAI GPT-4o", "created_at": "2024-05-23T23:56:03.078+00:00" } }, "model_configs": { "e606593e-def3-4662-9737-3fc4ac412dd3": { "name": "GPT_4o_multi_modal", "model_id": "036aeb03-b0b4-442e-a6e1-161382bc1228", "inference_params": { "top_p": 1, "detail": "high", "temperature": 0, "use_text_only": false, "max_new_tokens": 1024, "use_image_only": false, "use_attachments": false } }, "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2": { "name": "Gemini_pro_vison_multi_modal", "model_id": "5d9647e7-aafe-4275-944d-c2e9b122105a", "inference_params": { "use_attachments": false } } }, "project_model_configs": [ { "id": "clwrsxr1y009i07v93w2h45qm", "model_id": "036aeb03-b0b4-442e-a6e1-161382bc1228", "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" }, { "id": "clwrsyext00ar07v5czpo7w2a", "model_id": "5d9647e7-aafe-4275-944d-c2e9b122105a", "model_config_id": "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2" } ] }, "project_tags": [] } } ``` # Export overview Source: https://docs.labelbox.com/horizon/developer-guides/export-overview How to export data, with examples for each type of export along with details on optional parameters and filters. To export data via the SDK, use `export()`, a scalable and efficient method that allows streaming of unlimited data rows while providing a unique task object for tracking progress. ### export\_v2() is deprecated The previously available `export_v2()` method has been deprecated and will be removed in version 7.0 of the SDK. If you're currently using `export_v2()`, we strongly encourage you to switch to `export()` for its enhanced streamable implementation. To learn how to export data rows from a project via the app UI, visit [Export labels from project](/horizon/guides/export-labels). ## Export specifications | Data type | Annotation export formats | Project export | Model run export | | - | - | - | - | | Image | [See export formats](/horizon/developer-guides/export-image-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-image-annotations#sample-project-export) | [See sample](/horizon/developer-guides/export-image-annotations#sample-model-run-export) | | Video | [See export formats](/horizon/developer-guides/export-video-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-video-annotations#sample-project-export) | [See sample](/horizon/developer-guides/export-video-annotations#sample-model-run-export) | | Text | [See export formats](/horizon/developer-guides/export-text-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-text-annotations#sample-project-export) | [See sample](/horizon/developer-guides/export-text-annotations#sample-model-run-export) | | Geospatial | [See export formats](/horizon/developer-guides/export-geospatial-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-geospatial-annotations#sample-project-export) | [See sample](/horizon/developer-guides/export-geospatial-annotations#sample-model-run-export) | | Documents | [See export formats](/horizon/developer-guides/export-document-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-document-annotations#sample-project-export) | Not supported yet | | Audio | [See export formats](/horizon/developer-guides/export-audio-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-audio-annotations#sample-project-export) | Not supported yet | | Conversational text | [See export formats](/horizon/developer-guides/export-conversational-text-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-conversational-text-annotations#sample-project-export) | Not supported yet | | HTML | [See export formats](/horizon/developer-guides/export-html-annotations#annotation-export-formats) | [See sample](/horizon/developer-guides/export-html-annotations#sample-project-export) | Not supported yet | There are three ways to export data from Labelbox: export from Catalog, export from a labeling project, and export from a model run. ## Required & optional fields Below is the complete list of required and optional fields supported for exports.\ To include the `location.token` field in your export of text data, set `data_row_details` to `True`. | Field | Description | Project export | Model run export | Catalog export | | - | - | - | - | - | | `data_row` | Contains the basic information of the data row: - `id` - `row_data` - `global_key` - `data_row_details` (optional, see below) | Always | Always | Always | | `data_row_details` | Contains additional details of the data row: - `dataset_id` - `dataset_name` - `created_at` - `updated_at` - `last_activity_at` - `created_by` | Optional | Optional | Optional | | `media_attributes` | See [Media attributes](/horizon/developer-guides/media-attributes) | Optional | Optional | Optional | | `attachments` | See [Attachments](/horizon/guides/label-data#attachments) | Optional | Optional | Optional | | `metadata_fields` | See [Metadata](/horizon/developer-guides/metadata) | Optional | Optional | Optional | | `embeddings` | Contains a list of dictionaries with precomputed and custom embeddings | Optional | Optional | Optional | | `projects` | Contains the ID of the project in which the data row was labeled. | Always | n/a | Optional | | `` | Contains the following sections, which are expanded on below: - `labels` - `project_details` | Always | n/a | Optional | | `labels` | Contains a list of labels attached to this data row: - `label_kind` - `version` - `id` - `annotations` | Always | Always | Optional | | `label_details` | Contains details of each specific label: - `created_at` - `updated_at` - `created_by` - `reviews` | Optional | n/a | Optional | | `performance_details` | Contains label-specific [performance details](/horizon/developer-guides/export-glossary#performance_details): - `seconds_to_create` - `seconds_to_review` - `skipped` - `performance_details_v2`, which contains: - `seconds_to_create` - `seconds_to_review` - `seconds_to_rework` - `seconds_total` | Optional | n/a | Optional | | `project_details` | Contains project-specific information about this data row: - `ontology_id` - `task_id` - `task_name` - `batch_id` - `batch_name` - `workflow_status` - `priority` - `selected_label_id` - `consensus_expected_label_count` - `workflow_history` | Optional | n/a | Optional | | `project_tags` | See [Project tags](/horizon/guides/project-tagging) | Always | n/a | n/a | | `experiments` | Contains the ID of the model experiment(s) in which the data row was stored. | n/a | Always | Optional | | `` | Contains the following sections, which are expanded on below: - `name` - `runs` | n/a | Always | Optional | | `name` | Name of the model. | n/a | Always | Optional | | `runs` | Contains the ID of the model run(s) in which the data row was stored. | n/a | Always | Optional | | `` | Contains the following sections, which are expanded on below: - `name` - `annotation_group_id` - `labels` - `predictions` - `split` | n/a | Always | Optional | | `name` | Name of the model run. | n/a | Always | Optional | | `run_data_row_id` | Model run data row ID, similar to `data_row_id` but in a model run's context. | n/a | Always | Optional | | `labels` | Contains a list of the ground truth labels attached to this data row and included in this model run: - `label_kind` - `version` - `id` - `annotations` | n/a | Always | Always | | `predictions` | Contains a list of predictions attached to this data row and included in this model run: - `label_kind` - `version` - `id` - `annotations` | n/a | Optional | Optional | | `split` | Contains the split the data row belongs to (either `Training`, `Validation`, or `Test`). | n/a | Optional | Optional | ## Optional parameters and filters ### Parameters When you export data rows from a project, a model run, or Catalog, you can set parameters to include optional fields in the exports. The table below expresses the parameters available for each type of export. | Parameter | Project export | Model run export | Dataset export (Catalog) | Slice export (Catalog) | | - | - | - | - | - | | `attachments` | ✔ | ✔ | ✔ | ✔ | | `metadata_fields` | ✔ | ✔ | ✔ | ✔ | | `embeddings` | ✔ | ✔ | ✔ | ✔ | | `data_row_details` | ✔ | ✔ | ✔ | ✔ | | `project_details` | ✔ | - | ✔ | ✔ | | `label_details` | ✔ | - | ✔ | ✔ | | `performance_details` | ✔ | - | ✔ | ✔ | | `interpolated_frames` | ✔ | ✔ | ✔ | ✔ | | `predictions` | - | ✔ | - | - | | `model_run_details` | - | ✔ | - | - | | `model_run_ids` | - | - | ✔ | ✔ | | `project_ids` | - | - | ✔ | ✔ | | `all_projects` | - | - | ✔ | ✔ | | `all_model_runs` | - | - | ✔ | ✔ | For explanations of each field and subfield, see [Export glossary](/horizon/developer-guides/export-glossary). For a detailed explanation of the `project_ids`, `model_run_ids`, `all_projects`, and `all_model_runs` parameters, see [Export data rows from Catalog](/horizon/developer-guides/export-overview#export-data-rows-from-catalog) below. To learn how to apply these filters, see the below sections specific to each export type. ### Filters You can use filters to select a subset of data rows to export. The table below contains the filters supported for each export type. You can apply multiple supported filters to the same export. Combinations of filters apply `AND` operator logic. | Filter | Project export | Model run export | Dataset export (Catalog) | Slice export (Catalog) | | - | - | - | - | - | | `last_activity_at` | ✔ | - | ✔ | - | | `label_created_at` | ✔ | - | ✔ | - | | `workflow_status` | ✔ | - | - | - | | `batch_ids` | ✔ | - | - | - | | `global_keys` | ✔ | - | ✔ | - | | `data_row_ids` | ✔ | - | ✔ | - | The `last_activity_at` and `label_created_at` filters take the structure of `[, ]` and can have the following formats: * `YYYY-MM-DD`(this is an alias of `YYYY-MM-DD 00:00:00`) * `YYYY-MM-DD hh:mm:ss` * `YYYY-MM-DDThh:mm:ss±hhmm` (ISO 8601) * `None` The ISO 8601 format allows you to specify the timezone, while the other two formats assume the timezone from the user's workspace settings. #### Last activity at The `last_activity_at` filter captures only the data rows where the following changes have been made in the specified time frame: * Changes update a data row's data (`rowData`), external ID (`externalId`), or global key (`globalKey`) * Changes are made to annotations, attachments, embeddings, or metadata * Data rows are added to batches * Data row labeling tasks change * Labels, reviews, comments, or issues are added to a project containing the data row Data rows in multiple projects update `last_activity_at` when such changes occur in any project containing the data rows. #### Label created at The `label_created_at` filter captures only the data rows where labels have been created in the specified time frame. #### Workflow status The `workflow_status` filter allows you to export only the data rows in a specific status of a project's workflow. The filter accepts the following values: * `ToLabel` * `InReview` * `InRework` * `Done` This filter only accepts one value. For example, `filters = {"workflow_status": "InReview"}`. #### Batch IDs The `batch_ids` filter allows you to export only the data rows in a specific batch or batches. This filter accepts a list of batch IDs. For example, `filters = {"batch_ids": ["batch_id_1", "batch_id_2"]}`. To get the batches sent to a project and their associated information, you can use the `project.batches()` method. For more information, see [Get the batches](/horizon/developer-guides/project#get-the-batches). #### Global keys The `global_keys` filter allows you to export only the data rows with the specified global keys within a project or dataset. This filter accepts a list containing up to 2,000 values. For example, `filters = {"global_keys": ["global_key_1", "global_key_2"]}`. #### Data row IDs The `data_row_ids` filter allows you to export only the data rows with the specified IDs within a project or dataset. This filter accepts a list containing up to 2,000 values. For example, `filters = {"data_row_ids": ["data_row_id_1", "data_row_id_2"]}`. ## Streamable exports Streamable exports (compatible with SDK versions 3.56 and above) allow you to get real-time data flow and updates using any of the following streamable export methods: 1. `data_row.export()` 2. `dataset.export()` 3. `model_run.export()` 4. `project.export()` 5. `slice.export()` The return type of these methods is an object of the class `ExportTask`. This class serves as a wrapper around a [Task Objects](/horizon/developer-guides/tasks). Because of this relationship, most of the features present in the `Task` class are also available in the `ExportTask` class. `ExportTask` supports the following methods and properties from `Task`: * uid * deleted * wait\_till\_done * completion\_percentage * created\_at * name * status * type * updated\_at * get\_task * organization * created\_by ### Creating an ExportTask instance An instance of an `ExportTask` can be obtained via the `export()` method on the classes mentioned above, or by executing the following: ```python Python theme={"theme":"css-variables"} export_task = lb.ExportTask.get_task(client, task_id) # where `task_id` has to be of type `ExportTask` export_task.wait_till_done() ``` ### Checking for results and errors To check if a task has a result/errors, the following methods can be executed: ```python Python theme={"theme":"css-variables"} if not export_task.has_result(): print("no results") if export_task.has_errors(): print("there are errors") # These method will raise an ExportTask.TaskNotReadyExceptionexception if the task is neither in a COMPLETE or FAILED state. ``` ### Streaming results To stream the results of exported data rows: ```python Python theme={"theme":"css-variables"} # Start export from project export_task = project.export() export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` ### Simplified usage For fine-grained control over the streaming process, you can use a for loop to iterate through the converted items in the stream. This allows you to implement custom streaming logic, process partial results, or apply additional filtering. ```python Python theme={"theme":"css-variables"} export_task = project.export() export_task.wait_till_done() for data_row in export_task.get_buffered_stream(): print(data_row.json) ``` #### Start streaming at an offset or line You can define a particular offset to initiate streaming. In the given example, the stream will start from offset 25,548. ```python Python theme={"theme":"css-variables"} export_task.get_buffered_stream().with_offset(25548).start(stream_handler=json_stream_handler) ``` ### Note: Selecting a random offset might result in positioning within the middle of a JSON string, and this behavior is entirely acceptable. The impact of this choice will become apparent in the output as soon as the streaming starts. Likewise, a specific line can be specified. In the following example, the stream will skip the first 348 lines and start with the 349th line, where a single JSON string is considered a line. ```python Python theme={"theme":"css-variables"} export_task.get_buffered_stream().with_line(348).start(stream_handler=json_stream_handler) ``` ### Note: offsets and lines are indexed starting from 0, thus `with_line(3)` will start streaming from the 4th line. The offset within `with_offset()` cannot exceed the total size, and line in `with_line()` cannot exceed the total number of lines returned by these methods; otherwise, a `ValueError exception` will be raised. #### Print output size `ExportTask` has two methods to output the total size of the exported file and the total number of lines it contains: ```python Python theme={"theme":"css-variables"} total_size = export_task.get_total_file_size(lb.StreamType.RESULT) total_lines = export_task.get_total_lines(lb.StreamType.ERRORS) ``` #### Save export results and log errors You can store export results in a JSON file and log any errors for monitoring or further processing, like the following example: ```python Python theme={"theme":"css-variables"} try: export_task = project.export() export_task.wait_till_done() # Retrieve and save the export data export_data = list(export_task.result) with open(EXPORT_FILE_PATH, "w") as file: json.dump(export_data, file, indent=4) print(f"Exported data saved to {EXPORT_FILE_PATH}") except Exception as e: # Log the error with a timestamp for monitoring error_message = f"Error during export: {str(e)}" logging.error(error_message) print("An error occurred. Check the log file for details.") ``` ### Cancel export tasks You can cancel an ongoing export task before it completes, like the following example: ```python Python theme={"theme":"css-variables"} # Cancel the task before it completes success = client.cancel_task(export_task.uid) assert success is True # Verify the task was cancelled cancelled_task = client.get_task_by_id(export_task.uid) assert cancelled_task.status in ["CANCELING", "CANCELED"] ``` ## Export data rows from a project When you export data rows from a project, you can narrow down your data rows by label status, metadata, batch, annotations, and workflow history. Then, when you export from a project, you may choose to include or exclude certain attributes in your export. See the table at the top of this page to find the JSON export formats for each data type. ### Export from a project ```python Export theme={"theme":"css-variables"} # The return type of this method is an `ExportTask`, which is a wrapper of a`Task` # Most of `Task` features are also present in `ExportTask`. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "label_created_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "InReview", "batch_ids": ["batch_id_1", "batch_id_2"], "data_row_ids": ["data_row_id_1", "data_row_id_2"], "global_keys": ["global_key_1", "global_key_2"] } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Export data rows from Catalog You can export data rows and all their information from a [Dataset](/horizon/developer-guides/dataset) or a [Catalog Slice](/horizon/guides/slices). When exporting from Catalog, you can include information about a data row from all projects and model runs to which it belongs. Specifically, you can export the labels from multiple projects and/or the predictions from multiple model runs for the selected data rows. You can use the `all_projects` and `all_model_runs` parameters to get information from all projects and model runs attached to your data row. As shown below, the `project_ids` and `model_run_ids` parameters accept a list of IDs. See the table at the top of this page to find the JSON export formats for each data type. ### Export from a dataset ```python Export theme={"theme":"css-variables"} export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True, "all_projects": True, "all_model_runs": True, "project_ids": ["", ""], "model_run_ids": ["", ""] } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"] } dataset = client.get_dataset("") export_task = dataset.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ### Export a list of selected data rows from a dataset ```python Export theme={"theme":"css-variables"} export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True, "all_projects": True, "all_model_runs": True, "project_ids": ["", ""], "model_run_ids": ["", ""] } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "data_row_ids": ["data_row_id_1", "data_row_id_2"], "global_keys": ["global_key_1", "global_key_2"] } export_task = dataset.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ### Export from a slice ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params = { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True, "all_projects": True, "all_model_runs": True, "project_ids": ["", ""], "model_run_ids": ["", ""] } catalog_slice = client.get_catalog_slice("") export_task = catalog_slice.export(params=export_params) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ### Export your entire Catalog ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params = { "attachments": True, "embeddings": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True, "all_projects": True, "all_model_runs": True, "project_ids": ["", ""], "model_run_ids": ["", ""] } catalog = client.get_catalog() export_task = catalog.export(params=export_params) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Export data rows from a model run See the table at the top of this page to find the JSON export formats for each data type. ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params = { "attachments": True, "metadata_fields": True, "data_row_details": True, "interpolated_frames": True, "predictions": True } model_run = client.get_model_run("") export_task = model_run.export(params=export_params) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` # Export prompt and response data Source: https://docs.labelbox.com/horizon/developer-guides/export-prompt-response-data How to export prompt and response data ## Export JSON annotations ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Prompt export format ### Humans generate prompts ```json JSON theme={"theme":"css-variables"} { "feature_id": "cldne96y301wy13yd0wp5z87y", "feature_schema_id": "cljg9my6h01000741aemmcln8", "name": "sample_name", "text_answer": { "content": "sample text" } } ``` ## Response export formats ### Response - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "cldne96y201wq13ydu0qcc2up", "feature_schema_id": "cljggfygv0chn070w1v131s3v", "name": "sample_radio_name", "radio_answer": { "feature_id": "cldne96y201wr13yd23kr1pcr", "feature_schema_id": "cljgggcs200083b6lqbpml10p", "name": "first_radio_answer", "classifications": [] } } ``` ### Response - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "cldne96y201wu13ydohrclpra", "feature_schema_id": "cljgegd1p07m4073cfmfy5xkx", "name": "checklist_question", "checklist_answers": [ { "feature_id": "cldne96y301wv13ydatuxugbt", "name": "first_checklist_answer", "classifications": [] }, { "feature_id": "cldne96y301ww13yds4zkk49u", "name": "second_checklist_answer", "classifications": [] }, { "feature_id": "cldne96y301wx13ydvb5x2w6o", "name": "third_checklist_answer", "classifications": [] } ] } ``` ### Response - Text ```json JSON theme={"theme":"css-variables"} { "feature_id": "cldne96y301wy13yd0wp5z87y", "feature_schema_id": "cljg9my6h01000741aemmcln8", "name": "sample_name", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clpvnouh04uyy0723mmru42qn", "global_key": "clpvnou2v03js07xsghfo2nzc", "row_data": "{\"type\":\"application/llm.prompt-response-creation\",\"version\":1}", "details": { "dataset_id": "clpvnou0z004c0724pd4cmw8g", "dataset_name": "test-humans-generate-prompts-reponses-dataset", "created_at": "2023-12-07T20:34:06.540+00:00", "updated_at": "2023-12-07T20:34:06.815+00:00", "last_activity_at": "2024-04-10T15:21:31.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "application/llm.prompt-response-creation" }, "attachments": [], "metadata_fields": [], "projects": { "clpvnotzb03jo07xs48r7ewka": { "name": "Andrea-test-humans-generate-prompts-responses", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clutyida4041a07h4a8ojbu1g", "label_details": { "created_at": "2024-04-10T15:21:31.000+00:00", "updated_at": "2024-04-10T15:21:31.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-04-10T15:21:31.137+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 28, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clutykado00013b6rw65zj51e", "feature_schema_id": "clutyjef000xk07wfeurhc2qb", "name": "Is this a shirt?", "value": "Is this a shirt?", "text_answer": { "content": "Potentially this is a shirt, but keep in mind this is not a good quality prompt" } }, { "feature_id": "clutykap700033b6rmmlkpo6u", "feature_schema_id": "clutyjef000xm07wf5ym80ud6", "name": "Yes", "value": "yes", "radio_answer": { "feature_id": "clutykap700023b6rhktauyzc", "feature_schema_id": "clutyjef000xn07wf6smvc54b", "name": "Red shirt", "value": "red_shirt", "classifications": [] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clpvnqsqz01kv07zydvbscxzq", "task_id": "14b02ec0-71f3-4d1f-b720-c6318e9a9346", "task_name": "Initial review task", "batch_id": "00870bc0-9540-11ee-a202-8d3b90bd1707", "batch_name": "batch_clpvnotzb03jo07xs48r7ewka", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 2, "workflow_history": [ { "action": "Move", "created_at": "2024-04-10T15:21:31.517+00:00", "created_by": "[email protected]", "previous_task_name": "Initial labeling task", "previous_task_id": "1d0062f9-dfd9-0f86-baed-823235868a8c", "next_task_name": "Initial review task", "next_task_id": "14b02ec0-71f3-4d1f-b720-c6318e9a9346" }, { "action": "Move", "created_at": "2024-04-10T15:21:31.506+00:00", "created_by": "[email protected]", "next_task_name": "Initial labeling task", "next_task_id": "1d0062f9-dfd9-0f86-baed-823235868a8c" } ] }, "project_tags": [] } } } ``` # Export quick reference Source: https://docs.labelbox.com/horizon/developer-guides/export-quick-reference ## Image ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clf9spzxq029m071nehy60a6v", "global_key": "2560px-Kitano_Street_Kobe01s5s4110.jpeg", "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "details": { "dataset_id": "clf9spwgw04w207ymfwy826qv", "dataset_name": "demo_dataset_img", "created_at": "2023-03-15T14:46:58.000+00:00", "updated_at": "2023-03-15T14:46:58.000+00:00", "last_activity_at": "2024-04-08T16:34:01.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "height": 1707, "width": 2560, "mime_type": "image/jpeg" }, "attachments": [], "metadata_fields": [], "embeddings": [ { "id": "cn1cywy1r077807zblvo0m884", "name": "my_custom_embedding_2048_dimensions", "dimensions": 2048, "is_custom": true, "values": [ { "value": [ 1.1265901290930636, 1.0413733599834287, 1.3041214120015427, 1.893491997791636 ... ] } ] }, { "id": "c200000000000000000000000", "name": "Image Embedding V2 (CLIP ViT-B/32)", "dimensions": 512, "is_custom": false, "values": [ { "value": [ -0.4192526042461395, -0.048827216029167175 ... ] } ] } ], "projects": { "cltd3vkju021907u63w2odj69": { "name": "Image Annotation Import Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clur5tlda1n2a07dc0i0zdd4k", "label_details": { "created_at": "2024-04-08T16:21:26.000+00:00", "updated_at": "2024-04-08T16:34:02.000+00:00", "created_by": "user@labelbox.com", "content_last_updated_at": "2024-04-08T16:34:00.513+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 18, "seconds_to_review": 6, "skipped": false }, "annotations": { "objects": [ { "feature_id": "clur5tlyq1n2d07dcg9xldg90", "feature_schema_id": "cltd3vkc1019r07ts2lru4hi4", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 1264.0, "left": 2096.0, "height": 425.0, "width": 144.0 } }, { "feature_id": "clur5tlyr1n2f07dcf78sa4y5", "feature_schema_id": "cltd3vkc201a707ts9ullbhfo", "name": "point", "value": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } }, { "feature_id": "clur5tlyr1n2h07dc1pxnhoj3", "feature_schema_id": "cltd3vkc1019t07ts2bk6hjjj", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "ImageBoundingBox", "classifications": [ { "feature_id": "clur5tlyr1n2v07dcbnnfh1ah", "feature_schema_id": "cltd3vkc1019u07tsaari5i2k", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clur5tlyr1n3607dc98qpagxb", "feature_schema_id": "cltd3vkc1019v07tsbow08f9u", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ], "bounding_box": { "top": 933.0, "left": 541.0, "height": 191.0, "width": 330.0 } }, { "feature_id": "clur5tlyr1n2j07dc50cs4e11", "feature_schema_id": "cltd3vkc201a907ts69dr5m86", "name": "polyline", "value": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 2429.492, "y": 182.092 }, { "x": 2294.322, "y": 221.962 }, { "x": 2224.491, "y": 180.463 }, { "x": 2136.123, "y": 204.716 }, { "x": 1712.247, "y": 173.949 }, { "x": 1703.838, "y": 84.438 }, { "x": 1579.772, "y": 82.61 }, { "x": 1583.442, "y": 167.552 }, { "x": 1478.869, "y": 164.903 }, { "x": 1418.941, "y": 318.149 }, { "x": 1243.128, "y": 400.815 }, { "x": 1022.067, "y": 319.007 }, { "x": 892.367, "y": 379.216 }, { "x": 670.273, "y": 364.408 }, { "x": 613.114, "y": 288.16 }, { "x": 377.559, "y": 238.251 }, { "x": 368.087, "y": 185.064 }, { "x": 246.557, "y": 167.286 }, { "x": 236.648, "y": 285.61 }, { "x": 90.929, "y": 326.412 } ] }, { "feature_id": "clur5tlyr1n2l07dc1jsf15qo", "feature_schema_id": "cltd3vkc1019r07ts2lru4hi4", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 1346.0, "left": 2272.0, "height": 358.0, "width": 144.0 } }, { "feature_id": "clur5tlyr1n2m07dc4x6xhzep", "feature_schema_id": "cltd3vkc1019z07ts7g1n3o5m", "name": "polygon", "value": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 } ] }, { "feature_id": "clur5tlyr1n2n07dc4qot5gbx", "feature_schema_id": "cltd3vkc1019r07ts2lru4hi4", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 977.0, "left": 1690.0, "height": 330.0, "width": 225.0 } }, { "feature_id": "clur5tlyr1n2q07dc5joc4k7u", "feature_schema_id": "cltd3vkc101a107ts95sk63u0", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2q07dc5joc4k7u/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 10, 10, 125 ] } }, { "feature_id": "clur5tlyr1n2r07dc6ym4d4mc", "feature_schema_id": "cltd3vkc201a307tsforlev07", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "clur5tlyr1n2x07dc84a12l8i", "feature_schema_id": "cltd3vkc201a407tsfo3agh7x", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer sample" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2r07dc6ym4d4mc/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 105, 136, 97 ] } }, { "feature_id": "clur5tlyr1n2s07dc739a6kqs", "feature_schema_id": "cltd3vkc201a307tsforlev07", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "clur5tlyr1n2w07dccdv0g7gg", "feature_schema_id": "cltd3vkc201a407tsfo3agh7x", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer sample" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2s07dc739a6kqs/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 7, 85, 11 ] } }, { "feature_id": "clur5tlyr1n2t07dc0oxlgoc8", "feature_schema_id": "cltd3vkc201a307tsforlev07", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "clur5tlyr1n3107dccy5pbudr", "feature_schema_id": "cltd3vkc201a407tsfo3agh7x", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer sample" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2t07dc0oxlgoc8/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 35, 34, 212 ] } }, { "feature_id": "clur5tlyr1n2u07dc5wjmdm3v", "feature_schema_id": "cltd3vkc101a107ts95sk63u0", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd3vkju021907u63w2odj69/annotations/clur5tlyr1n2u07dc5wjmdm3v/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6dp8r04ul07wrgp9q4pla/masks/clur5tlyr1n2o07dc99lw3r8p/index/1", "color_rgb": [ 232, 194, 86 ] } } ], "classifications": [ { "feature_id": "clur5tlyr1n2e07dc2bez8p1u", "feature_schema_id": "cltd3vkc201ar07ts3gq6a6sn", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "clur5tlyr1n3307dcexth5ff7", "feature_schema_id": "cltd3vkc201as07tsh2prgosu", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "clur5tlyr1n3407dc306b5xib", "feature_schema_id": "cltd3vkc201at07tsdjx9dlbw", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clur5tlyr1n3507dcef304jmb", "feature_schema_id": "cltd3vkc201au07ts7nk47qny", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "clur5tlyr1n2g07dc7wdv7p5z", "feature_schema_id": "cltd3vkc201ap07ts58gma0bb", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "clur5tlyr1n2i07dcd8lx87be", "feature_schema_id": "cltd3vkc201az07ts7st3fn79", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n3207dc4u4nfg08", "feature_schema_id": "cltd3vkc201b007tsdhs07baf", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "clur5tlyr1n3707dc2cy805ac", "feature_schema_id": "cltd3vkc201b107ts12b1al1j", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n3807dcgpgih5qj", "feature_schema_id": "cltd3vkc201b207ts434b9iyx", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "clur5tlyr1n2k07dcezk08npu", "feature_schema_id": "cltd3vkc201ad07ts3t78873p", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "clur5tlyr1n2y07dcajpyg0d4", "feature_schema_id": "cltd3vkc201ag07tsfoc2bdpi", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "clur5tlyr1n2p07dchs791672", "feature_schema_id": "cltd3vkc201aj07tsait4cbzh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "clur5tlyr1n2z07dc3abv19e8", "feature_schema_id": "cltd3vkc201ak07tsfqxc740z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clur5tlyr1n3007dc4vbr0628", "feature_schema_id": "cltd3vkc201am07ts7s9e7x7k", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ], "relationships": [ { "feature_id": "clur69pfa00023b6rkftlkucz", "feature_schema_id": "cltd3vkc201ab07ts1f9dhpkt", "name": "relationship", "value": "relationship", "annotation_kind": "ImageUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "clur5tlyq1n2d07dcg9xldg90", "target": "clur5tlyr1n2l07dc1jsf15qo" } } ] } } ], "project_details": { "ontology_id": "cltd3vkbb019q07ts23eedy4o", "task_id": "a384279a-45c0-4200-b85a-3c18469b3809", "task_name": "Initial review task", "batch_id": "3f724e20-da3d-11ee-86a4-0145011952fd", "batch_name": "image-demo-batch", "workflow_status": "IN_REVIEW", "priority": 1, "consensus_expected_label_count": 1, "workflow_history": [ { "action": "Move", "created_at": "2024-04-08T16:21:26.492+00:00", "created_by": "aovalle@labelbox.com", "previous_task_name": "Initial labeling task", "previous_task_id": "22ee463b-fbe3-0791-8159-94083be9dee3", "next_task_name": "Initial review task", "next_task_id": "a384279a-45c0-4200-b85a-3c18469b3809" }, { "action": "Move", "created_at": "2024-04-08T16:21:26.481+00:00", "created_by": "aovalle@labelbox.com", "next_task_name": "Initial labeling task", "next_task_id": "22ee463b-fbe3-0791-8159-94083be9dee3" } ] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clo78lhhn3dxb07322mnn8plu", "global_key": "2560px-Kitano_Street_Kobe01s.jpeg", "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "details": { "dataset_id": "clo78lg8f003007321fe52lje", "dataset_name": "image_prediction_demo", "created_at": "2023-10-26T13:45:24.929+00:00", "updated_at": "2023-10-26T13:45:25.965+00:00", "created_by": "aovalle@labelbox.com" } }, "media_attributes": { "height": 1707, "width": 2560, "mime_type": "image/jpeg", "exif_rotation": "1" }, "attachments": [], "metadata_fields": [], "embeddings": [ { "id": "cn1cywy1r077807zblvo0m884", "name": "my_custom_embedding_2048_dimensions", "dimensions": 2048, "is_custom": true, "values": [ { "value": [ 1.1265901290930636, 1.0413733599834287, 1.3041214120015427, 1.893491997791636 ... ] } ] }, { "id": "c200000000000000000000000", "name": "Image Embedding V2 (CLIP ViT-B/32)", "dimensions": 512, "is_custom": false, "values": [ { "value": [ -0.4192526042461395, -0.048827216029167175 ... ] } ] } ], "experiments": { "a2fb1fec-83fb-0858-2cfb-98f4757eae95": { "name": "image_model_run_3e5bf56c-31a4-4eed-a731-9ca85e783225", "runs": { "a2fb1fec-b8a5-0f96-b61e-0d5643fb8dec": { "name": "iteration 1", "run_data_row_id": "42630da9-0dc4-462b-9aa0-5a17e85ff46a", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clpk3h87e005r14vsp4c33sfq", "annotations": { "objects": [ { "feature_id": "164937d0-7bb2-0f03-8031-34b57799736b", "feature_schema_id": "clpk3gs0t054y07vig605dopr", "name": "polygon", "value": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 } ] }, { "feature_id": "2738b5f8-e586-0090-974a-59b6c9b46ee2", "feature_schema_id": "clpk3gs0t055207vi1baq8bke", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "ImageSegmentationMask", "classifications": [ { "feature_id": "9001b75c-bb70-0b40-afbf-9479ecfa9f99", "feature_schema_id": "clpk3gs0t055307vi90s32rhi", "name": "sub_free_text", "value": "sub_free_text", "text_answer": { "content": "free text answer" } } ], "mask": { "url": "https://api.labelbox.com/api/v1/models/a2fb1fec-83fb-0858-2cfb-98f4757eae95/annotations/2738b5f8-e586-0090-974a-59b6c9b46ee2/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6mb7a0dz107xod5w53p61/masks/clpk3h7uk005114vsgru34yw7/index/1", "color_rgb": [ 41, 24, 175 ] } }, { "feature_id": "4defcaa5-623f-0589-932a-b703e1116fa4", "feature_schema_id": "clpk3gs0t055807vi49uy1rcm", "name": "polyline", "value": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 2429.492, "y": 182.092 }, { "x": 2294.322, "y": 221.962 }, { "x": 2224.491, "y": 180.463 }, { "x": 2136.123, "y": 204.716 }, { "x": 1712.247, "y": 173.949 }, { "x": 1703.838, "y": 84.438 }, { "x": 1579.772, "y": 82.61 }, { "x": 1583.442, "y": 167.552 }, { "x": 1478.869, "y": 164.903 }, { "x": 1418.941, "y": 318.149 }, { "x": 1243.128, "y": 400.815 }, { "x": 1022.067, "y": 319.007 }, { "x": 892.367, "y": 379.216 }, { "x": 670.273, "y": 364.408 }, { "x": 613.114, "y": 288.16 }, { "x": 377.559, "y": 238.251 }, { "x": 368.087, "y": 185.064 }, { "x": 246.557, "y": 167.286 }, { "x": 236.648, "y": 285.61 }, { "x": 90.929, "y": 326.412 } ] }, { "feature_id": "7d129546-45a6-060b-918e-35caec234d31", "feature_schema_id": "clpk3gs0s054s07vi3ddo8had", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "ImageBoundingBox", "classifications": [ { "feature_id": "cccba37c-b7a8-095b-8679-dd20aece6f90", "feature_schema_id": "clpk3gs0s054t07vi17aih47e", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "ea336ea5-041c-02b6-9e8b-6ac1098351b1", "feature_schema_id": "clpk3gs0s054u07vicrttdlgj", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ], "bounding_box": { "top": 933.0, "left": 541.0, "height": 191.0, "width": 330.0 } }, { "feature_id": "a3e0496d-3963-0d40-af89-48a907fbb48d", "feature_schema_id": "clpk3gs0s054q07vi3c30d1be", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 977.0, "left": 1690.0, "height": 330.0, "width": 225.0 } }, { "feature_id": "f2fc7c1f-7f10-0fc2-b676-fa2591f51a27", "feature_schema_id": "clpk3gs0t055607vi59sshqn0", "name": "point", "value": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } }, { "feature_id": "fb60f23f-8ae1-05b3-a4d8-353839975d59", "feature_schema_id": "clpk3gs0t055007vi4h7fgm59", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/models/a2fb1fec-83fb-0858-2cfb-98f4757eae95/annotations/fb60f23f-8ae1-05b3-a4d8-353839975d59/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6mb7a0dz107xod5w53p61/masks/clpk3h7uk005114vsgru34yw7/index/1", "color_rgb": [ 186, 26, 40 ] } } ], "classifications": [ { "feature_id": "1be71b85-08da-0ade-bbe4-cfdd431d3e8a", "feature_schema_id": "clpk3gs0t055g07viaine2doh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "0104948e-7be5-032d-80ca-87d052346618", "feature_schema_id": "clpk3gs0t055j07vi4h3y9wso", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "cb587406-5db3-0de2-99f0-cf6e6c344f43", "feature_schema_id": "clpk3gs0t055h07vi1hp0acnb", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "5b1f4994-1bad-03a1-bf0f-1a0b4682ea25", "feature_schema_id": "clpk3gs0t055m07vi53ow6auc", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "88f8cd4d-e1ca-0add-a7e8-a7349def31bc", "feature_schema_id": "clpk3gs0t055a07vifk5701ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "ee141c0d-064f-0de8-b746-e61ac14fbe62", "feature_schema_id": "clpk3gs0t055d07vibryq8f4l", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "cc8e57d8-5080-0b43-bab1-6e4d78b684de", "feature_schema_id": "clpk3gs0t055o07vifjxs63xi", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "81690da4-5cb5-037f-9a7b-3dd7fefc5481", "feature_schema_id": "clpk3gs0t055p07vigvkkeii3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "37453082-23dd-0c38-bdcb-90705102bc6e", "feature_schema_id": "clpk3gs0t055q07vifyau26jb", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "4b014520-7193-04d9-8d69-f800316b60f2", "feature_schema_id": "clpk3gs0t055r07vi406q70ga", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "f1d18441-41cd-0ff7-9f4f-75384e5a7b51", "feature_schema_id": "clpk3gs0u055w07vifjngez8o", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "a3c8745f-8151-0256-8b6d-8ac449797fd7", "feature_schema_id": "clpk3gs0u055x07vibwdt1pk3", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "aa00a3ff-1af4-038a-9be5-97a468656bc5", "feature_schema_id": "clpk3gs0u055y07vi3srh1g0l", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "0608a0d7-d9d9-0347-843b-3297e58a25dc", "feature_schema_id": "clpk3gs0u055z07vifkpvgap4", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clpk3h87e005r14vsp4c33sfq", "annotations": { "objects": [ { "feature_id": "028389ae-93ff-4614-a37e-e867a310371c", "feature_schema_id": "clpk3gs0s054q07vi3c30d1be", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "ImageBoundingBox", "classifications": [], "bounding_box": { "top": 977.0, "left": 1690.0, "height": 330.0, "width": 225.0 } }, { "feature_id": "37415b2f-f376-47b9-8f6a-6429c3efa6af", "feature_schema_id": "clpk3gs0t054y07vig605dopr", "name": "polygon", "value": "polygon", "annotation_kind": "ImagePolygon", "classifications": [], "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 } ] }, { "feature_id": "55bee3a4-59c2-4d85-95b3-4584c315037e", "feature_schema_id": "clpk3gs0t055607vi59sshqn0", "name": "point", "value": "point", "annotation_kind": "ImagePoint", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } }, { "feature_id": "671736d6-7c30-4399-bec4-a8a1779abf4d", "feature_schema_id": "clpk3gs0t055007vi4h7fgm59", "name": "mask", "value": "mask", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/models/a2fb1fec-83fb-0858-2cfb-98f4757eae95/annotations/671736d6-7c30-4399-bec4-a8a1779abf4d/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur6mb7a0dz107xod5w53p61/masks/clpk3gxbq00oi14ok1yykgp85/index/1", "color_rgb": [ 36, 67, 66 ] } }, { "feature_id": "7e103ed1-f66f-4cf9-9a36-3e08be3a9648", "feature_schema_id": "clpk3gs0t055807vi49uy1rcm", "name": "polyline", "value": "polyline", "annotation_kind": "ImagePolyline", "classifications": [], "line": [ { "x": 2534.353, "y": 249.471 }, { "x": 2429.492, "y": 182.092 }, { "x": 2294.322, "y": 221.962 }, { "x": 2224.491, "y": 180.463 }, { "x": 2136.123, "y": 204.716 }, { "x": 1712.247, "y": 173.949 }, { "x": 1703.838, "y": 84.438 }, { "x": 1579.772, "y": 82.61 }, { "x": 1583.442, "y": 167.552 }, { "x": 1478.869, "y": 164.903 }, { "x": 1418.941, "y": 318.149 }, { "x": 1243.128, "y": 400.815 }, { "x": 1022.067, "y": 319.007 }, { "x": 892.367, "y": 379.216 }, { "x": 670.273, "y": 364.408 }, { "x": 613.114, "y": 288.16 }, { "x": 377.559, "y": 238.251 }, { "x": 368.087, "y": 185.064 }, { "x": 246.557, "y": 167.286 }, { "x": 236.648, "y": 285.61 }, { "x": 90.929, "y": 326.412 } ] }, { "feature_id": "f3a445af-0475-4ad1-892b-fbbcce26073f", "feature_schema_id": "clpk3gs0s054s07vi3ddo8had", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "ImageBoundingBox", "classifications": [ { "feature_id": "d8427b71-d516-4686-bc52-660d1133c18d", "feature_schema_id": "clpk3gs0s054t07vi17aih47e", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "494d3af6-9bf8-477f-8827-dac0dd4c42a3", "feature_schema_id": "clpk3gs0s054u07vicrttdlgj", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ], "bounding_box": { "top": 933.0, "left": 541.0, "height": 191.0, "width": 330.0 } } ], "classifications": [ { "feature_id": "22778fd4-3fd3-48ad-9007-a8f2fc22d664", "feature_schema_id": "clpk3gs0t055o07vifjxs63xi", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "b992d812-7d0b-49b1-82da-14bcff5a76ef", "feature_schema_id": "clpk3gs0t055p07vigvkkeii3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "877d229e-c877-4aa7-a034-ddde21fa8417", "feature_schema_id": "clpk3gs0t055q07vifyau26jb", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "803cbe24-7c2f-400c-8d73-fb19bdc5302c", "feature_schema_id": "clpk3gs0t055r07vi406q70ga", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "2555cb5f-5a90-40f9-987f-7a2899688ad6", "feature_schema_id": "clpk3gs0t055a07vifk5701ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "246406e7-162c-43c5-a1ff-ca555be51e55", "feature_schema_id": "clpk3gs0t055d07vibryq8f4l", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "e4f143b7-5e3d-4f17-8202-efc78cd73f6e", "feature_schema_id": "clpk3gs0t055m07vi53ow6auc", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "ec7d6588-3c15-4fb6-baa1-3508c476bc32", "feature_schema_id": "clpk3gs0u055w07vifjngez8o", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "71fd6407-5fc6-43e4-8196-78487f0f9ec8", "feature_schema_id": "clpk3gs0u055x07vibwdt1pk3", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ab8bf663-3fba-4dd6-9e09-84e0bbd713d0", "feature_schema_id": "clpk3gs0u055y07vi3srh1g0l", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "58c98b94-b360-4ceb-b5ea-7765e096c40a", "feature_schema_id": "clpk3gs0u055z07vifkpvgap4", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "f9107ba1-8020-4b7a-8066-b80167a9cd6a", "feature_schema_id": "clpk3gs0t055g07viaine2doh", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "88cc2ed7-8b19-4396-b9ee-36949d23fac7", "feature_schema_id": "clpk3gs0t055h07vi1hp0acnb", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "b70e6eb1-2ee9-42f3-b47a-a56e764ea4ac", "feature_schema_id": "clpk3gs0t055j07vi4h3y9wso", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ] } } } }, "models": {} } ``` ## Video ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clp1jolki07uy0725flku4bmv", "global_key": "sample-video-jellyfish.mp4", "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "details": { "dataset_id": "clp1jokqp000p07307gtfy1w3", "dataset_name": "video_demo_dataset", "created_at": "2023-11-16T18:48:51.208+00:00", "updated_at": "2023-11-16T18:48:51.801+00:00", "last_activity_at": "2024-04-08T17:09:54.000+00:00", "created_by": "aovalle@labelbox.com" } }, "media_attributes": { "height": 1080, "width": 1920, "mime_type": "video/mp4", "frame_rate": 10, "frame_count": 100 }, "attachments": [], "metadata_fields": [], "embeddings": [ { "id": "c400000000000000000000000", "name": "Video Embedding V2 (Google MultiModal)", "dimensions": 1408, "is_custom": false, "values": [ { "value": [ -0.00489852577, 0.0287129097 ... ] } ] } ], "projects": { "cltd977uu016y08xyggep8aiv": { "name": "Video Annotation Import Demo", "labels": [ { "label_kind": "Video", "version": "1.0.0", "id": "cltd97caz01ub14ysq8rf4x8w", "label_details": { "created_at": "2024-03-04T18:07:33.000+00:00", "updated_at": "2024-03-04T18:07:33.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2024-03-04T18:07:33.369+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "frames": { "29": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tc14yspm0lev2d", "feature_schema_id": "cltd977np038307x55dlf3rgs", "name": "checklist_class", "value": "checklist_class", "checklist_answers": [ { "feature_id": "cltd97cag01td14ysxw2n8xkz", "feature_schema_id": "cltd977np038407x51hcvdad7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01te14ys1jdf1fku", "feature_schema_id": "cltd977np038607x58epu4nb5", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ] }, "35": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tc14yspm0lev2d", "feature_schema_id": "cltd977np038307x55dlf3rgs", "name": "checklist_class", "value": "checklist_class", "checklist_answers": [ { "feature_id": "cltd97cag01td14ysxw2n8xkz", "feature_schema_id": "cltd977np038407x51hcvdad7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01te14ys1jdf1fku", "feature_schema_id": "cltd977np038607x58epu4nb5", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ] }, "9": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.00000000000001 }, { "x": 100.00000000000001, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tf14ysho6tfl7j", "feature_schema_id": "cltd977np038907x5ah8l9ha7", "name": "radio_class", "value": "radio_class", "radio_answer": { "feature_id": "cltd97cag01tg14ys171a3qqr", "feature_schema_id": "cltd977np038a07x515axagkl", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ] }, "15": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tf14ysho6tfl7j", "feature_schema_id": "cltd977np038907x5ah8l9ha7", "name": "radio_class", "value": "radio_class", "radio_answer": { "feature_id": "cltd97cag01tg14ys171a3qqr", "feature_schema_id": "cltd977np038a07x515axagkl", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ] }, "13": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [ { "feature_id": "cltd97cag01tj14ysz455nenr", "feature_schema_id": "cltd977np037s07x5csbodf5f", "name": "checklist_class", "value": "checklist_class", "radio_answer": { "feature_id": "cltd97cag01tl14ysnbe8mw75", "feature_schema_id": "cltd977np037v07x54esmdmws", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } } ], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "14": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0000000000001, "left": 1371.0, "height": 418.9999999999999, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "16": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "17": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01to14yslakirofl": { "feature_id": "cltd97cag01to14yslakirofl", "feature_schema_id": "cltd977no037l07x56wfu4536", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "18": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "19": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "10": { "objects": { "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "11": { "objects": { "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [ { "feature_id": "cltd97cag01tj14ysz455nenr", "feature_schema_id": "cltd977np037s07x5csbodf5f", "name": "checklist_class", "value": "checklist_class", "radio_answer": { "feature_id": "cltd97cag01tk14ysejipk2y6", "feature_schema_id": "cltd977np037t07x5966ebxpe", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } } ], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "12": { "objects": { "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [ { "feature_id": "cltd97cag01tj14ysz455nenr", "feature_schema_id": "cltd977np037s07x5csbodf5f", "name": "checklist_class", "value": "checklist_class", "radio_answer": { "feature_id": "cltd97cag01tk14ysejipk2y6", "feature_schema_id": "cltd977np037t07x5966ebxpe", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } } ], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "22": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] }, "23": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0000000000002 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.00000000000006 } } }, "classifications": [] }, "24": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] }, "cltd97cag01u514ys2bfg9vvo": { "feature_id": "cltd97cag01u514ys2bfg9vvo", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cag01u514ys2bfg9vvo/index/24/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/24", "color_rgb": [ 76, 104, 177 ] } }, "cltd97cah01u614ysvntxcyub": { "feature_id": "cltd97cah01u614ysvntxcyub", "feature_schema_id": "cltd977np037z07x5gclv41y4", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u614ysvntxcyub/index/24/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/24", "color_rgb": [ 233, 218, 94 ] } }, "cltd97cah01u714ysbqbvqs6b": { "feature_id": "cltd97cah01u714ysbqbvqs6b", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u714ysbqbvqs6b/index/24/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/24", "color_rgb": [ 83, 152, 103 ] } } }, "classifications": [] }, "25": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 99.99999999999999 }, { "x": 99.99999999999999, "y": 189.99999999999997 } ] } }, "classifications": [] }, "26": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] }, "cltd97cah01ua14ysr6u1sh7w": { "feature_id": "cltd97cah01ua14ysr6u1sh7w", "feature_schema_id": "cltd977np037z07x5gclv41y4", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01ua14ysr6u1sh7w/index/26/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/26", "color_rgb": [ 226, 211, 41 ] } } }, "classifications": [] }, "27": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "5": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "6": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "7": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.00000000000003 } ] } }, "classifications": [] }, "8": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "20": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] }, "cltd97cag01u514ys2bfg9vvo": { "feature_id": "cltd97cag01u514ys2bfg9vvo", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cag01u514ys2bfg9vvo/index/20/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/20", "color_rgb": [ 76, 104, 177 ] } }, "cltd97cah01u614ysvntxcyub": { "feature_id": "cltd97cah01u614ysvntxcyub", "feature_schema_id": "cltd977np037z07x5gclv41y4", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u614ysvntxcyub/index/20/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/20", "color_rgb": [ 233, 218, 94 ] } }, "cltd97cah01u714ysbqbvqs6b": { "feature_id": "cltd97cah01u714ysbqbvqs6b", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u714ysbqbvqs6b/index/20/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/20", "color_rgb": [ 83, 152, 103 ] } } }, "classifications": [] }, "28": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "30": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "31": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "32": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "33": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "34": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "36": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "37": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "38": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "39": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "40": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "41": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "42": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "43": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "44": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "45": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "1": { "objects": { "cltd97cah01u814ys0k41cttu": { "feature_id": "cltd97cah01u814ys0k41cttu", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u814ys0k41cttu/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/1", "color_rgb": [ 169, 248, 152 ] } }, "cltd97cah01u914ysy7t58lvy": { "feature_id": "cltd97cah01u914ysy7t58lvy", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u914ysy7t58lvy/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/1", "color_rgb": [ 227, 135, 126 ] } } }, "classifications": [] } }, "segments": { "cltd97cag01td14ysxw2n8xkz": [ [ 29, 35 ] ], "cltd97cag01te14ys1jdf1fku": [ [ 29, 35 ] ], "cltd97cag01tg14ys171a3qqr": [ [ 9, 15 ] ], "cltd97cag01th14ys6fyyxyk9": [ [ 13, 19 ] ], "cltd97cag01ti14ys8wedf89d": [ [ 10, 13 ] ], "cltd97cag01tm14ys8tm4x7yl": [ [ 22, 27 ] ], "cltd97cag01tn14yspqge5yph": [ [ 22, 27 ] ], "cltd97cag01to14yslakirofl": [ [ 17, 17 ] ], "cltd97cag01tp14ysz3my848t": [ [ 5, 20 ], [ 24, 45 ] ] }, "key_frame_feature_map": { "cltd97cag01tc14yspm0lev2d": [ 29, 35 ], "cltd97cag01tp14ysz3my848t": [ 24, 5, 20, 45 ], "cltd97cag01tf14ysho6tfl7j": [ 9, 15 ], "cltd97cag01th14ys6fyyxyk9": [ 13, 19 ], "cltd97cag01ti14ys8wedf89d": [ 13, 10 ], "cltd97cag01to14yslakirofl": [ 17 ], "cltd97cag01tm14ys8tm4x7yl": [ 22, 27 ], "cltd97cag01tn14yspqge5yph": [ 22, 27 ], "cltd97cag01u514ys2bfg9vvo": [ 24, 20 ], "cltd97cah01u614ysvntxcyub": [ 24, 20 ], "cltd97cah01u714ysbqbvqs6b": [ 24, 20 ], "cltd97cah01ua14ysr6u1sh7w": [ 26 ], "cltd97cah01u814ys0k41cttu": [ 1 ], "cltd97cah01u914ysy7t58lvy": [ 1 ] }, "classifications": [ { "feature_id": "cltd97cag01tq14ysb4m3nyjo", "feature_schema_id": "cltd977nq039107x5dsml2knm", "name": "checklist_class_global", "value": "checklist_class_global", "checklist_answers": [ { "feature_id": "cltd97cag01tr14ysozjbx0c6", "feature_schema_id": "cltd977nq039207x5ei1605s1", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01ts14ys4vvxihnw", "feature_schema_id": "cltd977nq039407x50qs3g61l", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "cltd97cag01tt14yscmqh2izy", "feature_schema_id": "cltd977nq038v07x57qz18lnj", "name": "radio_class_global", "value": "radio_class_global", "radio_answer": { "feature_id": "cltd97cag01tu14ysq046ad6p", "feature_schema_id": "cltd977nq038w07x5bxw3exp9", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "cltd97cag01tv14ys1y3u4jre", "feature_schema_id": "cltd977nq038n07x52t8s0j1f", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "cltd97cag01tw14ysieenbrzy", "feature_schema_id": "cltd977nq038o07x528j39vc4", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "cltd97cag01tx14ysaqs15mjh", "feature_schema_id": "cltd977nq038p07x5bfuf7v56", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cltd97cag01ty14yshujgka3h", "feature_schema_id": "cltd977nq038q07x52m1h32mj", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "cltd97cag01tz14ys73p2w3oa", "feature_schema_id": "cltd977nq038f07x5bgrc2fm6", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "cltd97cag01u014ysho9dljue", "feature_schema_id": "cltd977nq038g07x50ewqbbi3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "cltd97cag01u114ysktcruoro", "feature_schema_id": "cltd977nq038h07x59zx07wfe", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cltd97cag01u214ysxi71u4qi", "feature_schema_id": "cltd977nq038i07x5et0717w6", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "cltd97cag01u314ysx1h72f0h", "feature_schema_id": "cltd977nq039707x535ufeo39", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ] } } ], "project_details": { "ontology_id": "cltd977mx037i07x52amua8iw", "task_name": "Done", "batch_id": "1026dd60-da52-11ee-9774-cfce344359c8", "batch_name": "first-batch-video-demo2", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "cldtd2nai107407y5979xbu97", "global_key": "3548cfa1-1a42-40f9-a246-2aa7c696dcbe", "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "details": { "dataset_id": "cldtd2mox11dz07wh3gkk9awx", "dataset_name": "video_prediction_demo", "created_at": "2023-02-06T22:04:54.000+00:00", "updated_at": "2023-02-06T22:04:54.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "height": 1080, "width": 1920, "mime_type": "video/mp4", "frame_rate": 10, "frame_count": 100 }, "attachments": [], "metadata_fields": [], "experiments": { "a088408e-81af-0152-f2ca-0b870c7a2bfa": { "name": "video_model_run_8d7dbe3d-30e0-4c9c-becd-26afb2386bc9", "runs": { "a088408e-ce36-09c1-cbd9-d43c677f7773": { "name": "iteration 1", "run_data_row_id": "999a247e-0b57-415e-af7e-d3a87cc6559b", "labels": [ { "label_kind": "Video", "version": "1.0.0", "id": "cldteelzh08us1331q4pzj2ke", "annotations": { "frames": { "17": { "objects": { "12a89fb9-73e4-4c5e-93c1-a8727e51c122": { "feature_id": "12a89fb9-73e4-4c5e-93c1-a8727e51c122", "feature_schema_id": "cldtd2nmm107i07y53ii0d6ly", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } } }, "classifications": [] }, "5": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 }, { "x": 190.0, "y": 220.0 } ] }, "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": { "feature_id": "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] }, "12": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 280.0 }, { "x": 300.0, "y": 380.0 }, { "x": 400.0, "y": 460.0 } ] } }, "classifications": [] }, "20": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 180.0 }, { "x": 100.0, "y": 200.0 }, { "x": 200.0, "y": 260.0 } ] } }, "classifications": [] }, "24": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 300.0, "y": 310.0 }, { "x": 330.0, "y": 430.0 } ] } }, "classifications": [] }, "45": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 600.0, "y": 810.0 }, { "x": 900.0, "y": 930.0 } ] } }, "classifications": [] }, "1": { "objects": { "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": { "feature_id": "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] } }, "segments": { "12a89fb9-73e4-4c5e-93c1-a8727e51c122": [ [ 17, 17 ] ], "878b4842-d57b-43e4-8327-a0c5295f2a84": [ [ 5, 5 ], [ 12, 12 ], [ 20, 20 ], [ 24, 24 ], [ 45, 45 ] ], "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": [ [ 5, 5 ], [ 1, 1 ] ] }, "key_frame_feature_map": { "12a89fb9-73e4-4c5e-93c1-a8727e51c122": [ 17 ], "878b4842-d57b-43e4-8327-a0c5295f2a84": [ 5, 12, 20, 24, 45 ], "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": [ 5, 1 ] }, "classifications": [ { "feature_id": "3644a35a-8aaa-4905-a2d2-9abac1f3dacb", "feature_schema_id": "cldtd2nmm107s07y57ddy5wnn", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "86805980-7632-4dd0-9669-299730927326", "feature_schema_id": "cldtd2nmm107v07y5cmixhywh", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "c5bfb5c1-aa9a-4a6a-b671-be7c20df4b19", "feature_schema_id": "cldtd2nmm107t07y5cqdw94z7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "3d302a82-cc91-41d9-8c69-0f1e2e66ea71", "feature_schema_id": "cldtd2nmm107m07y5h45sg6av", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "dab3bcfa-6c44-4f28-945b-1bc324edfab1", "feature_schema_id": "cldtd2nmm107p07y542ff3hvd", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "6dede674-18a6-4b95-abdd-13b3713f994a", "feature_schema_id": "cldtd2nmm108607y5ghou4ma6", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "9f748713-ffb5-44db-a16e-396676dab6bc", "feature_schema_id": "cldtd2nmm108707y50shn5996", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ca1f2173-38c6-4c23-96ba-8a3feb54e2c2", "feature_schema_id": "cldtd2nmm108807y52ps9d5m4", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "2181d943-8a6a-4aef-aae0-4c28146e9008", "feature_schema_id": "cldtd2nmm108907y5269ua7bz", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "b72dd664-93fc-46d1-8bd8-8dfe1fd2d7ef", "feature_schema_id": "cldtd2nmm107y07y5fvhkb6c7", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "5fc17086-601e-42e5-a5b1-a28eb96841ab", "feature_schema_id": "cldtd2nmm107z07y59j8n62ym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "a74e9958-345f-4b42-925d-9d5938011cf6", "feature_schema_id": "cldtd2nmm108007y5gs1y5eqv", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "734a2cf1-571d-4855-869e-d92ff20a4cb4", "feature_schema_id": "cldtd2nmm108107y53dvreuv2", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } } ] } } ], "predictions": [ { "label_kind": "Video", "version": "1.0.0", "id": "cldteelzh08us1331q4pzj2ke", "annotations": { "frames": { "5": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 }, { "x": 190.0, "y": 220.0 } ] }, "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": { "feature_id": "a505f426-5a2e-4dea-b4fe-ab913e1d2a99", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] }, "12": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 280.0 }, { "x": 300.0, "y": 380.0 }, { "x": 400.0, "y": 460.0 } ] } }, "classifications": [] }, "20": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 180.0 }, { "x": 100.0, "y": 200.0 }, { "x": 200.0, "y": 260.0 } ] } }, "classifications": [] }, "24": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 300.0, "y": 310.0 }, { "x": 330.0, "y": 430.0 } ] } }, "classifications": [] }, "45": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 600.0, "y": 810.0 }, { "x": 900.0, "y": 930.0 } ] } }, "classifications": [] }, "17": { "objects": { "89b4636f-08cc-4fa2-832b-18bcda636722": { "feature_id": "89b4636f-08cc-4fa2-832b-18bcda636722", "feature_schema_id": "cldtd2nmm107i07y53ii0d6ly", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } } }, "classifications": [] }, "1": { "objects": { "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": { "feature_id": "a505f426-5a2e-4dea-b4fe-ab913e1d2a99", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] } }, "segments": { "1306f719-d588-4154-8335-792347bfdee2": [ [ 5, 5 ], [ 12, 12 ], [ 20, 20 ], [ 24, 24 ], [ 45, 45 ] ], "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": [ [ 5, 5 ], [ 1, 1 ] ], "89b4636f-08cc-4fa2-832b-18bcda636722": [ [ 17, 17 ] ] }, "key_frame_feature_map": { "1306f719-d588-4154-8335-792347bfdee2": [ 5, 12, 20, 24, 45 ], "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": [ 5, 1 ], "89b4636f-08cc-4fa2-832b-18bcda636722": [ 17 ] }, "classifications": [ { "feature_id": "5deccc93-922a-4fcd-bf70-0bd90249ed65", "feature_schema_id": "cldtd2nmm108607y5ghou4ma6", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "3d9f3952-c303-42ed-91ba-b112927029c9", "feature_schema_id": "cldtd2nmm108707y50shn5996", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "fe4d0f5f-99e6-4abd-9401-b0ad04fe13ed", "feature_schema_id": "cldtd2nmm108807y52ps9d5m4", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "05fca900-787a-4774-a011-0b6931d9a012", "feature_schema_id": "cldtd2nmm108907y5269ua7bz", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "7a92c014-48e1-4367-8ff9-4ec146d9968e", "feature_schema_id": "cldtd2nmm107y07y5fvhkb6c7", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "9f35c500-be1d-4693-ae9a-71507931d558", "feature_schema_id": "cldtd2nmm107z07y59j8n62ym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "d1269040-bf2c-4e8c-8fa6-58c3d6a7e343", "feature_schema_id": "cldtd2nmm108007y5gs1y5eqv", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "2fecd9bc-98ca-49bd-8db4-cb9d1be6a63d", "feature_schema_id": "cldtd2nmm108107y53dvreuv2", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "95085369-6add-4ce1-9641-d6b690490eb3", "feature_schema_id": "cldtd2nmm107s07y57ddy5wnn", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "53a98fa1-19ea-4541-b42f-8df7f7e3b2fc", "feature_schema_id": "cldtd2nmm107v07y5cmixhywh", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "6383f391-7ca6-4d99-a855-cdc2e2afa811", "feature_schema_id": "cldtd2nmm107t07y5cqdw94z7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "af2f95fe-6ce6-4e3a-83a8-fd11b1ae0cdc", "feature_schema_id": "cldtd2nmm107m07y5h45sg6av", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "71d910d5-df6b-4def-b68e-caced48dd25c", "feature_schema_id": "cldtd2nmm107p07y542ff3hvd", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } } ] } } ] } } } }, "models": {}, "embeddings": [ { "id": "c400000000000000000000000", "name": "Video Embedding V2 (Google MultiModal)", "dimensions": 1408, "is_custom": false, "values": [ { "value": [ -0.00489852577, 0.0287129097 ... ] } ] } ] } ``` ## Text ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh5lsju0bd90763h2o22vvq", "global_key": "lorem-ipsum.txt", "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "details": { "dataset_id": "clfh5lpul063507x4a16r5j6x", "dataset_name": "text_annotation_import_demo_dataset", "created_at": "2023-03-20T18:22:00.000+00:00", "updated_at": "2023-03-20T18:22:00.000+00:00", "last_activity_at": "2024-04-10T14:43:10.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "text/plain" }, "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", "id": "clfh5lsjv0bdd07638apx7bf5" } ], "metadata_fields": [], "embeddings": [ { "id": "c300000000000000000000000", "name": "Text Embedding V2 (All-MPNet-base-v2)", "dimensions": 768, "is_custom": false, "values": [ { "value": [ 0.04099537059664726, 0.03790362924337387, -0.013698477298021317, 0.038478486239910126, -0.01930154114961624, 0.05670716613531113, 0.02421298250555992 ... ], "projects": { "clutx6nli00ks07x6efh6e1pw": { "name": "Text Annotation Import Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clutx70xi0oob148pgr7sz5v0", "label_details": { "created_at": "2024-04-10T14:43:11.000+00:00", "updated_at": "2024-04-10T14:43:11.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2024-04-10T14:43:10.580+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "clutx70x20onv148pb5ah49lb", "feature_schema_id": "clutx6n1g00r707y110ruh99w", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20, "token": "um dolor si" } } ], "classifications": [ { "feature_id": "clutx70x20onw148p7mlgpq90", "feature_schema_id": "clutx6n1h00r907y16s49do9e", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "clutx70x20onx148px2679qsp", "feature_schema_id": "clutx6n1h00ra07y100njafnj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "clutx70x20ony148p6gxk7ec8", "feature_schema_id": "clutx6n1h00rt07y16xe27hhv", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20onz148pjjl5r8m5", "feature_schema_id": "clutx6n1h00ru07y1hgvd3ekt", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clutx70x20oo0148p3w6es43a", "feature_schema_id": "clutx6n1h00rw07y16t1v8tvi", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "clutx70x20oo1148ps1lpz6dl", "feature_schema_id": "clutx6n1h00ry07y1fkbk0pde", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] }, { "feature_id": "clutx70x20oo2148pz466o8ep", "feature_schema_id": "clutx6n1i00s107y18bywdwuh", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "clutx70x20oo3148poaesoyw7", "feature_schema_id": "clutx6n1h00rl07y12xox0uqk", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20oo4148pgxhy7xr3", "feature_schema_id": "clutx6n1h00rm07y15qdj4mms", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "clutx70x20oo5148plvcm1yhy", "feature_schema_id": "clutx6n1h00rn07y13xi0e1au", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20oo6148pcqjahziw", "feature_schema_id": "clutx6n1h00ro07y164ddfda2", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "clutx70x20oo7148pkei2ymj3", "feature_schema_id": "clutx6n1h00rd07y188n4hd08", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "clutx70x20oo8148pjiiobfl5", "feature_schema_id": "clutx6n1h00re07y1b7v1dd8z", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "clutx70x20oo9148px2xcuanp", "feature_schema_id": "clutx6n1h00rf07y18f8f45nu", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clutx70x20ooa148pqhs0hmot", "feature_schema_id": "clutx6n1h00rg07y1fkkjgr7o", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clutx6n0k00r607y1a0a9bkzj", "task_name": "Done", "batch_id": "9f1c1db0-f748-11ee-b74f-f180a0c567c8", "batch_name": "first-batch-text-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh5lsju0bd90763h2o22vvq", "global_key": "lorem-ipsum.txt", "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "details": { "dataset_id": "clfh5lpul063507x4a16r5j6x", "dataset_name": "text_annotation_import_demo_dataset", "created_at": "2023-03-20T18:22:00.000+00:00", "updated_at": "2023-03-20T18:22:00.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "text/plain" }, "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", "id": "clfh5lsjv0bdd07638apx7bf5" } ], "metadata_fields": [], "experiments": { "a349da10-5d85-0c82-293d-8165aa611e7c": { "name": "text_model_run_8ad83b1f-c9c6-4ea4-83ce-a287393bb917", "runs": { "a349da10-aa0d-0879-711b-11f0cc035b65": { "name": "iteration 1", "run_data_row_id": "e1898158-c2a9-42fb-8c64-2c81beadafed", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clr16pmfw0g4o140zkwys3f7u", "annotations": { "objects": [ { "feature_id": "6d651dde-6567-0669-83e6-7a6677a5acb2", "feature_schema_id": "clr16p71n018r07tuawb18jt6", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20 } } ], "classifications": [ { "feature_id": "08a38f86-5cd1-0feb-ae98-5731b2d2d3bf", "feature_schema_id": "clr16p71p019f07tucv4g1cmm", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "3b97e87d-b196-0c21-a236-8b64f3f08086", "feature_schema_id": "clr16p71p019g07tu9sca1dde", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "8301a2fe-e898-044f-bc8b-a75e48b16f45", "feature_schema_id": "clr16p71p019h07tu5j9o7b4o", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "baea7ccd-57a6-0c4f-bc66-1ddc2a8aa2d9", "feature_schema_id": "clr16p71p019i07tu50tq966e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "60fd8cc0-b5a6-013b-b02b-d91adafb45c1", "feature_schema_id": "clr16p71o018t07tubkih92ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "f0211ed1-4139-08ee-8c35-ea2bcad92b79", "feature_schema_id": "clr16p71o018u07tu8bwc76w1", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "73beac75-7e6c-0805-a148-2f6b7465f468", "feature_schema_id": "clr16p71p019d07tufulw888j", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "7a87c79e-6213-032c-859b-547e039ceff7", "feature_schema_id": "clr16p71o018x07tuf7sj7c0x", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "3aead946-dafd-0f41-8609-826d0d702d68", "feature_schema_id": "clr16p71o018y07tu9oevflh8", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "ab632136-83c1-0f3e-a4af-87da69b86624", "feature_schema_id": "clr16p71o018z07tuas953oef", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "2823619a-03bf-0b27-a12e-69f869d8e334", "feature_schema_id": "clr16p71o019007tu8htocx3x", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "bd11b44d-3c8d-089d-9531-5177047736b7", "feature_schema_id": "clr16p71p019507tu0x8d0vu4", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "360c8a23-3a74-0e75-9553-286b1b152100", "feature_schema_id": "clr16p71p019a07tuetmgfe7c", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "4e12f117-9f03-0a51-8b5d-721b88ce4be0", "feature_schema_id": "clr16p71p019607tu4hkkbhxv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "501d328c-c0ea-0142-8809-ecd1b19961eb", "feature_schema_id": "clr16p71p019807tu3ya04d5x", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clr16pmfw0g4o140zkwys3f7u", "annotations": { "objects": [ { "feature_id": "4bafa398-69d8-4bf8-a3f0-834869ab3e5f", "feature_schema_id": "clr16p71n018r07tuawb18jt6", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20 } } ], "classifications": [ { "feature_id": "31737a80-4a5c-4c40-8362-8d59ae6c2047", "feature_schema_id": "clr16p71o018x07tuf7sj7c0x", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "a00eca18-fb50-4130-a33b-d505ba8e3d04", "feature_schema_id": "clr16p71o018y07tu9oevflh8", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "94b4ee14-2837-4fb5-b097-4e59cb92b07f", "feature_schema_id": "clr16p71o018z07tuas953oef", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "a5863817-4c81-424a-8c4c-4cdec05508bf", "feature_schema_id": "clr16p71o019007tu8htocx3x", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "741288f5-76ab-4cdf-9277-0f97f0e426fd", "feature_schema_id": "clr16p71o018t07tubkih92ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "64065994-7568-46e1-8bfc-bd96d20384cf", "feature_schema_id": "clr16p71o018u07tu8bwc76w1", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "a66cc378-aa78-4250-bde1-28da1310bedb", "feature_schema_id": "clr16p71p019507tu0x8d0vu4", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "6db15123-a2dd-4722-8253-32a0c0236850", "feature_schema_id": "clr16p71p019607tu4hkkbhxv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "8eb5629e-8ae1-44f1-b369-491c5c61ea02", "feature_schema_id": "clr16p71p019a07tuetmgfe7c", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "fdb2574a-fea7-4127-9ee1-b112126f117a", "feature_schema_id": "clr16p71p019807tu3ya04d5x", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "a8386f96-ae13-45c0-bb35-033380f86dc5", "feature_schema_id": "clr16p71p019d07tufulw888j", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "db0150e9-ecb5-4bad-8350-c82446244657", "feature_schema_id": "clr16p71p019f07tucv4g1cmm", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "84f14b81-301f-41fd-a842-6d48594a40fc", "feature_schema_id": "clr16p71p019g07tu9sca1dde", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "8367c018-cba4-40ff-85e5-c06b137773c3", "feature_schema_id": "clr16p71p019h07tu5j9o7b4o", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "c1e2da03-c23d-4ffa-a25c-e6b6c13a1dae", "feature_schema_id": "clr16p71p019i07tu50tq966e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ] } } } }, "models": {}, "embeddings": [ { "id": "c300000000000000000000000", "name": "Text Embedding V2 (All-MPNet-base-v2)", "dimensions": 768, "is_custom": false, "values": [ { "value": [ 0.04099537059664726, 0.03790362924337387, -0.013698477298021317, 0.038478486239910126, -0.01930154114961624, 0.05670716613531113, 0.02421298250555992 ... ] } ``` ## Geospatial (tiled imagery) ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "cldnf9zmt0d7n075ohgnvecul", "row_data": "{\"tileLayerUrl\":\"https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw\",\"bounds\":[[-122.31764674186705,37.87276155898985],[-122.31635199317932,37.87398109727749]],\"minZoom\":17,\"maxZoom\":23,\"maxNativeZoom\":null,\"epsg\":\"EPSG4326\",\"tileSize\":256,\"alternativeLayers\":[],\"version\":2}", "details": { "dataset_id": "cldnf9yvd041f070d33t94f41", "dataset_name": "geotiff_noaa.json", "created_at": "2023-02-02", "updated_at": "2023-02-02", "created_by": "kyang@labelbox.com" } }, "media_attributes": { "mime_type": "application/x-tms-geo" }, "attachments": [], "metadata_fields": [], "projects": { "cldnfa25103kd081ph3edg7xp": { "project_name": "geospatial_project_demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cldnfc62h029b13ydq6bgvyak", "label_details": { "created_at": "2023-02-02T18:21:40+00:00", "updated_at": "2023-02-02T18:21:40+00:00", "created_by": "kyang@labelbox.com", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "cldnfc61v028v13ydwgha3a1d", "name": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -122.31741, 37.873557 ] } }, { "feature_id": "cldnfc61v028w13ydnbm7vl2q", "name": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -122.317578, 37.873963 ], [ -122.316398, 37.873967 ], [ -122.31639, 37.872779 ] ] } }, { "feature_id": "cldnfc61v028x13yd5swk0drd", "name": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316918, 37.87329 ], [ -122.317102, 37.873043 ], [ -122.316801, 37.873036 ], [ -122.316918, 37.87329 ] ] ] } }, { "feature_id": "cldnfc61v028y13yd41qozic7", "name": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317345, 37.873713 ], [ -122.317345, 37.873859 ], [ -122.31673, 37.873859 ], [ -122.31673, 37.873713 ], [ -122.317345, 37.873713 ] ] ] } }, { "feature_id": "cldnfc61v029113ydvc788rx9", "name": "bbox_checklist_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "cldnfc61v029213yd344u0cyk", "name": "checklist_class_name", "checklist_answers": [ { "feature_id": "cldnfc61v029313yda92qm39k", "name": "first_checklist_answer", "classifications": [] } ] } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317113, 37.873402 ], [ -122.317113, 37.873608 ], [ -122.316655, 37.873608 ], [ -122.316655, 37.873402 ], [ -122.317113, 37.873402 ] ] ] } }, { "feature_id": "cldnfc61v029413yd5lgevg2c", "name": "bbox_text_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "cldnfc61v029513ydmluqvqyt", "name": "free_text_geo", "text_answer": { "content": "sample text" } } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317508, 37.873182 ], [ -122.317508, 37.87338 ], [ -122.3171, 37.87338 ], [ -122.3171, 37.873182 ], [ -122.317508, 37.873182 ] ] ] } }, { "feature_id": "cldnfc61v029a13ydmog1dhzw", "name": "polygon_geo_2", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316362, 37.872966 ], [ -122.316362, 37.872948 ], [ -122.316356, 37.872948 ], [ -122.316356, 37.872966 ], [ -122.316362, 37.872966 ] ] ] } } ], "classifications": [ { "feature_id": "cldnfc61v028z13ydy0xcffbj", "name": "radio_question_geo", "radio_answer": { "feature_id": "cldnfc61v029013ydj55ek20y", "name": "first_radio_answer", "classifications": [] } }, { "feature_id": "cldnfc61v029613ydu76vcgbt", "name": "checklist_question_geo", "checklist_answers": [ { "feature_id": "cldnfc61v029713yd5x16lyg3", "name": "first_checklist_answer", "classifications": [] }, { "feature_id": "cldnfc61v029813ydcsx4mc2t", "name": "second_checklist_answer", "classifications": [] }, { "feature_id": "cldnfc61v029913yd2smfzpft", "name": "third_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "project_details": { "ontology_id": "cl8emhd3e7bll070k6gbicl97", "task_id": "fdc4b3d2-e0fe-41dc-bdef-0dd556870a20", "task_name": "Initial review task", "batch_id": "cc4e7070-f20e-11ed-9dd9-639c7e9f70ba", "batch_name": "geotiff_noaa.json-cl8emco177d5g07y8e06j4acp", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 3, "workflow_history": [ { "action": "Move", "created_at": "2023-05-14T04:21:29.062+00:00", "created_by": "ptancre+10@labelbox.com", "previous_task_name": "Initial labeling task", "previous_task_id": "0dde450a-93ba-0b77-b97e-de8f7f479cee", "next_task_name": "Initial review task", "next_task_id": "fdc4b3d2-e0fe-41dc-bdef-0dd556870a20" }, { "action": "Move", "created_at": "2023-05-14T04:21:29.044+00:00", "created_by": "ptancre+10@labelbox.com", "next_task_name": "Initial labeling task", "next_task_id": "0dde450a-93ba-0b77-b97e-de8f7f479cee" } ] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh28su81agn07623rql6j44", "global_key": "slippy_maps", "row_data": "{\"tileLayerUrl\":\"https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw\",\"bounds\":[[-122.31764674186705,37.87276155898985],[-122.31635199317932,37.87398109727749]],\"minZoom\":17,\"maxZoom\":23,\"maxNativeZoom\":null,\"epsg\":\"EPSG4326\",\"tileSize\":256,\"alternativeLayers\":[],\"version\":2}", "details": { "dataset_id": "clfh28rib089g07134eqx03ew", "dataset_name": "geo_demo_dataset", "created_at": "2023-03-20T16:47:55.000+00:00", "updated_at": "2023-03-20T16:47:55.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "application/x-tms-geo" }, "attachments": [], "metadata_fields": [], "experiments": { "a15bda50-b901-02e0-07d6-e842d13b3f47": { "name": "geospatial_model_run_1f54f23a-0359-4f18-b3ec-808a6181a4fd", "runs": { "a15bda50-fd4e-06ff-8f9b-762516cf58c8": { "name": "iteration 1", "run_data_row_id": "247e2c89-0e82-4662-a757-5f7cfa07a8fd", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs22mh25b4d142bagekzwlv", "annotations": { "objects": [ { "feature_id": "12527b8c-e5c5-4cd5-9266-c6a1d90ef14c", "feature_schema_id": "clhs21cpy0c6t07y1fof39vn7", "name": "point_geo", "value": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -122.31741, 37.873557 ] } }, { "feature_id": "185de2b0-fca0-48fc-9f3c-982e1487ce13", "feature_schema_id": "clhs21cpy0c7307y1dg174k0l", "name": "bbox_checklist_geo", "value": "bbox_checklist_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "d2809ef9-01ae-4ad7-a4d0-1fee6938e186", "feature_schema_id": "clhs21cpy0c7407y1gl9g9ver", "name": "checklist_class_name", "value": "checklist_class_name", "checklist_answers": [ { "feature_id": "6cbc5158-ac53-445a-a6c1-8035b7a4cac1", "feature_schema_id": "clhs21cpy0c7507y16idc9qjy", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317113, 37.873608 ], [ -122.317113, 37.873402 ], [ -122.316655, 37.873402 ], [ -122.316655, 37.873608 ], [ -122.317113, 37.873608 ] ] ] } }, { "feature_id": "78024089-436d-4a0e-aea9-f458ee66cb42", "feature_schema_id": "clhs21cpy0c6v07y19w4zb3b7", "name": "polyline_geo", "value": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -122.317578, 37.873963 ], [ -122.316398, 37.873967 ], [ -122.31639, 37.872779 ] ] } }, { "feature_id": "97110d09-559b-46c9-abb4-7e5d5f578b3e", "feature_schema_id": "clhs21cpy0c6x07y18t2hgvdp", "name": "polygon_geo", "value": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316918, 37.87329 ], [ -122.317102, 37.873043 ], [ -122.316801, 37.873036 ], [ -122.316918, 37.87329 ] ] ] } }, { "feature_id": "cbbd8796-d6ff-4238-9347-bb12b239ac74", "feature_schema_id": "clhs21cpy0c7907y19etw75vg", "name": "bbox_text_geo", "value": "bbox_text_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "0492b8b4-217b-4fdb-bc93-1b0a30f5aa8b", "feature_schema_id": "clhs21cpz0c7a07y16uczf4dx", "name": "free_text_geo", "value": "free_text_geo", "text_answer": { "content": "sample text" } } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317508, 37.87338 ], [ -122.317508, 37.873182 ], [ -122.3171, 37.873182 ], [ -122.3171, 37.87338 ], [ -122.317508, 37.87338 ] ] ] } }, { "feature_id": "d80e2563-002a-48b8-b6c6-50f7df1a4a8b", "feature_schema_id": "clhs21cpy0c7107y1b07n6ef9", "name": "bbox_geo", "value": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317345, 37.873859 ], [ -122.317345, 37.873713 ], [ -122.31673, 37.873713 ], [ -122.31673, 37.873859 ], [ -122.317345, 37.873859 ] ] ] } }, { "feature_id": "f6abee6b-8130-4033-a223-be428dd59c0a", "feature_schema_id": "clhs21cpy0c6x07y18t2hgvdp", "name": "polygon_geo", "value": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.31703, 37.873978 ], [ -122.317024, 37.873935 ], [ -122.316989, 37.873899 ], [ -122.316979, 37.873859 ], [ -122.316958, 37.873856 ], [ -122.316955, 37.873816 ], [ -122.31692, 37.873779 ], [ -122.31691, 37.873734 ], [ -122.316886, 37.873697 ], [ -122.316855, 37.873685 ], [ -122.31682, 37.873611 ], [ -122.316765, 37.87355 ], [ -122.316758, 37.873517 ], [ -122.31671, 37.873489 ], [ -122.31671, 37.873452 ], [ -122.316679, 37.87344 ], [ -122.316634, 37.873355 ], [ -122.316603, 37.873348 ], [ -122.3166, 37.873321 ], [ -122.316545, 37.87329 ], [ -122.316528, 37.873199 ], [ -122.31649, 37.873183 ], [ -122.31649, 37.873162 ], [ -122.316417, 37.873092 ], [ -122.316411, 37.873055 ], [ -122.316397, 37.87304 ], [ -122.316356, 37.87304 ], [ -122.316356, 37.873981 ], [ -122.31703, 37.873978 ] ] ] } } ], "classifications": [ { "feature_id": "08c1417e-ac8f-4272-9f4f-8b523ae5e0d7", "feature_schema_id": "clhs21cpz0c7l07y160gh5xdw", "name": "radio_question_geo", "value": "radio_question_geo", "radio_answer": { "feature_id": "8c72625a-e663-4241-a2c2-8082804a59e3", "feature_schema_id": "clhs21cpz0c7m07y188gjbqbj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "a22dac3b-5c40-4f08-9723-d70e7bc4bff4", "feature_schema_id": "clhs21cpz0c7d07y14vnl7zpi", "name": "checklist_question_geo", "value": "checklist_question_geo", "checklist_answers": [ { "feature_id": "1c4a1950-6961-42bf-b134-50596f136164", "feature_schema_id": "clhs21cpz0c7e07y16k192u14", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "963cc214-b134-4b0e-80d5-8e63bcc000ae", "feature_schema_id": "clhs21cpz0c7g07y17yo0e2cn", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "bfa3e08b-5f2d-468e-aa7c-a6c2a29f9f4f", "feature_schema_id": "clhs21cpz0c7i07y1hytkftpk", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs22mh25b4d142bagekzwlv", "annotations": { "objects": [ { "feature_id": "21d3b49c-0e17-4eb1-9824-2b05a23386d5", "feature_schema_id": "clhs21cpy0c6v07y19w4zb3b7", "name": "polyline_geo", "value": "polyline_geo", "annotation_kind": "GeoPolyline", "classifications": [], "geojson": { "type": "LineString", "coordinates": [ [ -122.317578, 37.873963 ], [ -122.316398, 37.873967 ], [ -122.31639, 37.872779 ] ] } }, { "feature_id": "4bcd8a06-cdc9-4450-b59e-d6601d185b88", "feature_schema_id": "clhs21cpy0c6t07y1fof39vn7", "name": "point_geo", "value": "point_geo", "annotation_kind": "GeoPoint", "classifications": [], "geojson": { "type": "Point", "coordinates": [ -122.31741, 37.873557 ] } }, { "feature_id": "69d6cf96-1198-42e0-83a7-c75f0c945037", "feature_schema_id": "clhs21cpy0c7107y1b07n6ef9", "name": "bbox_geo", "value": "bbox_geo", "annotation_kind": "GeoBoundingBox", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317345, 37.873859 ], [ -122.317345, 37.873713 ], [ -122.31673, 37.873713 ], [ -122.31673, 37.873859 ], [ -122.317345, 37.873859 ] ] ] } }, { "feature_id": "9090fb5d-09f3-4a5f-9114-9b3c4ce6c909", "feature_schema_id": "clhs21cpy0c6x07y18t2hgvdp", "name": "polygon_geo", "value": "polygon_geo", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.316918, 37.87329 ], [ -122.317102, 37.873043 ], [ -122.316801, 37.873036 ], [ -122.316918, 37.87329 ] ] ] } }, { "feature_id": "9ef3b0dc-b085-4f64-9018-264db30f9018", "feature_schema_id": "clhs21cpy0c7307y1dg174k0l", "name": "bbox_checklist_geo", "value": "bbox_checklist_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "a77cd878-d15d-4628-ab08-9ecd192bbebc", "feature_schema_id": "clhs21cpy0c7407y1gl9g9ver", "name": "checklist_class_name", "value": "checklist_class_name", "checklist_answers": [ { "feature_id": "5a6a23f8-39c7-409c-953f-732d3cf4cbec", "feature_schema_id": "clhs21cpy0c7507y16idc9qjy", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317113, 37.873608 ], [ -122.317113, 37.873402 ], [ -122.316655, 37.873402 ], [ -122.316655, 37.873608 ], [ -122.317113, 37.873608 ] ] ] } }, { "feature_id": "f7df220a-27c0-4fc1-b67f-094b28efec3d", "feature_schema_id": "clhs21cpy0c6z07y1a99ubplb", "name": "polygon_geo_2", "value": "polygon_geo_2", "annotation_kind": "GeoPolygon", "classifications": [], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.31703, 37.873978 ], [ -122.317024, 37.873935 ], [ -122.316989, 37.873899 ], [ -122.316979, 37.873859 ], [ -122.316958, 37.873856 ], [ -122.316955, 37.873816 ], [ -122.31692, 37.873779 ], [ -122.31691, 37.873734 ], [ -122.316886, 37.873697 ], [ -122.316855, 37.873685 ], [ -122.31682, 37.873611 ], [ -122.316765, 37.87355 ], [ -122.316758, 37.873517 ], [ -122.31671, 37.873489 ], [ -122.31671, 37.873452 ], [ -122.316679, 37.87344 ], [ -122.316634, 37.873355 ], [ -122.316603, 37.873348 ], [ -122.3166, 37.873321 ], [ -122.316545, 37.87329 ], [ -122.316528, 37.873199 ], [ -122.31649, 37.873183 ], [ -122.31649, 37.873162 ], [ -122.316417, 37.873092 ], [ -122.316411, 37.873055 ], [ -122.316397, 37.87304 ], [ -122.316356, 37.87304 ], [ -122.316356, 37.873981 ], [ -122.31703, 37.873978 ] ] ] } }, { "feature_id": "fbc72808-7f01-4cae-b53d-e868ac2e6b30", "feature_schema_id": "clhs21cpy0c7907y19etw75vg", "name": "bbox_text_geo", "value": "bbox_text_geo", "annotation_kind": "GeoBoundingBox", "classifications": [ { "feature_id": "cd12abb2-2c30-48be-b4ef-a8e3b649e998", "feature_schema_id": "clhs21cpz0c7a07y16uczf4dx", "name": "free_text_geo", "value": "free_text_geo", "text_answer": { "content": "sample text" } } ], "geojson": { "type": "Polygon", "coordinates": [ [ [ -122.317508, 37.87338 ], [ -122.317508, 37.873182 ], [ -122.3171, 37.873182 ], [ -122.3171, 37.87338 ], [ -122.317508, 37.87338 ] ] ] } } ], "classifications": [ { "feature_id": "413f0623-6bb6-4631-9c2c-dd95cd01ff0b", "feature_schema_id": "clhs21cpz0c7d07y14vnl7zpi", "name": "checklist_question_geo", "value": "checklist_question_geo", "checklist_answers": [ { "feature_id": "27fe9828-16fc-446d-8ff9-563d1bda1df9", "feature_schema_id": "clhs21cpz0c7e07y16k192u14", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "8b97530e-96ce-4ce4-8232-e27c8b9bde4f", "feature_schema_id": "clhs21cpz0c7g07y17yo0e2cn", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "c8c5c1d3-c805-4cde-9522-254d390ca880", "feature_schema_id": "clhs21cpz0c7i07y1hytkftpk", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] }, { "feature_id": "dd36f2ec-91be-4bf6-9f6e-7531598628c0", "feature_schema_id": "clhs21cpz0c7l07y160gh5xdw", "name": "radio_question_geo", "value": "radio_question_geo", "radio_answer": { "feature_id": "06a746a8-78c0-4bb5-9f71-65e8303155b5", "feature_schema_id": "clhs21cpz0c7m07y188gjbqbj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ], "relationships": [] } } ] } } } }, "models": {} } ``` ## Document ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "cluu0c5g13a9s0735cjhi0hb0", "global_key": "0801.3483_doc.pdf", "row_data": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "details": { "dataset_id": "cluu0c389004l0723jubmv4xg", "dataset_name": "pdf_demo_dataset", "created_at": "2024-04-10T16:11:08.609+00:00", "updated_at": "2024-04-10T16:11:17.957+00:00", "last_activity_at": "2024-04-10T16:17:17.000+00:00", "created_by": "aovalle@labelbox.com" } }, "media_attributes": { "mime_type": "application/pdf", "text_layer_url": "https://storage.labelbox.com/cl3ahv73w1891087qbwzs3edd%2F0bh0ihjc5370s9a31g5c0uulc-text-layer.json?Expires=1712852458451&KeyName=labelbox-assets-key-3&Signature=5r7Ky5JCM0qbvvkCCpI_1sIw2cQ" }, "attachments": [], "metadata_fields": [], "projects": { "cluu0cl1901aa07zd6t2p7315": { "name": "PDF_annotation_demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu0jznq01zg07wc0lu192t5", "label_details": { "created_at": "2024-04-10T16:17:17.000+00:00", "updated_at": "2024-04-10T16:17:17.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2024-04-10T16:17:17.277+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 1, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "cluu0k04w05hk07h76lv05sb0", "feature_schema_id": "cluu0ckta024507udcols6x8y", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 1, "bounding_box": { "top": 66.251, "left": 96.424, "height": 80.68099999999998, "width": 82.65 } }, { "feature_id": "cluu0k04w05hl07h79bg9hhu0", "feature_schema_id": "cluu0cktb024h07ud57ra95o5", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "DocumentBoundingBox", "classifications": [ { "feature_id": "cluu0k04y05i207h7934rbph0", "feature_schema_id": "cluu0cktb024i07udefqrfcf1", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cluu0k04y05i607h76nvj24ls", "feature_schema_id": "cluu0cktb024j07ud89kieukk", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [ { "feature_id": "cluu0k04y05i807h75hpn55pv", "feature_schema_id": "cluu0cktb024k07udf0jw3423", "name": "second_sub_radio_question", "value": "second_sub_radio_question", "radio_answer": { "feature_id": "cluu0k04y05ia07h74p8mgt8q", "feature_schema_id": "cluu0cktb024l07ud7kw1gowr", "name": "second_sub_radio_answer", "value": "second_sub_radio_answer", "classifications": [] } } ] } } ], "page_number": 1, "bounding_box": { "top": 226.757, "left": 317.271, "height": 194.22899999999998, "width": 249.38600000000002 } }, { "feature_id": "cluu0k04w05ho07h73jx24ol7", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "1f54807e-b612-4f4a-9ca5-ee302ee9c659", "page_number": 1, "tokens": [ "839967fa-89a0-4ad1-896e-d25e5f4cb8e5", "9853c27e-30e0-45e0-b461-c28da6573b48", "e24f45c6-1efc-440f-a4dd-bb3eb522134f", "3339e940-3f73-4b56-b695-474808dad1e5", "a6f79ed8-fc20-4870-b9d1-aee3e92943bc", "07ac4d2e-68bb-4c25-bcc0-b03a697c4138", "7469a5c7-74f8-43c4-b1fb-1c89105a5c66", "eff97919-2529-4b67-9640-20301d45a2d4" ], "text": "Organic charge transfer salts based on the donor" } ] } }, { "feature_id": "cluu0k04w05hq07h726ie8atv", "feature_schema_id": "cluu0ckta024507udcols6x8y", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 0, "bounding_box": { "top": 135.3, "left": 102.771, "height": 109.84299999999999, "width": 415.8 } }, { "feature_id": "cluu0k04w05hr07h7dsu225r3", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "c41c90ad-4ef2-4c49-94cf-6b66b9db521e", "page_number": 1, "tokens": [ "db2d0b48-6deb-4013-95ea-e09fd9c09251", "f6f32337-4375-410a-b49b-046b3a07f0be", "327521e0-35e3-481c-940f-ff4548d41390", "23745fb8-8640-4220-badf-1efdbcd15ecd", "a41b5975-c531-4219-a979-70034eac3ce2", "1b5f236c-9919-4f46-92a9-e9b3f42736c8", "fc98ef37-9bad-4195-b837-93c5e9b989bc", "0d352bcd-36e9-4d37-9625-3974e7aba2ec" ], "text": "the experimental investigations on this issue have not" } ] } }, { "feature_id": "cluu0k04w05hs07h7ahsw7689", "feature_schema_id": "cluu0ckta024507udcols6x8y", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 1, "bounding_box": { "top": 68.875, "left": 188.257, "height": 80.68100000000001, "width": 82.64999999999998 } }, { "feature_id": "cluu0k04w05ht07h7gdlucd5k", "name": "ner_with_checklist_subclass", "value": "ner_with_checklist_subclass", "annotation_kind": "DocumentEntityToken", "classifications": [ { "feature_id": "cluu0k04y05hz07h7am3xc9s9", "feature_schema_id": "cluu0cktb024c07ud1dtpcax0", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cluu0k04y05i007h78cfagn14", "feature_schema_id": "cluu0cktb024d07udhx513yld", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ], "location": { "groups": [ { "id": "3e129182-09bf-47ee-a20a-6f9475a8bebb", "page_number": 1, "tokens": [ "b2265333-b4ea-43e1-9321-4a81f3c477b0", "ceb2a276-d628-4005-847d-45fe9fcedfab", "5624be95-c333-48b3-8232-28d754699ecb", "65f5ee88-45b9-4a0d-810c-859fac7a85fe", "1c067d79-c119-4fc2-92a1-ef409511cc71", "2b9c4e8a-586f-40c9-82a0-aab3f8fa4dd6", "707924a8-5368-4c6d-afb8-c4e5ab7e2b46", "588436fd-23c6-40cf-87c4-d630740741af", "ca87b137-ff15-4cf5-a74b-a0f5fc768e86" ], "text": "T. Sasaki , N. Yoneyama , and N. Kobayashi" } ] } }, { "feature_id": "cluu0k04y05hv07h79ci47rgw", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "e14a5896-7f3f-4a57-8cff-fa78523583d0", "page_number": 1, "tokens": [ "3158fc72-e758-4d39-ab05-2f785a51463f", "0b9da1f7-3af8-428a-9fbb-1d8972d4865c", "902c7e26-adbd-4b4a-89bf-3b7a28e0a9e0", "719c028c-9f23-49e2-9cc6-ab88c9bfab11", "31f7ab2f-2613-4074-ae92-554a61d199d5", "2bdc2a66-de50-48b9-91c6-5628e9696476", "8df51638-4fb1-4918-948e-5b081c8ac9bf", "cd63cdf8-e805-4082-92d1-037faf1f9fa6", "c796ca0e-55ff-4dfb-90ea-39ca260a2114", "863c87ca-8cff-4a61-975e-df6d865b9028", "79a87aed-444a-417d-a25e-0a63b76188c3", "1fc66034-14ed-481f-a76c-5d9b86e75997" ], "text": "Metal - insulator ( MI ) transitions have been one of the" } ] } } ], "classifications": [ { "feature_id": "cluu0k04w05hj07h71okedbdg", "feature_schema_id": "cluu0cktc024x07udc5fr0p96", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "cluu0k04y05hy07h73gy3awz7", "feature_schema_id": "cluu0cktc025007ud0d5w5o05", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "cluu0k04y05i107h73vpphndb", "feature_schema_id": "cluu0cktc024y07ud97zu7h1d", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "cluu0k04w05hm07h7f1m9c0pe", "feature_schema_id": "cluu0cktc025d07ud6kzzchyp", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "cluu0k04y05i307h7gt9herg8", "feature_schema_id": "cluu0cktc025e07udc46rfioc", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "cluu0k04y05i707h7c8hv88jc", "feature_schema_id": "cluu0cktc025f07ud8yhraccd", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cluu0k04y05ib07h7an9s5rln", "feature_schema_id": "cluu0cktc025g07udbpnd5578", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "cluu0k04w05hn07h7bf1dehdu", "feature_schema_id": "cluu0cktc025507udeiv10o12", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "cluu0k04y05i507h76d434ayj", "feature_schema_id": "cluu0cktc025607udbgogb22u", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "cluu0k04y05i907h74xuqh3c9", "feature_schema_id": "cluu0cktc025707udh9h9et5l", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cluu0k04y05ic07h7fv1m1h7d", "feature_schema_id": "cluu0cktc025807udcbpnbd9s", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "cluu0k04w05hp07h7fn0k9bdw", "feature_schema_id": "cluu0cktc025307udgntw3qxi", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "cluu0k04y05hw07h70s248skz", "feature_schema_id": "cluu0cktc024r07ud2x2oba9o", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "cluu0k04y05i407h7ehvn84w9", "feature_schema_id": "cluu0cktc024s07udc81y6lx3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ], "relationships": [ { "feature_id": "cluu0k04w05hu07h71d8aaiqh", "feature_schema_id": "cluu0cktb024907udbo0qhco1", "name": "relationship", "value": "relationship", "annotation_kind": "DocumentUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "cluu0k04w05ho07h73jx24ol7", "target": "cluu0k04w05hr07h7dsu225r3" } }, { "feature_id": "cluu0k04y05hx07h75lne7jh6", "feature_schema_id": "cluu0cktb024907udbo0qhco1", "name": "relationship", "value": "relationship", "annotation_kind": "DocumentUnidirectionalRelationship", "classifications": [], "unidirectional_relationship": { "source": "cluu0k04w05hs07h7ahsw7689", "target": "cluu0k04w05hk07h76lv05sb0" } } ] } } ], "project_details": { "ontology_id": "cluu0cksd024407udeatf87mf", "task_id": "0f42415e-708d-452b-ac10-8296b9744746", "task_name": "Initial review task", "batch_id": "fdf99590-f754-11ee-89ff-078b5fb2d273", "batch_name": "PDF_annotation_batch", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [ { "action": "Move", "created_at": "2024-04-10T16:17:17.681+00:00", "created_by": "aovalle@labelbox.com", "previous_task_name": "Initial labeling task", "previous_task_id": "63c893d3-a7f7-084e-9d56-329c60c58ae0", "next_task_name": "Initial review task", "next_task_id": "0f42415e-708d-452b-ac10-8296b9744746" }, { "action": "Move", "created_at": "2024-04-10T16:17:17.675+00:00", "created_by": "aovalle@labelbox.com", "next_task_name": "Initial labeling task", "next_task_id": "63c893d3-a7f7-084e-9d56-329c60c58ae0" } ] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh1n29e1mh2078d3jt3b5cx", "global_key": "0801.3483.pdf", "row_data": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "details": { "dataset_id": "clfh1n0um00wd071d2uf21fy6", "dataset_name": "pdf_demo_dataset", "created_at": "2023-03-20T16:31:01.000+00:00", "updated_at": "2023-03-20T16:31:01.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "application/pdf", "text_layer_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483-lb-textlayer.json" }, "attachments": [], "metadata_fields": [], "experiments": { "a15bd76b-e29f-02f2-b909-0e20f007f735": { "name": "PDF_model_run_9c051c54-bb5b-47be-a0f4-16c909a9afb0", "runs": { "a15bd76c-41aa-0f04-c633-ee3e40b1d0bc": { "name": "iteration 1", "run_data_row_id": "dff29c2a-27c2-443b-95a8-402d9c2be311", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs1ru105b3w142bd71v29u1", "annotations": { "objects": [ { "feature_id": "2aa47146-4d81-4fcd-b513-7f7f5e275db7", "feature_schema_id": "clhs1rh090buq07y15p2wd58d", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 0, "bounding_box": { "top": 135.3, "left": 102.771, "height": 109.84299999999999, "width": 415.8 } }, { "feature_id": "40cd3ef3-7258-4da6-bc8b-d4f2eb12abfb", "name": "ner_with_checklist_subclass", "value": "ner_with_checklist_subclass", "annotation_kind": "DocumentEntityToken", "classifications": [ { "feature_id": "4cf8dcba-32ab-4bf0-a97e-5a17d72335b5", "feature_schema_id": "clhs1rh0a0buv07y1e0wobtgu", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "b139a807-7b9f-41f2-97e2-1dca6cd9f460", "feature_schema_id": "clhs1rh0a0buw07y12tg3hm0x", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ], "location": { "groups": [ { "id": "80e1e326-0194-4f38-845c-d9d22b0054fb", "page_number": 1, "tokens": [ "c2639311-4db1-4ad5-9a8d-1f2ab2c2dae8", "2aa2f9df-df5a-4b3a-aa57-05aff053c147", "aa725467-7b86-4629-9c70-a6391decb39b", "6a4a0a69-f04e-44c0-b343-096aef413444", "8dcce11c-c2ae-4274-b634-c950c8a7f333", "85fccf77-474b-4707-85cc-592871d5c680", "5d16be99-fc40-4228-99db-0a121a0478e7" ], "text": "T. Sasaki,* N. Yoneyama, and N. Kobayashi" } ] } }, { "feature_id": "d2cd83b8-7a7b-4751-8b5d-dfa05be90bf5", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "2f4336f4-a07e-4e0a-a9e1-5629b03b719b", "page_number": 1, "tokens": [ "3f984bf3-1d61-44f5-b59a-9658a2e3440f", "3bf00b56-ff12-4e52-8cc1-08dbddb3c3b8", "6e1c3420-d4b7-4c5a-8fd6-ead43bf73d80", "87a43d32-af76-4a1d-b262-5c5f4d5ace3a", "e8606e8a-dfd9-4c49-a635-ad5c879c75d0", "67c7c19e-4654-425d-bf17-2adb8cf02c30", "149c5e80-3e07-49a7-ab2d-29ddfe6a38fa", "b0e94071-2187-461e-8e76-96c58738a52c" ], "text": "Metal-insulator (MI) transitions have been one of the" } ] } }, { "feature_id": "e166e286-1ead-438d-bb6e-0bc148b4c5f0", "feature_schema_id": "clhs1rh0a0bv007y1fyxw0gyg", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "DocumentBoundingBox", "classifications": [ { "feature_id": "68838874-d209-4672-891f-10954a698292", "feature_schema_id": "clhs1rh0a0bv107y14g5de74m", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "8ce7710d-7a15-4d95-b9a9-a9a2bf733380", "feature_schema_id": "clhs1rh0a0bv207y13bsndvuv", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [ { "feature_id": "bc3542d1-f373-4668-841b-15a4e9f8f67b", "feature_schema_id": "clhs1rh0a0bv307y17teq5ob3", "name": "second_sub_radio_question", "value": "second_sub_radio_question", "radio_answer": { "feature_id": "6d200263-2065-4363-b962-1ee5a1296a8e", "feature_schema_id": "clhs1rh0a0bv407y1d72h3bdp", "name": "second_sub_radio_answer", "value": "second_sub_radio_answer", "classifications": [] } } ] } } ], "page_number": 1, "bounding_box": { "top": 226.757, "left": 317.271, "height": 194.22899999999998, "width": 249.38600000000002 } } ], "classifications": [ { "feature_id": "2fbbc78f-d1df-428d-be26-da69a6516786", "feature_schema_id": "clhs1rh0a0bva07y11vb9gjzu", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "24924adc-cb1c-40fb-a945-364a2cb0782f", "feature_schema_id": "clhs1rh0a0bvb07y134ka29be", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "a10706c7-0e09-47d9-b273-ed61f6ee72c5", "feature_schema_id": "clhs1rh0b0bvg07y1as0cfj9r", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "03c2feb6-bda3-4ee2-ad9f-faef038ee029", "feature_schema_id": "clhs1rh0b0bvj07y1f1brb5f3", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "3653a591-4915-450f-88e4-f5afc8cd1440", "feature_schema_id": "clhs1rh0b0bvh07y19qgt6nsv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "da14ab32-c007-4e86-aaff-a64e51ff2503", "feature_schema_id": "clhs1rh0b0bvm07y1gpw00bw3", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "de0e9826-a0fc-4603-a59a-0c356895759c", "feature_schema_id": "clhs1rh0b0bvo07y1025n0p4a", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "913b5925-7b37-43a0-9086-c7734491d60a", "feature_schema_id": "clhs1rh0b0bvp07y1d1tbgpeg", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "7c10d9b2-0e07-4723-abef-ad022db24b10", "feature_schema_id": "clhs1rh0b0bvq07y13wux72rp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "ae62a004-f6b3-4436-916e-ce375cf8210e", "feature_schema_id": "clhs1rh0b0bvr07y15n246t5c", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "ee0a7c2c-cb78-4474-b12a-82406ee415c5", "feature_schema_id": "clhs1rh0c0bvw07y1hiyweequ", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "22d25a8a-0831-4499-8980-aaebbac1b4eb", "feature_schema_id": "clhs1rh0c0bvx07y1er8ge5qk", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "825ce160-5e9f-4926-aa63-ed17ad7fb534", "feature_schema_id": "clhs1rh0c0bvy07y1hmkv360v", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "32cd713f-c272-4657-8d0e-910e24e90414", "feature_schema_id": "clhs1rh0c0bvz07y1d1njge4e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clhs1ru105b3w142bd71v29u1", "annotations": { "objects": [ { "feature_id": "1d1a302a-2bf1-4a64-b2db-1283400fc8c6", "feature_schema_id": "clhs1rh0a0bv007y1fyxw0gyg", "name": "bbox_with_radio_subclass", "value": "bbox_with_radio_subclass", "annotation_kind": "DocumentBoundingBox", "classifications": [ { "feature_id": "f13c6571-b3c8-4584-886b-555e1808a955", "feature_schema_id": "clhs1rh0a0bv107y14g5de74m", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "610215da-b253-46f8-8a6d-7dd56b36e32c", "feature_schema_id": "clhs1rh0a0bv207y13bsndvuv", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [ { "feature_id": "a1002a15-c1f1-43ce-9606-9b02fb4d3d77", "feature_schema_id": "clhs1rh0a0bv307y17teq5ob3", "name": "second_sub_radio_question", "value": "second_sub_radio_question", "radio_answer": { "feature_id": "a225ff2c-dccc-4ad1-8dd5-d76f10c5aab2", "feature_schema_id": "clhs1rh0a0bv407y1d72h3bdp", "name": "second_sub_radio_answer", "value": "second_sub_radio_answer", "classifications": [] } } ] } } ], "page_number": 1, "bounding_box": { "top": 226.757, "left": 317.271, "height": 194.22899999999998, "width": 249.38600000000002 } }, { "feature_id": "469420eb-12a7-401f-a5ea-142b24c622e3", "feature_schema_id": "clhs1rh090buq07y15p2wd58d", "name": "bounding_box", "value": "bounding_box", "annotation_kind": "DocumentBoundingBox", "classifications": [], "page_number": 0, "bounding_box": { "top": 135.3, "left": 102.771, "height": 109.84299999999999, "width": 415.8 } }, { "feature_id": "d3c14b37-6b64-4c6d-91bc-446bf82dc0b5", "name": "named_entity", "value": "named_entity", "annotation_kind": "DocumentEntityToken", "classifications": [], "location": { "groups": [ { "id": "2f4336f4-a07e-4e0a-a9e1-5629b03b719b", "page_number": 1, "tokens": [ "3f984bf3-1d61-44f5-b59a-9658a2e3440f", "3bf00b56-ff12-4e52-8cc1-08dbddb3c3b8", "6e1c3420-d4b7-4c5a-8fd6-ead43bf73d80", "87a43d32-af76-4a1d-b262-5c5f4d5ace3a", "e8606e8a-dfd9-4c49-a635-ad5c879c75d0", "67c7c19e-4654-425d-bf17-2adb8cf02c30", "149c5e80-3e07-49a7-ab2d-29ddfe6a38fa", "b0e94071-2187-461e-8e76-96c58738a52c" ], "text": "Metal-insulator (MI) transitions have been one of the" } ] } }, { "feature_id": "efe907c9-b84b-4015-b29b-3a1cae1655cc", "name": "ner_with_checklist_subclass", "value": "ner_with_checklist_subclass", "annotation_kind": "DocumentEntityToken", "classifications": [ { "feature_id": "12fb5823-1a12-4de4-8613-8460cec5eaf1", "feature_schema_id": "clhs1rh0a0buv07y1e0wobtgu", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "8be1ca80-1f98-48ad-ae65-d38ab941ef63", "feature_schema_id": "clhs1rh0a0buw07y12tg3hm0x", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ], "location": { "groups": [ { "id": "", "page_number": 1, "tokens": [], "text": "" } ] } } ], "classifications": [ { "feature_id": "35f2a9ed-e245-4654-94a5-9e7e48adff8d", "feature_schema_id": "clhs1rh0b0bvo07y1025n0p4a", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "70a52e6d-eda7-48f3-a1dd-65672c273318", "feature_schema_id": "clhs1rh0b0bvp07y1d1tbgpeg", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "5f87ca53-3637-4f26-9068-8645b6036646", "feature_schema_id": "clhs1rh0b0bvq07y13wux72rp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "c652afc0-d24f-42ea-9a5a-dee14cc1d80d", "feature_schema_id": "clhs1rh0b0bvr07y15n246t5c", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "73114ba5-3045-46e0-9323-468914afbdf0", "feature_schema_id": "clhs1rh0b0bvg07y1as0cfj9r", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "666fc303-b30e-4647-a694-5c34dab4781f", "feature_schema_id": "clhs1rh0b0bvj07y1f1brb5f3", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "f8c4ce45-0e56-4ec2-bc5a-ce9364b471f8", "feature_schema_id": "clhs1rh0b0bvh07y19qgt6nsv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "911665a7-4901-4c31-a410-5c3a7a614c1d", "feature_schema_id": "clhs1rh0c0bvw07y1hiyweequ", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "0341d434-c589-49d4-9a4a-b554647c2a44", "feature_schema_id": "clhs1rh0c0bvx07y1er8ge5qk", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ee1c729c-d1f1-478e-98f8-baefa24dff28", "feature_schema_id": "clhs1rh0c0bvy07y1hmkv360v", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "a2c0d47f-811c-4d70-b024-8514d67b206c", "feature_schema_id": "clhs1rh0c0bvz07y1d1njge4e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "a7f83569-636d-4993-81f0-6d4de144bb24", "feature_schema_id": "clhs1rh0a0bva07y11vb9gjzu", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "58624513-6ab0-452b-a9a0-72cebf648cd0", "feature_schema_id": "clhs1rh0a0bvb07y134ka29be", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "c7e3f324-7b53-47a3-aeb0-4217717fcfed", "feature_schema_id": "clhs1rh0b0bvm07y1gpw00bw3", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ], "relationships": [] } } ] } } } }, "models": {} } ``` ## Audio ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clftvvbln01cb07343mpx8n1l", "global_key": "sample-audio-1.mp3", "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3", "details": { "dataset_id": "clftvva210b5t070d0no12z0v", "dataset_name": "audio_annotation_import_demo_dataset", "created_at": "2023-03-29T16:10:29.000+00:00", "updated_at": "2024-02-10T20:36:56.174+00:00", "last_activity_at": "2023-10-17T20:33:05.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "audio/mpeg" }, "attachments": [], "metadata_fields": [], "projects": { "clnus6sqx08k607yd0d33gfld": { "name": "audio_project", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clnus731g4gc6143uyfqfbjth", "label_details": { "created_at": "2023-10-17T20:33:05.000+00:00", "updated_at": "2023-10-17T20:33:05.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2023-10-17T20:33:05.001+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clnus73114gc0143urjlfk558", "feature_schema_id": "clnus6slu06xg07yjcg87apa0", "name": "text_audio", "value": "text_audio", "text_answer": { "content": "free text audio annotation" } }, { "feature_id": "clnus73114gc1143u7fzf4ebv", "feature_schema_id": "clnus6slu06xi07yj7m9d7rft", "name": "checklist_audio", "value": "checklist_audio", "checklist_answers": [ { "feature_id": "clnus73114gc2143u6ah5wdhn", "feature_schema_id": "clnus6slu06xj07yj9eb7dpvp", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clnus73114gc3143uq755vkzi", "feature_schema_id": "clnus6slu06xl07yjdab4gxe8", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "clnus73114gc4143u2dux654s", "feature_schema_id": "clnus6slv06xo07yja7l47gtw", "name": "radio_audio", "value": "radio_audio", "radio_answer": { "feature_id": "clnus73114gc5143u2jadnt6r", "feature_schema_id": "clnus6slv06xr07yj24yp1gdk", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clnus6sl206xf07yj7md9cbg0", "task_name": "Done", "batch_id": "58ad3900-6d2c-11ee-a344-51974336e7ee", "batch_name": "first-batch-audio-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] } } } } ``` ## HTML ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfskiyrx0e0q078k40evbanc", "global_key": "sample_html_1.html", "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "details": { "dataset_id": "clfskixq70ym7071scn9l16z0", "dataset_name": "html_annotation_import_demo_dataset", "created_at": "2023-03-28T18:05:11.000+00:00", "updated_at": "2024-01-19T23:05:36.279+00:00", "last_activity_at": "2023-04-24T20:19:59.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "text/html" }, "attachments": [], "metadata_fields": [], "projects": { "clgva7x400opk07x73jgs6p8o": { "name": "HTML Import Annotation Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clgva8bonhmg21401hcpq6tee", "label_details": { "created_at": "2023-04-24T20:19:59.000+00:00", "updated_at": "2023-04-24T20:19:59.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2023-04-24T20:19:59.466+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clgva8bo5hmfo140186f07jci", "feature_schema_id": "clgva7wwa0l9j07x51e4t0bq8", "name": "text_html", "value": "text_html", "text_answer": { "content": "sample text" } }, { "feature_id": "clgva8bo5hmfp14016xok5j73", "feature_schema_id": "clgva7wwa0l9l07x558vydexo", "name": "checklist_html", "value": "checklist_html", "checklist_answers": [ { "feature_id": "clgva8bo5hmfq1401xnngji4s", "feature_schema_id": "clgva7wwb0l9m07x5671qe9ej", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clgva8bo5hmfr1401w191mdo5", "feature_schema_id": "clgva7wwb0l9o07x5h08p9572", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "clgva8bo5hmfs1401auduvxht", "feature_schema_id": "clgva7wwb0l9r07x54f9h826z", "name": "radio_html", "value": "radio_html", "radio_answer": { "feature_id": "clgva8bo5hmft1401nhfbg7ja", "feature_schema_id": "clgva7wwb0l9u07x55fyxa5g3", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "clgva8bo5hmfu1401yuz7wvuy", "feature_schema_id": "clgva7wwb0l9x07x57si1gihw", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "clgva8bo6hmfv14015d33m625", "feature_schema_id": "clgva7wwb0l9y07x5dt6l44qv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "clgva8bo6hmfw1401u59hfep5", "feature_schema_id": "clgva7wwb0l9z07x5bvtx0agv", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "clgva8bo6hmfx14018asstw29", "feature_schema_id": "clgva7wwb0la007x515qu9lje", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "clgva8bo6hmfy1401bivtvjjt", "feature_schema_id": "clgva7wwb0la507x583lg7mvf", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "clgva8bo6hmfz1401y555xn92", "feature_schema_id": "clgva7wwb0la607x5h39u2z31", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "clgva8bo6hmg01401agmna01o", "feature_schema_id": "clgva7wwb0la707x5hxwe16bo", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clgva8bo6hmg11401dzqw0od8", "feature_schema_id": "clgva7wwb0la807x51792e6il", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clgva7wto0l9i07x5emb8b4uo", "task_name": "Done", "batch_id": "5ba71600-e2dd-11ed-8ee3-e3123dc756e1", "batch_name": "first-batch-html-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfcvkqx708ba079n4nfo1drf", "global_key": "sample_html_2.html", "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "details": { "dataset_id": "clfcvkpzc0v8l07yt9vhc16sv", "dataset_name": "html demo dataset", "created_at": "2023-03-17T18:30:11.000+00:00", "updated_at": "2024-01-19T22:55:40.282+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "text/html" }, "attachments": [], "metadata_fields": [], "experiments": { "a414cfe7-f6ab-094b-8307-68d5a7272397": { "name": "HTML_model_run_1d21d941-f392-41e9-85d4-a52b3a3b5bfd", "runs": { "a414cfe8-690d-0b10-6b9f-7e9bf6a7dd1c": { "name": "iteration 1", "run_data_row_id": "d2bdab47-af16-4052-a310-74fa324cda5f", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu1x19z0out148p9vadgmcu", "annotations": { "objects": [], "classifications": [ { "feature_id": "3625d4eb-ca0f-0385-9c74-c3538b40621c", "feature_schema_id": "cluu1wl30002007wo1gnn8htz", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "47d39827-416f-042f-a9f5-7d5bf45ba884", "feature_schema_id": "cluu1wl2z001k07wo17r02qrr", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "89c36638-ec65-095c-893a-49417a1611fc", "feature_schema_id": "cluu1wl2z001l07wo6kll5fc0", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "5de3b3eb-78a8-06e8-b420-491234d05743", "feature_schema_id": "cluu1wl2z001m07wodiwh0hjp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "684b6ca5-e5d4-0684-991d-597e5ea76700", "feature_schema_id": "cluu1wl2z001n07wo8ey5f1yw", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "9297d521-2b0a-04bd-b88b-b5512b85ec20", "feature_schema_id": "cluu1wl2z001s07woc5xmcz68", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "add68a9d-01b1-039a-bbdf-c96f63d408e6", "feature_schema_id": "cluu1wl30001v07wocp3hg8a0", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "ce4eca3a-b2f5-0861-954f-ad7ff07c1a7f", "feature_schema_id": "cluu1wl30001x07wobpdwgf4w", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "fab6d119-206f-0b6d-a5a7-8a08e17ed8cf", "feature_schema_id": "cluu1wl2z001t07wobsowcibp", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "9c1c6bad-ca20-08c9-8d1a-78907e65ebe4", "feature_schema_id": "cluu1wl2z001g07wo5elxfwut", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "28ea7b46-b891-0285-a8a3-3ba7a30cdeb4", "feature_schema_id": "cluu1wl2z001h07wo760uc0sb", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "d48dce83-f514-0728-9b5a-deb045493dc3", "feature_schema_id": "cluu1wl30002207woa3u870v7", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "ca4f111b-c6f6-013a-a38e-0ce1ee77b370", "feature_schema_id": "cluu1wl30002307wogixwdc5z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "6d08884d-ff25-0a41-a5aa-875e6385ea1e", "feature_schema_id": "cluu1wl30002407woea6e102z", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "02db9eb9-6763-0a2c-92ee-cdf3b11b8de4", "feature_schema_id": "cluu1wl30002507wo07av3u9e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu1x19z0out148p9vadgmcu", "annotations": { "objects": [], "classifications": [ { "feature_id": "162ea9d9-c6e8-48e9-b4fe-1beb5ec35e71", "feature_schema_id": "cluu1wl2z001k07wo17r02qrr", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "ecdb63be-bd8b-4768-ab51-6399e38c3fd8", "feature_schema_id": "cluu1wl2z001l07wo6kll5fc0", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "9bdb8546-6ee7-4993-bea7-9de7d1d4f0d5", "feature_schema_id": "cluu1wl2z001m07wodiwh0hjp", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "a0466f11-c940-4217-9589-56f261425e43", "feature_schema_id": "cluu1wl2z001n07wo8ey5f1yw", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "2b7e8e1b-a55d-4ac6-80e2-629bc64c5b97", "feature_schema_id": "cluu1wl30002007wo1gnn8htz", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "347dd2d9-7729-4ebd-970a-c4eedb1d41cc", "feature_schema_id": "cluu1wl30002207woa3u870v7", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "1a17cdf4-2159-4c21-b1d8-7b9490d8bb14", "feature_schema_id": "cluu1wl30002307wogixwdc5z", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ab20eb98-3002-4108-a541-37741a9a1531", "feature_schema_id": "cluu1wl30002407woea6e102z", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "31e09418-2b8e-4759-bd71-4f88302398ce", "feature_schema_id": "cluu1wl30002507wo07av3u9e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "70f54814-79c0-4d66-84c5-531e8b53e657", "feature_schema_id": "cluu1wl2z001g07wo5elxfwut", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "214cb3db-da72-4692-9fa4-c86e2933b11e", "feature_schema_id": "cluu1wl2z001h07wo760uc0sb", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "baa18a09-6626-48cb-9b4c-7d75aa7d8c57", "feature_schema_id": "cluu1wl2z001s07woc5xmcz68", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "c1cf3d18-238f-4817-9fed-93c14954c153", "feature_schema_id": "cluu1wl30001v07wocp3hg8a0", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "cf4606ab-1a5b-4c50-87a3-1f2b5dfc9547", "feature_schema_id": "cluu1wl30001x07wobpdwgf4w", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "f4f28750-5a7f-4da0-a081-b4da8eacb667", "feature_schema_id": "cluu1wl2z001t07wobsowcibp", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ] } } } }, "models": {} } ``` ## Conversational ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfbnskoxb9q9076fbyjngwhm", "global_key": "conversation-1.json", "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/conversational_text/1000-conversations/conversation-1.json", "details": { "dataset_id": "clfbnsjaj05hr07z605db0w3b", "dataset_name": "conversational_annotation_import_demo_dataset", "created_at": "2023-03-16T22:04:33.000+00:00", "updated_at": "2024-01-18T19:10:53.352+00:00", "last_activity_at": "2024-04-10T16:33:23.000+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "application/vnd.labelbox.conversational", "labelable_ids": [ "0", "2", "4", "6", "8", "10", "12", "14", "16", "18" ], "message_count": 20 }, "attachments": [], "metadata_fields": [], "projects": { "cluu14fwi01g0070321g9c1xi": { "name": "Conversational Text Annotation Import Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "cluu14r490oud148pdk29bwes", "label_details": { "created_at": "2024-04-10T16:33:23.000+00:00", "updated_at": "2024-04-10T16:33:23.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2024-04-10T16:33:23.063+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "cluu14r3p0oty148p5d2isiki", "feature_schema_id": "cluu14fnh01f40703aigj669b", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ], "classifications": [ { "feature_id": "cluu14r3p0otz148p09nol3tq", "feature_schema_id": "cluu14fnh01f60703b4uadjbd", "name": "text_convo", "value": "text_convo", "message_id": "0", "conversational_text_answer": { "content": "the answer to the text questions right here" } }, { "feature_id": "cluu14r3p0ou0148ptekhuz0a", "feature_schema_id": "cluu14fnh01f8070385tv57vy", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "cluu14r3p0ou1148p8pb3s49a", "feature_schema_id": "cluu14fnh01f90703cx3j6gqu", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cluu14r3p0ou2148ph8tyhtp1", "feature_schema_id": "cluu14fnh01fb07030q2m5hqc", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "cluu14r3p0ou3148p6d15q82b", "feature_schema_id": "cluu14fnh01fe0703abp5fjgx", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "cluu14r3p0ou4148pc028jtzk", "feature_schema_id": "cluu14fnh01ff070383rh4ryk", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "cluu14r3p0ou5148piuv74m17", "feature_schema_id": "cluu14fni01fs0703ax77277l", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "cluu14r3p0ou6148p0b4azzcu", "feature_schema_id": "cluu14fni01ft07033xic99k3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "cluu14r3p0ou7148pgmcqsf11", "feature_schema_id": "cluu14fni01fu0703c3iu2c0a", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cluu14r3p0ou8148pka3hvx26", "feature_schema_id": "cluu14fni01fv07031r8i190o", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "cluu14r3p0ou9148puacr13og", "feature_schema_id": "cluu14fni01fk07033aeye6ht", "name": "nested_checklist_question", "value": "nested_checklist_question", "message_id": "10", "conversational_checklist_answers": [ { "feature_id": "cluu14r3p0oua148pttkqa3fe", "feature_schema_id": "cluu14fni01fl0703eg9y2qgt", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "cluu14r3p0oub148p3hld5bh9", "feature_schema_id": "cluu14fni01fm0703b3mwa6ot", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cluu14r3p0ouc148pfpiwlzht", "feature_schema_id": "cluu14fni01fn0703apfk8j9q", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ], "project_details": { "ontology_id": "cluu14fmp01f307039z1h2c64", "task_name": "Done", "batch_id": "04e9d4c0-f758-11ee-8be6-db43b18899b6", "batch_name": "first-batch-convo-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] } } } } ``` ### Sample Model Run Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfbnskoxb9q9076fbyjngwhm", "global_key": "conversation-1.json", "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/conversational_text/1000-conversations/conversation-1.json", "details": { "dataset_id": "clfbnsjaj05hr07z605db0w3b", "dataset_name": "conversational_annotation_import_demo_dataset", "created_at": "2023-03-16T22:04:33.000+00:00", "updated_at": "2024-01-18T19:10:53.352+00:00", "created_by": "labelbox.public.user@labelbox.com" } }, "media_attributes": { "mime_type": "application/vnd.labelbox.conversational", "labelable_ids": [ "0", "2", "4", "6", "8", "10", "12", "14", "16", "18" ], "message_count": 20 }, "attachments": [], "metadata_fields": [], "experiments": { "a3143b06-742d-0576-d722-c187a5aad1b3": { "name": "Conversational_model_run_9985d371-1b0b-4a5b-8c94-4a6167b20090", "runs": { "a3143b06-adc7-086f-79a5-d251a0054cb8": { "name": "iteration 1", "run_data_row_id": "4f22e94f-71da-45ac-bab7-f95daf04e7a1", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clq10ymdubt0u1447v6s77glu", "annotations": { "objects": [ { "feature_id": "e063fe4e-4675-0988-9f81-659c83dd1b43", "feature_schema_id": "clq10ybnl067t07ztbavefdsy", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ], "classifications": [ { "feature_id": "169f1fee-5db4-02c9-a232-dfb245a158ec", "feature_schema_id": "clq10ybnl068907ztf1htgtoc", "name": "nested_checklist_question", "value": "nested_checklist_question", "message_id": "10", "conversational_checklist_answers": [ { "feature_id": "c1c5898b-f1ce-02c7-b806-930ac8140923", "feature_schema_id": "clq10ybnl068a07ztcnyzbhsn", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "1fc2b7e8-a292-02ad-81b6-10cb9bdd56c4", "feature_schema_id": "clq10ybnl068b07zth87h8c34", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "de42a672-041f-03f3-bbd9-e771e332030a", "feature_schema_id": "clq10ybnm068c07zt53sdcs0m", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "8adc0320-6d61-0011-bda0-80842b3733da", "feature_schema_id": "clq10ybnm068h07ztaugh4uma", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "475fb394-f606-0223-b625-48045ebf7208", "feature_schema_id": "clq10ybnm068i07zt1xxv25it", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "a61d83fd-4c47-0e88-9ae1-da3eb100eea0", "feature_schema_id": "clq10ybnm068j07ztcqf794w4", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "6dbde4c3-3a1e-0348-a5ac-7787a8ac0461", "feature_schema_id": "clq10ybnm068k07ztdx9dca3i", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "96dce41f-f1c1-0822-9848-39c098b449f5", "feature_schema_id": "clq10ybnl067v07ztb8f91r4u", "name": "text_convo", "value": "text_convo", "message_id": "0", "conversational_text_answer": { "content": "the answer to the text questions are right here" } }, { "feature_id": "ba7c519c-c618-01ba-ac4b-fdbbd8137bf0", "feature_schema_id": "clq10ybnl068307ztd255hndf", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "ec0fc3fb-1948-0ff9-8bc6-0377721e4f0e", "feature_schema_id": "clq10ybnl068407zt1x572rym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "fe3a901b-8e62-01fe-a792-10b15ec7fa0d", "feature_schema_id": "clq10ybnl067x07zt0u67b9h3", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "699a5673-bb4a-06d3-9b9e-19237e11f96d", "feature_schema_id": "clq10ybnl068007zt6ct25775", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "da192bd6-84ec-0acf-b875-d1ade525f466", "feature_schema_id": "clq10ybnl067y07zt6gvjczsg", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clq10ymdubt0u1447v6s77glu", "annotations": { "objects": [ { "feature_id": "9c8f89a7-7211-4b33-a3c5-a3dab59f0407", "feature_schema_id": "clq10ybnl067t07ztbavefdsy", "name": "ner", "value": "ner", "annotation_kind": "ConversationalTextEntity", "classifications": [], "conversational_location": { "message_id": "4", "location": { "start": 0, "end": 8 } } } ], "classifications": [ { "feature_id": "20cda99c-d0f7-4e59-8f43-a8b4f3929e2f", "feature_schema_id": "clq10ybnl068307ztd255hndf", "name": "radio_convo", "value": "radio_convo", "message_id": "0", "conversational_radio_answer": { "feature_id": "ec1ec509-79b6-48e7-973c-9c06e754630f", "feature_schema_id": "clq10ybnl068407zt1x572rym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "49e4667e-ed14-400b-9b75-b8b89bfb6f11", "feature_schema_id": "clq10ybnm068h07ztaugh4uma", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "723be4ec-fa0c-4a67-9df0-a18f56f7bbd8", "feature_schema_id": "clq10ybnm068i07zt1xxv25it", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "f28f3d0a-9428-4b45-8de1-4ec6714da726", "feature_schema_id": "clq10ybnm068j07ztcqf794w4", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "82808a1e-a004-4965-bb77-4ab9e499c747", "feature_schema_id": "clq10ybnm068k07ztdx9dca3i", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "988e5b9a-9a6d-4338-96bf-901475b4a2c0", "feature_schema_id": "clq10ybnl068907ztf1htgtoc", "name": "nested_checklist_question", "value": "nested_checklist_question", "message_id": "10", "conversational_checklist_answers": [ { "feature_id": "9ea5984d-fb21-48e4-96c8-ce19162b5850", "feature_schema_id": "clq10ybnl068a07ztcnyzbhsn", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "bf896185-5a77-410e-a4cd-abf0b07122fa", "feature_schema_id": "clq10ybnl068b07zth87h8c34", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "0812ee3d-7f5d-480b-82a2-4e965a48fc49", "feature_schema_id": "clq10ybnm068c07zt53sdcs0m", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "cb4730df-2e22-45db-bc74-fd439d577571", "feature_schema_id": "clq10ybnl067x07zt0u67b9h3", "name": "checklist_convo", "value": "checklist_convo", "message_id": "2", "conversational_checklist_answers": [ { "feature_id": "8184feca-970c-4ba7-bfbd-4d0eb5849a90", "feature_schema_id": "clq10ybnl068007zt6ct25775", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "dc40cfef-aa6f-436d-a5b5-0d56bbd1741e", "feature_schema_id": "clq10ybnl067y07zt6gvjczsg", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "db7109ed-e380-4389-ab2f-98e72b4c2afe", "feature_schema_id": "clq10ybnl067v07ztb8f91r4u", "name": "text_convo", "value": "text_convo", "message_id": "0", "conversational_text_answer": { "content": "the answer to the text questions are right here" } } ], "relationships": [] } } ] } } } }, "models": {} } ``` ## LLM Data Generation ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clpvnouh04uyy0723mmru42qn", "global_key": "clpvnou2v03js07xsghfo2nzc", "row_data": "{\"type\":\"application/llm.prompt-response-creation\",\"version\":1}", "details": { "dataset_id": "clpvnou0z004c0724pd4cmw8g", "dataset_name": "test-humans-generate-prompts-reponses-dataset", "created_at": "2023-12-07T20:34:06.540+00:00", "updated_at": "2023-12-07T20:34:06.815+00:00", "last_activity_at": "2024-04-10T15:21:31.000+00:00", "created_by": "aovalle@labelbox.com" } }, "media_attributes": { "mime_type": "application/llm.prompt-response-creation" }, "attachments": [], "metadata_fields": [], "projects": { "clpvnotzb03jo07xs48r7ewka": { "name": "Andrea-test-humans-generate-prompts-responses", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clutyida4041a07h4a8ojbu1g", "label_details": { "created_at": "2024-04-10T15:21:31.000+00:00", "updated_at": "2024-04-10T15:21:31.000+00:00", "created_by": "aovalle@labelbox.com", "content_last_updated_at": "2024-04-10T15:21:31.137+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 28, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clutykado00013b6rw65zj51e", "feature_schema_id": "clutyjef000xk07wfeurhc2qb", "name": "Is this a shirt?", "value": "Is this a shirt?", "text_answer": { "content": "Potentially this is a shirt, but keep in mind this is not a good quality prompt" } }, { "feature_id": "clutykap700033b6rmmlkpo6u", "feature_schema_id": "clutyjef000xm07wf5ym80ud6", "name": "Yes", "value": "yes", "radio_answer": { "feature_id": "clutykap700023b6rhktauyzc", "feature_schema_id": "clutyjef000xn07wf6smvc54b", "name": "Red shirt", "value": "red_shirt", "classifications": [] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clpvnqsqz01kv07zydvbscxzq", "task_id": "14b02ec0-71f3-4d1f-b720-c6318e9a9346", "task_name": "Initial review task", "batch_id": "00870bc0-9540-11ee-a202-8d3b90bd1707", "batch_name": "batch_clpvnotzb03jo07xs48r7ewka", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 2, "workflow_history": [ { "action": "Move", "created_at": "2024-04-10T15:21:31.517+00:00", "created_by": "aovalle@labelbox.com", "previous_task_name": "Initial labeling task", "previous_task_id": "1d0062f9-dfd9-0f86-baed-823235868a8c", "next_task_name": "Initial review task", "next_task_id": "14b02ec0-71f3-4d1f-b720-c6318e9a9346" }, { "action": "Move", "created_at": "2024-04-10T15:21:31.506+00:00", "created_by": "aovalle@labelbox.com", "next_task_name": "Initial labeling task", "next_task_id": "1d0062f9-dfd9-0f86-baed-823235868a8c" } ] } } } } ``` ## LLM Human Preference ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clpiqgsvn4lxr079096g5mt9v", "global_key": "0914c7da-5999-464b-b621-054a5247cf48", "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/Pranoy/shopping_3.json", "details": { "dataset_id": "clpiqgrsr00f60737sqeiaf4v", "dataset_name": "pairwise_demo_42454af2-b7b5-45c0-a25a-90f97efd49be", "created_at": "2023-11-28T19:30:49.729+00:00", "updated_at": "2023-11-28T19:30:50.609+00:00", "last_activity_at": "2023-11-28T23:11:08.000+00:00", "created_by": "user@labelbox.com" } }, "media_attributes": { "mime_type": "application/vnd.labelbox.conversational", "labelable_ids": [ "message-1", "message-3" ], "message_count": 4 }, "attachments": [], "metadata_fields": [], "projects": { "clpiy2itz08vg0705akh60zc1": { "name": "Test LLM Human Preference", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clpiyah63071f07e4756yer2d", "label_details": { "created_at": "2023-11-28T23:11:08.000+00:00", "updated_at": "2023-11-28T23:11:07.000+00:00", "created_by": "user@labelbox.com", "content_last_updated_at": "2023-11-28T23:11:07.568+00:00", "reviews": [] }, "annotations": { "objects": [], "classifications": [ { "feature_id": "clpiybl7h000g3b6mil1amjme", "feature_schema_id": "clpiqh0v104mq0729homqffup", "name": "Choose the best response", "radio_answer": { "feature_id": "clpiybl7h000f3b6m03fn8ry3", "feature_schema_id": "clpiqh0v104mt0729atflgvvw", "name": "Response B", "classifications": [] } }, { "feature_id": "clpiybzp3000h3b6m9uowaci4", "feature_schema_id": "clpiqh0v104my072907gt7id0", "name": "Provide a reason for your choice", "text_answer": { "content": "Direct approach that is convenient" } }, { "feature_id": "clpiyc10q000j3b6m01i2d1pv", "feature_schema_id": "clpiya8h507dx072efo9kb97j", "name": "Evaluations done", "checklist_answers": [ { "feature_id": "clpiyc10q000i3b6mhhvhdzqs", "name": "Relevance", "classifications": [] }, { "feature_id": "clpiyc1ie000l3b6m3kpff0lp", "name": "Usefulness", "classifications": [] } ] } ], "relationships": [] } } ], "project_details": { "ontology_id": "clpiqh0ud04mp0729adeoccsl", "task_id": "33e77b4d-424f-49ba-8792-628c1cb746b7", "task_name": "Initial review task", "batch_id": "66882c10-8e42-11ee-8f8d-71857f06db2f", "batch_name": "test_llm_human_preference_batch_1", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [ { "action": "Move", "created_at": "2023-11-28T23:11:07.654+00:00", "created_by": "user@labelbox.com", "previous_task_name": "Initial labeling task", "previous_task_id": "8f9917a7-6030-0914-befc-ac06f59a7ec9", "next_task_name": "Initial review task", "next_task_id": "33e77b4d-424f-49ba-8792-628c1cb746b7" }, { "action": "Move", "created_at": "2023-11-28T23:11:07.631+00:00", "created_by": "user@labelbox.com", "next_task_name": "Initial labeling task", "next_task_id": "8f9917a7-6030-0914-befc-ac06f59a7ec9" } ] } } } } ``` ## Model Chat Evaluation ### Sample Project Export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clwrm93488cpd0734f740r9lr", "global_key": "clwrm92sb0grg07wncr6dhihw", "row_data": "{\"type\":\"application/vnd.labelbox.conversational.model-chat-evaluation\",\"version\":2,\"actors\":{\"clwrt16zz00023b6pg6hlhtub\":{\"role\":\"human\",\"metadata\":{\"name\":\"User\"}},\"clwrt17a900043b6py2tepemd\":{\"role\":\"model\",\"metadata\":{\"modelConfigId\":\"e606593e-def3-4662-9737-3fc4ac412dd3\"}},\"clwrt17a900053b6p0xtyv1im\":{\"role\":\"model\",\"metadata\":{\"modelConfigId\":\"8fcda464-a0c7-4300-a9fa-c9a5cf82cab2\"}}},\"messages\":{\"clwrt16zz00033b6p6ud7w233\":{\"actorId\":\"clwrt16zz00023b6pg6hlhtub\",\"content\":[{\"type\":\"text\",\"content\":\"Is there a car in this image?\"},{\"type\":\"fileData\",\"fileUri\":\"https://c7.staticflickr.com/3/2447/3799589580_4b73283f92_o.jpg\",\"mimeType\":\"image/jpeg\"}],\"childMessageIds\":[\"clwrt1fw700063b6pe8zpo1xp\",\"clwrt1fw700073b6p8k3vkh1x\"]},\"clwrt1fw700063b6pe8zpo1xp\":{\"actorId\":\"clwrt17a900043b6py2tepemd\",\"content\":[{\"type\":\"text\",\"content\":\"Yes, there is a car in this image. It appears to be a white hatchback parked in a parking lot.\"}],\"childMessageIds\":[]},\"clwrt1fw700073b6p8k3vkh1x\":{\"actorId\":\"clwrt17a900053b6p0xtyv1im\",\"content\":[{\"type\":\"text\",\"content\":\" Yes, there is a white Volkswagen Golf in the image.\"}],\"childMessageIds\":[]}},\"rootMessageIds\":[\"clwrt16zz00033b6p6ud7w233\"]}", "details": { "dataset_id": "clwkf82ul001l0755mhckcmuy", "dataset_name": "Model evaluation 101", "created_at": "2024-05-29T09:20:43.342+00:00", "updated_at": "2024-05-29T12:31:52.027+00:00", "last_activity_at": "2024-05-29T12:33:21.203+00:00", "created_by": "user@labelbox.com" } }, "media_attributes": { "asset_type": "conversational", "mime_type": "application/vnd.labelbox.conversational.model-chat-evaluation", "labelable_ids": [ "clwrt16zz00033b6p6ud7w233", "clwrt1fw700063b6pe8zpo1xp", "clwrt1fw700073b6p8k3vkh1x" ], "message_count": 3 }, "attachments": [], "metadata_fields": [], "projects": { "clwrm92jc0grc07wncq654q5s": { "name": "Multi modal chat evalution", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clwrsyrdp02mr07x5378j3ysr", "label_details": { "created_at": "2024-05-29T12:33:21.000+00:00", "updated_at": "2024-05-29T12:33:21.000+00:00", "created_by": "user@labelbox.com", "content_last_updated_at": "2024-05-29T12:33:20.787+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 250, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "clwrt25ar00093b6po9g7fiu3", "feature_schema_id": "clwkfbcnn01kl07z68er64ffi", "name": "Rank given prompt", "annotation_kind": "MessageRanking", "classifications": [], "ranked_messages": { "parent_message_id": "clwrt16zz00033b6p6ud7w233", "ranked_messages": [ { "message_id": "clwrt1fw700063b6pe8zpo1xp", "order": 1, "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" }, { "message_id": "clwrt1fw700073b6p8k3vkh1x", "order": 2, "model_config_id": "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2" } ] } }, { "feature_id": "clwrt2dru000b3b6pkj3i5zxk", "feature_schema_id": "clwknckmp030v07xp6kow09xd", "name": "Subject relevance", "annotation_kind": "MessageMultiSelection", "classifications": [], "selected_messages": { "parent_message_id": "clwrt16zz00033b6p6ud7w233", "messages": [ { "message_id": "clwrt1fw700063b6pe8zpo1xp", "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" } ] } } ], "classifications": [], "relationships": [] } } ], "project_details": { "ontology_id": "clwkn6leb034b07waeolfb7tl", "task_id": "0c392f64-bc65-44e6-8848-ae5a12000328", "task_name": "Initial review task", "batch_id": "c2267a60-1d9c-11ef-9edf-ed6f8eede535", "batch_name": "batch_clwrm92jc0grc07wncq654q5s_clwrm9eph006d0750b3s5b2cr", "workflow_status": "IN_REVIEW", "priority": 5, "consensus_expected_label_count": 2, "workflow_history": [ { "action": "Move", "created_at": "2024-05-29T12:33:21.160+00:00", "created_by": "user@labelbox.com", "previous_task_name": "Initial labeling task", "previous_task_id": "ebf41e2b-e231-0e43-adee-92252c1858dd", "next_task_name": "Initial review task", "next_task_id": "0c392f64-bc65-44e6-8848-ae5a12000328" }, { "action": "Move", "created_at": "2024-05-29T12:33:21.151+00:00", "created_by": "user@labelbox.com", "next_task_name": "Initial labeling task", "next_task_id": "ebf41e2b-e231-0e43-adee-92252c1858dd" } ] }, "project_model_configs": { "models": { "5d9647e7-aafe-4275-944d-c2e9b122105a": { "name": "Google Gemini Pro Vision", "created_at": "2023-12-13T21:51:42.657+00:00" }, "036aeb03-b0b4-442e-a6e1-161382bc1228": { "name": "OpenAI GPT-4o", "created_at": "2024-05-23T23:56:03.078+00:00" } }, "model_configs": { "e606593e-def3-4662-9737-3fc4ac412dd3": { "name": "GPT_4o_multi_modal", "model_id": "036aeb03-b0b4-442e-a6e1-161382bc1228", "inference_params": { "top_p": 1, "detail": "high", "temperature": 0, "use_text_only": false, "max_new_tokens": 1024, "use_image_only": false, "use_attachments": false } }, "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2": { "name": "Gemini_pro_vison_multi_modal", "model_id": "5d9647e7-aafe-4275-944d-c2e9b122105a", "inference_params": { "use_attachments": false } } }, "project_model_configs": [ { "id": "clwrsxr1y009i07v93w2h45qm", "model_id": "036aeb03-b0b4-442e-a6e1-161382bc1228", "model_config_id": "e606593e-def3-4662-9737-3fc4ac412dd3" }, { "id": "clwrsyext00ar07v5czpo7w2a", "model_id": "5d9647e7-aafe-4275-944d-c2e9b122105a", "model_config_id": "8fcda464-a0c7-4300-a9fa-c9a5cf82cab2" } ] }, "project_tags": [] } } ``` # Export text annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-text-annotations How to export text annotations and sample export formats. ## Export JSON annotations ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Text entity To include the `token` field in your export, set `data_row_details` to `True` when exporting the data. ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutx70x20onv148pb5ah49lb", "feature_schema_id": "clutx6n1g00r707y110ruh99w", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20, "token": "um dolor si" } } ``` ### Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutx70x20onw148p7mlgpq90", "feature_schema_id": "clutx6n1h00r907y16s49do9e", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "clutx70x20onx148px2679qsp", "feature_schema_id": "clutx6n1h00ra07y100njafnj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ``` ### Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "clutx70x20ony148p6gxk7ec8", "feature_schema_id": "clutx6n1h00rt07y16xe27hhv", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20onz148pjjl5r8m5", "feature_schema_id": "clutx6n1h00ru07y1hgvd3ekt", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clutx70x20oo0148p3w6es43a", "feature_schema_id": "clutx6n1h00rw07y16t1v8tvi", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "clutx70x20oo1148ps1lpz6dl", "feature_schema_id": "clutx6n1h00ry07y1fkbk0pde", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] } ``` ### Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "clpsmujyn4z13143s2iw9n15v", "feature_schema_id": "clpsmu6sz003v07zp2vie3zd8", "name": "free_text", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh5lsju0bd90763h2o22vvq", "global_key": "lorem-ipsum.txt", "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "details": { "dataset_id": "clfh5lpul063507x4a16r5j6x", "dataset_name": "text_annotation_import_demo_dataset", "created_at": "2023-03-20T18:22:00.000+00:00", "updated_at": "2023-03-20T18:22:00.000+00:00", "last_activity_at": "2024-04-10T14:43:10.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "text/plain" }, "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", "id": "clfh5lsjv0bdd07638apx7bf5" } ], "metadata_fields": [], "embeddings": [ { "id": "c300000000000000000000000", "name": "Text Embedding V2 (All-MPNet-base-v2)", "dimensions": 768, "is_custom": false, "values": [ { "value": [ 0.04099537059664726, 0.03790362924337387, -0.013698477298021317, 0.038478486239910126, -0.01930154114961624, 0.05670716613531113, 0.02421298250555992 ... ], "projects": { "clutx6nli00ks07x6efh6e1pw": { "name": "Text Annotation Import Demo", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clutx70xi0oob148pgr7sz5v0", "label_details": { "created_at": "2024-04-10T14:43:11.000+00:00", "updated_at": "2024-04-10T14:43:11.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-04-10T14:43:10.580+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "objects": [ { "feature_id": "clutx70x20onv148pb5ah49lb", "feature_schema_id": "clutx6n1g00r707y110ruh99w", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20, "token": "um dolor si" } } ], "classifications": [ { "feature_id": "clutx70x20onw148p7mlgpq90", "feature_schema_id": "clutx6n1h00r907y16s49do9e", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "clutx70x20onx148px2679qsp", "feature_schema_id": "clutx6n1h00ra07y100njafnj", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "clutx70x20ony148p6gxk7ec8", "feature_schema_id": "clutx6n1h00rt07y16xe27hhv", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20onz148pjjl5r8m5", "feature_schema_id": "clutx6n1h00ru07y1hgvd3ekt", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "clutx70x20oo0148p3w6es43a", "feature_schema_id": "clutx6n1h00rw07y16t1v8tvi", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "clutx70x20oo1148ps1lpz6dl", "feature_schema_id": "clutx6n1h00ry07y1fkbk0pde", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] } ] }, { "feature_id": "clutx70x20oo2148pz466o8ep", "feature_schema_id": "clutx6n1i00s107y18bywdwuh", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "clutx70x20oo3148poaesoyw7", "feature_schema_id": "clutx6n1h00rl07y12xox0uqk", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20oo4148pgxhy7xr3", "feature_schema_id": "clutx6n1h00rm07y15qdj4mms", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "clutx70x20oo5148plvcm1yhy", "feature_schema_id": "clutx6n1h00rn07y13xi0e1au", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "clutx70x20oo6148pcqjahziw", "feature_schema_id": "clutx6n1h00ro07y164ddfda2", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "clutx70x20oo7148pkei2ymj3", "feature_schema_id": "clutx6n1h00rd07y188n4hd08", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "clutx70x20oo8148pjiiobfl5", "feature_schema_id": "clutx6n1h00re07y1b7v1dd8z", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "clutx70x20oo9148px2xcuanp", "feature_schema_id": "clutx6n1h00rf07y18f8f45nu", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "clutx70x20ooa148pqhs0hmot", "feature_schema_id": "clutx6n1h00rg07y1fkkjgr7o", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } } ], "relationships": [] } } ], "project_details": { "ontology_id": "clutx6n0k00r607y1a0a9bkzj", "task_name": "Done", "batch_id": "9f1c1db0-f748-11ee-b74f-f180a0c567c8", "batch_name": "first-batch-text-demo", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] }, "project_tags": [] } } } ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clfh5lsju0bd90763h2o22vvq", "global_key": "lorem-ipsum.txt", "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "details": { "dataset_id": "clfh5lpul063507x4a16r5j6x", "dataset_name": "text_annotation_import_demo_dataset", "created_at": "2023-03-20T18:22:00.000+00:00", "updated_at": "2023-03-20T18:22:00.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "mime_type": "text/plain" }, "attachments": [ { "type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt", "id": "clfh5lsjv0bdd07638apx7bf5" } ], "metadata_fields": [], "experiments": { "a349da10-5d85-0c82-293d-8165aa611e7c": { "name": "text_model_run_8ad83b1f-c9c6-4ea4-83ce-a287393bb917", "runs": { "a349da10-aa0d-0879-711b-11f0cc035b65": { "name": "iteration 1", "run_data_row_id": "e1898158-c2a9-42fb-8c64-2c81beadafed", "labels": [ { "label_kind": "Default", "version": "1.0.0", "id": "clr16pmfw0g4o140zkwys3f7u", "annotations": { "objects": [ { "feature_id": "6d651dde-6567-0669-83e6-7a6677a5acb2", "feature_schema_id": "clr16p71n018r07tuawb18jt6", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20 } } ], "classifications": [ { "feature_id": "08a38f86-5cd1-0feb-ae98-5731b2d2d3bf", "feature_schema_id": "clr16p71p019f07tucv4g1cmm", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "3b97e87d-b196-0c21-a236-8b64f3f08086", "feature_schema_id": "clr16p71p019g07tu9sca1dde", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "8301a2fe-e898-044f-bc8b-a75e48b16f45", "feature_schema_id": "clr16p71p019h07tu5j9o7b4o", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "baea7ccd-57a6-0c4f-bc66-1ddc2a8aa2d9", "feature_schema_id": "clr16p71p019i07tu50tq966e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "60fd8cc0-b5a6-013b-b02b-d91adafb45c1", "feature_schema_id": "clr16p71o018t07tubkih92ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "f0211ed1-4139-08ee-8c35-ea2bcad92b79", "feature_schema_id": "clr16p71o018u07tu8bwc76w1", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "73beac75-7e6c-0805-a148-2f6b7465f468", "feature_schema_id": "clr16p71p019d07tufulw888j", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "7a87c79e-6213-032c-859b-547e039ceff7", "feature_schema_id": "clr16p71o018x07tuf7sj7c0x", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "3aead946-dafd-0f41-8609-826d0d702d68", "feature_schema_id": "clr16p71o018y07tu9oevflh8", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "ab632136-83c1-0f3e-a4af-87da69b86624", "feature_schema_id": "clr16p71o018z07tuas953oef", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "2823619a-03bf-0b27-a12e-69f869d8e334", "feature_schema_id": "clr16p71o019007tu8htocx3x", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "bd11b44d-3c8d-089d-9531-5177047736b7", "feature_schema_id": "clr16p71p019507tu0x8d0vu4", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "360c8a23-3a74-0e75-9553-286b1b152100", "feature_schema_id": "clr16p71p019a07tuetmgfe7c", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "4e12f117-9f03-0a51-8b5d-721b88ce4be0", "feature_schema_id": "clr16p71p019607tu4hkkbhxv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "501d328c-c0ea-0142-8809-ecd1b19961eb", "feature_schema_id": "clr16p71p019807tu3ya04d5x", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ], "relationships": [] } } ], "predictions": [ { "label_kind": "Default", "version": "1.0.0", "id": "clr16pmfw0g4o140zkwys3f7u", "annotations": { "objects": [ { "feature_id": "4bafa398-69d8-4bf8-a3f0-834869ab3e5f", "feature_schema_id": "clr16p71n018r07tuawb18jt6", "name": "named_entity", "value": "named_entity", "annotation_kind": "TextEntity", "classifications": [], "location": { "start": 10, "end": 20 } } ], "classifications": [ { "feature_id": "31737a80-4a5c-4c40-8362-8d59ae6c2047", "feature_schema_id": "clr16p71o018x07tuf7sj7c0x", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "a00eca18-fb50-4130-a33b-d505ba8e3d04", "feature_schema_id": "clr16p71o018y07tu9oevflh8", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "94b4ee14-2837-4fb5-b097-4e59cb92b07f", "feature_schema_id": "clr16p71o018z07tuas953oef", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "a5863817-4c81-424a-8c4c-4cdec05508bf", "feature_schema_id": "clr16p71o019007tu8htocx3x", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "741288f5-76ab-4cdf-9277-0f97f0e426fd", "feature_schema_id": "clr16p71o018t07tubkih92ua", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "64065994-7568-46e1-8bfc-bd96d20384cf", "feature_schema_id": "clr16p71o018u07tu8bwc76w1", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "a66cc378-aa78-4250-bde1-28da1310bedb", "feature_schema_id": "clr16p71p019507tu0x8d0vu4", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "6db15123-a2dd-4722-8253-32a0c0236850", "feature_schema_id": "clr16p71p019607tu4hkkbhxv", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "8eb5629e-8ae1-44f1-b369-491c5c61ea02", "feature_schema_id": "clr16p71p019a07tuetmgfe7c", "name": "third_checklist_answer", "value": "third_checklist_answer", "classifications": [] }, { "feature_id": "fdb2574a-fea7-4127-9ee1-b112126f117a", "feature_schema_id": "clr16p71p019807tu3ya04d5x", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "a8386f96-ae13-45c0-bb35-033380f86dc5", "feature_schema_id": "clr16p71p019d07tufulw888j", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } }, { "feature_id": "db0150e9-ecb5-4bad-8350-c82446244657", "feature_schema_id": "clr16p71p019f07tucv4g1cmm", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "84f14b81-301f-41fd-a842-6d48594a40fc", "feature_schema_id": "clr16p71p019g07tu9sca1dde", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "8367c018-cba4-40ff-85e5-c06b137773c3", "feature_schema_id": "clr16p71p019h07tu5j9o7b4o", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "c1e2da03-c23d-4ffa-a25c-e6b6c13a1dae", "feature_schema_id": "clr16p71p019i07tu50tq966e", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] } ], "relationships": [] } } ] } } } }, "models": {}, "embeddings": [ { "id": "c300000000000000000000000", "name": "Text Embedding V2 (All-MPNet-base-v2)", "dimensions": 768, "is_custom": false, "values": [ { "value": [ 0.04099537059664726, 0.03790362924337387, -0.013698477298021317, 0.038478486239910126, -0.01930154114961624, 0.05670716613531113, 0.02421298250555992 ... ] } ``` # Export video annotations Source: https://docs.labelbox.com/horizon/developer-guides/export-video-annotations How to export video annotations and sample export formats. ## Export as JSON ```python Export theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "" } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ## Annotation export formats ### Bounding box ```json JSON theme={"theme":"css-variables"} { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } } ``` ### Point ```json JSON theme={"theme":"css-variables"} { "feature_id": "cltd97cag01to14yslakirofl", "feature_schema_id": "cltd977no037l07x56wfu4536", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } } ``` ### Polyline ```json JSON theme={"theme":"css-variables"} { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } ``` ### Mask Regardless of which [tool](/horizon/guides/image-editor#segmentation-mask) is used to create a segmentation mask, the annotation will appear in the following form in the export. Whether you use AutoSegment, the brush tool, the pen tool, or the fill tool, all masks take the same format in exports. #### Composite masks [Open this Colab](https://colab.research.google.com/github/Labelbox/labelbox-notebooks/blob/main/exports/composite_mask_export.ipynb) for an interactive tutorial on exporting composite masks. A composite mask is a cloud-hosted PNG file that contains all raster segmentation mask instances on a labeled data row (one composite mask per label). When you export data rows containing segmentation masks, you can use `composite_mask.url` to access all segmentation masks on that data row. To access a single instance of a segmentation mask, use the `color_rgb` values. The `color_rgb` value represents the color of a single annotation or instance mask in a label. ##### Sample composite masks export ```json JSON theme={"theme":"css-variables"} { "feature_id": "clsivokxg00043b70xuf7x5tz", "feature_schema_id": "clsgbcaqw000j071t2ga23jnc", "name": "car", "value": "car", "annotation_kind": "ImageSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/clsivnieg050k074i2jqaa0c3/annotations/clsivokxg00043b70xuf7x5tz/index/1/mask" }, "composite_mask": { // This URL contains all mask instances on the data row "url": "https://api.labelbox.com/api/v1/tasks/clsiw88n205mw072ke4n78y5o/masks/clsivok5f00023b70r1a4fgzy", // This RGB reflects a single mask instance "color_rgb": [ 31, 185, 101 ] } } ``` #### Access mask data To access the mask data, you must pass your Labelbox API key stored in `client.headers` in an API request. When you grab a URL from the mask annotation in the export, the `project_id` and `feature_id` will already be in place, as shown above. Here, we provide the framework for structuring a URL with any project ID and feature ID. ```python Python theme={"theme":"css-variables"} import urllib.request from PIL import Image # Provide a project ID and feature ID if using instance masks. Alternatively, replace the entire mask_url with a URL grabbed from your export. project_id = "" feature_id = "" mask_url = f"https://api.labelbox.com/api/v1/projects/{project_id}/annotations/{feature_id}/index/1/mask" # Instead of providing the individual variables, we recommend utilizing the URL from export for composite mask composite_mask_url = f"https://api.labelbox.com/api/v1/tasks/{task_id}/masks/{composite_mask_id}/index/1" # Make the API request req = urllib.request.Request(mask_url, headers=client.headers) # Optionally, print the image of the mask image = Image.open(urllib.request.urlopen(req)) image.show() ``` ### Classification - Radio ```json JSON theme={"theme":"css-variables"} { "feature_id": "cltd97cag01tf14ysho6tfl7j", "feature_schema_id": "cltd977np038907x5ah8l9ha7", "name": "radio_class", "value": "radio_class", "radio_answer": { "feature_id": "cltd97cag01tg14ys171a3qqr", "feature_schema_id": "cltd977np038a07x515axagkl", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ``` ### Classification - Checklist ```json JSON theme={"theme":"css-variables"} { "feature_id": "cltd97cag01tq14ysb4m3nyjo", "feature_schema_id": "cltd977nq039107x5dsml2knm", "name": "checklist_class_global", "value": "checklist_class_global", "checklist_answers": [ { "feature_id": "cltd97cag01tr14ysozjbx0c6", "feature_schema_id": "cltd977nq039207x5ei1605s1", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01ts14ys4vvxihnw", "feature_schema_id": "cltd977nq039407x50qs3g61l", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ``` ### Classification - Free-form text ```json JSON theme={"theme":"css-variables"} { "feature_id": "cltd97cag01u314ysx1h72f0h", "feature_schema_id": "cltd977nq039707x535ufeo39", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ``` ## Sample project export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "clp1jolki07uy0725flku4bmv", "global_key": "sample-video-jellyfish.mp4", "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "details": { "dataset_id": "clp1jokqp000p07307gtfy1w3", "dataset_name": "video_demo_dataset", "created_at": "2023-11-16T18:48:51.208+00:00", "updated_at": "2023-11-16T18:48:51.801+00:00", "last_activity_at": "2024-04-08T17:09:54.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "height": 1080, "width": 1920, "mime_type": "video/mp4", "frame_rate": 10, "frame_count": 100 }, "attachments": [], "metadata_fields": [], "embeddings": [ { "id": "c400000000000000000000000", "name": "Video Embedding V2 (Google MultiModal)", "dimensions": 1408, "is_custom": false, "values": [ { "value": [ -0.00489852577, 0.0287129097 ... ] } ] } ], "projects": { "cltd977uu016y08xyggep8aiv": { "name": "Video Annotation Import Demo", "labels": [ { "label_kind": "Video", "version": "1.0.0", "id": "cltd97caz01ub14ysq8rf4x8w", "label_details": { "created_at": "2024-03-04T18:07:33.000+00:00", "updated_at": "2024-03-04T18:07:33.000+00:00", "created_by": "[email protected]", "content_last_updated_at": "2024-03-04T18:07:33.369+00:00", "reviews": [] }, "performance_details": { "seconds_to_create": 0, "seconds_to_review": 0, "skipped": false }, "annotations": { "frames": { "29": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tc14yspm0lev2d", "feature_schema_id": "cltd977np038307x55dlf3rgs", "name": "checklist_class", "value": "checklist_class", "checklist_answers": [ { "feature_id": "cltd97cag01td14ysxw2n8xkz", "feature_schema_id": "cltd977np038407x51hcvdad7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01te14ys1jdf1fku", "feature_schema_id": "cltd977np038607x58epu4nb5", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ] }, "35": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tc14yspm0lev2d", "feature_schema_id": "cltd977np038307x55dlf3rgs", "name": "checklist_class", "value": "checklist_class", "checklist_answers": [ { "feature_id": "cltd97cag01td14ysxw2n8xkz", "feature_schema_id": "cltd977np038407x51hcvdad7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01te14ys1jdf1fku", "feature_schema_id": "cltd977np038607x58epu4nb5", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] } ] }, "9": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.00000000000001 }, { "x": 100.00000000000001, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tf14ysho6tfl7j", "feature_schema_id": "cltd977np038907x5ah8l9ha7", "name": "radio_class", "value": "radio_class", "radio_answer": { "feature_id": "cltd97cag01tg14ys171a3qqr", "feature_schema_id": "cltd977np038a07x515axagkl", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ] }, "15": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [ { "feature_id": "cltd97cag01tf14ysho6tfl7j", "feature_schema_id": "cltd977np038907x5ah8l9ha7", "name": "radio_class", "value": "radio_class", "radio_answer": { "feature_id": "cltd97cag01tg14ys171a3qqr", "feature_schema_id": "cltd977np038a07x515axagkl", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } } ] }, "13": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [ { "feature_id": "cltd97cag01tj14ysz455nenr", "feature_schema_id": "cltd977np037s07x5csbodf5f", "name": "checklist_class", "value": "checklist_class", "radio_answer": { "feature_id": "cltd97cag01tl14ysnbe8mw75", "feature_schema_id": "cltd977np037v07x54esmdmws", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } } ], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "14": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0000000000001, "left": 1371.0, "height": 418.9999999999999, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "16": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "17": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01to14yslakirofl": { "feature_id": "cltd97cag01to14yslakirofl", "feature_schema_id": "cltd977no037l07x56wfu4536", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "18": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "19": { "objects": { "cltd97cag01th14ys6fyyxyk9": { "feature_id": "cltd97cag01th14ys6fyyxyk9", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "10": { "objects": { "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "11": { "objects": { "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [ { "feature_id": "cltd97cag01tj14ysz455nenr", "feature_schema_id": "cltd977np037s07x5csbodf5f", "name": "checklist_class", "value": "checklist_class", "radio_answer": { "feature_id": "cltd97cag01tk14ysejipk2y6", "feature_schema_id": "cltd977np037t07x5966ebxpe", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } } ], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "12": { "objects": { "cltd97cag01ti14ys8wedf89d": { "feature_id": "cltd97cag01ti14ys8wedf89d", "feature_schema_id": "cltd977np037r07x512iteflz", "name": "bbox_class", "value": "bbox_class", "annotation_kind": "VideoBoundingBox", "classifications": [ { "feature_id": "cltd97cag01tj14ysz455nenr", "feature_schema_id": "cltd977np037s07x5csbodf5f", "name": "checklist_class", "value": "checklist_class", "radio_answer": { "feature_id": "cltd97cag01tk14ysejipk2y6", "feature_schema_id": "cltd977np037t07x5966ebxpe", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } } ], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "22": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] }, "23": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0000000000002 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.00000000000006 } } }, "classifications": [] }, "24": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] }, "cltd97cag01u514ys2bfg9vvo": { "feature_id": "cltd97cag01u514ys2bfg9vvo", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cag01u514ys2bfg9vvo/index/24/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/24", "color_rgb": [ 76, 104, 177 ] } }, "cltd97cah01u614ysvntxcyub": { "feature_id": "cltd97cah01u614ysvntxcyub", "feature_schema_id": "cltd977np037z07x5gclv41y4", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u614ysvntxcyub/index/24/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/24", "color_rgb": [ 233, 218, 94 ] } }, "cltd97cah01u714ysbqbvqs6b": { "feature_id": "cltd97cah01u714ysbqbvqs6b", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u714ysbqbvqs6b/index/24/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/24", "color_rgb": [ 83, 152, 103 ] } } }, "classifications": [] }, "25": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 99.99999999999999 }, { "x": 99.99999999999999, "y": 189.99999999999997 } ] } }, "classifications": [] }, "26": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] }, "cltd97cah01ua14ysr6u1sh7w": { "feature_id": "cltd97cah01ua14ysr6u1sh7w", "feature_schema_id": "cltd977np037z07x5gclv41y4", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01ua14ysr6u1sh7w/index/26/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/26", "color_rgb": [ 226, 211, 41 ] } } }, "classifications": [] }, "27": { "objects": { "cltd97cag01tm14ys8tm4x7yl": { "feature_id": "cltd97cag01tm14ys8tm4x7yl", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 617.0, "left": 1371.0, "height": 419.0, "width": 505.0 } }, "cltd97cag01tn14yspqge5yph": { "feature_id": "cltd97cag01tn14yspqge5yph", "feature_schema_id": "cltd977no037j07x5d82dacbc", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } }, "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "5": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "6": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "7": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.00000000000003 } ] } }, "classifications": [] }, "8": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "20": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] }, "cltd97cag01u514ys2bfg9vvo": { "feature_id": "cltd97cag01u514ys2bfg9vvo", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cag01u514ys2bfg9vvo/index/20/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/20", "color_rgb": [ 76, 104, 177 ] } }, "cltd97cah01u614ysvntxcyub": { "feature_id": "cltd97cah01u614ysvntxcyub", "feature_schema_id": "cltd977np037z07x5gclv41y4", "name": "mask_with_text_subclass", "value": "mask_with_text_subclass", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u614ysvntxcyub/index/20/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/20", "color_rgb": [ 233, 218, 94 ] } }, "cltd97cah01u714ysbqbvqs6b": { "feature_id": "cltd97cah01u714ysbqbvqs6b", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u714ysbqbvqs6b/index/20/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/20", "color_rgb": [ 83, 152, 103 ] } } }, "classifications": [] }, "28": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "30": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "31": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "32": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "33": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "34": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "36": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "37": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "38": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "39": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "40": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "41": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "42": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "43": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "44": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "45": { "objects": { "cltd97cag01tp14ysz3my848t": { "feature_id": "cltd97cag01tp14ysz3my848t", "feature_schema_id": "cltd977np037n07x5d1buckz9", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 } ] } }, "classifications": [] }, "1": { "objects": { "cltd97cah01u814ys0k41cttu": { "feature_id": "cltd97cah01u814ys0k41cttu", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u814ys0k41cttu/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/1", "color_rgb": [ 169, 248, 152 ] } }, "cltd97cah01u914ysy7t58lvy": { "feature_id": "cltd97cah01u914ysy7t58lvy", "feature_schema_id": "cltd977np037p07x55hu71x6k", "name": "video_mask", "value": "video_mask", "annotation_kind": "VideoSegmentationMask", "classifications": [], "mask": { "url": "https://api.labelbox.com/api/v1/projects/cltd977uu016y08xyggep8aiv/annotations/cltd97cah01u914ysy7t58lvy/index/1/mask" }, "composite_mask": { "url": "https://api.labelbox.com/api/v1/tasks/clur7t5d501i707wfawrp050b/masks/cltd97a7h01tb14ysldafd9es/index/1", "color_rgb": [ 227, 135, 126 ] } } }, "classifications": [] } }, "segments": { "cltd97cag01td14ysxw2n8xkz": [ [ 29, 35 ] ], "cltd97cag01te14ys1jdf1fku": [ [ 29, 35 ] ], "cltd97cag01tg14ys171a3qqr": [ [ 9, 15 ] ], "cltd97cag01th14ys6fyyxyk9": [ [ 13, 19 ] ], "cltd97cag01ti14ys8wedf89d": [ [ 10, 13 ] ], "cltd97cag01tm14ys8tm4x7yl": [ [ 22, 27 ] ], "cltd97cag01tn14yspqge5yph": [ [ 22, 27 ] ], "cltd97cag01to14yslakirofl": [ [ 17, 17 ] ], "cltd97cag01tp14ysz3my848t": [ [ 5, 20 ], [ 24, 45 ] ] }, "key_frame_feature_map": { "cltd97cag01tc14yspm0lev2d": [ 29, 35 ], "cltd97cag01tp14ysz3my848t": [ 24, 5, 20, 45 ], "cltd97cag01tf14ysho6tfl7j": [ 9, 15 ], "cltd97cag01th14ys6fyyxyk9": [ 13, 19 ], "cltd97cag01ti14ys8wedf89d": [ 13, 10 ], "cltd97cag01to14yslakirofl": [ 17 ], "cltd97cag01tm14ys8tm4x7yl": [ 22, 27 ], "cltd97cag01tn14yspqge5yph": [ 22, 27 ], "cltd97cag01u514ys2bfg9vvo": [ 24, 20 ], "cltd97cah01u614ysvntxcyub": [ 24, 20 ], "cltd97cah01u714ysbqbvqs6b": [ 24, 20 ], "cltd97cah01ua14ysr6u1sh7w": [ 26 ], "cltd97cah01u814ys0k41cttu": [ 1 ], "cltd97cah01u914ysy7t58lvy": [ 1 ] }, "classifications": [ { "feature_id": "cltd97cag01tq14ysb4m3nyjo", "feature_schema_id": "cltd977nq039107x5dsml2knm", "name": "checklist_class_global", "value": "checklist_class_global", "checklist_answers": [ { "feature_id": "cltd97cag01tr14ysozjbx0c6", "feature_schema_id": "cltd977nq039207x5ei1605s1", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] }, { "feature_id": "cltd97cag01ts14ys4vvxihnw", "feature_schema_id": "cltd977nq039407x50qs3g61l", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] } ] }, { "feature_id": "cltd97cag01tt14yscmqh2izy", "feature_schema_id": "cltd977nq038v07x57qz18lnj", "name": "radio_class_global", "value": "radio_class_global", "radio_answer": { "feature_id": "cltd97cag01tu14ysq046ad6p", "feature_schema_id": "cltd977nq038w07x5bxw3exp9", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [] } }, { "feature_id": "cltd97cag01tv14ys1y3u4jre", "feature_schema_id": "cltd977nq038n07x52t8s0j1f", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "cltd97cag01tw14ysieenbrzy", "feature_schema_id": "cltd977nq038o07x528j39vc4", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "cltd97cag01tx14ysaqs15mjh", "feature_schema_id": "cltd977nq038p07x5bfuf7v56", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "cltd97cag01ty14yshujgka3h", "feature_schema_id": "cltd977nq038q07x52m1h32mj", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "cltd97cag01tz14ys73p2w3oa", "feature_schema_id": "cltd977nq038f07x5bgrc2fm6", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "cltd97cag01u014ysho9dljue", "feature_schema_id": "cltd977nq038g07x50ewqbbi3", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "cltd97cag01u114ysktcruoro", "feature_schema_id": "cltd977nq038h07x59zx07wfe", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "cltd97cag01u214ysxi71u4qi", "feature_schema_id": "cltd977nq038i07x5et0717w6", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "cltd97cag01u314ysx1h72f0h", "feature_schema_id": "cltd977nq039707x535ufeo39", "name": "free_text", "value": "free_text", "text_answer": { "content": "sample text" } } ] } } ], "project_details": { "ontology_id": "cltd977mx037i07x52amua8iw", "task_name": "Done", "batch_id": "1026dd60-da52-11ee-9774-cfce344359c8", "batch_name": "first-batch-video-demo2", "workflow_status": "DONE", "priority": 5, "consensus_expected_label_count": 1, "workflow_history": [] }, "project_tags": [] } } } ``` ## Sample model run export ```json JSON expandable theme={"theme":"css-variables"} { "data_row": { "id": "cldtd2nai107407y5979xbu97", "global_key": "3548cfa1-1a42-40f9-a246-2aa7c696dcbe", "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "details": { "dataset_id": "cldtd2mox11dz07wh3gkk9awx", "dataset_name": "video_prediction_demo", "created_at": "2023-02-06T22:04:54.000+00:00", "updated_at": "2023-02-06T22:04:54.000+00:00", "created_by": "[email protected]" } }, "media_attributes": { "height": 1080, "width": 1920, "mime_type": "video/mp4", "frame_rate": 10, "frame_count": 100 }, "attachments": [], "metadata_fields": [], "experiments": { "a088408e-81af-0152-f2ca-0b870c7a2bfa": { "name": "video_model_run_8d7dbe3d-30e0-4c9c-becd-26afb2386bc9", "runs": { "a088408e-ce36-09c1-cbd9-d43c677f7773": { "name": "iteration 1", "run_data_row_id": "999a247e-0b57-415e-af7e-d3a87cc6559b", "labels": [ { "label_kind": "Video", "version": "1.0.0", "id": "cldteelzh08us1331q4pzj2ke", "annotations": { "frames": { "17": { "objects": { "12a89fb9-73e4-4c5e-93c1-a8727e51c122": { "feature_id": "12a89fb9-73e4-4c5e-93c1-a8727e51c122", "feature_schema_id": "cldtd2nmm107i07y53ii0d6ly", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } } }, "classifications": [] }, "5": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 }, { "x": 190.0, "y": 220.0 } ] }, "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": { "feature_id": "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] }, "12": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 280.0 }, { "x": 300.0, "y": 380.0 }, { "x": 400.0, "y": 460.0 } ] } }, "classifications": [] }, "20": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 180.0 }, { "x": 100.0, "y": 200.0 }, { "x": 200.0, "y": 260.0 } ] } }, "classifications": [] }, "24": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 300.0, "y": 310.0 }, { "x": 330.0, "y": 430.0 } ] } }, "classifications": [] }, "45": { "objects": { "878b4842-d57b-43e4-8327-a0c5295f2a84": { "feature_id": "878b4842-d57b-43e4-8327-a0c5295f2a84", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 600.0, "y": 810.0 }, { "x": 900.0, "y": 930.0 } ] } }, "classifications": [] }, "1": { "objects": { "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": { "feature_id": "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] } }, "segments": { "12a89fb9-73e4-4c5e-93c1-a8727e51c122": [ [ 17, 17 ] ], "878b4842-d57b-43e4-8327-a0c5295f2a84": [ [ 5, 5 ], [ 12, 12 ], [ 20, 20 ], [ 24, 24 ], [ 45, 45 ] ], "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": [ [ 5, 5 ], [ 1, 1 ] ] }, "key_frame_feature_map": { "12a89fb9-73e4-4c5e-93c1-a8727e51c122": [ 17 ], "878b4842-d57b-43e4-8327-a0c5295f2a84": [ 5, 12, 20, 24, 45 ], "9cd6cd8e-0511-42a9-bc2f-726cf5c8f983": [ 5, 1 ] }, "classifications": [ { "feature_id": "3644a35a-8aaa-4905-a2d2-9abac1f3dacb", "feature_schema_id": "cldtd2nmm107s07y57ddy5wnn", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "86805980-7632-4dd0-9669-299730927326", "feature_schema_id": "cldtd2nmm107v07y5cmixhywh", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "c5bfb5c1-aa9a-4a6a-b671-be7c20df4b19", "feature_schema_id": "cldtd2nmm107t07y5cqdw94z7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "3d302a82-cc91-41d9-8c69-0f1e2e66ea71", "feature_schema_id": "cldtd2nmm107m07y5h45sg6av", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "dab3bcfa-6c44-4f28-945b-1bc324edfab1", "feature_schema_id": "cldtd2nmm107p07y542ff3hvd", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } }, { "feature_id": "6dede674-18a6-4b95-abdd-13b3713f994a", "feature_schema_id": "cldtd2nmm108607y5ghou4ma6", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "9f748713-ffb5-44db-a16e-396676dab6bc", "feature_schema_id": "cldtd2nmm108707y50shn5996", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "ca1f2173-38c6-4c23-96ba-8a3feb54e2c2", "feature_schema_id": "cldtd2nmm108807y52ps9d5m4", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "2181d943-8a6a-4aef-aae0-4c28146e9008", "feature_schema_id": "cldtd2nmm108907y5269ua7bz", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "b72dd664-93fc-46d1-8bd8-8dfe1fd2d7ef", "feature_schema_id": "cldtd2nmm107y07y5fvhkb6c7", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "5fc17086-601e-42e5-a5b1-a28eb96841ab", "feature_schema_id": "cldtd2nmm107z07y59j8n62ym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "a74e9958-345f-4b42-925d-9d5938011cf6", "feature_schema_id": "cldtd2nmm108007y5gs1y5eqv", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "734a2cf1-571d-4855-869e-d92ff20a4cb4", "feature_schema_id": "cldtd2nmm108107y53dvreuv2", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } } ] } } ], "predictions": [ { "label_kind": "Video", "version": "1.0.0", "id": "cldteelzh08us1331q4pzj2ke", "annotations": { "frames": { "5": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 100.0 }, { "x": 100.0, "y": 190.0 }, { "x": 190.0, "y": 220.0 } ] }, "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": { "feature_id": "a505f426-5a2e-4dea-b4fe-ab913e1d2a99", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] }, "12": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 280.0 }, { "x": 300.0, "y": 380.0 }, { "x": 400.0, "y": 460.0 } ] } }, "classifications": [] }, "20": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 680.0, "y": 180.0 }, { "x": 100.0, "y": 200.0 }, { "x": 200.0, "y": 260.0 } ] } }, "classifications": [] }, "24": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 300.0, "y": 310.0 }, { "x": 330.0, "y": 430.0 } ] } }, "classifications": [] }, "45": { "objects": { "1306f719-d588-4154-8335-792347bfdee2": { "feature_id": "1306f719-d588-4154-8335-792347bfdee2", "feature_schema_id": "cldtd2nmm107k07y577q807az", "name": "line_video_frame", "value": "line_video_frame", "annotation_kind": "VideoPolyline", "classifications": [], "line": [ { "x": 600.0, "y": 810.0 }, { "x": 900.0, "y": 930.0 } ] } }, "classifications": [] }, "17": { "objects": { "89b4636f-08cc-4fa2-832b-18bcda636722": { "feature_id": "89b4636f-08cc-4fa2-832b-18bcda636722", "feature_schema_id": "cldtd2nmm107i07y53ii0d6ly", "name": "point_video", "value": "point_video", "annotation_kind": "VideoPoint", "classifications": [], "point": { "x": 660.134, "y": 407.926 } } }, "classifications": [] }, "1": { "objects": { "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": { "feature_id": "a505f426-5a2e-4dea-b4fe-ab913e1d2a99", "feature_schema_id": "cldtd2nml107a07y51qtydeoj", "name": "bbox_video", "value": "bbox_video", "annotation_kind": "VideoBoundingBox", "classifications": [], "bounding_box": { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } } }, "classifications": [] } }, "segments": { "1306f719-d588-4154-8335-792347bfdee2": [ [ 5, 5 ], [ 12, 12 ], [ 20, 20 ], [ 24, 24 ], [ 45, 45 ] ], "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": [ [ 5, 5 ], [ 1, 1 ] ], "89b4636f-08cc-4fa2-832b-18bcda636722": [ [ 17, 17 ] ] }, "key_frame_feature_map": { "1306f719-d588-4154-8335-792347bfdee2": [ 5, 12, 20, 24, 45 ], "a505f426-5a2e-4dea-b4fe-ab913e1d2a99": [ 5, 1 ], "89b4636f-08cc-4fa2-832b-18bcda636722": [ 17 ] }, "classifications": [ { "feature_id": "5deccc93-922a-4fcd-bf70-0bd90249ed65", "feature_schema_id": "cldtd2nmm108607y5ghou4ma6", "name": "nested_checklist_question", "value": "nested_checklist_question", "checklist_answers": [ { "feature_id": "3d9f3952-c303-42ed-91ba-b112927029c9", "feature_schema_id": "cldtd2nmm108707y50shn5996", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [ { "feature_id": "fe4d0f5f-99e6-4abd-9401-b0ad04fe13ed", "feature_schema_id": "cldtd2nmm108807y52ps9d5m4", "name": "sub_checklist_question", "value": "sub_checklist_question", "checklist_answers": [ { "feature_id": "05fca900-787a-4774-a011-0b6931d9a012", "feature_schema_id": "cldtd2nmm108907y5269ua7bz", "name": "first_sub_checklist_answer", "value": "first_sub_checklist_answer", "classifications": [] } ] } ] } ] }, { "feature_id": "7a92c014-48e1-4367-8ff9-4ec146d9968e", "feature_schema_id": "cldtd2nmm107y07y5fvhkb6c7", "name": "nested_radio_question", "value": "nested_radio_question", "radio_answer": { "feature_id": "9f35c500-be1d-4693-ae9a-71507931d558", "feature_schema_id": "cldtd2nmm107z07y59j8n62ym", "name": "first_radio_answer", "value": "first_radio_answer", "classifications": [ { "feature_id": "d1269040-bf2c-4e8c-8fa6-58c3d6a7e343", "feature_schema_id": "cldtd2nmm108007y5gs1y5eqv", "name": "sub_radio_question", "value": "sub_radio_question", "radio_answer": { "feature_id": "2fecd9bc-98ca-49bd-8db4-cb9d1be6a63d", "feature_schema_id": "cldtd2nmm108107y53dvreuv2", "name": "first_sub_radio_answer", "value": "first_sub_radio_answer", "classifications": [] } } ] } }, { "feature_id": "95085369-6add-4ce1-9641-d6b690490eb3", "feature_schema_id": "cldtd2nmm107s07y57ddy5wnn", "name": "checklist_question", "value": "checklist_question", "checklist_answers": [ { "feature_id": "53a98fa1-19ea-4541-b42f-8df7f7e3b2fc", "feature_schema_id": "cldtd2nmm107v07y5cmixhywh", "name": "second_checklist_answer", "value": "second_checklist_answer", "classifications": [] }, { "feature_id": "6383f391-7ca6-4d99-a855-cdc2e2afa811", "feature_schema_id": "cldtd2nmm107t07y5cqdw94z7", "name": "first_checklist_answer", "value": "first_checklist_answer", "classifications": [] } ] }, { "feature_id": "af2f95fe-6ce6-4e3a-83a8-fd11b1ae0cdc", "feature_schema_id": "cldtd2nmm107m07y5h45sg6av", "name": "radio_question", "value": "radio_question", "radio_answer": { "feature_id": "71d910d5-df6b-4def-b68e-caced48dd25c", "feature_schema_id": "cldtd2nmm107p07y542ff3hvd", "name": "second_radio_answer", "value": "second_radio_answer", "classifications": [] } } ] } } ] } } } }, "models": {}, "embeddings": [ { "id": "c400000000000000000000000", "name": "Video Embedding V2 (Google MultiModal)", "dimensions": 1408, "is_custom": false, "values": [ { "value": [ -0.00489852577, 0.0287129097 ... ] } ] } ] } ``` # Exporting to CSV Source: https://docs.labelbox.com/horizon/developer-guides/exporting-to-csv How-to guide on exporting Labelbox data to a CSV or Pandas-friendly format. Creating a CSV file for your Labelbox data can be difficult, especially if you want to include information on the annotations associated with your label. This guide will provide examples and show the process to get your Labelbox export to a CSV or [Pandas](https://pandas.pydata.org/) friendly format. ### Information This guide will assume you have a basic understanding of Python data structures and interacting with Labelbox exports. ## Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid from pprint import pprint import csv import pandas as pd ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ## Create or select example project The below steps will set up a project that can be used for this demo. Please feel free to delete the code block below and uncomment the code block that fetches your own project directly. For more information on this setup, visit our [quickstart](/horizon/developer-guides/quick-start) guide. ### Create Project ```python Python theme={"theme":"css-variables"} # Create dataset with image data row global_key = str(uuid.uuid4()) test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "global_key": global_key, } dataset = client.create_dataset(name="image-demo-dataset") task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print("Errors:", task.errors) print("Failed data rows:", task.failed_data_rows) # Create ontology ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer"), ], ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), ], ), lb.Classification(class_type=lb.Classification.Type.TEXT, name="free_text"), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option( "first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")], ) ], ) ], ), ], tools=[ # List of Tool objects lb.Tool(tool=lb.Tool.Type.BBOX, name="bounding_box"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option(value="tool_first_sub_radio_answer")], ), ], ), ], ) ontology = client.create_ontology( "Image CSV Demo Ontology", ontology_builder.asdict(), media_type=lb.MediaType.Image, ) # Set up project and connect ontology project = client.create_project(name="Image Annotation Import Demo", media_type=lb.MediaType.Image) project.connect_ontology(ontology) # Send data row towards our project batch = project.create_batch( "image-demo-batch", global_keys=[ global_key ], # paginated collection of data row objects, list of data row ids or global keys priority=1, ) print(f"Batch: {batch}") # Create a label and imported it towards our project radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="second_radio_answer")), ) checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer=[ lb_types.ClassificationAnswer(name="first_checklist_answer"), lb_types.ClassificationAnswer(name="second_checklist_answer"), ]), ) text_annotation = lb_types.ClassificationAnnotation( name="free_text", value=lb_types.Text(answer="sample text"), ) nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer")), ) ], )), ) bbox_annotation = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.Rectangle( start=lb_types.Point(x=1690, y=977), end=lb_types.Point(x=1915, y=1307), ), ) bbox_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", value=lb_types.Rectangle( start=lb_types.Point(x=541, y=933), # x = left, y = top end=lb_types.Point(x=871, y=1124), # x= left + width , y = top + height ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="tool_first_sub_radio_answer")), ) ], ) label = [] annotations = [ radio_annotation, nested_radio_annotation, checklist_annotation, text_annotation, bbox_annotation, bbox_with_radio_subclass_annotation, ] label.append( lb_types.Label(data={"global_key": global_key}, annotations=annotations)) upload_job = lb.LabelImport.create_from_objects( client=client, project_id=project.uid, name="label_import_job" + str(uuid.uuid4()), labels=label, ) upload_job.wait_until_done() print("Errors:", upload_job.errors) print("Status of uploads: ", upload_job.statuses) ``` ### Select project ```python Python theme={"theme":"css-variables"} # PROJECT_ID = None # project = client.get_project(PROJECT_ID) ``` ## CSV format overview To convert our Labelbox JSON data to a format more CSV-friendly, we must first define the needed structure of our JSON. A common format that is versatile for both the built-in Python CSV writer and Pandas is as follows: ```python Python theme={"theme":"css-variables"} [ {"":"":"":"":" Essentially, we need to get our JSON data towards a list of Python dictionaries, with each Python dictionary representing one row, each key representing a column, and each value being an individual cell of our CSV table. Once we have our data in this format, creating Pandas DataFrames or writing our CSV file is trivial. The tricky part is getting Labelbox to export JSON to this format. ## Labelbox JSON format Labelbox JSON format is centralized at the individual data row of your export. This format allows expandability when things evolve and provides a centralized view of fields such as metadata or data row details. The main labels are located inside the project key and can be nested, making it difficult to parse. For complete samples of our project export format, visit our [export overview](/horizon/developer-guides/export-overview) page. To get Labelbox export JSON format to our CSV format, we established we must do the following: 1. Establish our base data row columns (project\_id, data\_row\_id, global\_key etc) 2. Create our columns for label fields (label detail and annotations we care about) 3. Define our functions and strategy used to parse through our data 4. Setting up our main data row handler function 5. Export our data 6. Convert to our desired format ## Step 1: Establish our base columns We first establish our base columns that represent individual data row details. Typically, this column's information can be received from within one or two levels of a Labelbox export per data row. Please modify the below columns if you want to include more. You must update the code later in this guide to pick up any additional columns. ```python Python theme={"theme":"css-variables"} data_row_base_columns = [ "Data Row ID", "Global Key", "External ID", "Project ID", ] ``` ## Step 2: Create our columns for label fields In this step, we define the label details base columns we want to include in our CSV. In this case, we will use the following: ```python Python theme={"theme":"css-variables"} label_base_columns = ["Label ID", "Created By", "Skipped"] ``` We then must establish the annotations we want to include in our columns. The order of our list matters since that is the order in which our columns will be presented. You can approach getting the annotations in a list in a number of ways, including hard defining the columns. We will be mapping between feature\_schema\_ids and our column name. The reason for introducing this mapping is the annotation name can be the same in certain situations, but feature\_schema\_ids are completely unique. This also allows you to change the column names to something other than what is included in the ontology. In the code below, I will be recursively going through the ontology we created to get our feature\_schema\_ids and column names based on the names of the features. In the next step of this guide, we will provide more information on recursion in the context of parsing through JSON or Python dictionaries. ```python Python theme={"theme":"css-variables"} def get_classification_features(classifications: list, class_list=[]) -> None: """Finds classification features inside an ontology recursively and returns them in a list""" for classification in classifications: if "name" in classification: class_list.append({ "feature_schema_id": classification["featureSchemaId"], "column_name": classification["instructions"], }) if "options" in classification: get_classification_features(classification["options"], class_list) return class_list def get_tool_features(tools: list) -> None: """Creates list of tool names from ontology""" tool_list = [] for tool in tools: tool_list.append({ "feature_schema_id": tool["featureSchemaId"], "column_name": tool["name"], }) if "classifications" in tool: tool_list = get_classification_features(tool["classifications"], tool_list) return tool_list ``` ```python Python theme={"theme":"css-variables"} # Get ontology from project and normalized towards python dictionary ontology = project.ontology().normalized class_annotation_columns = get_classification_features( ontology["classifications"]) tool_annotation_columns = get_tool_features(ontology["tools"]) ``` ## Step 3: Define our functions and strategy used to parse through our data Now that our columns are defined, we must develop a strategy for navigating our export data. Review this [sample export](/horizon/developer-guides/export-image-annotations#sample-project-export) to follow along. While creating our columns, it is always best to first check if a key exists in your data row before populating a column. This is especially important for optional fields. In this demo, we will populate the value `None` for anything absent, resulting in a blank cell in our CSV. ### Data row detail base columns The data row details can be accessed within a depth of one or two keys. Below is a function we will use to access the columns we defined. The parameters are the data row, the dictionary row used to make our list, and our base columns list. ```python Python theme={"theme":"css-variables"} def get_base_data_row_columns(data_row: dict[str:str], csv_row: dict[str:str], base_columns: list[str]) -> dict[str:str]: for base_column in base_columns: if base_column == "Data Row ID": csv_row[base_column] = data_row["data_row"]["id"] elif base_column == "Global Key": if ("global_key" in data_row["data_row"]): # Check if global key exists csv_row[base_column] = data_row["data_row"]["global_key"] else: csv_row[base_column] = ( None # If global key does not exist on data row set cell to None. This will create a blank cell on your csv ) elif base_column == "External ID": if ("external_id" in data_row["data_row"]): # Check if external_id exists csv_row[base_column] = data_row["data_row"]["external_id"] else: csv_row[base_column] = ( None # If external id does not exist on data row set cell to None. This will create a blank cell on your csv ) elif base_column == "Project ID": csv_row[base_column] = project.uid return csv_row ``` ### Label detail base columns The label details are similar to data row details but exist at our export's label level. Later in the guide we will demonstrate how to get our exported data row at this level. The function below shows the process of obtaining the details we defined above. The parameters are the label, the dictionary row we will modify, and the label detail column list we created. ```python Python theme={"theme":"css-variables"} def get_base_label_columns(label: dict[str:str], csv_row: dict[str:str], label_base_columns: list[str]) -> dict[str:str]: for label_base_column in label_base_columns: if label_base_column == "Label ID": csv_row[label_base_column] = label["id"] elif label_base_column == "Created By": if ( "label_details" in label ): # Check if label details is present. This field can be omitted in export. csv_row[label_base_column] = label[ "label_details"]["created_by"] else: csv_row[label_base_column] = None elif label_base_column == "Skipped": if ( "performance_details" in label ): # Check if performance details are present. This field can be omitted in export. csv_row[label_base_column] = label["performance_details"][ "skipped"] else: csv_row[label_base_column] = None return csv_row ``` ### Label annotation columns The label annotations are the final columns we will need to obtain. Obtaining these fields is more challenging than our approach for our detail columns. Suppose we attempt to obtain the fields with conditional statements and hard-defined paths. In that case, we will run into issues as each label can have annotations in different orders, at different depths, or not present. This will quickly create a mess, especially when we want our methods to work for multiple ontology. The best and cleanest way of obtaining these annotations inside our export data is through a recursive function. #### Recursion A recursive function can be defined as a routine that calls itself directly or indirectly. They solve problems by solving smaller instances of the same problem. This technique is commonly used in programming to solve problems that can be broken down into simpler, similar subproblems. Our sub-problem, in this case, is obtaining each individual annotation. A recursive function is divided into two components: * **Base case:** This is our termination condition that prevents the function from calling itself indefinitely. * **Recursive case:** The function calls itself with the modified arguments in the recursive case. The recursive case should move closer to the base case with each iteration. For our example, our base case will be either the annotation exists on the label (return the value/answer), or it does not (return `None`). Our recursive case would be finding more classifications to parse. In the code block below, I will highlight a few important details about our function. Essentially, we will be navigating through our JSON file by moving one classification key at a time until we find our annotation or, if everything has been searched, returning `None`, which will populate a blank cell on our CSV table. #### Tools Tools are not nested, but they can have nested classifications we will use or `get_feature_answers` function below to find the nested classification. Since tools are at the base level of a label and each tool has a different value key name, we will only be searching for bounding boxes for this tutorial. If you want to include other tools, reference our export guide for your data type and find the appropriate key to add on. ```python Python theme={"theme":"css-variables"} def get_feature_answers(feature: str, annotations: list[dict[str:str]]) -> None | str: """Returns answer of feature provided by navigating through a label's annotation list. Will return None if answer is not found. Args: feature (str): feature we are searching classifications (list[dict[str:str]]): annotation list that we will be searching for our feature with. Returns: None | str: The answer/value of the feature returns None if nothing is found """ for annotation in annotations: print(annotation) if (annotation["feature_schema_id"] == feature["feature_schema_id"] ): # Base conditions (found feature) if "text_answer" in annotation: return annotation["text_answer"]["content"] if "radio_answer" in annotation: return annotation["radio_answer"]["value"] if "checklist_answers" in annotation: # Since classifications can have more then one answer. This is set up to combine all classifications separated by a comma. Feel free to modify. return ", ".join([ check_list_ans["value"] for check_list_ans in annotation["checklist_answers"] ]) if "bounding_box" in annotation: return annotation["bounding_box"] # Add more tools here with similar pattern as above # Recursion cases (found more classifications to search through) if "radio_answer" in annotation: if len(annotation["radio_answer"]["classifications"]) > 0: value = get_feature_answers( feature, annotation["radio_answer"]["classifications"] ) # Call function again return value if answer found if value: return value if "checklist_answers" in annotation: for checklist_ans in annotation["checklist_answers"]: if len(checklist_ans["classifications"]) > 0: value = get_feature_answers( feature, checklist_ans["classifications"]) if value: return value if ("classifications" in annotation): # case for if tool has classifications if len(annotation["classifications"]) > 0: value = get_feature_answers(feature, annotation["classifications"]) if value: return value return None # Base case if searched through classifications and nothing was found (end of JSON). This can be omitted but included to visualize ``` ## Step 4: Setting up our main data row handler function Before exporting, we need to set up our main data row handler. This function will be fed straight into our export. This function will put everything together and connect all the pieces. We will also be defining our global dictionary list that will be used to create our CSVs. The output parameter represents each data row. ```python Python theme={"theme":"css-variables"} GLOBAL_CSV_LIST = [] def main(output: lb.BufferedJsonConverterOutput): # Navigate to our label list labels = output.json["projects"][project.uid]["labels"] for label in labels: # Define our CSV "row" csv_row = dict() # Start with data row base columns csv_row = get_base_data_row_columns(output.json, csv_row, data_row_base_columns) # Add our label details csv_row = get_base_label_columns(label, csv_row, label_base_columns) # Add classification features for classification in class_annotation_columns: csv_row[classification["column_name"]] = get_feature_answers( classification, label["annotations"]["classifications"]) # Add tools features for tool in tool_annotation_columns: csv_row[tool["column_name"]] = get_feature_answers( tool, label["annotations"]["objects"]) # Append to global csv list GLOBAL_CSV_LIST.append(csv_row) ``` ## Step 5: Export our data We are ready to export now that we have defined functions and strategies. Below, we export directly from our project and feed in the main function we created above. ```python Python theme={"theme":"css-variables"} # Params required to obtain all fields we need params = {"performance_details": True, "label_details": True} export_task = project.export(params=params) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` If everything went through correctly, you should see your `GLOBAL_CSV_LIST` printed out below with all your "rows" filled out. ```python Python theme={"theme":"css-variables"} pprint(GLOBAL_CSV_LIST) ``` ## Step 6: Convert to our desired format The hard part is now completed!🚀 Now that you have your export in a flattened format, you can easily convert to a CSV or a Pandas DataFrame! ### Option A: CSV writer ```python Python theme={"theme":"css-variables"} with open("file.csv", "w", newline="") as csvfile: # Columns fieldnames = (data_row_base_columns + label_base_columns + [name["column_name"] for name in class_annotation_columns] + [name["column_name"] for name in tool_annotation_columns]) writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for row in GLOBAL_CSV_LIST: writer.writerow(row) ``` ### Option B: Pandas DataFrame ```python Python theme={"theme":"css-variables"} columns = (data_row_base_columns + label_base_columns + [name["column_name"] for name in class_annotation_columns] + [name["column_name"] for name in tool_annotation_columns]) pd.DataFrame(GLOBAL_CSV_LIST, columns=columns) ``` # Features Source: https://docs.labelbox.com/horizon/developer-guides/feature Developer guide for creating and modifying features via the Python SDK. View [Features](/horizon/guides/working-with-features) guide page for feature schema definitions and details. ## Create a feature **Features** define the **tools** and **classifications** used to annotate your data. To create features for use in an ontology, use the `client.create_feature_schema()` method. ```python Tool theme={"theme":"css-variables"} from labelbox import Tool # Defining tool features # required is an optional field defaults to False if not specified bbox_tool = Tool(tool=Tool.Type.BBOX, name="dog_box", required=True) poly_tool = Tool(tool=Tool.Type.POLYGON, name="dog_poly") seg_tool = Tool(tool=Tool.Type.SEGMENTATION, name="dog_seg") point_tool = Tool(tool=Tool.Type.POINT, name="dog_center") line_tool = Tool(tool=Tool.Type.LINE, name="dog_orientation") ner_tool = Tool(tool=Tool.Type.NER, name="dog_reference", required=True) # Creating feature schema for each defined tool feature_schema_bbox = client.create_feature_schema(bbox_tool.asdict()) feature_schema_poly = client.create_feature_schema(poly_tool.asdict()) feature_schema_seg= client.create_feature_schema(seg_tool.asdict()) feature_schema_tool = client.create_feature_schema(point_tool.asdict()) feature_schema_line = client.create_feature_schema(line_tool.asdict()) feature_schema_ner = client.create_feature_schema(ner_tool.asdict()) ``` ```python Classification theme={"theme":"css-variables"} # Defining classification features # required is an optional field defaults to False if not specified from labelbox import Classification, Option text_classification = Classification(class_type=Classification.Type.TEXT, name="dog_name", required=True) radio_classification = Classification(class_type=Classification.Type.RADIO, name="dog_breed", options=[Option("poodle")], required=True) checklist_classification = Classification( class_type=Classification.Type.CHECKLIST, name="background", options=[Option("at_park"), Option("has_leash")]) nested_classification = Classification( class_type=Classification.Type.CHECKLIST, name="Appliance Features", options=[ Option( "HasWarranty", options=[ # Nested options Option("1 Year"), Option("2 Years"), Option("5 Years") ] ), Option("EnergyEfficient"), Option("SmartHomeCompatible") ] ) # Creating feature schema for each defined classification feature_schema_text = client.create_feature_schema(text_classification.asdict()) feature_schema_radio = client.create_feature_schema(radio_classification.asdict()) feature_schema_checklist = client.create_feature_schema(checklist_classification.asdict()) feature_schema_nested = client.create_feature_schema(nested_classification.asdict()) ``` After creating features and schemas, you can add them to ontologies. To learn how to upsert features into an ontology, see [Ontology](/horizon/developer-guides/ontology#create-ontology-from-new-features). ## Set a`UIMode` for a feature You can set a `UIMode` for your classification, which works similarly like switching the `dropdown` toggle inside the platform. The `UIMode` has the following two options: 1. `Classification.UIMode.SEARCHABLE` allows the feature to be searched inside a dropdown menu, equivalent to enabling the dropdown toggle. 2. `Classification.UIMode.HOTKEY` gives each answer option a dedicated hotkey, equivalent to disabling the dropdown toggle. ```python Python theme={"theme":"css-variables"} radio_classification = Classification(class_type=Classification.Type.RADIO, name="dog_breed", options=[Option("poodle")], required=True, ui_mode=Classification.UIMode.SEARCHABLE) checklist_classification = Classification(class_type=Classification.Type.CHECKLIST, name="background", options=[Option("at_park"), Option("has_leash")] ui_mode=Classification.UIMode.HOTKEY) ``` ## Set a `likert scale` for a feature You can set the parameter `is_likert_scale` for your radio classification if all option values are integers. ```python theme={"theme":"css-variables"} classification_features = [ Classification( class_type=lb.Classification.Type.RADIO, name="Quality Issues", options=[ Option(value="1", label="Blurry"), Option(value="2", label="Distorted"), Option(value="3", label="OutOfFocus"), ], required=True, is_likert_scale=True, ) ] ``` ## Get a feature You can get the feature schema by name or schema id. Only top-level feature schemas are supported. ```python Python theme={"theme":"css-variables"} from labelbox import client client = Client(api_key="") ## Search feature by name in your org regulatory_sign_feature_schema = next(client.get_feature_schemas("regulatory-sign")) classification_feature = next(client.get_feature_schemas("Quality Issues")) ## Get feature by feature schema ID. You can get this from the UI regulatory_sign_feature_schema = client.get_feature_schema("FEATURE_SCHEMA_ID") print(regulatory_sign_feature_schema) print(classification_feature) ``` ## Update feature schema name Updates the title/name of a feature schema. Only top-level feature schemas are supported. ```python Python theme={"theme":"css-variables"} client.update_feature_schema_title("", "New Title") ``` ## Delete or archive a feature in an ontology Deletes or archives a feature schema from an ontology. If the feature schema is a root-level node with associated labels, it will be *archived*. If the feature schema is a nested node in the ontology without associated labels, it will be deleted. If the feature schema is a nested ontology node with associated labels, it will neither be deleted nor archived. To *archive* a feature means you can unarchive it later on and retrieve annotations made with this feature. If you delete a feature, the feature and its associated annotations cannot be recovered. ```python Python theme={"theme":"css-variables"} client.delete_feature_schema_from_ontology(ontology_id="", feature_schema_id="") ``` ## Unarchive a feature in an ontology Unarchives a feature schema node in an ontology. Only root-level feature schema nodes can be unarchived. ```python Python theme={"theme":"css-variables"} client.unarchive_feature_schema_node( ontology_id="", root_feature_schema_id="" ) ``` ## Check whether a feature is archived Returns `True` if a feature schema is archived in the specified ontology, returns `False` otherwise. ```python Python theme={"theme":"css-variables"} result = client.is_feature_schema_archived( ontology_id="", feature_schema_id="" ) # True or False print(result) ``` # Foundry Source: https://docs.labelbox.com/horizon/developer-guides/foundry-app-developer-guide Developer guide for creating and managing Foundry applications. Foundry apps help automate data labeling and enrichment. Here, we should how to use the Labelbox SDK to manage Foundry apps and how to use a Foundry app to predict and import annotations into a dataset. ## Create a Foundry app A Foundry app (short for Foundry application) helps automate data labeling and enrichment. Once you’ve created a Foundry app, you can run it repeatedly against new data. To create a Foundry app, use the Labelbox Model to create [your Foundry app](/horizon/guides/foundry-apps#create-foundry-apps). Once the app is created, you can use the Labelbox SDK to run your app and manage its results. ## Run a Foundry app You can use the Labelbox Model to run Foundry apps. Results are displayed like any [other model run](/horizon/guides/foundry-view-predictions#view-model-runs). When running a Foundry app, you must provide an app ID. You can find the app ID by going to the **Model** tab and selecting **Apps**. When you select a Foundry app, the app ID will appear in the top right corner of the screen. In addition to the app ID, the method also requires a `DataRowIdentifiables`object and a unique name. ```python Python theme={"theme":"css-variables"} task = client.run_foundry_app(model_run_name=f"Amazon-{str(uuid.uuid4())}", data_rows=lb.GlobalKeys( [global_key] # Provide a list of global keys ), app_id=AMAZON_REKOGNITION_APP_ID) task.wait_till_done() print(f"Errors: {task.errors}") #Obtain model run ID from task MODEL_RUN_ID = task.metadata["modelRunId"] ``` ## Send Foundry annotations to Annotate When you send predictions to **Annotate** from **Catalog**, you may choose to include or exclude certain parameters. ### Parameters | Parameter | Required? | Description | | - | - | - | | `predictions_ontology_mapping` | Required | A dictionary containing the mapping of the model's ontology feature schema IDs to the project's ontology feature schema IDs. See [Send predictions to Annotate](/horizon/guides/foundry-annotate-predictions#resolving-issues) for more information on where to access your predictions map. | | `exclude_data_rows_in_project` | Optional | Excludes data rows that are already in the project. | | `override_existing_annotations_rule` | Optional | The strategy defines how to handle conflicts in classifications between the existing data rows in the project and incoming predictions from the source model run or annotations from the source project. Options include: -`ConflictResolutionStrategy.KeepExisting` (DEFAULT) -`ConflictResolutionStrategy.OverrideWithPredictions` -`ConflictResolutionStrategy.OverrideWithAnnotations` | | `batch_priority` | Optional | Assigned priority to the batch of data rows (1-5). | ### Sample script ```python Python theme={"theme":"css-variables"} model_run = client.get_model_run(" ## Example: Run and import annotations from a Foundry app Regardless of what model you use from Foundry, the workflow through the SDK is similar. Step 6 (Map ontology through the UI) is where the process can slightly differ. An [interactive tutorial](https://colab.research.google.com/github/Labelbox/labelbox-notebooks/blob/main/foundry/object_detection.ipynb) is also available as a [Colab notebook](https://research.google.com/colaboratory/faq.html#whats-colaboratory); it shows shows how to run and import annotations from a Foundry app. ### Before you start You will need to import these libraries to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb from labelbox.schema.conflict_resolution_strategy import ConflictResolutionStrategy import uuid ``` ### Replace with your API key ```python Python theme={"theme":"css-variables"} API_KEY = "" client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog You must have data rows in Catalog before you can run them through Foundry. In this example, we are using an image data row. ```python Python theme={"theme":"css-variables"} # send a sample image as data row for a dataset global_key = str(uuid.uuid4()) test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "global_key": global_key } dataset = client.create_dataset(name="foundry-demo-dataset") task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print(f"Errors: {task.errors}") print(f"Failed data rows: {task.failed_data_rows}") ``` ### Step 2: Set up ontology Your project should have an ontology configured the tools and classifications supported for your model and selected data type. To illustrate, [Amazon Rekognition](https://aws.amazon.com/rekognition) only supports object detection scenarios. Consequently, your ontology must include a bounding box annotation. for your ontology since it only supports object detection. ```python Python theme={"theme":"css-variables"} # Create ontology with two bounding boxes that is included with Amazon Rekognition: Car and Person ontology_builder = lb.OntologyBuilder( classifications=[], tools=[ lb.Tool(tool=lb.Tool.Type.BBOX, name="Car"), lb.Tool(tool=lb.Tool.Type.BBOX, name="Person") ] ) ontology = client.create_ontology("Image Bounding Box Annotation Demo Foundry", ontology_builder.asdict(), media_type=lb.MediaType.Image) ``` ### Step 3: Create a labeling project Connect the ontology to the labeling project. ```python Python theme={"theme":"css-variables"} project = client.create_project(name="Foundry Image Demo", media_type=lb.MediaType.Image) project.connect_ontology(ontology) ``` ### Step 4: Create new Foundry app & copy App ID To use the Labelbox app to create Foundry apps: From the main menu, select **Model** and then select **App** from the **Create** menu. Select **Amazon Rekognition**, name your Foundry app, and then select **Proceed**. Customize your parameters and then select **Save & Create**. In **Model**, select \**Aps* and then select your new Foundry app. Use the **Copy** button to copy the App ID to the clipboard. Paste the copied App ID into your code: ```python Python theme={"theme":"css-variables"} #Select your foundry application inside the UI and copy the APP ID from the top right corner AMAZON_REKOGNITION_APP_ID = "" ``` Foundry apps can only be created using the app; you cannot use the SDK to create Foundry apps. ### Step 5: Run Foundry app on data rows This step generates annotations that can later be reused as pre-labels or ground in a project. ```python Python theme={"theme":"css-variables"} task = client.run_foundry_app(model_run_name=f"Amazon-{str(uuid.uuid4())}", data_rows=lb.GlobalKeys( [global_key] # Provide a list of global keys ), app_id=AMAZON_REKOGNITION_APP_ID) task.wait_till_done() print(f"Errors: {task.errors}") v #Obtain model run ID from task MODEL_RUN_ID = task.metadata["modelRunId"] ``` ### Step 6: Map ontology through the UI Predictions may not directly map to ontology features. When this happens, you need to map predictions to your ontology. To do this using the Labelbox app: Use **Catalog** to select the dataset for your model run and then select the data rows Select **Select all** in the top right corner. Select **Manage selection** > **Send to Annotate**. Select your project from the **Project** menu. When sending annotations for **Annotate** review, you typically select a workflow step. This isn't necessary for this example. Place a checkmark next to **Include model predictions** and then select **Map**. Select the incoming ontology and matching ontology features for *Car* and *Person*. When the features are mapped, select **Copy ontology mapping as JSON**. Paste your copied JSON into the definition of `PREDICTIONS_ONTOLOGY_MAPPING` ```python Python theme={"theme":"css-variables"} # Copy map ontology through the UI then paste JSON here PREDICTIONS_ONTOLOGY_MAPPING = {} ``` In a production workflow, you would typically save your configuration. You can skip this step for the sake of this example. While you cannot the SDK to map predictions to ontology features, you can use it to send predictions to **Annotate**. ### Step 7: Send annotations from **Catalog** to **Annotate** ```python Python theme={"theme":"css-variables"} model_run = client.get_model_run(MODEL_RUN_ID) send_to_annotations_params = { "predictions_ontology_mapping": PREDICTIONS_ONTOLOGY_MAPPING, "exclude_data_rows_in_project": False, "override_existing_annotations_rule": ConflictResolutionStrategy.OverrideWithPredictions, "batch_priority": 5, } task = model_run.send_to_annotate_from_model( destination_project_id=project.uid, task_queue_id=None, #ID of workflow task, set ID to None if you want to convert pre-labels to ground truths or obtain task queue id through project.task_queues(). batch_name="Foundry Demo Batch", data_rows=lb.GlobalKeys( [global_key] # Provide a list of global keys from foundry app task ), params=send_to_annotations_params ) task.wait_till_done() print(f"Errors: {task.errors}") ``` # Overview Source: https://docs.labelbox.com/horizon/developer-guides/getting-started Description of Labelbox-Python SDK and our developer guides. Welcome to Labelbox-Python SDK developer guides! The Labelbox-Python SDK is an open-source project that provides access to the Labelbox API and can automate many actions and workflows in the Labelbox app. The SDK's source code can be found on our [GitHub page](https://github.com/Labelbox/labelbox-python). For a complete granular API reference, visit our [readthedocs](https://labelbox-python.readthedocs.io/en/latest/) page. ### SDK version deprecation SDK versions earlier than 3.68 are no longer supported. Please update to a supported version to maintain compatibility. Connect an MCP client to Labelbox for project insights and approved actions. No SDK installation is required. ## Installation To install, run either `pip install labelbox` or`pip install "labelbox[data]"` in your command line. ```shell shell theme={"theme":"css-variables"} pip install "labelbox[data]" # or pip install labelbox ``` | Module | Description | | - | - | | `"labelbox[data]"` | Installs all [required dependencies](https://github.com/Labelbox/labelbox-python/blob/develop/requirements.lock) (libraries, tools to manipulate annotations, and more.) | | `labelbox` | Installs all required libraries for client-only functionality | For more in-depth installation information, please visit our [installation guide](/horizon/developer-guides/install-python-sdk). ## How to use our developer guides Our developer documentation is designed to help you understand the different methods in the Labelbox-Python SDK. You are encouraged to run the code blocks locally as you read through the guides. Some guides include links to Google Colab Notebooks and our GitHub repository at the top of the page. These links allow you to run Python code quickly, demonstrating how specific methods work and providing example workflows. ## Best practices To get the best experience from the SDK, we recommend the following: 1. Always use bulk operations. Unless you are testing or working on small datasets, use bulk operations for faster and more reliable performance. * `create_data_rows` instead of `create_data_row` * `project.export()` instead of `project.labels()` * `Label.bulk_delete(labels)` instead of `[label.delete() for label in labels]` 2. Use `get_one()` and `get_many()` methods to get elements of a paginated collection instead of `list`. * `list(dataset.data_rows())[0]` is going to query for every single `data_row` in your dataset. 3. Make sure to use the latest version of the SDK. * Labelbox is a rapidly evolving company, and we are constantly adding new features and optimizations. ## GraphQL API ### GraphQL APIs are not meant to be consumed directly GraphQL APIs may be updated or deprecated at any time without notice. For this reason, Labelbox strongly advises that customers use the Python SDK for all development. ## How to contribute To learn how to contribute towards the Labelbox Python SDK and our example notebooks, refer to our dedicated [contributing guide](https://github.com/Labelbox/labelbox-python/blob/develop/CONTRIBUTING.md) . For feedback or to report any issues with our documentation, please email [docs@labelbox.com](mailto:docs@labelbox.com) . # Hugging Face integration Source: https://docs.labelbox.com/horizon/developer-guides/hugging-face-integration Describes how to combine the Python SDK and HuggingFace Transformer models to improve similarity search. You can improve similarity search for your data rows by using the Labelbox Python SDK and Hugging Face Transformer models to custom embeddings. This lets you use off-the-shelf models to create search terms that lets you compare custom embedding and model performance. # Import a labeled dataset (images) Source: https://docs.labelbox.com/horizon/developer-guides/import-a-labeled-dataset-images How to import a labeled dataset ## Overview In this tutorial, you will: * Create a dataset in Labelbox * Import custom metadata and ground truth # End-to-end example: import ground truth ## Import libraries ```python Python theme={"theme":"css-variables"} import labelbox as lb from labelbox.schema.data_row_metadata import DataRowMetadataField, DataRowMetadataKind import datetime import random import os import json from PIL import Image from labelbox.schema.ontology import OntologyBuilder, Tool import requests from tqdm.notebook import tqdm import uuid from labelbox.data.annotation_types import Label, ImageData, ObjectAnnotation, Rectangle ``` ## Set up Labelbox client ```python Python theme={"theme":"css-variables"} # Initialize the Labelbox client API_KEY = "" # Place API key client = lb.Client(API_KEY) ``` ## Download a public dataset ```python Python theme={"theme":"css-variables"} # Function to download files def download_files(filemap): path, uri = filemap if not os.path.exists(path): response = requests.get(uri, stream=True) with open(path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return path ``` #### Download data rows and annotations ```python Python theme={"theme":"css-variables"} # Download data rows and annotations DATA_ROWS_URL = "https://storage.googleapis.com/labelbox-datasets/VHR_geospatial/geospatial_datarows.json" ANNOTATIONS_URL = "https://storage.googleapis.com/labelbox-datasets/VHR_geospatial/geospatial_annotations.json" download_files(("data_rows.json", DATA_ROWS_URL)) download_files(("annotations.json", ANNOTATIONS_URL)) ``` ```python Python theme={"theme":"css-variables"} with open('data_rows.json', 'r') as fp: data_rows = json.load(fp) with open('annotations.json', 'r') as fp: annotations = json.load(fp) ``` ## Create a dataset ```python Python theme={"theme":"css-variables"} # Create a new dataset dataset = client.create_dataset(name="Geospatial vessel detection") print(f"Created dataset with ID: {dataset.uid}") ``` ## Import Data Rows and metadata ```python Python theme={"theme":"css-variables"} # Here is an example of adding two metadata fields to your Data Rows: a "captureDateTime" field with datetime value, and a "tag" field with string value metadata_ontology = client.get_data_row_metadata_ontology() datetime_schema_id = metadata_ontology.reserved_by_name["captureDateTime"].uid tag_schema_id = metadata_ontology.reserved_by_name["tag"].uid tag_items = ["WorldView-1", "WorldView-2", "WorldView-3", "WorldView-4"] for datarow in tqdm(data_rows): dt = datetime.datetime.utcnow() + datetime.timedelta(days=random.random()*30) # this is random datetime value tag_item = random.choice(tag_items) # this is a random tag value # Option 1: Specify metadata with a list of DataRowMetadataField. This is the recommended option since it comes with validation for metadata fields. metadata_fields = [ DataRowMetadataField(schema_id=datetime_schema_id, value=dt), DataRowMetadataField(schema_id=tag_schema_id, value=tag_item) ] # Option 2: Uncomment to try. Alternatively, you can specify the metadata fields with dictionary format without declaring the DataRowMetadataField objects. It is equivalent to Option 1. # metadata_fields = [ # {"schema_id": datetime_schema_id, "value": dt}, # {"schema_id": tag_schema_id, "value": tag_item} # ] datarow["metadata_fields"] = metadata_fields ``` ```python Python theme={"theme":"css-variables"} task = dataset.create_data_rows(data_rows) task.wait_till_done() print(f"Failed data rows: {task.failed_data_rows}") print(f"Errors: {task.errors}") if task.errors: for error in task.errors: if 'Duplicate global key' in error['message'] and dataset.row_count == 0: # If the global key already exists in the workspace the dataset will be created empty, so we can delete it. print(f"Deleting empty dataset: {dataset}") dataset.delete() ``` ## Examine a data row ```python Python theme={"theme":"css-variables"} datarow = next(dataset.data_rows()) print(datarow) ``` ## Set up a labeling project ```python Python theme={"theme":"css-variables"} # Initialize the OntologyBuilder ontology_builder = OntologyBuilder() # Assuming 'annotations' is defined and contains the necessary data for category in annotations['categories']: print(category['name']) # Add tools to the ontology builder ontology_builder.add_tool(Tool(tool=Tool.Type.BBOX, name=category['name'])) # Create the ontology in Labelbox ontology = client.create_ontology("Vessel Detection Ontology", ontology_builder.asdict(), media_type=lb.MediaType.Image) print(f"Created ontology with ID: {ontology.uid}") # Create a project and connect the ontology project = client.create_project(name="Vessel Detection", media_type=lb.MediaType.Image) project.connect_ontology(ontology=ontology) print(f"Created project with ID: {project.uid}") ``` ## Send a batch of data rows to the project ```python Python theme={"theme":"css-variables"} # Minimal Export parameters focused solely on data row IDs export_params = { "data_row_details": True # Only export data row details } # Initiate the streamable export task from catalog dataset = client.get_dataset(dataset.uid) # Update with the actual dataset ID export_task = dataset.export(params=export_params) export_task.wait_till_done() print(export_task) data_rows = [] # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] # Randomly select 200 Data Rows (or fewer if the dataset has less than 200 data rows) sampled_data_rows = random.sample(data_rows, min(len(data_rows), 200)) # Create a new batch in the project and add the sampled data rows batch = project.create_batch( "Initial batch", # name of the batch sampled_data_rows, # list of Data Rows 1 # priority between 1-5 ) print(f"Created batch with ID: {batch.uid}") ``` ## Create annotations payload ```python Python theme={"theme":"css-variables"} # Set export parameters focused on data row details export_params = { "data_row_details": True, # Only export data row details "batch_ids": [batch.uid], # Optional: Include batch ids to filter by specific batches } # Initialize the streamable export task from project export_task = project.export(params=export_params) export_task.wait_till_done() data_rows = [] # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] labels = [] for datarow in data_rows: annotations_list = [] # Access the 'data_row' dictionary first data_row_dict = datarow['data_row'] folder = data_row_dict['external_id'].split("/")[0] id = data_row_dict['external_id'].split("/")[1] if folder == "positive_image_set": for image in annotations['images']: if image['file_name'] == id: for annotation in annotations['annotations']: if annotation['image_id'] == image['id']: bbox = annotation['bbox'] category_id = annotation['category_id'] - 1 class_name = None ontology = ontology_builder.asdict() # Get the ontology dictionary for category in ontology['tools']: if category['name'] == annotations['categories'][category_id]['name']: class_name = category['name'] break if class_name: annotations_list.append(ObjectAnnotation( name=class_name, value=Rectangle(start=Point(x=bbox[0], y=bbox[1]), end=Point(x=bbox[2]+bbox[0], y=bbox[3]+bbox[1])) )) image_data = ImageData(uid=data_row_dict['id']) labels.append(Label(data=image_data, annotations=annotations_list)) ``` ## Import ground truth annotations ```python Python theme={"theme":"css-variables"} upload_job = lb.LabelImport.create_from_objects( client=client, project_id=project.uid, name=f"label_import_job_{str(uuid.uuid4())}", labels=labels ) # Wait for the upload to finish and print the results upload_job.wait_until_done() print(f"Errors: {upload_job.errors}") print(f"Status of uploads: {upload_job.statuses}") ``` # Import a labeled dataset (text) Source: https://docs.labelbox.com/horizon/developer-guides/import-a-labeled-dataset-text ## Initialize Huggingface dataset, models and pipeline ```python Python theme={"theme":"css-variables"} datasets = load_dataset("Babelscape/wikineural") tokenizer = AutoTokenizer.from_pretrained("dslim/bert-large-NER") model = AutoModelForTokenClassification.from_pretrained("dslim/bert-large-NER") ## Use GPU if it exists gpu_info = !nvidia-smi gpu_info = '\n'.join(gpu_info) if gpu_info.find('failed') >= 0: print('Not connected to a GPU') nlp = pipeline("ner", model=model, tokenizer=tokenizer) embedding_model = SentenceTransformer('sentence-transformers/bert-base-nli-mean-tokens') else: print(gpu_info) nlp = pipeline("ner", model=model, tokenizer=tokenizer, device=0) embedding_model = SentenceTransformer('sentence-transformers/bert-base-nli-mean-tokens', device=0) ``` ## Set up client and configure parameters ```python Python theme={"theme":"css-variables"} ## Enter your API key here LB_API_KEY = "YOUR_API_KEY" client = labelbox.Client(LB_API_KEY) ## Set batch size for batching data rows and annotation bulk import. 500-1000 is recommended size. BATCH_SIZE = 500 ## Set max number of data rows to import. WikiNeural dataset has ~1.1M data rows MAX_DATA_ROW_LIMIT = 2000 ``` ## Functions ```python Python theme={"theme":"css-variables"} def create_ner_objects(class_name, st, en): named_enity = TextEntity(start=st,end=en) named_enity_annotation = ObjectAnnotation(value=named_enity, name=class_name) return named_enity_annotation def generate_predictions(datarow): external_id = datarow["external_id"] dataset_name = external_id.split("_")[0] + "_" + external_id.split("_")[1] datarow_index = int(external_id.split("_")[2].split(".")[0]) uid = datarow['id'] text_data_row = datasets[dataset_name][datarow_index] tokens = text_data_row["tokens"] tokenized_input = tokenizer(tokens, is_split_into_words=True) sentence = tokenizer.decode(tokenized_input["input_ids"], skip_special_tokens=True) annotations = [] ## Generate prediction predictions = nlp(sentence) ## process predictions and compute text entities try: for item in predictions: score = item['score'] if score > 0.99: entity = item['entity'] start = item['start'] end = item['end'] index = predictions.index(item) if entity =="B-PER": for next_item in predictions[index+1:]: if next_item['entity']=="I-PER": end = next_item['end'] else: break annotations.append(create_ner_objects("PER", start, end-1)) if entity =="B-ORG": for next_item in predictions[index+1:]: if next_item['entity']=="I-ORG": end = next_item['end'] else: break annotations.append(create_ner_objects("ORG", start, end-1)) if entity =="B-LOC": for next_item in predictions[index+1:]: if next_item['entity']=="I-LOC": end = next_item['end'] else: break annotations.append(create_ner_objects("LOC", start, end-1)) if entity =="B-MISC": for next_item in predictions[index+1:]: if next_item['entity']=="I-MISC": end = next_item['end'] else: break annotations.append(create_ner_objects("MISC", start, end-1)) except Exception as e: print(e) text_data = TextData(uid=uid) return text_data, annotations def create_data_rows_payload(payload): data_row_content = None label_content = None try: h, text_data_row, lang = payload file_name = lang + "_" + str(h) +'.txt' tokens = text_data_row["tokens"] tokenized_input = tokenizer(tokens, is_split_into_words=True) sentence = tokenizer.decode(tokenized_input["input_ids"], skip_special_tokens=True) embeddings = embedding_model.encode(sentence) embeddings_metadata = DataRowMetadataField( schema_id=embedding_field.uid, ## Labelbox currently only supports custom embedding of 128 max length value=embeddings[:128].tolist(), ) language_metadata = DataRowMetadataField( schema_id=language_field.uid, value=lang, ) metadata_payload = [language_metadata, embeddings_metadata] data_row_content = {DataRow.row_data: "gs://labelbox-datasets/wiki_neural_text_ner/"+file_name, DataRow.external_id: file_name, DataRow.metadata_fields: metadata_payload} except Exception as e: print(e) return data_row_content, label_content ``` ## Create a dataset ```python Python theme={"theme":"css-variables"} dataset = client.create_dataset(name="WikiNEuRal Text NER") ``` ## Set up ontology This example requires a custom metadata string field named `language`. For help, see [Custom fields](/horizon/guides/datarow-metadata#custom-fields). ```python Python theme={"theme":"css-variables"} metadata_ontology = client.get_data_row_metadata_ontology() ## Create a custom metadata schema called language of string kind: /horizon/guides/datarow-metadata#custom-fields language_field = metadata_ontology.custom_by_name["language"] embedding_field = metadata_ontology.reserved_by_name["embedding"] ``` ```python Python theme={"theme":"css-variables"} ontology = OntologyBuilder() PER = Tool(tool = Tool.Type.NER, name = "PER") ontology.add_tool(PER) ORG = Tool(tool = Tool.Type.NER, name = "ORG") ontology.add_tool(ORG) LOC = Tool(tool = Tool.Type.NER, name = "LOC") ontology.add_tool(LOC) MISC = Tool(tool = Tool.Type.NER, name = "MISC") ontology.add_tool(MISC) ontology = client.create_ontology("WikiNEuRal Text NER", ontology.asdict()) ``` ## Set up a labeling project ```python Python theme={"theme":"css-variables"} project = client.create_project(name = "WikiNEuRal Text NER", media_type=MediaType.Text) project.connect_ontology(ontology) ontology_from_project = OntologyBuilder.from_project(project) project.update(queue_mode=project.QueueMode.Batch) ``` ## Process and create data row payload in batches ```python Python theme={"theme":"css-variables"} tuples = [] for item in datasets: # if item == "train_en": for h, text_data_row in enumerate(datasets[item]): tuples.append((h, text_data_row, item)) if MAX_DATA_ROW_LIMIT !=None: tuples = random.sample(tuples, MAX_DATA_ROW_LIMIT) ``` ```python Python theme={"theme":"css-variables"} chunked_tuples = list() for i in range(0, len(tuples), BATCH_SIZE): chunked_tuples.append(tuples[i:i+BATCH_SIZE]) ``` ## Main iterator loop to import data ```python Python theme={"theme":"css-variables"} for chunk in chunked_tuples: start_time = time.time() current_index = chunked_tuples.index(chunk) print("Executing {} of {} iteration".format(current_index, len(chunked_tuples))) ## Generate data row payload data_rows = [] for item in tqdm.tqdm(chunk): datarow,label = create_data_rows_payload(item) data_rows.append(datarow) ## Create data rows in Labelbox task = dataset.create_data_rows(data_rows) task.wait_till_done() print(task) ## Submit a batch of the recently created data rows batch_datarows = [] for item in task.result: batch_datarows.append(item['id']) batch = project.create_batch( str(current_index) + "_" + str(binascii.b2a_hex(os.urandom(5))), # name of the batch batch_datarows, # list of Data Rows 1 # priority between 1-5 ) ## Generate model predictions ground_truth_list = LabelList() results = [] for item in tqdm.tqdm(task.result): result = generate_predictions(item) ground_truth_list.append(Label( data=result[0], annotations = result[1] )) ## Convert model predictions to NDJSON format ground_truth_list.assign_feature_schema_ids(OntologyBuilder.from_project(project)) ground_truth_ndjson = list(NDJsonConverter.serialize(ground_truth_list)) ## Upload model predictions as ground truth upload_task = LabelImport.create_from_objects(client, project.uid, f"upload-job-{uuid4()}", ground_truth_ndjson) upload_task.wait_until_done() print(upload_task.errors) print(str((time.time() - start_time))+" seconds") ``` # Import audio annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-audio-annotations Developer guide for importing annotations on audio data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported Annotations The following annotations are supported for an audio data row: * Radio * Checklist * Free-form text * Temporal classifications (milliseconds): text, radio, checklist ## Classifications ### Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer"), lb_types.ClassificationAnswer(name = "third_checklist_answer") ]) ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"}, {"name": "third_checklist_answer"}, ] } ``` ### Free-form text ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "answer": "sample text", } ``` ### Nested classifications ```python Python annotation theme={"theme":"css-variables"} nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_radio_annotation_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [ { "name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer"} } ] } } nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } ``` ## Temporal classifications (milliseconds) Use temporal classifications to attach **time-based** classification labels to an audio asset. Temporal ranges are represented in **milliseconds** from the start of the asset. When using NDJSON, temporal classifications use a **unified** `answer: [...]` list structure: * For **temporal radio**, `answer` is a **single-item list** (unlike global radio, which uses an object). * For **temporal checklist**, `answer` is a **multi-item list**. * For **temporal text**, `answer` is a list of `{ "value": , "frames": [{"start": , "end": }] }`. Temporal classifications support nested hierarchies (for example: Text > Text > Text, or Radio > Radio > Radio). ### Free-form text (temporal) ```python Python annotation theme={"theme":"css-variables"} temporal_text_annotation = lb_types.TemporalClassificationText( name="transcription", value=[ (1000, 1100, "Hello"), (1500, 2400, "How can I help you?"), ], ) ``` ```json NDJSON theme={"theme":"css-variables"} temporal_text_annotation_ndjson = { "name": "transcription", "answer": [ { "value": "Hello", "frames": [{"start": 1000, "end": 1100}] }, { "value": "How can I help you?", "frames": [{"start": 1500, "end": 2400}] } ] } ``` ### Radio (temporal) ```python Python annotation theme={"theme":"css-variables"} temporal_radio_annotation = lb_types.TemporalClassificationQuestion( name="speaker", value=[ lb_types.TemporalClassificationAnswer( name="user", frames=[(200, 1600)], ) ], ) ``` ```json NDJSON theme={"theme":"css-variables"} temporal_radio_annotation_ndjson = { "name": "speaker", "answer": [ { "name": "user", "frames": [{"start": 200, "end": 1600}] } ] } ``` ### Checklist (temporal) ```python Python annotation theme={"theme":"css-variables"} temporal_checklist_annotation = lb_types.TemporalClassificationQuestion( name="audio_quality", value=[ lb_types.TemporalClassificationAnswer( name="background_noise", frames=[(0, 1500), (2000, 3000)], ), lb_types.TemporalClassificationAnswer( name="echo", frames=[(2200, 2900)], ), ], ) ``` ```json NDJSON theme={"theme":"css-variables"} temporal_checklist_annotation_ndjson = { "name": "audio_quality", "answer": [ { "name": "background_noise", "frames": [{"start": 0, "end": 1500}, {"start": 2000, "end": 3000}] }, { "name": "echo", "frames": [{"start": 2200, "end": 2900}] } ] } ``` ### Nested classifications (temporal) ```python Python annotation theme={"theme":"css-variables"} nested_temporal_radio_annotation = lb_types.TemporalClassificationQuestion( name="speaker", value=[ lb_types.TemporalClassificationAnswer( name="user", frames=[(200, 1600)], classifications=[ lb_types.TemporalClassificationQuestion( name="tone", value=[ lb_types.TemporalClassificationAnswer( name="professional", frames=[(1000, 1600)], classifications=[ lb_types.TemporalClassificationQuestion( name="clarity", value=[ lb_types.TemporalClassificationAnswer( name="clear", frames=[(1300, 1600)], ) ], ) ], ) ], ) ], ) ], ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_temporal_radio_annotation_ndjson = { "name": "speaker", "answer": [ { "name": "user", "frames": [{"start": 200, "end": 1600}], "classifications": [ { "name": "tone", "answer": [ { "name": "professional", "frames": [{"start": 1000, "end": 1600}], "classifications": [ { "name": "clarity", "answer": [ { "name": "clear", "frames": [{"start": 1300, "end": 1600}] } ] } ] } ] } ] } ] } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid import labelbox.types as lb_types ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} global_key = "sample-audio-1.mp3" asset = { "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3", "global_key": global_key } dataset = client.create_dataset(name="audio_annotation_import_demo_dataset") task = dataset.create_data_rows([asset]) task.wait_till_done() print("Errors:", task.errors) print("Failed data rows: ", task.failed_data_rows) ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="text_audio"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_audio", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_audio", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), # Temporal classifications for audio require INDEX scope lb.Classification( class_type=lb.Classification.Type.TEXT, name="transcription", scope=lb.Classification.Scope.INDEX ), ] ) ontology = client.create_ontology("Ontology Audio Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Audio) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # Create Labelbox project project = client.create_project(name="audio_project", media_type=lb.MediaType.Audio) # Setup your ontology project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} # Create a batch to send to your MAL project batch = project.create_batch( "first-batch-audio-demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. For annotations that you want to import as ground truth labels, you can also specify [benchmarks](/horizon/guides/benchmark) using the `is_benchmark_reference` flag. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python Annotation Payload theme={"theme":"css-variables"} label = [] label.append( lb_types.Label( data={"global_key" : global_key }, annotations=[ text_annotation, checklist_annotation, radio_annotation, temporal_text_annotation, temporal_radio_annotation, temporal_checklist_annotation ], # Optional: set the label as a benchmark # Only supported for groud truth imports is_benchmark_reference = True ) ) ``` ```python NDJSON Payload theme={"theme":"css-variables"} label_ndjson = [] for annotations in [text_annotation_ndjson, checklist_annotation_ndjson, radio_annotation_ndjson, temporal_text_annotation_ndjson, temporal_radio_annotation_ndjson, temporal_checklist_annotation_ndjson]: annotations.update({ 'dataRow': { 'globalKey': global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import audio data Source: https://docs.labelbox.com/horizon/developer-guides/import-audio-data How to import audio data and sample import formats. ## Specifications File formats: MP3, WAV Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) * Direct upload of local files(256 MB max file size) Note: Direct upload currently does not support adding additional metadata and attachments, see below Python example. When importing audio data to Labelbox, your JSON file must include the following information for each audio file. ## Parameters | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to a cloud-hosted audio. For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). For older regions, your S3 bucket may be in `https://.s3..amazonaws.com/` format. If your object URLs are formatted this way, make sure they are in the virtual-hosted-style format before importing. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"AUDIO"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ## Import format ```json Delegated Access URL theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-1.mp3", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-1.mp3", "media_type": "AUDIO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" }] }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-2.mp3", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/audio-samples/sample-audio-2.mp3", "media_type": "AUDIO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Standard URL theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3", "global_key": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3", "media_type": "AUDIO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-2.mp3", "global_key": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-2.mp3", "media_type": "AUDIO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ## Python example ```python bulk import example theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Audio") assets = [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3", "global_key": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-1.mp3", "media_type": "AUDIO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-2.mp3", "global_key": "https://storage.googleapis.com/labelbox-datasets/audio-sample-data/sample-audio-2.mp3", "media_type": "AUDIO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` ```python local files theme={"theme":"css-variables"} local_file_paths = ['path/to/local/file1', 'path/to/local/file1'] # limit: 15k files new_dataset = client.create_dataset(name = "Local files upload") try: task = new_dataset.create_data_rows(local_file_paths) task.wait_till_done() except Exception as err: print(f'Error while creating labelbox dataset - Error: {err}') ``` # Import conversation & model response data Source: https://docs.labelbox.com/horizon/developer-guides/import-conversation-model-response-data How to import conversation text data & model response data. ## Specifications File format: JSON in [our conversation format](#sample-conversation-json) Encoding: UTF-8 (Note: The Editor does not process any special character sequences like HTML Entities, Unicode Escape Sequence, or colon emoji aliases.) Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) When importing conversation or thread data to Labelbox, your JSON file should include the following information for each data row. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to a cloud-hosted conversational text JSON file. See the section below for details on our conversation format. For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). For older regions, your S3 bucket may be in `https://.s3..amazonaws.com/` format. If your object URLs are formatted this way, make sure they are in the virtual-hosted-style format before importing. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if their global keys are duplicated to existing data rows. | | `media_type` | No | `"CONVERSATIONAL"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | ## Import format ```json json theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_1.json", "global_key": "global_key_1" }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": "global_key_2" }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_3.json", "global_key": "global_key_3" } ] ``` ## Python example ```python Python theme={"theme":"css-variables"} # Generate dummy global keys global_key_1 = str(uuid.uuid4()) global_key_2 = str(uuid.uuid4()) global_key_3 = str(uuid.uuid4()) # Create a dataset dataset = client.create_dataset( name="pairwise_demo_"+str(uuid.uuid4()), iam_integration=None ) # Upload data rows task = dataset.create_data_rows([ { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_1.json", "global_key": global_key_1 }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": global_key_2 }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_3.json", "global_key": global_key_3 } ]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ## Model Response Conversation JSON format Each data row should have its own cloud-hosted JSON file. Each JSON file should include the following. | Parameter | Required | Description | | - | - | - | | `type` | Yes | This should always be populated with `application/vnd.labelbox.conversational` | | `version` | Yes | This should be populated with `1` | | `messages` | Yes | This is the array of [Message Objects](/horizon/developer-guides/import-conversational-text-data#message-object) that makes up the conversation. Please see below for the fields that must be included for each message. The current maximum is 250 messages in a conversation | | `modelOutputs` | Yes | This is the array of [Model outputs](/horizon/developer-guides/import-conversational-text-data#model-outputs-object) that makes up the model output to the conversation. See the section below for fields that must be included. | ### Message object | Parameter | Required | Description | | - | - | - | | `messageId` | Yes | This is the external ID for the message. This must be unique within the conversation file and will be returned on the exports to help match annotations to message. | | `timestampUsec` | No | The unix epoch timestamp of the message. | | `content` | Yes | This field contains the text string of the conversation. This must be under 10000 characters. | | `user` | Yes | This field is used to describe the person that is speaking. The user is defined by two fields: userId and name. The name field is used to show the user name in the editor. `{ "userId": "ID", "name": "Name" }` | | `align` | No | This field Controls the alignment and indentation of the text string in the editor. If left blank, we will default to `left`. Options:`left``0-left-indent``1-left-indent``2-left-indent``3-left-indent``4-left-indent``5-left-indent``right``0-right-indent``1-right-indent``2-right-indent``3-right-indent``4-right-indent``5-right-indent` | ### Model outputs object | Parameter | Required | Description | | - | - | - | | `title` | Yes | Use this field to name the model response | | `content` | Yes | This field displays the content of the model output | | `modelConfigName` | Yes | Use this field to specify the corresponding model config name | ## Sample Conversation JSON ### Sample note This sample contains `modelOutputs`field which is unique to importing data rows for LLM human preference. ```json json theme={"theme":"css-variables"} { "type": "application/vnd.labelbox.conversational", "version": 1, "messages": [ { "messageId": "message-0", "timestampUsec": 1530718491, "content": "Hi! How can I help?", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-1", "timestampUsec": 1530718503, "content": "I just bought a vacuum cleaner from you guys like a week ago and it's already broken!!", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true } ], "modelOutputs": [ { "title": "Response A", "content": "I'm really sorry to hear that your vacuum cleaner is not working as expected. We certainly understand how frustrating this can be, especially with a recent purchase. I assure you, we're here to help!\n\n To proceed with resolving this issue, could you please provide some details about the problem you're experiencing with the vacuum cleaner? Additionally, if you could share your order number or receipt details, that would be very helpful. We will definitely look into the available options for you, which may include a replacement or a refund, depending on the specific circumstances and our return policy.\n\n Your satisfaction is our priority, and we'll do our best to resolve this matter promptly and to your satisfaction.", "modelConfigName": "GPT-3.5 with temperature 0" }, { "title": "Response B", "content": "I'm sorry about the issue with your vacuum cleaner. Please send us the order number or receipt details so we can quickly assist you with a replacement. Your satisfaction is important to us!", "modelConfigName": "Fine Tuned GPT-3.5 with demo data" } ] } ``` # Import conversational text annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-conversational-text-annotations Developer guide for importing annotations on conversational text data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for a conversational text data row: * Radio * Checklist * Free-form text * Entity * Relationship ### Annotations can be message or global based Classification annotations can be supported both message and global based while tool annotations are supported only message specific. Removing the message\_id key inside a conversation text classification annotation results in the annotation becoming global. ## Classifications ### Radio (single-choice, message-based) ```python Python Annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_convo", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer")), message_id="0" # Remove argument if importing annotation as a global classification (not message-based) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_convo", "answer": { "name": "first_radio_answer" }, "messageId": "0", # Remove argument if importing global classifications } ``` ### Checklist (multi-choice, message-based) ```python Python Annotation theme={"theme":"css-variables"} checklist_annotation= lb_types.ClassificationAnnotation( name="checklist_convo", # must match your ontology feature"s name value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ), message_id="2" # Remove argument if importing annotation as a global classification (not message-based) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_convo", "answers": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"} ], "messageId": "2" # Remove argument if importing annotation as a global classification (not message-based) } ``` ### Free-form text (message-based) ```python Python Annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name="text_convo", value=lb_types.Text(answer="the answer to the text questions right here"), message_id="0" # Remove argument if importing annotation as a global classification (not message-based) ) ``` ```python NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "text_convo", "answer": "the answer to the text questions right here", "messageId": "0" # Remove argument if importing annotation as a global classification (not message-based) } ``` ## Tools ### Entity (message-based) ```python Python Annotation theme={"theme":"css-variables"} ner_annotation = lb_types.ObjectAnnotation( name="ner", value=lb_types.ConversationEntity( start=0, end=8, message_id="4" ) ) ``` ```python NDJSON theme={"theme":"css-variables"} ner_annotation = { "name": "ner", "location": { "start": 0, "end": 8 }, "messageId": "4" # this should match the message } ``` ## Relationships (message-based) ```python Python Annotation theme={"theme":"css-variables"} ner_source = lb_types.ObjectAnnotation( name="ner", value=lb_types.ConversationEntity( start=16, end=26, message_id="4" ) ) ner_target = lb_types.ObjectAnnotation( name="ner", value=lb_types.ConversationEntity( start=29, end=34, message_id="4" ) ) ner_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=ner_source, # Alternatively, specify a source_ontology_name target=ner_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python NDJSON theme={"theme":"css-variables"} uuid_source = str(uuid.uuid4()) uuid_target = str(uuid.uuid4()) ner_source_ndjson = { "uuid": uuid_source, "name": "ner", "location": { "start": 16, "end": 26 }, "messageId": "4" } ner_target_ndjson = { "uuid": uuid_target, "name": "ner", "location": { "start": 29, "end": 34 }, "messageId": "4" } ner_relationship_annotation_ndjson = { "name": "relationship", "relationship": { "source": uuid_source, #UUID reference to the source annotation "target": uuid_target, # UUID reference to the target annotation "type": "bidirectional" } } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid import json import numpy as np ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} # Create one Labelbox dataset global_key = "conversation-1.json" asset = { "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/conversational_text/1000-conversations/conversation-1.json", "global_key": global_key } dataset = client.create_dataset(name="conversational_annotation_import_demo_dataset") task = dataset.create_data_rows([asset]) task.wait_till_done() print("Errors:", task.errors) print("Failed data rows: ", task.failed_data_rows) ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.NER,name="ner"), lb.Tool(tool=lb.Tool.Type.RELATIONSHIP,name="relationship") ], classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, scope=lb.Classification.Scope.INDEX, # Remove this line or set scope to "GLOBAL" if importing global text annotations instructions="text_convo"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, scope=lb.Classification.Scope.INDEX, # Remove this line or set scope to "GLOBAL" if importing global checklist annotations instructions="checklist_convo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, instructions="radio_convo", scope=lb.Classification.Scope.INDEX, # Remove this line or set scope to "GLOBAL" if importing global radio annotations options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ) ] ) ontology = client.create_ontology("Ontology Conversation Annotations", ontology_builder.asdict()) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # Create Labelbox project project = client.create_project(name="conversational_project", media_type=lb.MediaType.Conversational) # Setup your ontology project.connect_ontology(ontology) # Connect your ontology ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} # Setup Batches and Ontology # Create a batch to send to your MAL project batch = project.create_batch( "first-batch-convo-demo", # Each batch in a project must have a unique name global_keys=[global_key], # a list of global keys, data row ids or global keys priority=5 # priority between 1(highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python Annotation Payload theme={"theme":"css-variables"} label = [] label.append( lb_types.Label( data={"global_key" : global_key }, annotations=[ ner_annotation, text_annotation, checklist_annotation, radio_annotation, ner_source, ner_target, ner_relationship ] ) ) ``` ```python NDJSON Payload theme={"theme":"css-variables"} label_ndjson = [] for annotations in [ ner_annotation_ndjson, text_annotation_ndjson, checklist_annotation_ndjson, radio_annotation_ndjson, ner_source_ndjson, ner_target_ndjson, ner_relationship_annotation_ndjson, ]: annotations.update({ "dataRow": { "globalKey": global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. ### Warning Relationship annotations are not supported for ground truth import jobs. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import conversational text data Source: https://docs.labelbox.com/horizon/developer-guides/import-conversational-text-data How to import conversational text data and sample import formats. ## Specifications File format: JSON in [our conversation format](#sample-conversation-json) Encoding: UTF-8 (Note: The Editor does not process any special character sequences like HTML Entities, Unicode Escape Sequence, or colon emoji aliases.) Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) When importing conversation or thread data to Labelbox, your JSON file should include the following information for each data row. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to a cloud-hosted conversational text JSON file. See the section below for details on our conversation format. For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). For older regions, your S3 bucket may be in `https://.s3..amazonaws.com/` format. If your object URLs are formatted this way, make sure they are in the virtual-hosted-style format before importing. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if their global keys are duplicated to existing data rows. | | `media_type` | No | `"CONVERSATIONAL"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | ## Import format ```json json theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_1.json", "global_key": "global_key_1" }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": "global_key_2" }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_3.json", "global_key": "global_key_3" } ] ``` ## Python example ```python Python theme={"theme":"css-variables"} # Generate dummy global keys global_key_1 = str(uuid.uuid4()) global_key_2 = str(uuid.uuid4()) global_key_3 = str(uuid.uuid4()) # Create a dataset dataset = client.create_dataset( name="pairwise_demo_"+str(uuid.uuid4()), iam_integration=None ) # Upload data rows task = dataset.create_data_rows([ { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_1.json", "global_key": global_key_1 }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": global_key_2 }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_3.json", "global_key": global_key_3 } ]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ## Conversation JSON ### Max conversation size For conversations, we support up to: * 250 messages. * 10000 character limit per message | Parameter | Required | Description | | - | - | - | | `type` | Yes | This should always be populated with `application/vnd.labelbox.conversational` | | `version` | Yes | This should be populated with `1` | | `messages` | Yes | This is the array of [Message Objects](/horizon/developer-guides/import-conversational-text-data#message-object) that makes up the conversation. Please see below for the fields that must be included for each message. The current maximum is 250 messages in a conversation | ### Message object | Parameter | Required | Description | | - | - | - | | `messageId` | Yes | This is the external ID for the message. This must be unique within the conversation file and will be returned on the exports to help match annotations to message. | | `timestampUsec` | No | The unix epoch timestamp of the message. | | `content` | Yes | This field contains the text string of the conversation. This must be under 10000 characters. | | `user` | Yes | This field is used to describe the person that is speaking. The user defined by two fields: userId and name. The name field is used to show the user name in the editor. `{ "userId": "ID", "name": "Name" }` | | `align` | No | This field Controls the alignment and indentation of the text string in the editor. If left blank, we will default to `left`. Options:`left``0-left-indent``1-left-indent``2-left-indent``3-left-indent``4-left-indent``5-left-indent``right``0-right-indent``1-right-indent``2-right-indent``3-right-indent``4-right-indent``5-right-indent` | | `canLabel` | Yes | This field determines if the text string can be annotated as a part of the editor workflow. If set to `true`, the editor will show a white background for the text string and the user will be able to annotated entities on the content string. If set to `false`, the editor will show a grey background for the text and the user will **not** be able to annotate the content string. | ## Sample Conversation JSON ### Note You can't upload the following file from the web interface directly. You must use an import file as described in [Import format](/horizon/developer-guides/import-conversational-text-data#import-format) ```json Sample conversation json expandable theme={"theme":"css-variables"} { "type": "application/vnd.labelbox.conversational", "version": 1, "messages": [ { "messageId": "message-0", "timestampUsec": 1530718491, "content": "I love iphone! i just bought new iphone! 🥰 📲", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-1", "timestampUsec": 1530718503, "content": "Thats good for you, i'm not very into new tech", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true }, { "messageId": "message-2", "timestampUsec": 1530718516, "content": "I am a college student and i am a college student 👩🏻‍🏫", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-3", "timestampUsec": 1530718528, "content": "I am go to gym and live on donations 🏋🏿‍♀️", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true }, { "messageId": "message-4", "timestampUsec": 1530718540, "content": "I am a vegan and i am in the midwest 🌱", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-5", "timestampUsec": 1530718553, "content": "So vegan... i have dogs maybe i should told then that they may eat cheap salads insted of meat 🐩", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true }, { "messageId": "message-6", "timestampUsec": 1530718565, "content": "I would not mind having them in the office that would be hard for me", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-7", "timestampUsec": 1530718578, "content": "Dogs or vegan in office?", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true }, { "messageId": "message-8", "timestampUsec": 1530718590, "content": "I am a vegetarian so i am vegan", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-9", "timestampUsec": 1530718603, "content": "Strange answer", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true }, { "messageId": "message-10", "timestampUsec": 1530718615, "content": "I guess i just do not know what to ps4 experience with.", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false } ] } ``` # Import document annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-document-annotations Developer guide for importing annotations on document (PDF) data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for a document data row: * Radio * Checklist * Free-form text * Bounding box * Entity * Relationship ### Annotations are global and page based Classification annotations are only supported globally while page-specific tool annotations are supported at a per page basis. ## Classifications ### Radio (single-choice, global) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Checklist (multi-choice, global) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer") ]) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"} ] } ``` ### Free-form text (global) ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name="free_text", # must match your ontology feature"s name value=lb_types.Text(answer="sample text") ) ``` ```python NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "answer": "sample text" } ``` ### Nested Classifications (global) ```python Python annotation theme={"theme":"css-variables"} nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } nested_radio_annotation_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer"} }] } } ``` ## Tools ### Bounding box (page specific) When importing a bounding box annotation, you need to specify its `DocumentRectangle` value, which defines its precise area on a document page. You can choose from the following `RectangleUnit` options, which determine the measurement unit used to define the dimensions and coordinates of the bounding box: * INCHES * PIXELS * POINTS * PERCENT ```python Python annotation theme={"theme":"css-variables"} bbox_annotation = lb_types.ObjectAnnotation( name="bounding_box", # must match your ontology feature's name value=lb_types.DocumentRectangle( start=lb_types.Point(x=102.771, y=135.3), # x = left, y = top end=lb_types.Point(x=518.571, y=245.143), # x = left + width , y = top + height page=0, unit=lb_types.RectangleUnit.POINTS ) ) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_annotation_ndjson = { "name": "bounding_box", "bbox": { "top": 135.3, "left": 102.771, "height": 109.843, "width": 415.8 }, "page": 0, "unit": "POINTS" } ``` ### Entity (page specific) `textSelections` is the payload required for each entity annotation. Each`textSelections` item in the list requires the following fields: * The`groupId` associated with a group of words. * A list of `tokenIds` for each word in the group of words. * The `page` of the document (1-indexed). Both `tokenIds` and `groupdId` can extracted from the text layer URL attached to the data row. For more information on text layers, visit our [import document data guide](/horizon/developer-guides/import-document-data). ```python Python annotation theme={"theme":"css-variables"} entities_annotations = lb_types.ObjectAnnotation( name="named_entity", value= lb_types.DocumentEntity( name="named_entity", textSelections=[ lb_types.DocumentTextSelection( token_ids=[], group_id="", page=1 ) ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} entities_annotations_ndjson = { "name": "named_entity", "textSelections": [ { "tokenIds": [ "", ## ids associated with each word in a group ], "groupId": "", ## id associated with a group of words "page": 1, } ] } ``` ### Tool with nested classifications (page specific) ```python Bounding Box Example theme={"theme":"css-variables"} bbox_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", value=lb_types.DocumentRectangle( start=lb_types.Point(x=317.271, y=226.757), # x = left, y = top end=lb_types.Point(x=566.657, y=420.986), # x = left + width , y = top + height unit=lb_types.RectangleUnit.POINTS, page=1 ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="second_sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="second_sub_radio_answer" ) ) ) ] ) ) ) ] ) ``` ```python Entity Example theme={"theme":"css-variables"} ner_with_checklist_subclass_annotation = lb_types.ObjectAnnotation( name="ner_with_checklist_subclass", value=lb_types.DocumentEntity( name="ner_with_checklist_subclass", text_selections=[ lb_types.DocumentTextSelection( token_ids=[], group_id="", page=1 ) ] ), classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer(name="first_sub_checklist_answer")] ) ) ] ) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_with_radio_subclass_annotation_ndjson = { "name": "bbox_with_radio_subclass", "classifications": [ { "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer", "classifications": [ { "name": "second_sub_radio_question", "answer": { "name": "second_sub_radio_answer"} } ] } } ], "bbox": { "top": 226.757, "left": 317.271, "height": 194.229, "width": 249.386 }, "page": 1, "unit": "POINTS" } ``` ## Relationships (page specific) ```python Bounding Box Example theme={"theme":"css-variables"} bbox_source = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.DocumentRectangle( start=lb_types.Point(x=188.257, y=68.875), # x = left, y = top end=lb_types.Point(x=270.907, y=149.556), # x = left + width , y = top + height unit=lb_types.RectangleUnit.POINTS, page=1 ), ) bbox_target = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.DocumentRectangle( start=lb_types.Point(x=96.424, y=66.251), end=lb_types.Point(x=179.074, y=146.932), unit=lb_types.RectangleUnit.POINTS, page=1 ), ) bbox_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=bbox_source, # Alternatively, specify a source_ontology_name target=bbox_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python Entity Example theme={"theme":"css-variables"} entity_source = lb_types.ObjectAnnotation( name="named_entity", value= lb_types.DocumentEntity( name="named_entity", textSelections=[ lb_types.DocumentTextSelection( token_ids=[], group_id="", page=1 ) ] ) ) entity_target = lb_types.ObjectAnnotation( name="named_entity", value=lb_types.DocumentEntity( name="named_entity", textSelections=[ lb_types.DocumentTextSelection( token_ids=[], group_id="", page=1 ) ] ) ) entity_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=entity_source, # Alternatively, specify a source_ontology_name target=entity_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python NDJSON theme={"theme":"css-variables"} uuid_source = str(uuid.uuid4()) uuid_target = str(uuid.uuid4()) entity_source_ndjson = { "name": "named_entity", "uuid": uuid_source, "textSelections": [ { "tokenIds": [ "" ], "groupId": "", "page": 1 } ] } entity_target_ndjson = { "name": "named_entity", "uuid": uuid_target, "textSelections": [ { "tokenIds": [ "" ], "groupId": "", "page": 1 } ] } ner_relationship_annotation_ndjson = { "name": "relationship", "relationship": { "source": uuid_source, # UUID reference to the source annotation. "target": uuid_target, # UUID reference to the target annotation "type": "unidirectional" } } ``` ### Read-only relationships ### This is a [beta](/horizon/guides/product-release-phases#beta) feature Admins can mark relationships as read\_only to prevent other roles from deleting them: ```python Python theme={"theme":"css-variables"} bbox_source = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.DocumentRectangle( start=lb_types.Point(x=188.257, y=68.875), # x = left, y = top end=lb_types.Point(x=270.907, y=149.556), # x = left + width , y = top + height unit=lb_types.RectangleUnit.POINTS, page=1 ), ) bbox_target = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.DocumentRectangle( start=lb_types.Point(x=96.424, y=66.251), end=lb_types.Point(x=179.074, y=146.932), unit=lb_types.RectangleUnit.POINTS, page=1 ), ) bbox_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=bbox_source, # Alternatively, specify a source_ontology_name target=bbox_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, readonly=True, )) ``` ### Relationships between classifications and objects ### This is a [beta](/horizon/guides/product-release-phases#beta) feature ```python Linking multiple objects to classifications expandable theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_pdf", value=lb_types.Radio( answer=lb_types.ClassificationAnswer(name="second_radio_answer") ), ) checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_pdf", # must match your ontology feature's name value=lb_types.Checklist( answer=[ lb_types.ClassificationAnswer(name="first_checklist_answer"), lb_types.ClassificationAnswer(name="second_checklist_answer"), ] ), ) text_annotation = lb_types.ClassificationAnnotation( name="text_pdf", value=lb_types.Text(answer="sample text") ) bbox_annotation = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.DocumentRectangle( start=lb_types.Point(x=0.5, y=0.5), # x = left, y = top end=lb_types.Point(x=0.7, y=0.7), # x= left + width , y = top + height page=1, unit=RectangleUnit.PERCENT, ), ) bbox_annotation_2 = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.DocumentRectangle( start=lb_types.Point(x=0.5, y=0.5), end=lb_types.Point(x=0.7, y=0.7), page=2, unit=RectangleUnit.PERCENT, ), ) entity_annotation = lb_types.ObjectAnnotation( name="named_entity", value=lb_types.DocumentEntity( name="named_entity", textSelections=[ lb_types.DocumentTextSelection( token_ids=["9763b68b-9ea7-472d-af40-20ab69fbee71"], group_id="ee108a07-059a-4302-910f-b16eb148f123", page=1, ) ], ), ) text_bbox_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=text_annotation, # Alternatively, specify a source_ontology_name target=bbox_annotation, type=lb_types.Relationship.Type.UNIDIRECTIONAL, ), ) radio_bbox_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=radio_annotation, # Alternatively, specify a source_ontology_name target=bbox_annotation, type=lb_types.Relationship.Type.UNIDIRECTIONAL, ), ) checklist_bbox_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=checklist_annotation, # Alternatively, specify a source_ontology_name target=bbox_annotation, type=lb_types.Relationship.Type.UNIDIRECTIONAL, ), ) text_entity_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=text_annotation, # Alternatively, specify a source_ontology_name target=entity_annotation, type=lb_types.Relationship.Type.UNIDIRECTIONAL, ), ) bbox_bbox2_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=bbox_annotation, # Alternatively, specify a source_ontology_name target=bbox_annotation_2, type=lb_types.Relationship.Type.UNIDIRECTIONAL, ), ) ``` ```python NDJSON example theme={"theme":"css-variables"} uuid_radio = str(uuid.uuid4()) uuid_checklist = str(uuid.uuid4()) uuid_text = str(uuid.uuid4()) uuid_bbox_1 = str(uuid.uuid4()) uuid_bbox_2 = str(uuid.uuid4()) uuid_entity = str(uuid.uuid4()) # Radio annotation radio_annotation_ndjson = { "name": "radio_pdf", "radio": { "answer": { "name": "second_radio_answer" } } } # Checklist annotation checklist_annotation_ndjson = { "name": "checklist_pdf", "checklist": { "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"} ] } } # Text annotation text_annotation_ndjson = { "name": "text_pdf", "text": { "answer": "sample text" } } # Bounding box annotation 1 bbox_annotation_ndjson = { "name": "bounding_box", "documentRectangle": { "start": {"x": 0.5, "y": 0.5}, "end": {"x": 0.7, "y": 0.7}, "page": 1, "unit": "PERCENT" } } # Bounding box annotation 2 bbox_annotation_2_ndjson = { "name": "bounding_box", "documentRectangle": { "start": {"x": 0.5, "y": 0.5}, "end": {"x": 0.7, "y": 0.7}, "page": 2, "unit": "PERCENT" } } # Named entity annotation entity_annotation_ndjson = { "name": "named_entity", "documentEntity": { "name": "named_entity", "textSelections": [ { "tokenIds": ["9763b68b-9ea7-472d-af40-20ab69fbee71"], "groupId": "ee108a07-059a-4302-910f-b16eb148f123", "page": 1 } ] } } # Relationships text_bbox_relationship_ndjson = { "name": "relationship", "relationship": { "source": uuid_text, "target": uuid_bbox_1, "type": "unidirectional" } } radio_bbox_relationship_ndjson = { "name": "relationship", "relationship": { "source": uuid_radio, "target": uuid_bbox_1, "type": "unidirectional" } } checklist_bbox_relationship_ndjson = { "name": "relationship", "relationship": { "source": uuid_checklist, "target": uuid_bbox_1, "type": "unidirectional" } } text_entity_relationship_ndjson = { "name": "relationship", "relationship": { "source": uuid_text, "target": uuid_entity, "type": "unidirectional" } } bbox_bbox2_relationship_ndjson = { "name": "relationship", "relationship": { "source": uuid_bbox_1, "target": uuid_bbox_2, "type": "unidirectional" } } ``` ## Creating text entity annotation For importing entity annotations, you can use your own `text_layer_url` or a Labelbox-generated `text_layer_url`. You can get the Labelbox-generated `text_layer_url` by exporting the data row. The below code snippet demonstrates this process by exporting from a data row. ```python Python theme={"theme":"css-variables"} # Export data row task = lb.DataRow.export(client=client,global_keys=[global_key]) task.wait_till_done() if task.has_result(): stream = task.get_buffered_stream() text_layer = "" for output in stream: output_json = output.json text_layer = output_json['media_attributes']['text_layer_url'] print(text_layer) ``` ```python Python theme={"theme":"css-variables"} import requests import json # Helper method for updating text selections def update_text_selections(annotation, group_id, list_tokens, page): return annotation.update({ "textSelections": [ { "groupId": group_id, "tokenIds": list_tokens, "page": page } ] }) # Fetch the content of the text layer res = requests.get(text_layer) # Phrases that we want to annotation obtained from the text layer url content_phrases = ["Metal-insulator (MI) transitions have been one of the" , "T. Sasaki, N. Yoneyama, and N. Kobayashi",, "Organic charge transfer salts based on the donor", "the experimental investigations on this issue have not"] # Parse the text layer text_selections = [] text_selections_ner = [] text_selections_source = [] text_selections_target = [] for obj in json.loads(res.text): for group in obj["groups"]: if group["content"] == content_phrases[0]: list_tokens = [x["id"] for x in group["tokens"]] # build text selections for Python Annotation Types document_text_selection = lb_types.DocumentTextSelection(groupId=group["id"], tokenIds=list_tokens, page=1) text_selections.append(document_text_selection) # build text selection for the NDJson annotations update_text_selections(annotation=entities_annotations_ndjson, group_id=group["id"], # id representing group of words list_tokens=list_tokens, # ids representing individual words from the group page=1) if group["content"] == content_phrases[1]: list_tokens_2 = [x["id"] for x in group["tokens"]] # build text selections for Python Annotation Types ner_text_selection = lb_types.DocumentTextSelection(groupId=group["id"], tokenIds=list_tokens_2, page=1) text_selections_ner.append(ner_text_selection) # build text selection for the NDJson annotations update_text_selections(annotation=ner_with_checklist_subclass_annotation_ndjson, group_id=group["id"], # id representing group of words list_tokens=list_tokens_2, # ids representing individual words from the group page=1) if group["content"] == content_phrases[2]: relationship_source = [x["id"] for x in group["tokens"]] # build text selections for Python Annotation Types text_selection_entity_source = lb_types.DocumentTextSelection(groupId=group["id"], tokenIds=relationship_source, page=1) text_selections_source.append(text_selection_entity_source) # build text selection for the NDJson annotations update_text_selections(annotation=entity_source_ndjson, group_id=group["id"], # id representing group of words list_tokens=relationship_source, # ids representing individual words from the group page=1) if group["content"] == content_phrases[3]: relationship_target = [x["id"] for x in group["tokens"]] # build text selections for Python Annotation Types text_selection_entity_target = lb_types.DocumentTextSelection(group_id=group["id"], tokenIds=relationship_target, page=1) text_selections_target.append(text_selection_entity_target) # build text selections forthe NDJson annotations update_text_selections(annotation=entity_target_ndjson, group_id=group["id"], # id representing group of words list_tokens=relationship_target, # ids representing individual words from the group page=1) ``` Re-write the Python annotations to include text selections (only required for Python annotation types) ```python theme={"theme":"css-variables"} # re-write the entity annotation with text selections entities_annotation_document_entity = lb_types.DocumentEntity(name="named_entity", textSelections = text_selections) entities_annotation = lb_types.ObjectAnnotation(name="named_entity",value=entities_annotation_document_entity) # re-write the entity annotation + subclassification with text selections classifications = [ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer(name="first_sub_checklist_answer")] ) ) ] ner_annotation_with_subclass = lb_types.DocumentEntity(name="ner_with_checklist_subclass", textSelections= text_selections_ner) ner_with_checklist_subclass_annotation = lb_types.ObjectAnnotation(name="ner_with_checklist_subclass", value=ner_annotation_with_subclass, classifications=classifications) # re-write the entity source and target annotations withe text selectios entity_source_doc = lb_types.DocumentEntity(name="named_entity", text_selections= text_selections_source) entity_source = lb_types.ObjectAnnotation(name="named_entity", value=entity_source_doc) entity_target_doc = lb_types.DocumentEntity(name="named_entity", text_selections=text_selections_target) entity_target = lb_types.ObjectAnnotation(name="named_entity", value=entity_target_doc) # re-write the entity relationship with the re-created entities entity_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=entity_source, target=entity_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) print(f"entities_annotations_ndjson={entities_annotations_ndjson}") print(f"entities_annotation={entities_annotation}") print(f"nested_entities_annotation_ndjson={ner_with_checklist_subclass_annotation_ndjson}") print(f"nested_entities_annotation={ner_with_checklist_subclass_annotation}") print(f"entity_source_ndjson={entity_source_ndjson}") print(f"entity_target_ndjson={entity_target_ndjson}") print(f"entity_source={entity_source}") print(f"entity_target={entity_target}") ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import uuid import json import labelbox as lb import labelbox.types as lb_types ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} global_key = "0801.3483.pdf" img_url = { "row_data": { "pdf_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", }, "global_key": global_key } dataset = client.create_dataset(name="pdf_demo_dataset") task = dataset.create_data_rows([img_url]) task.wait_till_done() print(f"Failed data rows: {task.failed_data_rows}") print(f"Errors: {task.errors}") ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text", scope = lb.Classification.Scope.GLOBAL ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ]) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ]) ] ), ], tools=[ # List of Tool objects lb.Tool( tool=lb.Tool.Type.BBOX,name="bounding_box"), lb.Tool(tool=lb.Tool.Type.NER, name="named_entity"), lb.Tool(tool=lb.Tool.Type.RELATIONSHIP,name="relationship"), lb.Tool(tool=lb.Tool.Type.NER, name="ner_with_checklist_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[ lb.Option(value="first_sub_checklist_answer") ] ) ]), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option( value="first_sub_radio_answer" , options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="second_sub_radio_question", options=[lb.Option("second_sub_radio_answer")] )] )] )] )] ) ontology = client.create_ontology("Document Annotation Import Demo", ontology_builder.asdict(), media_type=lb.MediaType.Document) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # Create a Labelbox project project = client.create_project(name="PDF_annotation_demo", queue_mode=QueueMode.Batch, media_type=lb.MediaType.Document) project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} project.create_batch( "PDF_annotation_batch", # Each batch in a project must have a unique name global_keys=[global_key] , # a list of global keys, data rows, or data row ids priority=5 # priority between 1(highest) - 5(lowest) ) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python annotation payload theme={"theme":"css-variables"} # create a Label labels = [] labels.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ entities_annotation, checklist_annotation, nested_checklist_annotation, text_annotation, radio_annotation, nested_radio_annotation, bbox_annotation, bbox_with_radio_subclass_annotation, ner_with_checklist_subclass_annotation, entity_source, entity_target, entity_relationship,# Only supported for MAL imports bbox_source, bbox_target, bbox_relationship # Only supported for MAL imports ] ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_ndjson = [] for annot in [ entities_annotations_ndjson, checklist_annotation_ndjson, nested_checklist_annotation_ndjson, text_annotation_ndjson, radio_annotation_ndjson, nested_radio_annotation_ndjson, bbox_annotation_ndjson, bbox_with_radio_subclass_annotation_ndjson, ner_with_checklist_subclass_annotation_ndjson, entity_source_ndjson, entity_target_ndjson, ner_relationship_annotation_ndjson, # Only supported for MAL imports bbox_source_ndjson, bbox_target_ndjson, bbox_relationship_annotation_ndjson # Only supported for MAL imports ]: annot.update({ "dataRow": {"globalKey": global_key}, }) label_ndjson.append(annot) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. ### Warning Relationship annotations are not supported for ground truth import jobs. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import document data Source: https://docs.labelbox.com/horizon/developer-guides/import-document-data How to import document data and sample import formats. ## Specifications Format: PDF Recommended size: 100 pages or fewer Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) When importing document data to Labelbox, you are no longer required to provide an OCR extract in the form of a JSON file. Labelbox generates text layers automatically during PDF import using Google Document AI if the data row doesn't include a text layer. The JSON file generated will be your text layer, rendered on top of your PDF in the Document Editor. ### Text layer limit Previously generated PDF documents without text layers can't be retroactively filled with the text layer generated by Labelbox. Google Document AI has the following limitations: * The document must have no more than 15 pages * The file size should not exceed 20 MB. Additionally, Google Document AI optimizes documents before OCR processing. This optimization might include rotating images or pages to ensure text appears horizontally. Consequently, token coordinates are calculated based on the rotated/optimized images, resulting in potential discrepancies with the original PDF document. For example, the document is rotated 90 degrees before processing in a landscape-oriented PDF. As a result, all tokens in the text layer are also rotated by 90 degrees. ### Image encoding If your PDF files contain images, use JPEG encoding and RGB colorspace for color images. ## Text Layer Validation Schema If you want to upload your own text layer, the textLayer JSON file must adhere to the following JSON schema. ```json Text Layer Validation Schema expandable theme={"theme":"css-variables"} { "type": "array", "items": { "$ref": "#/$defs/page" }, "$defs": { "page": { "type": "object", "properties": { "width": { "type": "number" }, "height": { "type": "number" }, "number": { "type": "number" }, "units": { "enum": ["POINTS", "PERCENT"] }, "groups": { "type": "array", "items": { "$ref": "#/$defs/group" } } }, "required": ["number", "units", "groups"] }, "group": { "type": "object", "properties": { "id": { "type": "string" }, "content": { "type": "string" }, "geometry": { "$ref": "#/$defs/geometry" }, "tokens": { "type": "array", "items": { "$ref": "#/$defs/token" } } }, "required": ["id", "content", "geometry", "tokens"] }, "geometry": { "type": "object", "properties": { "left": { "type": "number" }, "top": { "type": "number" }, "width": { "type": "number" }, "height": { "type": "number" } }, "required": ["left", "top", "width", "height"] }, "token": { "type": "object", "properties": { "id": { "type": "string" }, "content": { "type": "string" }, "geometry": { "$ref": "#/$defs/geometry" } }, "required": ["id", "geometry", "content"] } } } ``` ```json Document AI example expandable theme={"theme":"css-variables"} [ { "width": 1601, "height": 2498, "number": 1, "units": "PERCENT", "groups": [ { "id": "b4f4e1da-4088-44b3-a578-22f88ce9e166", "content": "ΑΝ", "geometry": { "left": 0.4846970736980438, "top": 0.17333866655826569, "width": 0.028107434511184692, "height": 0.007205769419670105 }, "tokens": [ { "id": "0791adf8-80e9-4d3d-9d37-b3ad42dd061e", "content": "ΑΝ", "geometry": { "left": 0.4846970736980438, "top": 0.17333866655826569, "width": 0.028107434511184692, "height": 0.007205769419670105 } } ] }, { "id": "6f7c7e45-b9e8-4845-8c4e-63915e4a2e3d", "content": "ESSAY", "geometry": { "left": 0.43410369753837585, "top": 0.22377902269363403, "width": 0.1274203360080719, "height": 0.014811843633651733 }, "tokens": [ { "id": "5adfb30a-235a-41c0-9902-a930cad660fc", "content": "ESSAY", "geometry": { "left": 0.43410369753837585, "top": 0.22377902269363403, "width": 0.1274203360080719, "height": 0.014811843633651733 } } ] }, { "id": "beec4d6e-cc9f-45e8-858c-c6c3f7c3ae49", "content": "ON THE", "geometry": { "left": 0.46283572912216187, "top": 0.2810248136520386, "width": 0.07307934761047363, "height": 0.006805449724197388 }, "tokens": [ { "id": "a9456ab4-bfd0-49f2-a10b-c0ad2fa2fbb4", "content": "ON", "geometry": { "left": 0.46283572912216187, "top": 0.2810248136520386, "width": 0.0237351655960083, "height": 0.006405144929885864 } }, { "id": "fbdb497f-8241-406c-a06e-87027fb9b0b2", "content": "THE", "geometry": { "left": 0.4971892535686493, "top": 0.2810248136520386, "width": 0.038725823163986206, "height": 0.006405144929885864 } } ] }, { "id": "4caeda09-c92e-4c47-bb7a-3aea91a7dc29", "content": "PRINCIPLE OF POPULATION,", "geometry": { "left": 0.2841973900794983, "top": 0.31545236706733704, "width": 0.425983726978302, "height": 0.013210564851760864 }, "tokens": [ { "id": "493ef02d-8c10-45cb-a889-10abc907a30c", "content": "PRINCIPLE", "geometry": { "left": 0.2841973900794983, "top": 0.31545236706733704, "width": 0.15990003943443298, "height": 0.013210564851760864 } }, { "id": "23ae78f7-9c04-43cb-b7a0-180806ec4472", "content": "OF", "geometry": { "left": 0.4609619081020355, "top": 0.31545236706733704, "width": 0.036851972341537476, "height": 0.013210564851760864 } }, { "id": "33462d2b-af22-456e-bd3b-5ed03ca091f3", "content": "POPULATION", "geometry": { "left": 0.5115552544593811, "top": 0.31545236706733704, "width": 0.19300436973571777, "height": 0.013210564851760864 } }, { "id": "f989c150-075b-4ea8-ac30-3cb8bd5697a7", "content": ",", "geometry": { "left": 0.7033104300498962, "top": 0.31545236706733704, "width": 0.006870687007904053, "height": 0.013210564851760864 } } ] }, { "id": "be9c8936-10b8-4013-8259-b34d309b1ce9", "content": "AS IT AFFECTS", "geometry": { "left": 0.42910680174827576, "top": 0.3570856750011444, "width": 0.13991257548332214, "height": 0.006805449724197388 }, "tokens": [ { "id": "3af460fb-30cf-4988-82a3-4bdd6814d37e", "content": "AS", "geometry": { "left": 0.42910680174827576, "top": 0.3570856750011444, "width": 0.02123674750328064, "height": 0.006805449724197388 } }, { "id": "56b8b804-d802-48ca-af12-9fca0f16fe22", "content": "IT", "geometry": { "left": 0.4603372812271118, "top": 0.3570856750011444, "width": 0.018113672733306885, "height": 0.006805449724197388 } }, { "id": "29c69737-a6ff-48c6-b09d-fa6b04739c91", "content": "AFFECTS", "geometry": { "left": 0.4890693426132202, "top": 0.3570856750011444, "width": 0.07995003461837769, "height": 0.006805449724197388 } } ] }, { "id": "e0334ded-e13b-4b56-a53e-e8671eae6871", "content": "THE FUTURE IMPROVEMENT OF SOCIETY.", "geometry": { "left": 0.21549031138420105, "top": 0.3995196223258972, "width": 0.5671455562114716, "height": 0.010008007287979126 }, "tokens": [ { "id": "fbf57954-5e06-4a78-8d62-f012d18683d7", "content": "THE", "geometry": { "left": 0.21549031138420105, "top": 0.3995196223258972, "width": 0.05496564507484436, "height": 0.010008007287979126 } }, { "id": "43be97d3-1179-4ea9-b07d-aa0e369f770f", "content": "FUTURE", "geometry": { "left": 0.2841973900794983, "top": 0.3995196223258972, "width": 0.1043097972869873, "height": 0.010008007287979126 } }, { "id": "57d3e552-8fae-466a-a090-5c3f1245c803", "content": "IMPROVEMENT", "geometry": { "left": 0.4022485911846161, "top": 0.3995196223258972, "width": 0.20112428069114685, "height": 0.010008007287979126 } }, { "id": "b2f2323b-6e5b-41ab-80c8-4c4dc3d7d1d5", "content": "OF", "geometry": { "left": 0.6171143054962158, "top": 0.3995196223258972, "width": 0.031230449676513672, "height": 0.010008007287979126 } }, { "id": "1284a561-2359-4658-9a23-1957ea9c34e0", "content": "SOCIETY", "geometry": { "left": 0.6627107858657837, "top": 0.3995196223258972, "width": 0.11242973804473877, "height": 0.010008007287979126 } }, { "id": "1e91b8d6-668f-4d25-bf30-75c4478ae33e", "content": ".", "geometry": { "left": 0.7763897776603699, "top": 0.3995196223258972, "width": 0.006246089935302734, "height": 0.010008007287979126 } } ] }, { "id": "027c0a96-c979-47de-8fca-44d0b07c3ac3", "content": "WITH REMARKS", "geometry": { "left": 0.42410993576049805, "top": 0.44515612721443176, "width": 0.14990627765655518, "height": 0.0064051151275634766 }, "tokens": [ { "id": "60143ecf-ae4f-4635-a1e6-01fca29d286c", "content": "WITH", "geometry": { "left": 0.42410993576049805, "top": 0.44515612721443176, "width": 0.05059337615966797, "height": 0.0064051151275634766 } }, { "id": "daa24689-add8-4de3-9b58-36d6ea8b871f", "content": "REMARKS", "geometry": { "left": 0.48657089471817017, "top": 0.44515612721443176, "width": 0.08744531869888306, "height": 0.0064051151275634766 } } ] }, { "id": "6f9dac4a-4f7e-4409-bbaa-205817a45880", "content": "ON THE SPECULATIONS OF MR. GODWIN,", "geometry": { "left": 0.2804497182369232, "top": 0.47718173265457153, "width": 0.43847593665122986, "height": 0.011208981275558472 }, "tokens": [ { "id": "b4828d24-46a6-4530-ae0f-dc6290f5d34d", "content": "ON", "geometry": { "left": 0.2804497182369232, "top": 0.47758206725120544, "width": 0.027482837438583374, "height": 0.009207367897033691 } }, { "id": "472a19ce-d3b9-4fd2-8d2f-645a1b1ce6da", "content": "THE", "geometry": { "left": 0.31980013847351074, "top": 0.47758206725120544, "width": 0.042473435401916504, "height": 0.009207367897033691 } }, { "id": "ce03974a-0d54-43b0-8ded-78f67732e070", "content": "SPECULATIONS", "geometry": { "left": 0.37289193272590637, "top": 0.47758206725120544, "width": 0.15427860617637634, "height": 0.010008007287979126 } }, { "id": "0d9584f4-c1cd-4acf-b8b1-2bb2a812f344", "content": "OF", "geometry": { "left": 0.5377888679504395, "top": 0.4783827066421509, "width": 0.02560901641845703, "height": 0.009207367897033691 } }, { "id": "6d44ba8a-f037-4762-83e7-864d3d91293b", "content": "MR", "geometry": { "left": 0.5740162134170532, "top": 0.4783827066421509, "width": 0.031855106353759766, "height": 0.009207367897033691 } }, { "id": "6aa31fc2-ca8f-49b8-b640-75d1ba9c14b5", "content": ".", "geometry": { "left": 0.6071205735206604, "top": 0.4787830412387848, "width": 0.004372239112854004, "height": 0.009207338094711304 } }, { "id": "90d91952-9233-47f2-aefd-ec4a446a51f0", "content": "GODWIN", "geometry": { "left": 0.6233603954315186, "top": 0.4783827066421509, "width": 0.0855715274810791, "height": 0.010008007287979126 } }, { "id": "a5719dea-8694-4ed6-97ad-eea86a92c0ea", "content": ",", "geometry": { "left": 0.712054967880249, "top": 0.4791833460330963, "width": 0.006870687007904053, "height": 0.009207367897033691 } } ] }, { "id": "8b5a88c6-c39e-4f8d-88f2-30eaa7c3f935", "content": "M. CONDORCET,", "geometry": { "left": 0.41474080085754395, "top": 0.5148118734359741, "width": 0.1698938012123108, "height": 0.01080864667892456 }, "tokens": [ { "id": "63b99375-0adc-461b-9539-0d907eebac3b", "content": "M.", "geometry": { "left": 0.41474080085754395, "top": 0.5152121782302856, "width": 0.02435976266860962, "height": 0.009207367897033691 } }, { "id": "2a2f1ef8-6018-41f7-9b06-9f957313251b", "content": "CONDORCET", "geometry": { "left": 0.4509681463241577, "top": 0.5152121782302856, "width": 0.12679576873779297, "height": 0.010408341884613037 } }, { "id": "0623b165-4bb6-4ac7-b115-5989dab0d188", "content": ",", "geometry": { "left": 0.5771392583847046, "top": 0.516413152217865, "width": 0.0074953436851501465, "height": 0.008807003498077393 } } ] }, { "id": "4f5337fe-082a-4937-a08f-8c3304b84c7a", "content": "AND OTHER WRITERS.", "geometry": { "left": 0.3791380524635315, "top": 0.5536429286003113, "width": 0.23860085010528564, "height": 0.0072057247161865234 }, "tokens": [ { "id": "ba603d0c-b146-4d01-9e1f-ad47ce600c29", "content": "AND", "geometry": { "left": 0.3791380524635315, "top": 0.5536429286003113, "width": 0.04434725642204285, "height": 0.0072057247161865234 } }, { "id": "3bdbfa39-0418-4758-a855-1104a8d8fb02", "content": "OTHER", "geometry": { "left": 0.4347282946109772, "top": 0.5536429286003113, "width": 0.07245472073554993, "height": 0.0072057247161865234 } }, { "id": "2a729df2-5199-45dd-bdf6-4787b95efe65", "content": "WRITERS", "geometry": { "left": 0.5165521502494812, "top": 0.5536429286003113, "width": 0.09369146823883057, "height": 0.0072057247161865234 } }, { "id": "40924cb4-fc4f-45a2-b4f7-1c31368f243b", "content": ".", "geometry": { "left": 0.612742006778717, "top": 0.5536429286003113, "width": 0.004996895790100098, "height": 0.0072057247161865234 } } ] }, { "id": "75a050e5-46d5-4773-9909-0bc2309892da", "content": "LONDON:", "geometry": { "left": 0.45284196734428406, "top": 0.6016813516616821, "width": 0.09306684136390686, "height": 0.00840669870376587 }, "tokens": [ { "id": "cea98dbb-45d8-4562-8dee-a965cee86db7", "content": "LONDON", "geometry": { "left": 0.45284196734428406, "top": 0.6016813516616821, "width": 0.08432230353355408, "height": 0.00840669870376587 } }, { "id": "92b73d06-bae1-4a32-9970-e6cbc99ee74f", "content": ":", "geometry": { "left": 0.5402873158454895, "top": 0.6024819612503052, "width": 0.005621492862701416, "height": 0.007606089115142822 } } ] }, { "id": "c31fd2a2-f11e-4b8e-8b84-8592bd65b235", "content": "PRINTED FOR J. JOHNSON, IN ST. PAUL'S", "geometry": { "left": 0.30605870485305786, "top": 0.6381104588508606, "width": 0.38538414239883423, "height": 0.008406758308410645 }, "tokens": [ { "id": "920b4d6d-150a-4e53-b380-315190fc3c96", "content": "PRINTED", "geometry": { "left": 0.30605870485305786, "top": 0.6381104588508606, "width": 0.08119925856590271, "height": 0.00960773229598999 } }, { "id": "356a78e0-ebd7-44a0-8f04-015d3fe3527b", "content": "FOR", "geometry": { "left": 0.3978763222694397, "top": 0.6377101540565491, "width": 0.03560274839401245, "height": 0.010008037090301514 } }, { "id": "b2ddf115-27c1-4078-b96c-a80464d54c2a", "content": "J.", "geometry": { "left": 0.4409743845462799, "top": 0.6377101540565491, "width": 0.012492209672927856, "height": 0.009607672691345215 } }, { "id": "d25272ac-5ae1-4510-9778-1f8c72613960", "content": "JOHNSON", "geometry": { "left": 0.46283572912216187, "top": 0.6377101540565491, "width": 0.08432227373123169, "height": 0.009607672691345215 } }, { "id": "79866cbf-139e-4b14-805b-6e8f8ea1c8cb", "content": ",", "geometry": { "left": 0.5496564507484436, "top": 0.6373098492622375, "width": 0.004372298717498779, "height": 0.009607672691345215 } }, { "id": "dcddbbd2-b954-48ff-91b2-a323310724a2", "content": "IN", "geometry": { "left": 0.5652716755867004, "top": 0.6373098492622375, "width": 0.01873832941055298, "height": 0.009607672691345215 } }, { "id": "46dbb8a6-dc00-4a7b-8703-aec10245d262", "content": "ST", "geometry": { "left": 0.5946283340454102, "top": 0.6373098492622375, "width": 0.019362926483154297, "height": 0.009607672691345215 } }, { "id": "e6ef7add-c8c1-4f18-8bfc-55cc3a810449", "content": ".", "geometry": { "left": 0.6164897084236145, "top": 0.6373098492622375, "width": 0.004372239112854004, "height": 0.009607672691345215 } }, { "id": "4a743990-3683-4017-9c62-4e6cae7513a2", "content": "PAUL'S", "geometry": { "left": 0.6308557391166687, "top": 0.636909544467926, "width": 0.06058710813522339, "height": 0.009607672691345215 } } ] }, { "id": "47f38d95-c06c-47e3-8c4e-e7db108ad5c8", "content": "CHURCH-YARD", "geometry": { "left": 0.4303560256958008, "top": 0.6717373728752136, "width": 0.13616490364074707, "height": 0.006004810333251953 }, "tokens": [ { "id": "9805d04e-d122-48cc-8ed3-ea49ec5fd231", "content": "CHURCH", "geometry": { "left": 0.4303560256958008, "top": 0.6717373728752136, "width": 0.076202392578125, "height": 0.006004810333251953 } }, { "id": "6949f56c-30db-484c-99ad-8c3f15d4c7f2", "content": "-", "geometry": { "left": 0.5103060603141785, "top": 0.6717373728752136, "width": 0.008119940757751465, "height": 0.006004810333251953 } }, { "id": "b17eee41-1697-4065-ae24-757f35a79178", "content": "YARD", "geometry": { "left": 0.5190505981445312, "top": 0.6717373728752136, "width": 0.0474703311920166, "height": 0.006004810333251953 } } ] }, { "id": "87f29527-2a25-4ec9-b946-e2b3005758c2", "content": "1798.", "geometry": { "left": 0.46221113204956055, "top": 0.7233787178993225, "width": 0.07432854175567627, "height": 0.014011204242706299 }, "tokens": [ { "id": "e6ee28b1-65a5-4926-938d-6548045bb9b2", "content": "1798", "geometry": { "left": 0.46221113204956055, "top": 0.723779022693634, "width": 0.06433475017547607, "height": 0.013610899448394775 } }, { "id": "e575c04a-33d5-4332-bfb0-9f208632108b", "content": ".", "geometry": { "left": 0.5302935838699341, "top": 0.723779022693634, "width": 0.006246089935302734, "height": 0.013210594654083252 } } ] } ] } ] ``` ## Parameters | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | A dictionary of`{ "pdf_url": str, "text_layer_url": str }` For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). | | `row_data['pdf_url']` | Yes | `https` path to a cloud-hosted PDF. It must be specified within `row_data` dictionary. | | `row_data['text_layer_url']` | No | `https` path to a cloud-hosted JSON extract of the PDF. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"PDF"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ## Import format ```json Delegated Access URL theme={"theme":"css-variables"} [ { "row_data": { "pdf_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/0801.3483.pdf", // You don't need to provide a text_layer_url. Labelbox automatically generates a text layer when importing an asset without one. "text_layer_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/0801.3483-lb-textlayer.json" }, "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/0801.3483.pdf", "media_type": "PDF", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://www.wikipedia.org/" }] }, { "row_data": { "pdf_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/0803.1972.pdf", // You don't need to provide a text_layer_url. Labelbox automatically generates a text layer when importing an asset without one. "text_layer_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/0803.1972-lb-textlayer.json" }, "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/document-samples/0803.1972.pdf", "media_type": "PDF", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Standard URL theme={"theme":"css-variables"} [ { "row_data": { "pdf_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", // You don't need to provide a text_layer_url. Labelbox automatically generates a text layer when importing an asset without one. "text_layer_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483-lb-textlayer.json" }, "global_key": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "media_type": "PDF", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://www.wikipedia.org/" }] } ] ``` ## Python example ```python import example with custom text layer theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Documents") assets = [ { "row_data": { "pdf_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", # You don't need to provide a text_layer_url. Labelbox automatically generates a text layer when importing an asset without one. "text_layer_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483-lb-textlayer.json" }, "global_key": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "media_type": "PDF", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://www.wikipedia.org/" }] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` ```python local files theme={"theme":"css-variables"} local_file_paths = ['path/to/local/file1', 'path/to/local/file1'] # limit: 15k files new_dataset = client.create_dataset(name = "Local files upload") try: task = new_dataset.create_data_rows(local_file_paths) task.wait_till_done() except Exception as err: print(f'Error while creating labelbox dataset - Error: {err}') ``` ### Verify files are processed ### File processing can take up to 20 mins Since PDFs and OCR'ed files can be very large, the conversion can sometimes take up to 20 minutes to perform a data upload. By checking the Media Attributes section, you can verify whether a file conversion using a custom or Labelbox-generated text layer is complete. * If `Is text layer valid = true`, the file was successfully processed. # Import geospatial annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-geospatial-annotations Developer guide for importing annotations on geospatial data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for an geospatial data row: * Radio * Checklist * Free-form text * Point * Polyline * Polygon * Bounding Box ## Classifications ### Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```json NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer"), lb_types.ClassificationAnswer(name = "third_checklist_answer") ]) ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"}, {"name": "third_checklist_answer"}, ] } ``` ### Free-form text ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "answer": "sample text", } ``` ### Nested classifications ```python Python annotation theme={"theme":"css-variables"} nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_radio_annotation_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [ { "name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer"} } ] } } nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } ``` ### Point ```python Python annotation theme={"theme":"css-variables"} point_annotation = lb_types.ObjectAnnotation( name = "point_geo", value = lb_types.Point(x=-99.20647859573366, y=19.40018029091072), ) ``` ```python NDJSON theme={"theme":"css-variables"} point_annotation_ndjson = { "name": "point_geo", "point": { "x": -99.20647859573366, "y": 19.40018029091072 } } ``` ### Polyline ```python Python annotation theme={"theme":"css-variables"} # Coordinates in the desired EPSG coordinate system coords = [ [ -99.20842051506044, 19.40032196622975 ], [ -99.20809864997865, 19.39758963475322 ], [ -99.20758366584778, 19.39776167179227 ], [ -99.20728325843811, 19.3973265189299 ] ] line_points = [] line_points_ndjson = [] for sub in coords: line_points.append(lb_types.Point(x=sub[0], y=sub[1])) line_points_ndjson.append({"x":sub[0], "y":sub[1]}) # Python Annotation polyline_annotation = lb_types.ObjectAnnotation( name = "polyline_geo", value = lb_types.Line(points=line_points), ) ``` ```python NDJSON theme={"theme":"css-variables"} # Coordinates in the desired EPSG coordinate system coords = [ [ -99.20842051506044, 19.40032196622975 ], [ -99.20809864997865, 19.39758963475322 ], [ -99.20758366584778, 19.39776167179227 ], [ -99.20728325843811, 19.3973265189299 ] ] line_points = [] line_points_ndjson = [] for sub in coords: line_points.append(lb_types.Point(x=sub[0], y=sub[1])) line_points_ndjson.append({"x":sub[0], "y":sub[1]}) # NDJSON polyline_annotation_ndjson = { "name": "polyline_geo", "line": line_points_ndjson } ``` ### Polygon ```python Python annotation theme={"theme":"css-variables"} polygon_points = [] for sub in coords_polygon: polygon_points.append(lb_types.Point(x=sub[0], y=sub[1])) polygon_annotation = lb_types.ObjectAnnotation( name = "polygon_geo", value = lb_types.Polygon(points=polygon_points), ) ``` ```python NDJSON theme={"theme":"css-variables"} # Coordinates in the desired EPSG coordinate system polygon_points_ndjson = [] for sub in coords_polygon: polygon_points_ndjson.append({"x":sub[0], "y":sub[1]}) polygon_annotation_ndjson = { "name": "polygon_geo", "polygon": polygon_points_ndjson } ``` ```python coords_polygon expandable theme={"theme":"css-variables"} coords_polygon = [ [ -99.21042680740356, 19.40036244486966 ], [ -99.2104160785675, 19.40017017124035 ], [ -99.2103409767151, 19.400008256428897 ], [ -99.21014785766603, 19.400008256428897 ], [ -99.21019077301027, 19.39983622176518 ], [ -99.21022295951845, 19.399674306621385 ], [ -99.21029806137086, 19.39951239131646 ], [ -99.2102873325348, 19.399340356128437 ], [ -99.21025514602663, 19.399117722085677 ], [ -99.21024441719057, 19.39892544698541 ], [ -99.2102336883545, 19.39874329141769 ], [ -99.21021223068239, 19.398561135646027 ], [ -99.21018004417421, 19.398399219233365 ], [ -99.21011567115785, 19.39822718286836 ], [ -99.20992255210878, 19.398136104719125 ], [ -99.20974016189577, 19.398085505725305 ], [ -99.20957922935487, 19.398004547302467 ], [ -99.20939683914186, 19.39792358883935 ], [ -99.20918226242067, 19.39786286996558 ], [ -99.20899987220764, 19.397822390703805 ], [ -99.20891404151918, 19.397994427496787 ], [ -99.20890331268312, 19.398176583902874 ], [ -99.20889258384706, 19.398368859888045 ], [ -99.20889258384706, 19.398540896103246 ], [ -99.20890331268312, 19.39872305189756 ], [ -99.20889258384706, 19.39890520748796 ], [ -99.20889258384706, 19.39907724313608 ], [ -99.20889258384706, 19.399259398329956 ], [ -99.20890331268312, 19.399431433603585 ], [ -99.20890331268312, 19.39961358840092 ], [ -99.20890331268312, 19.399785623300048 ], [ -99.20897841453552, 19.399937418648214 ], [ -99.20919299125673, 19.399937418648214 ], [ -99.2093861103058, 19.39991717927664 ], [ -99.20956850051881, 19.39996777770086 ], [ -99.20961141586305, 19.40013981222548 ], [ -99.20963287353517, 19.40032196622975 ], [ -99.20978307724, 19.4004130431554 ], [ -99.20996546745302, 19.40039280384301 ], [ -99.21019077301027, 19.400372564528084 ], [ -99.21042680740356, 19.40036244486966 ] ] ``` ### Bounding Box ```python Python annotation theme={"theme":"css-variables"} bbox_top_left = lb_types.Point(x= -99.20746564865112, y=19.39799442829336) bbox_bottom_right = lb_types.Point(x=-99.20568466186523, y=19.39925939999194) # Python Annotation bbox_annotation = lb_types.ObjectAnnotation( name = "bbox_geo", value = lb_types.Rectangle(start=bbox_top_left, end=bbox_bottom_right) ) ``` ```python NDJSON theme={"theme":"css-variables"} # Coordinates in the desired EPSG coordinate system coord_object = { "coordinates" : [[ [ -99.20746564865112, 19.39799442829336 ], [ -99.20746564865112, 19.39925939999194 ], [ -99.20568466186523, 19.39925939999194 ], [ -99.20568466186523, 19.39799442829336 ], [ -99.20746564865112, 19.39799442829336 ] ]] } # NDJSON bbox_annotation_ndjson = { "name" : "bbox_geo", "bbox" : { 'top': coord_object["coordinates"][0][1][1], 'left': coord_object["coordinates"][0][1][0], 'height': coord_object["coordinates"][0][3][1] - coord_object["coordinates"][0][1][1], 'width': coord_object["coordinates"][0][3][0] - coord_object["coordinates"][0][1][0] } } ``` ### Mask to bounding box Additionally, you create a bounding box with a segmentation mask using the code below. ```python Python annotation theme={"theme":"css-variables"} # Let's create another polygon annotation with Python annotation tools that draws the image using cv2 libraries hsv = cv2.cvtColor(tiled*image_data.value, cv2.COLOR_RGB2HSV) mask = cv2.inRange(hsv, (25, 50, 25), (100, 150, 255)) kernel = np.ones((15, 20), np.uint8) mask = cv2.erode(mask, kernel) mask = cv2.dilate(mask, kernel) mask_annotation = lb_types.MaskData.from_2D_arr(mask) mask_data = lb_types.Mask(mask=mask_annotation, color=[255, 255, 255]) h, w, * = tiled_image_data.value.shape pixel_bounds = lb_types.TiledBounds(epsg=lb_types.EPSG.SIMPLEPIXEL, bounds=[lb_types.Point(x=0, y=0), lb_types.Point(x=w, y=h)]) transformer = lb_types.EPSGTransformer.create_pixel_to_geo_transformer( src_epsg=pixel_bounds.epsg, pixel_bounds=pixel_bounds, geo_bounds=tiled_image_data.tile_bounds, zoom=20) pixel_polygons = mask_data.shapely.simplify(3) list_of_polygons = [transformer(lb_types.Polygon.from_shapely(p)) for p in pixel_polygons.geoms] polygon_annotation_two = lb_types.ObjectAnnotation(value=list_of_polygons[0], name="polygon_geo_2") ``` ### Tool with nested classification ```python Python annotation theme={"theme":"css-variables"} tool_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name=# Feature name, value=# Add tool annotation (lb_types."tool"), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python Bounding Box Example theme={"theme":"css-variables"} bbox_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", value=lb_types.Rectangle( start=lb_types.Point(x=541, y=933), # x = left, y = top end=lb_types.Point(x=871, y=1124), # x= left + width , y = top + height ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_with_radio_subclass_ndjson = { "name": "bbox_with_radio_subclass", "classifications": [{ "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer" } }], "bbox": { "top": 933, "left": 541, "height": 191, "width": 330 } } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import uuid import numpy as np import cv2 import labelbox as lb import labelbox.types as lb_types ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python theme={"theme":"css-variables"} top_left_bound = lb_types.Point(x=-99.21052827588443, y=19.400498983095076) bottom_right_bound = lb_types.Point(x=-99.20534818927473, y=19.39533555271248) epsg = lb_types.EPSG.EPSG4326 bounds = lb_types.TiledBounds(epsg=epsg, bounds=[top_left_bound, bottom_right_bound]) global_key = "mexico_city" tile_layer = lb_types.TileLayer( url="https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png" ) tiled_image_data = lb_types.TiledImageData(tile_layer=tile_layer, tile_bounds=bounds, zoom_levels=[17, 23]) asset = { "row_data": tiled_image_data.asdict(), "global_key": global_key, "media_type": "TMS_GEO" } dataset = client.create_dataset(name="geo_demo_dataset") task= dataset.create_data_rows([asset]) print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.POINT, name="point_geo"), lb.Tool(tool=lb.Tool.Type.LINE, name="polyline_geo"), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo"), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo_2"), lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_geo"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_checklist_geo", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class_name", options=[ lb.Option(value="first_checklist_answer") ] ), ] ), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_text_geo", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text_geo" ), ] ) ], classifications = [ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question_geo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question_geo", options=[ lb.Option(value="first_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ), ], ), lb.Classification(class_type=lb.Classification.Type.TEXT, name="free_text"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ) ] ) ontology = client.create_ontology("Ontology Geospatial Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Geospatial_Tile) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # Create a project project = client.create_project( name="Geospatial Project Demo", media_type=lb.MediaType.Geospatial_Tile ) # Connect the ontology project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} # Create a batch to send to your MAL project batch = project.create_batch( "first-batch-geo-demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. For annotations that you want to import as ground truth labels, you can also specify [benchmarks](/horizon/guides/benchmark) using the `is_benchmark_reference` flag. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python annotation payload theme={"theme":"css-variables"} labels =[] labels.append( lb_types.Label( data={ "global_key": global_key, "tile_layer": tile_layer, "tile_bounds":bounds, "zoom_levels": [12, 20] }, annotations = [ point_annotation, polyline_annotation, polygon_annotation, bbox_annotation, radio_annotation, bbox_with_checklist_subclass, bbox_with_free_text_subclass, checklist_annotation, polygon_annotation_two, nested_checklist_annotation, nested_radio_annotation, text_annotation ], # Optional: set the label as a benchmark # Only supported for groud truth imports is_benchmark_reference = True ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_ndjson = [] for annotations in [point_annotation_ndjson, polyline_annotation_ndjson, polygon_annotation_ndjson, bbox_annotation_ndjson, radio_annotation_ndjson, bbox_with_checklist_subclass_ndjson, bbox_with_free_text_subclass_ndjson, checklist_annotation_ndjson, nested_checklist_annotation_ndjson, nested_radio_annotation_ndjson, text_annotation_ndjson ]: annotations.update({ 'dataRow': { 'globalKey': global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import geospatial data Source: https://docs.labelbox.com/horizon/developer-guides/import-geospatial-data How to import geospatial data and sample import formats. ## Specifications Supported file formats: * [Slippy maps:](https://developers.planet.com/docs/planetschool/xyz-tiles-and-slippy-maps/) `url/zoom-level/x-coordinate/y-coordinate.png` * Deep zoom servers for pathology use case, such as [Openslide](https://openslide.org/api/python/) and [tiffslide](https://github.com/bayer-science-for-a-better-life/tiffslide) * GeoTIFF: Standard or Cloud-optimized * NITF: 2.0 or 2.1 format Import methods: * Slippy map URL * IAM Delegated Access (NITF, GeoTIFF, or COG files only) * Signed URLs (`https` URLs only) **IAM Delegated Access not supported with slippy maps** IAM Delegated Access is not supported with slippy maps due to the nature of how slippy maps work. We strongly encourage you to use cloud-optimized GeoTIFFs instead of slippy maps. To use IAM Delegated Access with geospatial/tiled imagery, please use NITF, GeoTIFF, or COG files. ## Slippy maps ### Definitions When importing slippy map data, make sure your import file contains this information and follows this format. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | A dictionary of`{ "tile_layer_url": str, "min_zoom": int, "max_zoom": int, "bounds": \[[float, float],[float, ffloat]], "epsg": "EPSG3857" ... }` For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). | | `row_data['version']` | No | Add version to your import to distinguish it from the legacy format. If `version: 2` is present, Labelbox will read all coordinates as `[long, lat]` or `[x, y]` depending on the data format. If `version: 1` is present, Labelbox will read all coordinates as `[lat, long]` or `[y, x]` depending on the data format. If no version is provided, the default value for Slippy map files using an espg coordinate system and COG files is `version: 1` and the default value for cartesian and simple coordinate system is `version: 2` | | `row_data['tile_layer_url']` | Yes | URL where map data is hosted. Must be in the following format: ` Slippy map tiles in JPG or PNG. | | `row_data['min_zoom']` | No | Minimum map zoom level down to which this layer will be displayed (inclusive). If not provided, users will potentially be able to zoom beyond where tiles are available. | | `row_data['max_zoom']` | No | Maximum map zoom level up to which this layer will be displayed (inclusive). If not provided, users will potentially be able to zoom beyond where tiles are available. | | `row_data['max_native_zoom']` | No | Maximum zoom number the tile source has available (`maxNativeZoom` & `maxZoom`). If specified, the tiles on all zoom levels higher than `maxNativeZoom` will be auto-scaled. | | `row_data['bounds']` | Yes | The bounds where the projection is valid. For the Simple coordinate system, bounds are in x,y. For the EPSG coordinate system, bounds are in lat,long. | | `row_data['epsg']` | Yes | Structured dataset of coordinate reference systems and coordinate transformations. Can be `EPSG3857`, `EPSG3395`, `EPSG4326`, `Simple`, or `Cartesian`. If using `Cartesian`, you must pass in the parameters for the pixel dimensions in the `dimensions` field. | | `row_data['tile_size']` | No | Specifies the size of the individual slippy map tiles. Common sizes are 256, 512, 1024. Note: Currently, only square tiles are supported which is why `tileSize` is a single number that indicates the width and height of the tile. | | `row_data['alternative_layers']` | No | Additional tile layers. Only `alternativeLayers.tileLayerUrl` and `alternativeLayers.name` are required. If no values for optional parameters are given, those values will default to the top-level keys. | | `row_data['dimensions']` | No | This is required if using the Cartesian coordinate system. This represents the pixel height and width of the entire image. The format is: `"dimensions": { "width": , "height": }` | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"TMS_GEO"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ### Import format ```json json theme={"theme":"css-variables"} [ { "row_data":{ "tile_layer_url": "https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png", "bounds": [ [ 19.405662413477728, -99.21052827588443 ], [ 19.400498983095076, -99.20534818927473 ] ], "min_zoom": 12, "max_zoom": 20, "epsg": "EPSG4326", "alternative_layers": [ { "tile_layer_url": "https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw", "name": "Satellite" }, { "tile_layer_url": "https://api.mapbox.com/styles/v1/mapbox/navigation-guidance-night-v4/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw", "name": "Guidance" } ] }, "global_key": "https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png", "media_type": "TMS_GEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Additional layers theme={"theme":"css-variables"} [ { "row_data": { "tile_layer_url": "https://labelbox.s3-us-west-2.amazonaws.com/tiler/drone-map/rgb/{z}/{x}/{y}.png", "bounds": [ [17.983835501492813,74.41417694091798], [17.996825561127697, 74.44644927978517] ], "min_zoom": 8, "max_zoom": 22, "max_native_zoom": 22, "epsg": "EPSG3857", "version": 2, "alternative_layers": [ { "tileLayer_url": "https://labelbox.s3-us-west-2.amazonaws.com/tiler/drone-map/ndvi/{z}/{x}/{y}.png", "name": "NDVI" }, { "tileLayer_url": "https://labelbox.s3-us-west-2.amazonaws.com/tiler/drone-map/dtm/{z}/{x}/{y}.png", "name": "DTM" } ] }, "global_key": "https://labelbox.s3-us-west-2.amazonaws.com/tiler/drone-map/rgb/{z}/{x}/{y}.png", "media_type": "TMS_GEO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Map sections theme={"theme":"css-variables"} [ { "row_data": { "tile_layer_url": "https://public-tiles.dronedeploy.com/1499994155_DANIELOPENPIPELINE_ortho_qfs/{z}/{x}/{y}.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9wdWJsaWMtdGlsZXMuZHJvbmVkZXBsb3kuY29tLzE0OTk5OTQxNTVfREFOSUVMT1BFTlBJUEVMSU5FX29ydGhvX3Fmcy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoyMTQ1OTE0MTE4fX19XX0_&Signature=O~50rrGXdEC6Hi8jPJ3dbT~UtBd7Cw6iQPTxdJ8LU2IaoxeP22R3JpKPkLN3T3~Lcw3CyX7uft2Baj0MH93qUoCYyN~~jNX3OMkYV2jbrHDezf6zQRHAabXX-L2bL-JEGfFL6z3DWccOFeCH56CuhgC29k5CJx7I34P-LQJdnAUsA-KaqKH1IyYsHStRIfmMzdXNAWU58FTfqVljq9SbKXxfgdr2SZ~7VgLaZ8IhA0WnlKUo-JgqTd~jYa5mGCpR8351IMK0aMuY4Mld4SOXssQ-rOtlZtypvo8FDp474TlGIEGz5PHxGOPsqLPF19hEYTgoPqsUj8QEuiTfg-cmsg__&Key-Pair-Id=APKAJXGC45PGQXCMCXSA", "bounds": [ [ 37.87488726890353, -122.32488870620728 ], [ 37.87280390440759, -122.32154130935669 ] ], "min_zoom": 10, "max_zoom": 23, "epsg": "EPSG4326", "version": 2 } }, { "row_data": { "tile_layer_url": "https://public-tiles.dronedeploy.com/1499994155_DANIELOPENPIPELINE_ortho_qfs/{z}/{x}/{y}.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9wdWJsaWMtdGlsZXMuZHJvbmVkZXBsb3kuY29tLzE0OTk5OTQxNTVfREFOSUVMT1BFTlBJUEVMSU5FX29ydGhvX3Fmcy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoyMTQ1OTE0MTE4fX19XX0_&Signature=O~50rrGXdEC6Hi8jPJ3dbT~UtBd7Cw6iQPTxdJ8LU2IaoxeP22R3JpKPkLN3T3~Lcw3CyX7uft2Baj0MH93qUoCYyN~~jNX3OMkYV2jbrHDezf6zQRHAabXX-L2bL-JEGfFL6z3DWccOFeCH56CuhgC29k5CJx7I34P-LQJdnAUsA-KaqKH1IyYsHStRIfmMzdXNAWU58FTfqVljq9SbKXxfgdr2SZ~7VgLaZ8IhA0WnlKUo-JgqTd~jYa5mGCpR8351IMK0aMuY4Mld4SOXssQ-rOtlZtypvo8FDp474TlGIEGz5PHxGOPsqLPF19hEYTgoPqsUj8QEuiTfg-cmsg__&Key-Pair-Id=APKAJXGC45PGQXCMCXSA", "bounds": [ [ 37.8749042065848, -122.32149839401245 ], [ 37.87278696624341, -122.31810808181763 ] ], "min_zoom": 10, "max_zoom": 23, "epsg": "EPSG4326", "version": 2 } }, { "row_data": { "tile_layer_url": "https://public-tiles.dronedeploy.com/1499994155_DANIELOPENPIPELINE_ortho_qfs/{z}/{x}/{y}.png?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9wdWJsaWMtdGlsZXMuZHJvbmVkZXBsb3kuY29tLzE0OTk5OTQxNTVfREFOSUVMT1BFTlBJUEVMSU5FX29ydGhvX3Fmcy8qIiwiQ29uZGl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoyMTQ1OTE0MTE4fX19XX0_&Signature=O~50rrGXdEC6Hi8jPJ3dbT~UtBd7Cw6iQPTxdJ8LU2IaoxeP22R3JpKPkLN3T3~Lcw3CyX7uft2Baj0MH93qUoCYyN~~jNX3OMkYV2jbrHDezf6zQRHAabXX-L2bL-JEGfFL6z3DWccOFeCH56CuhgC29k5CJx7I34P-LQJdnAUsA-KaqKH1IyYsHStRIfmMzdXNAWU58FTfqVljq9SbKXxfgdr2SZ~7VgLaZ8IhA0WnlKUo-JgqTd~jYa5mGCpR8351IMK0aMuY4Mld4SOXssQ-rOtlZtypvo8FDp474TlGIEGz5PHxGOPsqLPF19hEYTgoPqsUj8QEuiTfg-cmsg__&Key-Pair-Id=APKAJXGC45PGQXCMCXSA", "bounds": [ [ 37.8749042065848, -122.31806516647339 ], [ 37.87278696624341, -122.31529712677002 ] ], "min_zoom": 10, "max_zoom": 23, "epsg": "EPSG4326", "version": 2 } } ] ``` ```json Cartesian Coordinates Examples theme={"theme":"css-variables"} [ { "row_data": { "tile_layer_url": "https://storage.googleapis.com/labelbox-developer-testing-assets/cartesian/sample_5184x2000_maxZoom_3/{z}/{x}/{y}.png", "bounds": [[0,0],[500,500]], "epsg": "Cartesian", "min_zoom": 0, "max_zoom": 3, "dimensions": { "width": 5184, "height": 2000 }, } }, { "row_data":{ "tile_layer_url": "https://storage.googleapis.com/labelbox-developer-testing-assets/cartesian/sample_5184x2000_maxZoom_3/{z}/{x}/{y}.png", "bounds": [[500, 500], [1000,1000]], "epsg": "Cartesian", "min_zoom": 0, "max_zoom": 3, "dimensions": { "width": 5184, "height": 2000 }, } }, { "row_data":{ "tile_layer_url": "https://storage.googleapis.com/labelbox-developer-testing-assets/cartesian/sample_5184x2000_maxZoom_3/{z}/{x}/{y}.png", "bounds": [[0, 0],[5184,2000]], "epsg": "Cartesian", "min_zoom": 0, "max_zoom": 3, "dimensions": { "width": 5184, "height": 2000 }, } } ] ``` ## Deep Zoom Servers (ex. Pathology Slides) ### Definitions When using a Deep Zoom server endpoint (like OpenSlide or tiffslides), please use the following format. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | A dictionary of`{ "tile_layer_url": str, "is_deep_zoom": bool, "min_zoom": int, "max_zoom": int, "bounds": [[float, float],[float, float]], "epsg": "EPSG3857" ... }` For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). | | `row_data['version']` | No | Add version to your import to distinguish it from the legacy format. If `version: 2` is present, Labelbox will read all coordinates as `[long, lat]` or `[x, y]` depending on the data format. If `version: 1` is present, Labelbox will read all coordinates as `[lat, long]` or `[y, x]` depending on the data format. If no version is provided, the default value for DeepZoom files is `version: 2` | | `row_data['tile_layer_url']` | Yes | For DeepZoom servers, the URL should be the API to return the tile with parameters for \{x}, \{y}, and \{z}. If using a Deep Zoom server, you must set up your coordinate system as 'Cartesian' | | `row_data['is_deep_zoom']` | No | This must be set to "true" if using a Deep Zoom server | | `row_data['min_zoom']` | No | Minimum map zoom level down to which this layer will be displayed (inclusive). If not populated, we will default to 0. | | `row_data['max_zoom']` | No | Maximum map zoom level up to which this layer will be displayed (inclusive). If not populated, we will calculate this from your data. | | `row_data['max_native_zoom']` | No | Maximum zoom number the tile source has available (`maxNativeZoom` & `maxZoom`). If specified, the tiles on all zoom levels higher than `maxNativeZoom` will be auto-scaled. | | `row_data['bounds']` | Yes | The bounds where the projection is valid. For the Simple coordinate system, bounds are in x,y. For the EPSG coordinate system, bounds are in lat,long. To annotate the entire asset, the bounds should match up with your values in `dimensions` | | `row_data['epsg']` | Yes | This should be set to `Cartesian` for Deep Zoom use cases. | | `row_data['alternative_layers']` | No | Additional tile layers. Only `alternativeLayers.tileLayerUrl` and `alternativeLayers.name` are required. If no values for optional parameters are given, those values will default to the top-level keys. | | `row_data['dimensions']` | No | This is required if using the Cartesian coordinate system. This represents the pixel height and width of the entire image. The format is: `"dimensions": { "width": , "height": }` | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"TMS_GEO"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ### Import format ```json Pathology Deep Zoom Exampe theme={"theme":"css-variables"} [ { "row_data":{ "tile_layer_url": "https://storage.googleapis.com/labelbox-datasets/openslides/CMU-1/slide_files/{z}/{x}_{y}.png", "is_deep_zoom": true, "epsg": "Cartesian", "min_zoom": 8, "max_xoom": 16, "dimensions": { "height": 32914, "width": 46000 }, "bounds": [[0,0], [46000,32914]] }, "global_key": "https://storage.googleapis.com/labelbox-datasets/openslides/CMU-1/slide_files/{z}/{x}_{y}.png", "media_type": "TMS_GEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ## GeoTIFF (standard or cloud-optimized) and NITF specifications ### Definitions When you upload a NITF file, Labelbox will convert it to a COG file and upload the asset. If it is already a COG file, Labelbox will skip the conversion and upload the asset natively. Since COG files can have many extension names, Labelbox will check the header of the file to verify that it is a COG file. | Item | Supported Options | Comments | | - | - | - | | Coordinate Systems | `EPSG3857`, `EPSG3395`, or `EPSG4326` | If your COG files are not one of these supported coordinate systems, it must be converted prior to upload into Labelbox. We require that the files are geo-referenced in order to render on our map | | Coordinate System Type | Projected CRS | Our editor only supports a Projected CRS (coordinate reference system). If you are using a geographic coordinate reference system, please convert it. | | Bands | We support up to 4 bands: - Red - Green - Blue - Alpha Internal color-interpretation must be set on NITF files. | If your COG files have a band outside of these four support bands, the tiles will not be able to render in our editor. The alpha band must also be an "explicit alpha" meaning that the fourth band must be grouped under the alpha band and not in the "unknown" band | | Alternative layers | | Additional GeoTIFF/NITF layer, specified as a URL/path to the file. | | NITF Version | If you are using NITF files, they must be in NITF 2.0 or 2.1 format. | | | File Size | up to 1 GB recommended | If you require larger files - please convert the files to cloud-optimized GeoTIFF and upload | ### Import format ```json Standard URL - GeoTIFF theme={"theme":"css-variables"} [ { "tileLayerUrl": "https://storage.googleapis.com/jakub-test/pre_DSM_hill_camera_4326.cog.tif", "alternativeLayers": [ { "tileLayerUrl": "https://storage.googleapis.com/jakub-test/pre_DSM_hill_camera_4326_red.cog.tif", "name": "Red Lee Hill" } ], "bounds": [ [40.05578109295705, -105.32837712340128], [40.06485226245013, -105.2813925407782] ] }, { "tileLayerUrl": "https://storage.googleapis.com/jakub-test/pre_DSM_hill_camera_4326.cog.tif", "alternativeLayers": [ { "tileLayerUrl": "https://storage.googleapis.com/jakub-test/pre_DSM_hill_camera_4326_red.cog.tif", "name": "Red Lee Hill" } ], "bounds": [ [40.06485226245013, -105.32837712340128], [40.07392343194322, -105.2813925407782] ] } ] ``` ```json Delegated Access URL - GeoTIFF theme={"theme":"css-variables"} [ { "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/geospatial-samples/sample-geotiff.tif", "row_data": { "tile_layer_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/geospatial-samples/sample-geotiff.tif", }, "media_type": "TMS_GEO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ### Verify files are processed ### File processing can take up to 20 mins Since GeoTIFF or NITF files can be very large, the conversion can sometimes take up to 20 minutes to perform a data upload. You can verify whether a file conversion is complete by checking the Media Attributes section. If the Mime type shows as `application/x-tms-geo`, the file was successfully processed. If the Mime type still shows as `application/json`, the file pre-processing has not yet been completed. ## Python example For geospatial and custom-defined Data Rows, you have to specify the Data Row in a JSON format. Here's an example with alternative layers, bounds, attachments (text, image, and google maps widget). ```python import example theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Geospatial") assets = [ { "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/geospatial-samples/sample-geotiff.tif", "row_data": { "tile_layer_url": "https://lb-test-data.s3.us-west-1.amazonaws.com/geospatial-samples/sample-geotiff.tif", }, "media_type": "TMS_GEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ```
# Import HTML annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-html-annotations Developer guide for importing annotations on HTML data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for a HTML data row: * Radio * Checklist * Free-form text ## Classifications ### Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```json NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer"), lb_types.ClassificationAnswer(name = "third_checklist_answer") ]) ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"}, {"name": "third_checklist_answer"}, ] } ``` ### Free-form text ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "answer": "sample text", } ``` ### Nested classifications ```python Python annotation theme={"theme":"css-variables"} nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_radio_annotation_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [ { "name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer"} } ] } } nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid import labelbox.types as lb_types ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python theme={"theme":"css-variables"} global_key = "sample_html_1.html" asset = { "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "global_key": global_key } dataset = client.create_dataset( name="html_annotation_import_demo_dataset", iam_integration=None # Removing this argument will default to the organziation's default iam integration ) task = dataset.create_data_rows([asset]) task.wait_till_done() print("Errors:", task.errors) print("Failed data rows: ", task.failed_data_rows) ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="text_html"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_html", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_html", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ) ] ) ontology = client.create_ontology("Ontology HTML Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Html) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} project = client.create_project(name="html_project", media_type=lb.MediaType.Html) # Setup your ontology project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} batch = project.create_batch( "first-batch-html-demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python Annotation Payload theme={"theme":"css-variables"} label = [] label.append( lb_types.Label( data={"global_key" : global_key }, annotations=[ text_annotation, checklist_annotation, radio_annotation ] ) ) ``` ```python NDJSON Payload theme={"theme":"css-variables"} label_ndjson = [] for annotations in [text_annotation_ndjson, checklist_annotation_ndjson, radio_annotation_ndjson]: annotations.update({ 'dataRow': { 'globalKey': global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import HTML data Source: https://docs.labelbox.com/horizon/developer-guides/import-html-data How to import HTML data and sample import formats. ### Supported file formats and import methods Format: HTML Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) ## Parameters Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to an HTML file. For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). For older regions, your S3 bucket may be in the`https://.s3..amazonaws.com/` format. If your object URLs are formatted this way, make sure they are in the virtual-hosted-style format before importing. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"HTML"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data), | ## Import format ```json Delegated Access URL theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/sample_html_1.html", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/sample_html_1.html", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html" }] }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/sample_html_2.html", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/sample_html_2.html", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Standard URL theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "global_key": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "global_key": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```html HTML example theme={"theme":"css-variables"} HTML File Example

Get to production AI faster

Save time by creating and managing your training data, people, and processes in a single place — so you can focus on building the next big thing.

Get a demo or start for free.


``` ## Python example ```python bulk import example theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - HTML") assets = [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "global_key": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_1.html", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "global_key": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` ```python local files theme={"theme":"css-variables"} local_file_paths = ['path/to/local/file1', 'path/to/local/file1'] # limit: 15k files new_dataset = client.create_dataset(name = "Local files upload") try: task = new_dataset.create_data_rows(local_file_paths) task.wait_till_done() except Exception as err: print(f'Error while creating labelbox dataset - Error: {err}') ``` # Import image annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-image-annotations Developer guide for importing annotations on image data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for an image data row: * Radio * Checklist * Free-form text * Bounding box * Point * Polyline * Polygon * Segmentation masks * Relationships ## Classifications ### Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```json NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer"), lb_types.ClassificationAnswer(name = "third_checklist_answer") ]) ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"}, {"name": "third_checklist_answer"}, ] } ``` ### Free-form text ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "answer": "sample text", } ``` ### Nested classifications ```python Python annotation theme={"theme":"css-variables"} nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_radio_annotation_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [ { "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer"} } ] } } nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } ``` ## Tools ### Bounding Box ```python Python Annotation theme={"theme":"css-variables"} bbox_annotation = lb_types.ObjectAnnotation( name="bounding_box", # must match your ontology feature's name value=lb_types.Rectangle( start=lb_types.Point(x=1690, y=977), # x = left, y = top end=lb_types.Point(x=1915, y=1307), # x= left + width , y = top + height )) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_annotation_ndjson = { "name": "bounding_box", "bbox": { "top": 977, "left": 1690, "height": 330, "width": 225 } } ``` ### Segmentation mask The following examples show how to import composite masks or a specific annotation instance from a composite mask by using the instance's unique `color_rgb` value. ### Size limit Mask imports are limited to 9,000 x 9,000 pixels. Larger masks can't be imported. ```python Composite masks theme={"theme":"css-variables"} # First we need to extract all the unique colors from the composite mask def extract_rgb_colors_from_url(image_url): response = requests.get(image_url) img = Image.open(BytesIO(response.content)) colors = set() for x in range(img.width): for y in range(img.height): pixel = img.getpixel((x, y)) if pixel[:3] != (0,0,0): colors.add(pixel[:3]) # Get only the RGB values return colors ``` ```python Specific annotations theme={"theme":"css-variables"} def extract_single_rgb_color(image_url, target_color_rgb): response = requests.get(image_url) img = Image.open(BytesIO(response.content)) # Check if the target color exists in the image for x in range(img.width): for y in range(img.height): pixel = img.getpixel((x, y)) if pixel[:3] == target_color_rgb: return target_color_rgb raise ValueError(f"Target color {target_color_rgb} not found in the image.") ``` ```python Python Annotation theme={"theme":"css-variables"} cp_mask_url = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/composite_mask.png" colors = extract_rgb_colors_from_url(cp_mask_url) mask_data = lb.types.MaskData(url=cp_mask_url) # These are the colors that will be associated with the mask_with_text_subclass tool rgb_colors_for_mask_with_text_subclass_tool = [(73, 39, 85), (111, 87, 176), (23, 169, 254)] cp_mask = [] for color in colors: # We are assigning the color related to the mask_with_text_subclass tool by identifying the unique RGB colors if color in rgb_colors_for_mask_with_text_subclass_tool: cp_mask.append( lb_types.ObjectAnnotation( name = "mask_with_text_subclass", # must match your ontology feature"s name value=lb_types.Mask( mask=mask_data, color=color), classifications=[ lb_types.ClassificationAnnotation( name="sub_free_text", value=lb_types.Text(answer="free text answer sample") )] ) ) else: # Create ObjectAnnotation for other masks cp_mask.append( lb_types.ObjectAnnotation( name="mask", value=lb_types.Mask( mask=mask_data, color=color ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} # NDJSON using bytes array cp_mask_url = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/composite_mask.png" colors = extract_rgb_colors_from_url(cp_mask_url) cp_mask_ndjson = [] #Using bytes array. response = requests.get(cp_mask_url) im_bytes = base64.b64encode(response.content).decode('utf-8') for color in colors: if color in rgb_colors_for_mask_with_text_subclass_tool: cp_mask_ndjson.append({ "name": "mask_with_text_subclass", "mask": {"imBytes": im_bytes, "colorRGB": color }, "classifications":[{ "name": "sub_free_text", "answer": "free text answer" }] } ) else: cp_mask_ndjson.append({ "name": "mask", "classifications": [], "mask": { "imBytes": im_bytes, "colorRGB": color } } ) ``` ### Point ```python Python Annotation theme={"theme":"css-variables"} point_annotation = lb_types.ObjectAnnotation( name="point", # must match your ontology feature's name value=lb_types.Point(x=1166.606, y=1441.768), ) ``` ```python NDJSON theme={"theme":"css-variables"} point_annotation_ndjson = { "name": "point", "classifications": [], "point": { "x": 1166.606, "y": 1441.768 } } ``` ### Polyline ```python Python Annotation theme={"theme":"css-variables"} polyline_annotation = lb_types.ObjectAnnotation( name="polyline", # must match your ontology feature's name value=lb_types.Line( # Coordinates for the keypoints in your polyline points=[ lb_types.Point(x=2534.353, y=249.471), lb_types.Point(x=2429.492, y=182.092), lb_types.Point(x=2294.322, y=221.962), lb_types.Point(x=2224.491, y=180.463), lb_types.Point(x=2136.123, y=204.716), lb_types.Point(x=1712.247, y=173.949), lb_types.Point(x=1703.838, y=84.438), lb_types.Point(x=1579.772, y=82.61), lb_types.Point(x=1583.442, y=167.552), lb_types.Point(x=1478.869, y=164.903), lb_types.Point(x=1418.941, y=318.149), lb_types.Point(x=1243.128, y=400.815), lb_types.Point(x=1022.067, y=319.007), lb_types.Point(x=892.367, y=379.216), lb_types.Point(x=670.273, y=364.408), lb_types.Point(x=613.114, y=288.16), lb_types.Point(x=377.559, y=238.251), lb_types.Point(x=368.087, y=185.064), lb_types.Point(x=246.557, y=167.286), lb_types.Point(x=236.648, y=285.61), lb_types.Point(x=90.929, y=326.412) ]), ) ``` ```python NDJSON theme={"theme":"css-variables"} polyline_annotation_ndjson = { "name": "polyline", "classifications": [], "line": [ {"x": 2534.353, "y": 249.471}, {"x": 2429.492, "y": 182.092}, {"x": 2294.322, "y": 221.962}, {"x": 2224.491, "y": 180.463}, {"x": 2136.123, "y": 204.716}, {"x": 1712.247, "y": 173.949}, {"x": 1703.838, "y": 84.438}, {"x": 1579.772, "y": 82.61}, {"x": 1583.442, "y": 167.552}, {"x": 1478.869, "y": 164.903}, {"x": 1418.941, "y": 318.149}, {"x": 1243.128, "y": 400.815}, {"x": 1022.067, "y": 319.007}, {"x": 892.367, "y": 379.216}, {"x": 670.273, "y": 364.408}, {"x": 613.114, "y": 288.16}, {"x": 377.559, "y": 238.251}, {"x": 368.087, "y": 185.064}, {"x": 246.557, "y": 167.286}, {"x": 236.648, "y": 285.61}, {"x": 90.929, "y": 326.412} ] } ``` ### Polygon ```python Python Annotation theme={"theme":"css-variables"} polygon_annotation = lb_types.ObjectAnnotation( name="polygon", # must match your ontology feature"s name value=lb_types.Polygon( # Coordinates for the vertices of your polygon points=[ lb_types.Point(x=1489.581, y=183.934), lb_types.Point(x=2278.306, y=256.885), lb_types.Point(x=2428.197, y=200.437), lb_types.Point(x=2560.0, y=335.419), lb_types.Point(x=2557.386, y=503.165), lb_types.Point(x=2320.596, y=503.103), lb_types.Point(x=2156.083, y=628.943), lb_types.Point(x=2161.111, y=785.519), lb_types.Point(x=2002.115, y=894.647), lb_types.Point(x=1838.456, y=877.874), lb_types.Point(x=1436.53, y=874.636), lb_types.Point(x=1411.403, y=758.579), lb_types.Point(x=1353.853, y=751.74), lb_types.Point(x=1345.264, y=453.461), lb_types.Point(x=1426.011, y=421.129), ]), ) ``` ```json NDJSON theme={"theme":"css-variables"} polygon_annotation_ndjson = { "name": "polygon", "polygon": [ { "x": 1489.581, "y": 183.934 }, { "x": 2278.306, "y": 256.885 }, { "x": 2428.197, "y": 200.437 }, { "x": 2560.0, "y": 335.419 }, { "x": 2557.386, "y": 503.165 }, { "x": 2320.596, "y": 503.103 }, { "x": 2156.083, "y": 628.943 }, { "x": 2161.111, "y": 785.519 }, { "x": 2002.115, "y": 894.647 }, { "x": 1838.456, "y": 877.874 }, { "x": 1436.53, "y": 874.636 }, { "x": 1411.403, "y": 758.579 }, { "x": 1353.853, "y": 751.74 }, { "x": 1345.264, "y": 453.461 }, { "x": 1426.011, "y": 421.129 }, { "x": 1489.581, "y": 183.934 }, ], } ``` ### Tool with nested classification ```python Python annotation theme={"theme":"css-variables"} tool_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name=# Feature name, value=# Add tool annotation (lb_types."tool"), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python Bounding Box Example theme={"theme":"css-variables"} bbox_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", value=lb_types.Rectangle( start=lb_types.Point(x=541, y=933), # x = left, y = top end=lb_types.Point(x=871, y=1124), # x= left + width , y = top + height ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_with_radio_subclass_ndjson = { "name": "bbox_with_radio_subclass", "classifications": [{ "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer" } }], "bbox": { "top": 933, "left": 541, "height": 191, "width": 330 } } ``` ## Relationships ```python Python annotation theme={"theme":"css-variables"} relationship = lb_types.RelationshipAnnotation( name=# Relationship name, value=lb_types.Relationship( source= # Source tool. Alternatively, specify a source_ontology_name target= # Target tool, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python Bounding Box Example theme={"theme":"css-variables"} bbox_source = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.Rectangle( start=lb_types.Point(x=2096, y=1264), end=lb_types.Point(x=2240, y=1689), ), ) bbox_target = lb_types.ObjectAnnotation( name="bounding_box", value=lb_types.Rectangle( start=lb_types.Point(x=2272, y=1346), end=lb_types.Point(x=2416, y=1704), ), ) relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=bbox_source, # Python annotations do not required a UUID reference target=bbox_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python NDJSON theme={"theme":"css-variables"} uuid_source = str(uuid.uuid4()) uuid_target = str(uuid.uuid4()) bbox_source_ndjson = { "uuid": uuid_source, "name": "bounding_box", "bbox": { "top": 1264.0, "left": 2096.0, "height": 425.0, "width": 144.0 } } bbox_target_ndjson = { "uuid": uuid_target, "name": "bounding_box", "bbox": { "top": 1346.0, "left": 2272.0, "height": 358.0, "width": 144.0 } } relationship_ndjson = { "name": "relationship", "relationship": { "source": uuid_source, # Alternatively, specify a source_ontology_name "target": uuid_target, # UUID reference to the target annotation "type": "unidirectional" } } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import uuid from PIL import Image import requests import base64 import labelbox as lb import labelbox.types as lb_types from io import BytesIO ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} # send a sample image as batch to the project global_key = "2560px-Kitano_Street_Kobe01s5s4110.jpeg" test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "global_key": global_key } dataset = client.create_dataset(name="image-demo-dataset") task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print(f"Failed data rows: {task.failed_data_rows}") print(f"Errors: {task.errors}") if task.errors: for error in task.errors: if 'Duplicate global key' in error['message'] and dataset.row_count == 0: # If the global key already exists in the workspace the dataset will be created empty, so we can delete it. print(f"Deleting empty dataset: {dataset}") dataset.delete() ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # list of classification objects lb.Classification(class_type=lb.Classification.Type.RADIO, name="radio_question", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ]), lb.Classification(class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ]), lb.Classification(class_type=lb.Classification.Type.TEXT, name="free_text"), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")]) ]) ]), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option( "first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")]) ]) ]), ], tools=[ # List of Tool objects lb.Tool(tool=lb.Tool.Type.BBOX, name="bounding_box"), lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option(value="first_sub_radio_answer")]), ]), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon"), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask"), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask_with_text_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="sub_free_text") ]), lb.Tool(tool=lb.Tool.Type.POINT, name="point"), lb.Tool(tool=lb.Tool.Type.LINE, name="polyline"), lb.Tool(tool=lb.Tool.Type.RELATIONSHIP, name="relationship") ]) ontology = client.create_ontology("Image Annotation Import Demo Ontology", ontology_builder.asdict(), media_type=lb.MediaType.Image) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # create a project and configure the ontology project = client.create_project(name="Image Annotation Import Demo", media_type=lb.MediaType.Image) project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} batch = project.create_batch( "image-demo-batch", # each batch in a project must have a unique name global_keys=[global_key], # paginated collection of data row objects, list of data row ids or global keys priority=1 # priority between 1(highest) - 5(lowest) ) print(f"Batch: {batch}") ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. For annotations that you want to import as ground truth labels, you can also specify [benchmarks](/horizon/guides/benchmark) using the `is_benchmark_reference` flag. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python annotation payload theme={"theme":"css-variables"} # create a Label label = [] annotations = [ radio_annotation, nested_radio_annotation, checklist_annotation, nested_checklist_annotation, text_annotation, bbox_annotation, bbox_with_radio_subclass_annotation, polygon_annotation, mask_annotation, mask_with_text_subclass_annotation, point_annotation, polyline_annotation, bbox_source, bbox_target, relationship, ] + cp_mask label.append( lb_types.Label(data={"global_key" : global_key }, annotations=annotations # Optional: set the label as a benchmark # Only supported for groud truth imports is_benchmark_reference = True ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_ndjson = [] annotations = [ radio_annotation_ndjson, nested_radio_annotation_ndjson, nested_checklist_annotation_ndjson, checklist_annotation_ndjson, text_annotation_ndjson, bbox_annotation_ndjson, bbox_with_radio_subclass_ndjson, polygon_annotation_ndjson, mask_annotation_ndjson, mask_with_text_subclass_ndjson, point_annotation_ndjson, polyline_annotation_ndjson, bbox_source_ndjson, bbox_target_ndjson, relationship_ndjson, ## Only supported for MAL imports ] + cp_mask_ndjson for annotation in annotations: annotation.update({ "dataRow": { "globalKey": global_key }, }) label_ndjson.append(annotation) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. ### Warning Relationship annotations are not supported for ground truth import jobs. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import image data Source: https://docs.labelbox.com/horizon/developer-guides/import-image-data How to import image data and sample import formats. ## Specifications Supported file formats: JPG, PNG, and BMP Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) * Direct upload of local files (256 MB max file size) Note: Direct upload currently does not support adding additional metadata and attachments, see below Python example. **Image size limit** Please note that Labelbox does not recommend labeling images larger than 9000px by 9000px. Labeling images of this size may cause annotations to be irretrievable. When importing cloud-hosted image data to Labelbox, your JSON file must include the following information for each image. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to a cloud-hosted image. For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). For older regions, your S3 bucket may be in `https://.s3..amazonaws.com/` format. If your object URLs are formatted this way, make sure they are in the virtual-hosted-style format before importing. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"IMAGE"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ## Import format This import format can be used for both importing data assets via [Uploading a file](/horizon/guides/datasets-datarows##option-2-upload-a-json-file-with-delegated-access-urls), and for importing data assets via Python SDK. ```json Delegated Access URL theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "media_type": "IMAGE", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg" }] }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-2.jpg", "media_type": "IMAGE", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Standard URL theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/image-sample-1.jpg", "global_key": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/image-sample-1.jpg", "media_type": "IMAGE", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg", "name": "RGB" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/image-sample-2.jpg", "global_key": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/image-sample-2.jpg", "media_type": "IMAGE", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ## Python example ```python single data row import theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") metadata_ontology = client.get_data_row_metadata_ontology() dataset = client.create_dataset(name="Single import example - Image") asset = { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": str(uuid4()), "media_type": "IMAGE", "metadata_fields": [{"name": "captureDateTime", "value": datetime.datetime.utcnow()}, {"name": "tag", "value": "tag_string"}, {"name": "split", "value": "train"}], "attachments": [{"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg"}, {"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/cir.jpg"}, {"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/weeds.jpg"}, {"type": "RAW_TEXT", "value": "IOWA, Zone 2232, June 2022 [Text string]"}, {"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}, {"type": "IMAGE", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/disease_attachment.jpeg"}, {"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4"}, {"type": "HTML", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/windy.html"}], } dataset.create_data_row(**asset) # equivalent method if using create_data_rows bulk endpoint dataset.create_data_rows([asset]) ``` ```python bulk data rows import theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Image") assets = [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg" + str(uuid4()), "media_type": "IMAGE", "metadata_fields": [{"name": "tag", "value": "tag_string"}], "attachments": [{"type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg", "name": "RGB" }], }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-1.jpg", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/image-samples/sample-image-2.jpg-1"+ str(uuid4()), "media_type": "IMAGE", "metadata_fields": [{"name": "tag", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` ```python local files theme={"theme":"css-variables"} local_file_paths = ['path/to/local/file1', 'path/to/local/file1'] # limit: 15k files new_dataset = client.create_dataset(name = "Local files upload") try: task = new_dataset.create_data_rows(local_file_paths) task.wait_till_done() except Exception as err: print(f'Error while creating labelbox dataset - Error: {err}') ``` # Import LLM response evaluations annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-llm-response-evaluation-annotations Developer guide for importing LLM response evaluations annotations and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for a LLM human preference data row: * Radio * Checklist * Free-form text * Entity ### Annotations can be message or global based Classification annotations can be supported both message and global based while tool annotations are supported only message specific. Removing the message\_id key inside a conversation text classification annotation results in the annotation becoming global. ## Classifications ### Radio (single-choice) ```python Python Annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="Choose the best response", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="Response B"))) ``` ```json NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_convo", "answer": { "name": "first_radio_answer" } } ``` ### Checklist (multi-choice) ```python Python Annotation theme={"theme":"css-variables"} checklist_annotation= lb_types.ClassificationAnnotation( name="checklist_convo", # must match your ontology feature"s name value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ), message_id="message-1" # Message specific annotation ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_convo", "answers": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"} ] } ``` ### Free-form text ```python Python Annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name="Provide a reason for your choice", value=lb_types.Text(answer="the answer to the text questions right here") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "text_convo", "answer": "the answer to the text questions right here", } ``` ### Nested classification ```python Python Annotation theme={"theme":"css-variables"} nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", message_id="message-1", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_annotation_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer"} }] } } nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "messageId": "message-1", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": { "name": "first_sub_checklist_answer", } } ] }] } ``` ## Tools ### Entity ```python Python Annotation theme={"theme":"css-variables"} ner_annotation = lb_types.ObjectAnnotation( name="ner", value=lb_types.ConversationEntity( start=0, end=8, message_id="message-1" ) ) ``` ```json NDJSON theme={"theme":"css-variables"} ner_annotation_ndjson = { "name": "ner", "location": { "start": 0, "end": 8 }, "messageId": "message-1" } ``` ### Tool with nested classification ```python Python annotation theme={"theme":"css-variables"} tool_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name=# Feature name, value=# Add tool annotation (lb_types."tool"), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python Entity Example theme={"theme":"css-variables"} entity_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="ner", value=lb_types.ConversationEntity( start=0, end=8, message_id="message-1" ) ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```json NDJSON theme={"theme":"css-variables"} ner_with_radio_subclass_ndjson = { "name": "bbox_with_radio_subclass", "classifications": [{ "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer" } }], "ner": { "location": { "start": 0, "end": 8 }, "messageId": "message-1" } } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid import labelbox.types as lb_types ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} global_key = "pairwise_shooping_asset" # Upload data rows convo_data = { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": global_key } # Create a dataset dataset = client.create_dataset(name="pairwise_annotation_demo") # Create a datarow task = dataset.create_data_rows([convo_data]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ```json Sample pairwise_shopping_2.json theme={"theme":"css-variables"} { "type": "application/vnd.labelbox.conversational", "version": 1, "messages": [ { "messageId": "message-0", "timestampUsec": 1530718491, "content": "Hi! How can I help?", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-1", "timestampUsec": 1530718503, "content": "I just bought a vacuum cleaner from you guys like a week ago and it's already broken!!", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true } ], "modelOutputs": [ { "title": "Response A", "content": "I'm really sorry to hear that your vacuum cleaner is not working as expected. We certainly understand how frustrating this can be, especially with a recent purchase. I assure you, we're here to help!\n\n To proceed with resolving this issue, could you please provide some details about the problem you're experiencing with the vacuum cleaner? Additionally, if you could share your order number or receipt details, that would be very helpful. We will definitely look into the available options for you, which may include a replacement or a refund, depending on the specific circumstances and our return policy.\n\n Your satisfaction is our priority, and we'll do our best to resolve this matter promptly and to your satisfaction.", "modelConfigName": "GPT-3.5 with temperature 0" }, { "title": "Response B", "content": "I'm sorry about the issue with your vacuum cleaner. Please send us the order number or receipt details so we can quickly assist you with a replacement. Your satisfaction is important to us!", "modelConfigName": "Fine Tuned GPT-3.5 with demo data" } ] } ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.NER,name="ner"), ], classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, scope=lb.Classification.Scope.GLOBAL, name="Choose the best response", options=[lb.Option(value="Response A"), lb.Option(value="Response B"), lb.Option(value="Tie")] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="Provide a reason for your choice" ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, scope=lb.Classification.Scope.INDEX, name="checklist_convo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", scope = lb.Classification.Scope.INDEX, options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ]) ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ]) ] ) ] ) ontology = client.create_ontology("Pairwise comparison ontology", ontology_builder.asdict(), media_type=lb.MediaType.Conversational) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # Create Labelbox project project = client.create_project(name="Conversational Text Annotation Import Demo (Pairwise comparison)", media_type=lb.MediaType.Conversational) # Setup your ontology project.connect_ontology(ontology) # Connect the ontology to your project ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} # Create a batch to send to your project batch = project.create_batch( "first-batch-convo-demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python annotation theme={"theme":"css-variables"} label = [] label.append( lb_types.Label( data={"global_key" : global_key }, annotations=[ ner_annotation, text_annotation, checklist_annotation, radio_annotation, nested_radio_annotation, nested_checklist_annotation ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} label_ndjson = [] for annotations in [ ner_annotation_ndjson, text_annotation_ndjson, checklist_annotation_ndjson, radio_annotation_ndjson, nested_checklist_annotation_ndjson, nested_radio_annotation_ndjson ]: annotations.update({ "dataRow": { "globalKey": global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import multimodal chat annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-multimodal-chat-annotations Learn how to import multimodal chat evaluation annotations and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](/horizon/guides/import-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for a LLM human preference data row: * `Tool` * Message ranking * Single message selection * Multiple message selection * `Classification` * Radio (single-choice) * Checklist (multi-choice) * Free-form text ### Message and global-based annotations Radio and free-form text annotations can be both message and global based. To make a message-based annotation global, remove the `message_id` key inside the annotation. ## Tools ### Message ranking ```python Python Annotation theme={"theme":"css-variables"} message_ranking_annotation = lb_types.MessageEvaluationTaskAnnotation( name="Message ranking", value=MessageRankingTask( parent_message_id="message-0", ranked_messages=[ OrderedMessageInfo( message_id="message-1", model_config_name="model-config-1", order=1, ), OrderedMessageInfo( message_id="message-2", model_config_name="model-config-2", order=2, ), ], ), ) ``` ```json NDJSON theme={"theme":"css-variables"} message_ranking_annotation_ndjson = { "name": "model output multi ranking", "messageEvaluationTask": { "format": "message-ranking", "data": { "parentMessageId": "message-0", "rankedMessages": [ { "messageId": "message-1", "modelConfigName": "model-config-1", "order": 2, }, { "messageId": "message-2", "modelConfigName": "model-config-2", "order": 1, }, ], }, }, } ``` ### Single message selection ```python Python Annotation theme={"theme":"css-variables"} single_message_selection_annotation = lb_types.MessageEvaluationTaskAnnotation( name="Single message selection", value=MessageSingleSelectionTask( message_id="message-1", parent_message_id="message-0", model_config_name="model-config-1", ), ) ``` ```json NDJSON theme={"theme":"css-variables"} single_message_selection_annotation_ndjson = { "name": "Single message selection", "messageEvaluationTask": { "format": "message-single-selection", "data": { "messageId": "message-1", "parentMessageId": "message-0", "modelConfigName": "model-config-1", }, }, } ``` ### Multiple message selection ```python Python Annotation theme={"theme":"css-variables"} multiple_message_selection_annotation = lb_types.MessageEvaluationTaskAnnotation( name="Multi message selection", value=MessageMultiSelectionTask( parent_message_id="message-0", selected_messages=[ MessageInfo( message_id="message-1", model_config_name="model-config-1", ), MessageInfo( message_id="message-2", model_config_name="model-config-2", ), ], ), ) ``` ```json NDJSON theme={"theme":"css-variables"} multiple_message_selection_annotation_ndjson = { "name": "Multi message selection", "messageEvaluationTask": { "format": "message-multi-selection", "data": { "parentMessageId": "message-0", "selectedMessages": [ { "messageId": "message-1", "modelConfigName": "model-config-1", }, { "messageId": "message-2", "modelConfigName": "model-config-2", }, ], }, }, } ``` ## Classifications ### Radio ```python Python Annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="Choose the best response", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="Response B"))) ``` ```json NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_convo", "answer": { "name": "first_radio_answer" } } ``` ### Checklist ```python Python Annotation theme={"theme":"css-variables"} checklist_annotation= lb_types.ClassificationAnnotation( name="checklist_convo", # must match your ontology feature"s name value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ), message_id="message-1" # Message specific annotation ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_convo", "answers": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"} ] } ``` ### Free-form text ```python Python Annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name="Provide a reason for your choice", value=lb_types.Text(answer="the answer to the text questions right here") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "text_convo", "answer": "the answer to the text questions right here", } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to the steps to import annotations as ground truth labels, and we will describe the slight differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid import labelbox.types as lb_types from labelbox.types import ( Label, MessageEvaluationTaskAnnotation, MessageInfo, MessageMultiSelectionTask, MessageRankingTask, MessageSingleSelectionTask, OrderedMessageInfo, ) ``` ### Step 1: Import data rows You need to import data rows to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} mmc_asset = "https://storage.googleapis.com/labelbox-datasets/conversational_model_evaluation_sample/offline-model-chat-evaluation.json" global_key = "offline-multimodal_chat_evaluation" # Upload data rows convo_data = { "row_data": mmc_asset , "global_key": global_key } # Create a dataset dataset = client.create_dataset(name="offline-multimodal_chat_evaluation_demo") # Create a datarow task = dataset.create_data_rows([convo_data]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ```json Sample import theme={"theme":"css-variables"} { "type": "application/vnd.labelbox.conversational.model-chat-evaluation", "version": 2, "actors": { "clxfzhair00013b6vlatdodq2": { "role": "human", "metadata": { "name": "User" } }, "clxfzhbwl00033b6vbq14ufri": { "role": "model", "metadata": { "modelConfigName": "GPT 4 with temperature 0.7" } }, "clxfzhbwl00043b6vgajlwovx": { "role": "model", "metadata": { "modelConfigName": "GPT 5" } } }, "messages": { "clxfzhair00023b6vb607bqo6": { "actorId": "clxfzhair00013b6vlatdodq2", "content": [ { "type": "text", "content": "The best restaurant in Wroclaw" } ], "childMessageIds": [ "clxfzi3r400053b6vm5udpdgo", "clxfzi3r400063b6vuaeajylo" ] }, "clxfzi3r400053b6vm5udpdgo": { "actorId": "clxfzhbwl00033b6vbq14ufri", "content": [ { "type": "text", "content": "It's impossible to definitively say which restaurant in Wroclaw is \"the best\" because it's subjective and depends on individual preferences. However, I can provide you with a list of some of the most popular and highly-rated restaurants in Wroclaw, representing a variety of cuisines and dining experiences:\n\n**Fine Dining:**\n\n* **JaDka:** Offers modern Polish cuisine with a focus on local and seasonal ingredients. \n* **Szara Ges:** Located in the heart of the city, this restaurant is known for its elegant atmosphere and refined Polish and European dishes. \n* **Młoda Polska Bistro & Pianino:** Serves traditional Polish cuisine with a modern twist in a cozy and stylish setting.\n\n**Casual Dining:**\n\n* **Bernard:** A popular spot for traditional Polish food, known for its generous portions and affordable prices. \n* **Konspira:** Offers a unique dining experience in a former safe house from the communist era, serving Polish and European cuisine. \n* **U Gruzina:** A Georgian restaurant with delicious and authentic dishes, including khachapuri (cheese-filled bread) and khinkali (dumplings).\n\n**International Cuisine:**\n\n* **Dinette:** Serves up delicious and creative breakfast and brunch dishes all day long.\n* **Pizza Pany:** A great option for Neapolitan-style pizza with fresh ingredients and a lively atmosphere. \n* **Curry House:** Offers a wide variety of authentic Indian curries and dishes. \n\n**Vegetarian/Vegan:**\n\n* **Vega:** A vegetarian restaurant with a diverse menu featuring Polish and international dishes. \n* **Zielony Szpinak:** Offers vegan versions of traditional Polish dishes as well as international options. \n\n**For a truly unique experience:**\n\n* **Wrocławskie Podwale:** Dine in a medieval cellar with vaulted ceilings and enjoy traditional Polish cuisine. \n* **Stary Młyn:** Located in a former mill, this restaurant offers a romantic atmosphere and serves Polish and European dishes. \n\nTo narrow down your search, consider what kind of cuisine you're in the mood for, your budget, and the occasion. I recommend checking online reviews and making a reservation, especially if you're dining during peak hours. \n" } ], "childMessageIds": ["clxfznjb800073b6v43ppx9ca"] }, "clxfzi3r400063b6vuaeajylo": { "actorId": "clxfzhbwl00043b6vgajlwovx", "content": [ { "type": "text", "content": "There are several highly-rated restaurants in Wroclaw, but one of the best is \"Szynkarnia\". It's known for its delicious Polish cuisine, cozy atmosphere, and excellent service. Other top contenders include \"The White Dog\" known for its modern European cuisine, and \"Restauracja JaDka\" for those who want to try traditional Polish dishes." } ], "childMessageIds": ["clxfznjb800073b6v43ppx9ca"] }, "clxfznjb800073b6v43ppx9ca": { "actorId": "clxfzhair00013b6vlatdodq2", "content": [ { "type": "text", "content": "Tell me more" } ], "childMessageIds": [ "clxfzocbm00083b6v8vczsept", "clxfzocbm00093b6vx4ndisub" ] }, "clxfzocbm00083b6v8vczsept": { "actorId": "clxfzhbwl00033b6vbq14ufri", "content": [ { "type": "text", "content": "Alright, tell me more about what you're looking for in a restaurant! To give you a more personalized recommendation, I need some more information. Could you tell me:\n\n* **What kind of food are you craving?** (e.g., Italian, Polish, Asian, seafood, etc.) \n* **What's your budget?** (e.g., fine dining, mid-range, budget-friendly)\n* **What's the occasion?** (e.g., romantic dinner, family meal, night out with friends)\n* **What kind of atmosphere are you looking for?** (e.g., lively, cozy, romantic, trendy)\n* **Are there any dietary restrictions or preferences I should know about?** (e.g., vegetarian, vegan, gluten-free)\n\nThe more information you can give me, the better I can tailor my recommendation to your specific needs and preferences. \n" } ], "childMessageIds": [] }, "clxfzocbm00093b6vx4ndisub": { "actorId": "clxfzhbwl00043b6vgajlwovx", "content": [ { "type": "text", "content": "Sure, here's more information about these restaurants:\n\n1. Szynkarnia: This is a traditional Polish restaurant located in the heart of Wroclaw. It offers a variety of Polish dishes, including pierogi (dumplings), bigos (hunter's stew), and kielbasa (sausage). The restaurant is also known for its wide selection of local beers. The interior is rustic and cozy, with wooden furniture and warm lighting.\n\n2. The White Dog: This restaurant offers a modern take on European cuisine. The menu changes seasonally to take advantage of fresh, local ingredients. The White Dog also has an extensive wine list and a stylish, contemporary interior. It's a great place for a romantic dinner or a special occasion.\n\n3. Restauracja JaDka: This restaurant is located in a beautiful, historic building in Wroclaw. It specializes in Polish cuisine, with dishes like duck with apples, beetroot soup, and a variety of pierogi. The interior is elegant and atmospheric, with exposed brick walls and vintage furniture.\n\nRemember, it's always a good idea to make a reservation in advance, as these popular spots can fill up quickly." } ], "childMessageIds": [] } }, "rootMessageIds": ["clxfzhair00023b6vb607bqo6"] } ``` ### Step 2: Set up ontology Your project ontology needs to support the classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the name parameter needs to match the value of the name field in your annotation. For example, if you provide a name `annotation_name` for your message ranking annotation, you need to name the message ranking tool as `anotations_name` when setting up your ontology. The same alignment must hold true for the other tools and classifications that you create in the ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool( tool=lb.Tool.Type.MESSAGE_SINGLE_SELECTION, name="Single message selection", ), lb.Tool( tool=lb.Tool.Type.MESSAGE_MULTI_SELECTION, name="Multi message selection", ), lb.Tool(tool=lb.Tool.Type.MESSAGE_RANKING, name="Message ranking"), ], classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, scope=lb.Classification.Scope.GLOBAL, name="Choose the best response", options=[lb.Option(value="Response A"), lb.Option(value="Response B"), lb.Option(value="Tie")] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="Provide a reason for your choice" ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, scope=lb.Classification.Scope.INDEX, name="checklist_convo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ) ] ) # Create ontology ontology = client.create_ontology( "MMC ontology", ontology_builder.asdict(), media_type=lb.MediaType.Conversational, ontology_kind=lb.OntologyKind.ModelEvaluation, ) ``` ### Step 3: Set up a labeling project Use the following code to create an offline multimodal evaluation project: ```python Python theme={"theme":"css-variables"} # Create Labelbox project project = client.create_offline_model_evaluation_project( name="Offline MMC Import Demo", description="", # optional ) # Setup your ontology project.connect_ontology(ontology) # Connect the ontology to your project ``` ### Step 4: Send data rows to the project Use the following code to send data rows to the project you just created: ```python Python theme={"theme":"css-variables"} # Create a batch to send to your project batch = project.create_batch( "first-batch-convo-demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. To understand annotation payloads, see [overview](#overview). These examples demonstrate each format and how to compose annotations into labels attached to data rows. ### Replace placeholder fields with actual values Replace `message_id` and `model_config_name` with the actual message ID and model configuration name before appending annotations. ```python Python annotation theme={"theme":"css-variables"} label = [] label.append( lb_types.Label( data={"global_key" : global_key }, annotations=[ message_ranking_annotation, single_message_selection_annotation, multiple_message_selection_annotation, text_annotation, checklist_annotation, radio_annotation, ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} label_ndjson = [] for annotations in [ message_ranking_annotation_ndjson, single_message_selection_annotation_ndjson, multiple_message_selection_annotation_ndjson, text_annotation_ndjson, checklist_annotation_ndjson, radio_annotation_ndjson, nested_checklist_annotation_ndjson, nested_radio_annotation_ndjson ]: annotations.update({ "dataRow": { "globalKey": global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Option A: import as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Import as [ground truth](/horizon/guides/import-ground-truth) labels This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import multimodal chat data Source: https://docs.labelbox.com/horizon/developer-guides/import-multimodal-chat-data How to import multimodal chat data and sample import formats. You need to set up a multimodal chat evaluation project before importing data. The two types of multimodal chat evaluation projects have different project creation methods and data row setups: * For **offline multimodal chat evaluation** projects, use `create_offline_model_evaluation_project` and import data rows of existing conversations. * For **live multimodal chat evaluation projects**, use `client.create_model_evaluation_project` and either: * (Recommended) Create data rows and send them to projects, like other types of projects. * Generate empty data rows upon project creation, which can't create data rows with attachments and metadata. For a full walk-through of setting up a multimodal chat evaluation project, see [Multimodal chat evaluation](/horizon/guides/multimodal-chat-evaluation-editor). ## Set up live multimodal chat evaluation projects Use `client.create_model_evaluation_project` to create a live multimodal chat evaluation project. This method takes the same parameters as the traditional `client.create_project`, with a few additional parameters specific to multimodal chat evaluation projects. The `client.create_model_evaluation_project` methods require the following parameters: * `name`: The name of your new project. * `description`: An optional description of your project. * `dataset_name` (optional): The name of the dataset where the generated data rows will be located. Include this parameter only if you want to create a new dataset. * `dataset_id` (optional): The dataset ID of an existing Labelbox dataset. Include this parameter if you want to append it to an existing dataset. * `data_row_count` (optional): The number of data row assets that will be generated and used with your project. Defaults to 100 if a `dataset_name` or `dataset_id` is included. ### Option A: Create and send data rows to projects ```python Python expandable theme={"theme":"css-variables"} # Create the project project = client.create_model_evaluation_project( name="Example live multimodal chat project", description="", # optional ) def make_data_rows(dataset_id=None): # If a dataset ID is provided, fetch the dataset using that ID. # Otherwise, create a new dataset with the specified name. if dataset_id: dataset = client.get_dataset(dataset_id) else: dataset = client.create_dataset(name="example live mmc dataset") # Helper function to generate a single data row def generate_data(ind): return { "row_data": { # The chat data format 'type': 'application/vnd.labelbox.conversational.model-chat-evaluation', 'draft': True, 'rootMessageIds': [], 'actors': {}, 'version': 2, 'messages': {} }, "global_key": f"global_key_{dataset.uid}_{ind}", "metadata_fields": [{"name": "tag", "value": "val_tag"}], "attachments": [ { "type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg" } ] } # Generate a list of 100 data rows data_list = [generate_data(ind) for ind in range(100)] # Upload the generated data rows to the dataset task = dataset.create_data_rows(data_list) print("Processing task ", task.uid) # Print the unique ID of the task task.wait_till_done() # Ensure that the task status is 'COMPLETE' to confirm success assert task.status == "COMPLETE" # Return the dataset object return dataset # Create a new data set. Alternatively, pass an existing dataset ID dataset = make_data_rows() # Retrieve the data row IDs from the dataset data_row_ids = [data_row.uid for data_row in dataset.data_rows()] # Send data rows to the project batch = project.create_batch( name="mmc-batch", # each batch in a project must have a unique name data_rows=data_row_ids, # data row IDs to include in the batch priority=1 # priority between 1(highest) - 5(lowest) ) print(f"Batch: {batch}") ``` ### Option B: Generate empty data rows ### No metadata support Only use this option if your project doesn't require metadata attachments or embeddings for data rows. ```python Python theme={"theme":"css-variables"} # Create the project and generate data rows project = client.create_model_evaluation_project( name="Example live multimodal chat project", description="", # optional dataset_name="Example live multimodal chat dataset", data_row_count=100, ) # Connect the project to the created ontology project.connect_ontology(ontology) ``` ## Set up offline multimodal chat evaluation projects Use `client.create_offline_model_evaluation_project` to create offline multimodal chat evaluation projects. This method uses the same parameters as `client.create_project` and adds validation to ensure the project is set up correctly. ```python Python theme={"theme":"css-variables"} project = client.create_offline_model_evaluation_project( name="", description="", # optional ) ``` After creating the project, you can import conversational version 2 data rows to the project. To learn how to import annotations, see [Import multimodal chat annotations](/horizon/developer-guides/import-multimodal-chat-annotations). ## Specifications File format: chat data JSON in [conversation v2 format](#sample-conversation-v2-json) Import methods: * Local upload (maximum character count: 2,621,440) * IAM Delegated Access * Signed URLs (`https` URLs only) When importing conversation or thread data to Labelbox, include the following information for each data row in your JSON file. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to a cloud-hosted conversational text JSON file. See the section below for details on our conversation format. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if their global keys are duplicated to existing data rows. | | `media_type` | No | `"CONVERSATIONAL"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [metadata](/horizon/guides/datarow-metadata) | ## Import format ```json Local upload expandable theme={"theme":"css-variables"} { "row_data": { "type": "application/vnd.labelbox.conversational.model-chat-evaluation", "version": 2, "actors": { "cm1qu8krf00063b72cutnbn5l": { "role": "human", "metadata": { "name": "User" } }, "cm1vjleif00023b6y4fw4ew94": { "role": "model", "metadata": { "modelConfigName": "Gem Pro-Copy" } }, "cm1vjleif00033b6yifzroser": { "role": "model", "metadata": { "modelConfigName": "gpt 4-Copy" } } }, "messages": { "cm1qu8krf00073b72fyar00vh": { "actorId": "cm1qu8krf00063b72cutnbn5l", "content": [{ "type": "text", "content": "Hello " }], "childMessageIds": [ "cm1vjlitg00043b6y1tgssq1r", "cm1vjlitg00053b6y19ve1qra" ] }, "cm1vjlitg00043b6y1tgssq1r": { "actorId": "cm1vjleif00023b6y4fw4ew94", "content": [ { "type": "text", "content": "Hello! 👋 How can I assist you today? 😊 \\n" } ], "childMessageIds": [] }, "cm1vjlitg00053b6y19ve1qra": { "actorId": "cm1vjleif00033b6yifzroser", "content": [ { "type": "text", "content": "Hi! How can I assist you today?" } ], "childMessageIds": [] } }, "rootMessageIds": ["cm1qu8krf00073b72fyar00vh"] }, "global_key": "global_key" } ``` ```json Cloud storage theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_1.json", "global_key": "global_key_1" }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": "global_key_2" }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_3.json", "global_key": "global_key_3" } ] ``` ## Python example ```python Local upload expandable theme={"theme":"css-variables"} # Embed the chat conversation data row_data = { "type": "application/vnd.labelbox.conversational.model-chat-evaluation", "version": 2, "actors": { "cm1qu8krf00063b72cutnbn5l": { "role": "human", "metadata": { "name": "User" } }, "cm1vjleif00023b6y4fw4ew94": { "role": "model", "metadata": { "modelConfigName": "Gem Pro-Copy" } }, "cm1vjleif00033b6yifzroser": { "role": "model", "metadata": { "modelConfigName": "gpt 4-Copy" } } }, "messages": { "cm1qu8krf00073b72fyar00vh": { "actorId": "cm1qu8krf00063b72cutnbn5l", "content": [{ "type": "text", "content": "Hello " }], "childMessageIds": [ "cm1vjlitg00043b6y1tgssq1r", "cm1vjlitg00053b6y19ve1qra" ] }, "cm1vjlitg00043b6y1tgssq1r": { "actorId": "cm1vjleif00023b6y4fw4ew94", "content": [ { "type": "text", "content": "Hello! 👋 How can I assist you today? 😊 \\n" } ], "childMessageIds": [] }, "cm1vjlitg00053b6y19ve1qra": { "actorId": "cm1vjleif00033b6yifzroser", "content": [ { "type": "text", "content": "Hi! How can I assist you today?" } ], "childMessageIds": [] } }, "rootMessageIds": ["cm1qu8krf00073b72fyar00vh"] } # Create a dataset dataset = client.create_dataset( name="mmc_dataset", ) # Upload the conversation data to the dataset as a data row. task = dataset.create_data_rows([{"row_data": row_data}]) task.wait_till_done() # Output any errors that occurred during the import. print("Errors:", task.errors) print("Failed data rows:", task.failed_data_rows) ``` ```python Cloud storage theme={"theme":"css-variables"} # Generate dummy global keys global_key_1 = str(uuid.uuid4()) global_key_2 = str(uuid.uuid4()) global_key_3 = str(uuid.uuid4()) # Create a dataset dataset = client.create_dataset( name="pairwise_demo_"+str(uuid.uuid4()), iam_integration=None ) # Upload data rows task = dataset.create_data_rows([ { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_1.json", "global_key": global_key_1 }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": global_key_2 }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_3.json", "global_key": global_key_3 } ]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ## Conversation v2 JSON | Parameter | Required | Description | | - | - | - | | `type` | Yes | Populate with `application/vnd.labelbox.conversational.model-chat-evaluation` | | `version` | Yes | Populate with `2` | | `actors` | Yes | An object of [actors](#actor-object) of the chat conversation. | | `messages` | Yes | An object of messages from each actor. | | `rootMessageIds` | Yes | An array of message ids. You would include the id of first message given from a human actor. | ### Actor object Actor objects start with a key value of a unique user given id. Each actor object has a `role` key and a `metadata` key. The metadata contains the specifics of the actor and will vary depending on the actor's role. | Parameter | Required | Description | | - | - | - | | `role` | Yes | The role the actor receives. Either `human` or `model`. | | `name` | No | The name of the actor. This is applicable and required for actors with the human role. Placed inside the `metadata` actor key. | | `modelConfigName` | Yes | The model config name of the actor. This is required for actors with the model role. Placed inside the `metadata` actor key. | ### Message object Message objects start with a key value of a unique user given id. | Parameter | Required | Description | | - | - | - | | `actorId` | Yes | The id of the actor who produced the message. | | `content` | Yes | An array of content for the message. See [message content](#message-content). | | `childMessageIds` | No | An array of message ids that are children of the message object. Typically this would be the next series of messages. If you were comparing more then one model response, multiple message ids can be included. | #### Message content | Parameter | Required | Description | | - | - | - | | `type` | Yes | The type of message. This will be `fileData` for attachments, `text` for raw text, and `dataRowAttachment` for attachments on data rows. | | `content` | No | The raw text content of your message. This field supports markdown. This field is used for `text` type messages. | | `fileUri` | No | `https` path to a public cloud-hosted attachment file. This field is used for `fileData` type messages. If you want to use [IAM delegated access](/horizon/guides/iam-delegated-access) to store conversation files, you should first add them as data row attachments. See [attachments](/horizon/developer-guides/attachments) on how to add an attachment to a data row. After you add your attachments to your data row, you can use the `type` and `attachmentName` keys to include your attachment inside your conversational data. | | `attachmentName` | No | The name of the attachment on the data row. | | `mimeType` | No | The `mimeType` of your attachment `fileUri` data. The following types are supported: - `video/mp4` - `image/png` - `application/pdf` | #### Embed images You can either embed images directly in the message `content` or add them as attachments.\ For `dataRowAttachment`, the value of `attachmentName` must exist in the `attachments` section. ```json Embedded image content theme={"theme":"css-variables"} // Message { "actorId": "", "childMessageIds": [], "content": [ { "type": "text", "content": f"{model_name2}" } ] } ``` ```json Image attachment theme={"theme":"css-variables"} // Message { "actorId": "", "childMessageIds": [], "content": [ { "type": "text", "content": "What do you see in this image?" }, { "type": "fileData", "fileUri": "https://link-to-my-image", "mimeType": "image/png" }, { "type": "dataRowAttachment", "attachmentName": "attachment_name_1", } ] } ``` ## Sample conversation v2 JSON ### SDK import only You can't upload the following file from the web interface directly. You must use an import file as described in [Import format](#import-format). ```json Sample conversation v2 json expandable theme={"theme":"css-variables"} "type": "application/vnd.labelbox.conversational.model-chat-evaluation", "version": 2, "actors": { "actor1": { "role": "human", "metadata": { "name": "User" } }, "actor2": { "role": "model", "metadata": { "modelConfigName": "Model 1" } }, "actor3": { "role": "model", "metadata": { "modelConfigName": "Model 2" } } }, "messages": { "clxcboi1e00053p6n0ya733nn": { "actorId": "actor1", "content": [ { "type": "text", "content": "What's in the images?" }, { "type": "fileData", "fileUri": "https://storage.googleapis.com/labelbox-developer-testing-assets/image/giraffe-combunetes.png", "mimeType": "image/png" }, { "type": "fileData", "fileUri": "https://storage.googleapis.com/labelbox-developer-testing-assets/image/label-blocks.png", "mimeType": "image/png" }, { "type": "fileData", "fileUri": "https://storage.googleapis.com/labelbox-developer-testing-assets/image/tim-test.png", "mimeType": "image/png" } ], "childMessageIds": [ "clxcboue900083p6no6emql83", "clxcboue900093p6nrepe8jjd" ] }, "clxcboue900083p6no6emql83": { "actorId": "actor2", "content": [ { "type": "text", "content": "The images show:\n 1. **A giraffe in an office:** The giraffe is standing in front of a desk with a computer showing code on the screen. There's also a whiteboard with diagrams about Kubernetes." } ], "childMessageIds": [ "clxmrt0hh00023p6qykkdaqtk" ] }, "clxcboue900093p6nrepe8jjd": { "actorId": "actor3", "content": [ { "type": "text", "content": "1. The first image shows a giraffe standing in an office environment, interacting with a computer. The giraffe is looking at a large monitor displaying code, and there is a whiteboard in the background with various diagrams and notes.\n2." } ], "childMessageIds": [ "clxmrt0hh00023p6qykkdaqtk" ] }, "clxmrt0hh00023p6qykkdaqtk": { "actorId": "actor1", "content": [ { "type": "text", "content": "What's in this PDF file?" }, { "type": "fileData", "fileUri": "https://storage.googleapis.com/labelbox-developer-testing-assets/pdf/data_files/south-platte-tree-map.pdf", "mimeType": "application/pdf" } ], "childMessageIds": [ "clxmrtgxg00033p6qqzl2596o", "clxmrtgxg00043p6qiehsvww4" ] }, "clxmrtgxg00033p6qqzl2596o": { "actorId": "actor2", "content": [ { "type": "text", "content": "This PDF file contains directions and maps for Christmas tree cutting areas managed by the South Platte Ranger District. Here's a breakdown:\n**Page 1: Directions**\n* Provides written directions to three Christmas tree cutting areas:\n **Buffalo Creek:** Located southeast of Pine Junction.\n **Camp Fickes:** Located west of Buffalo Creek.\n **Sugar Creek:** Located along County Road\n" } ], "childMessageIds": [ "clxmru9j600053p6q0qh89zm4" ] }, "clxmrtgxg00043p6qiehsvww4": { "actorId": "actor3", "content": [ { "type": "text", "content": "Via fugit referre [duasque longumque](http://luminavale.com/) fateri sumite\ncalidumque arma spatiis fuerit genialiter errore iacent; cuncta hausit memori.\nAestus a omnia nomenque inlimis captantur ipsumque fuga. Aeneadae dona tenero\nclipei tamen, sed de amor flagellari quas; corpore, grande.\n[Pectore inclinatcadunt](http://tardoset.com/uni-et.html), Hectoreis defensatque virga altera\nsecum caliturasque militia pennas." } ], "childMessageIds": [ "clxmru9j600053p6q0qh89zm4" ] }, "clxmru9j600053p6q0qh89zm4": { "actorId": "actor1", "content": [ { "type": "text", "content": "What have astronauts brought back from the moon?" } ], "childMessageIds": [ "clxmrupyh00063p6q4wxj97sz", "clxmrupyh00073p6qeszn06l7" ] }, "clxmrupyh00063p6q4wxj97sz": { "actorId": "actor2", "content": [ { "type": "text", "content": "## Petebat semine\nDiurnis parsque, tanti nuper novi, extremae caede *Psophidaque spiro* dum visa.\nUsu dicebat obstet meritos." } ], "childMessageIds": [] }, "clxmrupyh00073p6qeszn06l7": { "actorId": "actor3", "content": [ { "type": "text", "content": "## Ossa custos captabat insanis humus Cipe temptatum\nLorem markdownum adflatuque est Tydides medios. Notatas te Pandrose **solent**\npartes saucius animal certamen, plures opem corpora. Est magni duce, illiarcus: Iuno atque aderat amplexo genusque." } ], "childMessageIds": [] } }, "rootMessageIds": [ "clxcboi1e00053p6n0ya733nn" ] ``` ### LaTeX support To add LaTeX formatting, wrap your math expressions using backticks and dollar signs. The editor supports both inline and block LaTeX formatting. For example, to add LaTeX formatting for `x=2`, put `$$x = 2$$`. # Import prompt and response annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-prompt-and-response-annotations Developer guide for importing annotations on prompt and response projects. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs. * Supports easy conversion to NDJSON format to quickly import annotations to Labelbox. * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type. * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label Import Types Labelbox supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) allows you to import computer-generated predictions and simple annotations created outside of Labelbox as pre-labels on an asset. * [Ground truth](/horizon/guides/import-ground-truth) allows you to bulk import ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data can consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported Annotations Prompt and response generated projects support the following annotations data row: * Prompt and response creation projects * Prompt text * Radio * Checklist * Response text * Prompt creation projects * Prompt text * Response creation projects * Radio * Checklist * Response text ## Prompt ### Free-form text ### Information Only one prompt annotation is allowed per label. ```python Python annotation theme={"theme":"css-variables"} prompt_annotation = lb_types.PromptClassificationAnnotation( name = "Follow the prompt and select answers", value = lb_types.PromptText(answer = "This is an example of a prompt") ) ``` ```json NDJSON theme={"theme":"css-variables"} prompt_annotation_ndjson = { "name": "Follow the prompt and select answers", "answer": "This is an example of a prompt" } ``` ## Responses ### Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} response_radio_annotation = lb_types.ClassificationAnnotation( name="response radio feature", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```json NDJSON theme={"theme":"css-variables"} response_radio_annotation_ndjson = { "name": "response radio feature", "answer": { "name": "first_radio_answer" } } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} response_checklist_annotation = lb_types.ClassificationAnnotation( name="response checklist feature", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "option_1"), lb_types.ClassificationAnswer(name = "option_2"), ]) ) ``` ```json NDJSON theme={"theme":"css-variables"} response_checklist_annotation_ndjson = { "name": "response checklist feature", "answer": [ { "name": "option_1" }, { "name": "option_2" } ] } ``` ### Free-form text ```python Python annotation theme={"theme":"css-variables"} response_text_annotation = lb_types.ClassificationAnnotation( name = "Provide a reason for your choice", value = lb_types.Text(answer = "This is an example of a response text") ) ``` ```json NDJSON theme={"theme":"css-variables"} response_text_annotation_ndjson = { "name": "Provide a reason for your choice", "answer": "This is an example of a response text" } ``` ### Nested classification ```python Python annotation theme={"theme":"css-variables"} nested_response_radio_annotation = lb_types.ClassificationAnnotation( name="nested_response_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_response_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_response_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_response_radio_annotation_ndjson = { "name": "nested_radio_question", "answer": [{ "name": "first_radio_answer", "classifications" : [ { "name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer"} } ] }] } nested_response_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to the steps to import annotations as ground truth labels, and we will describe the slight differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid import time ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import or generate data rows and create projects Each type of the prompt and response generation project requires different setup. See [prompt and response project](/horizon/developer-guides/prompt-and-response-projects) for more details on the differences. #### Prompt response and prompt creation A prompts and responses creation project automatically generates empty data rows upon creation. You will then need to obtain either the `global_keys` or `data_row_ids` attached to the generated data rows by exporting them from the created project or obtaining them directly on the [data row tab](/horizon/guides/data-rows-activity) using the UI. ```python Prompt and response creation theme={"theme":"css-variables"} prompt_response_project = client.create_prompt_response_generation_project( name="Demo prompt response project", media_type=lb.MediaType.LLMPromptResponseCreation, dataset_name="Demo prompt response dataset", data_row_count=1, ) ``` ```python Prompt creation theme={"theme":"css-variables"} prompt_project = client.create_prompt_response_generation_project( name="Demo prompt project", media_type=lb.MediaType.LLMPromptCreation, dataset_name="Demo prompt dataset", data_row_count=1, ) ``` #### Response creation For response creation projects, text data rows are used and are not generated upon project creation. The following steps create a dataset with a text data row attached, create a response creation project, and batch the created data row towards the project. ```python Python theme={"theme":"css-variables"} # Create dataset with text data row global_key = "lorem-ipsum.txt" text_asset = { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "global_key": global_key, "media_type": "TEXT", } dataset = client.create_dataset(name="text_annotation_import_demo_dataset") task = dataset.create_data_rows([text_asset]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) # Create response creation project project = client.create_response_creation_project( name="Demo response project", ) # Create a batch of data rows for newly created project batch = project.create_batch( name="Demo response batch", # each batch in a project must have a unique name global_keys=[global_key], # paginated collection of data row objects, list of data row ids or global keys priority=1 # priority between 1(highest) - 5(lowest) ) ``` ### Step 2: Set up ontology Your project ontology needs to support the classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter needs to match the value of the `name` field in your annotation. For example, if you provide a name `annotation_name` for your created annotation, you need to name the bounding box tool as `anotations_name` when setting up your ontology. The same alignment must hold true for the other tools and classifications that you create in the ontology. This example shows how to create an ontology containing all supported by prompt and response project types [annotation types](#supported-annotations). ```python Prompt and response creation expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[], classifications=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.PROMPT, name="prompt text", character_min = 1, # Minimum character count of prompt field (optional) character_max = 20, # Maximum character count of prompt field (optional) ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_CHECKLIST, name="response checklist feature", options=[ lb.ResponseOption(value="option_1", label="option_1"), lb.ResponseOption(value="option_2", label="option_2"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_RADIO, name="response radio feature", options=[ lb.ResponseOption(value="first_radio_answer"), lb.ResponseOption(value="second_radio_answer"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_TEXT, name="response text", character_min = 1, # Minimum character count of response text field (optional) character_max = 20, # Maximum character count of response text field (optional) ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_RADIO, name="nested_response_radio_question", options=[ lb.ResponseOption("first_radio_answer", options=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.RESPONSE_RADIO, name="sub_radio_question", options=[lb.ResponseOption("first_sub_radio_answer")] ) ]) ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_CHECKLIST, name="nested_response_checklist_question", options=[ lb.ResponseOption("first_checklist_answer", options=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.RESPONSE_CHECKLIST, name="sub_checklist_question", options=[lb.ResponseOption("first_sub_checklist_answer")] ) ]) ], ), ], ) # Create ontology ontology = client.create_ontology( "Prompt and response ontology", ontology_builder.asdict(), media_type=lb.MediaType.LLMPromptResponseCreation, ) # Connect ontology prompt_response_project.connect_ontology(ontology) ``` ```python Prompt creation theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[], classifications=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.PROMPT, name=f"prompt text", character_min = 1, # Minimum character count of prompt field (optional) character_max = 20, # Maximum character count of prompt field (optional) ) ], ) # Create ontology ontology = client.create_ontology( "Prompt ontology", ontology_builder.asdict(), media_type=lb.MediaType.LLMPromptCreation, ) # Connect ontology prompt_project.connect_ontology(ontology) ``` ```python Response creation theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[], classifications=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_CHECKLIST, name="response checklist feature", options=[ lb.ResponseOption(value="option 1", label="option 1"), lb.ResponseOption(value="option 2", label="option 2"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_RADIO, name="response radio feature", options=[ lb.ResponseOption(value="first_radio_answer"), lb.ResponseOption(value="second_radio_answer"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_TEXT, name="response text", character_min = 1, # Minimum character count of response text field (optional) character_max = 20, # Maximum character count of response text field (optional) ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_RADIO, name="nested_response_radio_question", options=[ lb.ResponseOption("first_radio_answer", options=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.RESPONSE_RADIO name="sub_radio_question", options=[lb.ResponseOption("first_sub_radio_answer")] ) ]) ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_CHECKLIST, name="nested_response_checklist_question", options=[ lb.ResponseOption("first_checklist_answer", options=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.RESPONSE_CHECKLIST name="sub_checklist_question", options=[lb.ResponseOption("first_sub_checklist_answer")] ) ]) ], ), ], ) # Create ontology ontology = client.create_ontology( "Response ontology", ontology_builder.asdict(), media_type=lb.MediaType.Text, ontology_kind=lb.OntologyKind.ResponseCreation ) # Connect ontology response_project.connect_ontology(ontology) ``` ### Step 3: Export for global\_keys For prompt response creation and prompt creation projects you will need to obtain either the `global_keys` or `data_row_ids` attached to the generated data rows by exporting them from the created project. Since the generation of data rows is an async process you will need to wait for the project data rows to be completed before exporting. ```python Prompt and response export theme={"theme":"css-variables"} time.sleep(20) export_task = prompt_response_project.export() export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] # Obtain global keys to be used later on global_keys = [dr.json["data_row"]["global_key"] for dr in stream] ``` ```python Prompt export theme={"theme":"css-variables"} time.sleep(20) export_task = prompt_project.export() export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] # Obtain global keys to be used later on global_keys = [dr.json["data_row"]["global_key"] for dr in stream] ``` ### Step 4: Import Annotations For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Payload Depending on the type of prompt and response project you are using your payload might look different. For the response payload, you can also use `is_benchmark_reference` to specify [benchmarks](/horizon/guides/benchmark). ```python Prompt and response payload theme={"theme":"css-variables"} # Python annotation objects label = [] annotations = [ prompt_annotation, response_radio_annotation, response_checklist_annotation, response_text_annotation, nested_response_radio_annotation, nested_response_checklist_annotation ] label.append( lb_types.Label(data={"global_key" : global_keys[0] }, annotations=annotations) ) # NDJSON label_ndjson = [] annotations = [ prompt_annotation_ndjson, response_radio_annotation_ndjson, response_checklist_annotation_ndjson, response_text_annotation_ndjson, nested_response_radio_annotation_ndjson, nested_response_checklist_annotation_ndjson ] for annotation in annotations: annotation.update({ "dataRow": { "globalKey": global_keys[0] }, }) label_ndjson.append(annotation) ``` ```python Prompt payload theme={"theme":"css-variables"} # Python annotation objects label = [] annotations = [ prompt_annotation ] label.append( lb_types.Label(data={"global_key" : global_keys[0] }, annotations=annotations) ) # NDJSON label_ndjson = [] annotations = [ prompt_annotation_ndjson ] for annotation in annotations: annotation.update({ "dataRow": { "globalKey": global_keys[0] }, }) label_ndjson.append(annotation) ``` ```python Response payload theme={"theme":"css-variables"} # Python annotation objects label = [] annotations = [ response_radio_annotation, response_checklist_annotation, response_text_annotation, nested_response_radio_annotation, nested_response_checklist_annotation ] label.append( lb_types.Label(data={"global_key" : global_key }, annotations=annotations # Optional: set the label as a benchmark # Only supported for groud truth imports is_benchmark_reference = True) ) # NDJSON label_ndjson = [] annotations = [ response_radio_annotation_ndjson, response_checklist_annotation_ndjson, response_text_annotation_ndjson, nested_response_radio_annotation_ndjson, nested_response_checklist_annotation_ndjson ] for annotation in annotations: annotation.update({ "dataRow": { "globalKey": global_key }, }) label_ndjson.append(annotation) ``` #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = prompt_response_project.uid, # Replace with project of different prompt and response project types name = "mal_job"+str(uuid.uuid4()), predictions = label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = prompt_response_project.uid, # Replace with project of different prompt and response project types name = "label_import_job" + str(uuid.uuid4()), labels = label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import text annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-text-annotations Developer guide for importing annotations on text data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for an video data row: * Radio * Checklist * Free-form text * Entity * Relationships ## Classifications ### Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) ``` ```json NDJSON theme={"theme":"css-variables"} radio_annotation_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer"), lb_types.ClassificationAnswer(name = "third_checklist_answer") ]) ) ``` ```json NDJSON theme={"theme":"css-variables"} checklist_annotation_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer"}, {"name": "second_checklist_answer"}, {"name": "third_checklist_answer"}, ] } ``` ### Free-form text ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text") ) ``` ```json NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "answer": "sample text", } ``` ### Nested classifications ```python Python annotation theme={"theme":"css-variables"} nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_radio_annotation_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [ { "name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer"} } ] } } nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } ``` ## Tools ### Entity ```python Python annotation theme={"theme":"css-variables"} named_entity = lb_types.TextEntity(start=10, end=20) named_entitity_annotation = lb_types.ObjectAnnotation(value=named_entity, name = "named_entity") ``` ```python NDJSON theme={"theme":"css-variables"} entities_ndjson = { "name": "named_entity", "location": { "start": 67, "end": 128 } } ``` ### Tool with nested classification ```python Python annotation theme={"theme":"css-variables"} tool_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name=# Feature name, value=# Add tool annotation (lb_types."tool"), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python Entity Example theme={"theme":"css-variables"} bbox_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="entity_with_radio_subclass", value=lb_types.TextEntity(start=10, end=20), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer"))) ]) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_with_radio_subclass_ndjson = { "name": "bbox_with_radio_subclass", "classifications": [{ "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer" } }], "bbox": { "top": 933, "left": 541, "height": 191, "width": 330 } } ``` ## Relationship ```python Python annotation theme={"theme":"css-variables"} relationship = lb_types.RelationshipAnnotation( name=# Relationship name, value=lb_types.Relationship( source=# Source tool, target=# Target tool, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python Entity Example theme={"theme":"css-variables"} ner_source = lb_types.ObjectAnnotation( name="named_entity", value=lb_types.TextEntity( start=133, end=140 ) ) ner_target = lb_types.ObjectAnnotation( name="named_entity", value=lb_types.TextEntity( start=143, end=159 ) ) ner_relationship = lb_types.RelationshipAnnotation( name="relationship", value=lb_types.Relationship( source=ner_source, # UUID is not required for annotation types target=ner_target, type=lb_types.Relationship.Type.UNIDIRECTIONAL, )) ``` ```python NDJSON theme={"theme":"css-variables"} uuid_source = str(uuid.uuid4()) uuid_target = str(uuid.uuid4()) entity_source_ndjson = { "name": "named_entity", "uuid": uuid_source, "location": { "start" : 133, "end": 140 } } entity_target_ndjson = { "name": "named_entity", "uuid": uuid_target, "location": { "start": 143, "end": 159 } } ner_relationship_annotation_ndjson = { "name": "relationship", "relationship": { "source": uuid_source, # UUID reference to entity source annotation "target": uuid_target, # UUID reference to target source annotation "type": "unidirectional" } } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid import json ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} # You can now include ohter fields like attachments, media type and metadata in the data row creation step: /horizon/developer-guides/import-text-data global_key = "lorem-ipsum.txt" text_asset = { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "global_key": global_key, "media_type": "TEXT", "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } dataset = client.create_dataset(name="text_annotation_import_demo_dataset") task = dataset.create_data_rows([text_asset]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[lb.Option(value="first_radio_answer")] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ), ], ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ) ], tools=[ # List of Tool objects lb.Tool( tool=lb.Tool.Type.NER, name="named_entity" ), lb.Tool( tool=lb.Tool.Type.RELATIONSHIP, name="relationship" ) ] ) ontology = client.create_ontology("Ontology Text Annotations", ontology_builder.asdict()) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} # Project defaults to batch mode with benchmark quality settings if this argument is not provided # Queue mode will be deprecated once dataset mode is deprecated project = client.create_project(name="text_project_demo", queue_mode=lb.QueueMode.Batch, media_type=lb.MediaType.Text) project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} # Set up batches and ontology # Create a batch to send to your MAL project batch = project.create_batch( "first-batch-text-demo", # Each batch in a project must have a unique name global_keys=[global_key] , # a list of global keys, data rows, or data row ids priority=5 # priority between 1(highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. For annotations that you want to import as ground truth labels, you can also specify [benchmarks](/horizon/guides/benchmark) using the `is_benchmark_reference` flag. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python annotation payload theme={"theme":"css-variables"} labels = [] labels.append( lb_types.Label( data={"global_key" : global_key } annotations = [ named_entitity_annotation, radio_annotation, checklist_annotation, text_annotation, ner_source, ner_target, ner_relationship, nested_checklist_annotation, nested_radio_annotation ], # optional: set the label as a benchmark is_benchmark_reference = True ) ) labels = [] labels.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ named_entitity_annotation, radio_annotation, checklist_annotation, text_annotation, nested_checklist_annotation, nested_radio_annotation ], # Optional: set the label as a benchmark # Only supported for groud truth imports is_benchmark_reference = True ) ) ``` ```python NDJSON Payload theme={"theme":"css-variables"} label_ndjson = [] for annotations in [entities_ndjson, radio_annotation_ndjson, checklist_annotation_ndjson, text_annotation_ndjson, nested_radio_annotation_ndjson, nested_checklist_annotation_ndjson, entity_source_ndjson, entity_target_ndjson, ner_relationship_annotation_ndjson, ] : annotations.update({ "dataRow": { "globalKey": global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. ### Warning Relationship annotations are not supported for ground truth import jobs. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import text data Source: https://docs.labelbox.com/horizon/developer-guides/import-text-data How to import annotations on text data and sample import formats. ## Specifications File format: TXT Text encoding: UTF-8 (Note: The Editor does not process any special character sequences like HTML Entities, Unicode Escape Sequence, or colon emoji aliases.) Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) * Direct upload of local files(256 MB max file size) Note: Direct upload currently does not support adding additional metadata and attachments, see below Python example. ## Parameters | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | You can upload text in two ways: 1) A `https` url to cloud hosted text file. The txt file must be encoded as UTF-8. 2) A text string (up to 16MB) | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"TEXT"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ## Import format ```json Delegated Access URL theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "media_type": "TEXT", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-2.txt", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-2.txt", "media_type": "TEXT", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Standard URL theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "media_type": "TEXT", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "HTML", "value": "https://www.wikipedia.org/" }] } ] ``` ```json Raw Text Upload theme={"theme":"css-variables"} [ { "row_data": "👋 I am a raw text string with emojis 🙏 😃", "global_key": "483e64b0-c2fb-41bc-9f69-82c036f1ca5c", "media_type": "TEXT", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] }, { "row_data": "Active learning is a method of learning in which students are actively or experientially involved in the learning process and where there are different levels of active learning, depending on student involvement.[1] Bonwell & Eison (1991) states that students participate [in active learning] when they are doing something besides passively listening. According to Hanson and Moser (2003) using active teaching techniques in the classroom create better academic outcomes for students. Scheyvens, Griffin, Jocoy, Liu, & Bradford (2008) further noted that “by utilizing learning strategies that can include small-group work, role-play and simulations, data collection and analysis, active learning is purported to increase student interest and motivation and to build students ‘critical thinking, problem-solving and social skills”. In a report from the Association for the Study of Higher Education (ASHE), authors discuss a variety of methodologies for promoting active learning. They cite literature that indicates students must do more than just listen in order to learn. They must read, write, discuss, and be engaged in solving problems. This process relates to the three learning domains referred to as knowledge, skills and attitudes (KSA). This taxonomy of learning behaviors can be thought of as the goals of the learning process.[2] In particular, students must engage in such higher-order thinking tasks as analysis, synthesis, and evaluation.[3]", "global_key": "6a12ff36-8a3e-4e7d-aff1-261840500c96", "media_type": "TEXT", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ## Python example ```python bulk import example theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Text") assets = [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/text-samples/sample-text-1.txt", "media_type": "TEXT", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` ```python local files theme={"theme":"css-variables"} from labelbox import Client client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Text") assets = [ { "row_data": "path/to/local/file1", "global_key": "path/to/local/file1", "media_type": "TEXT" }, { "row_data": "path/to/local/file2", "global_key": "path/to/local/file2", "media_type": "TEXT" }, ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` # Import video annotations Source: https://docs.labelbox.com/horizon/developer-guides/import-video-annotations Developer guide for importing annotations on video data and sample import formats. ## Overview To import annotations in Labelbox, you need to create an annotations payload. In this section, we provide this payload for every supported annotation type. ### Annotation payload types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Label import types Labelbox additionally supports two types of label imports: * [Model-assisted labeling (MAL)](/horizon/guides/model-assisted-labeling) * This workflow allows you to import computer-generated predictions (or simply annotations created outside of Labelbox) as pre-labels on an asset. * [Ground truth](https://www.google.com/url?q=https%3A%2F%2Fdocs.labelbox.com%2Fdocs%2Fimport-ground-truth) * This workflow functionality allows you to bulk import your ground truth annotations from an external or third-party labeling system into Labelbox *Annotate*. Using the label import API to import external data is a useful way to consolidate and migrate all annotations into Labelbox as a single source of truth. ## Supported annotations The following annotations are supported for an video data row: * Radio * Checklist * Free-form text * Bounding box * Point * Polyline * Segmentation masks ### Tool features are frame-based Global and frame-based classifications are supported on video assets. However, tool annotations are required to be frame-based. ## Classifications You can use per-framed annotations or temporal annotations interchangeably. ### Global classifications #### Checklist (global) ```python Python annotation theme={"theme":"css-variables"} # Use ClassificationAnnotation instead of VideoClassification for global annotations global_checklist_annotation=[lb_types.ClassificationAnnotation( name="checklist_class_global", value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ) )] ``` ```python NDJSON theme={"theme":"css-variables"} global_checklist_classification_ndjson = { "name": "checklist_class_global", "answer": [ { "name": "first_checklist_answer" }, { "name": "second_checklist_answer"} ] } ``` #### Radio (global) ```python Python annotation theme={"theme":"css-variables"} # Use ClassificationAnnotation instead of VideoClassification for global annotations global_radio_annotation = [lb_types.ClassificationAnnotation( name="radio_class_global", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer")) )] ``` ```r NDJSON theme={"theme":"css-variables"} global_radio_classification_ndjson = { "name": "radio_class_global", "answer": { "name": "first_radio_answer"} } ``` #### Free-form text (global) ```python Python annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name="free_text_global", value=lb_types.Text(answer="sample text") ) ``` ```python NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text_global", "answer": "sample text", } ``` #### Nested classifications (global) ```python Python annotation theme={"theme":"css-variables"} nested_checklist_annotation = [lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) )] nested_radio_annotation =[lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) )] ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_annotation_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer"} } ] }] } nested_radio_annotation_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "classifications": [ {"name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer"}} ], }, } ``` ### Frame-based classifications #### Checklist (frame-based) ```python Python annotation theme={"theme":"css-variables"} checklist_annotation= [ lb_types.VideoClassificationAnnotation( name="checklist_class", frame=29, segment_index=0, value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ) ), lb_types.VideoClassificationAnnotation( name="checklist_class", frame=35, segment_index=0, value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} frame_checklist_classification_ndjson = { "name": "checklist_class", "answer": [ { "name": "first_checklist_answer" , "frames": [{"start": 29, "end": 35 }]}, { "name": "second_checklist_answer" , "frames": [{"start": 29, "end": 35 }]} ] } ``` #### Radio (frame-based) ```python Python annotation theme={"theme":"css-variables"} radio_annotation = [ lb_types.VideoClassificationAnnotation( name="radio_class", frame=9, segment_index=0, value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer")) ), lb_types.VideoClassificationAnnotation( name="radio_class", frame=15, segment_index=0, value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer")) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} frame_radio_classification_ndjson = { "name": "radio_class", "answer": { "name": "first_radio_answer", "frames": [{"start": 9, "end": 15}]} } ``` #### Free-form text (frame-based) ```python Python annotation theme={"theme":"css-variables"} text_annotation = [ lb_types.TemporalClassificationText( name="your_classification_name", value=[ (start_frame, end_frame, "your text value"), (start_frame, end_frame, "another text value"), ], classifications=[ # optional nested classifications ], ) ] ``` ```json NDJSON theme={"theme":"css-variables"} frame_text_classification_ndjson = { "name": "free_text", "answer": [ { "value": "sample text", "frames": [{"start": 13, "end": 19}]} ] } ``` ### Temporal classifications (frames) Use temporal classifications to attach **time-based classification labels** to a video asset. Temporal ranges are represented in **frame indices** from the start of the asset. When using NDJSON, temporal classifications use a unified `answer: [...]` list structure: * For **temporal radio**, `answer` must be a single-item list (unlike global radio, which uses an object). * For **temporal checklist**, `answer` is a multi-item list. * For **temporal text**, `answer` is a list of: `{ "value": , "frames": [{"start": , "end": }] }`. Temporal classifications support nested hierarchies (for example: Radio → Radio → Radio, or Text → Text → Text). Child classifications must use frame ranges that fall within the parent classification's frame range. #### Checklist (temporal) ```python Python annotation theme={"theme":"css-variables"} temporal_checklist_annotation = lb_types.TemporalClassificationQuestion( name="road_conditions", value=[ lb_types.TemporalClassificationAnswer( name="rain", frames=[(0, 450), (900, 1200)], ), lb_types.TemporalClassificationAnswer( name="construction_zone", frames=[(600, 1000)], ), lb_types.TemporalClassificationAnswer( name="low_visibility", frames=[(950, 1200)], ), ], ) ``` ```json NDJSON theme={"theme":"css-variables"} temporal_checklist_annotation_ndjson = { "name": "road_conditions", "answer": [ { "name": "rain", "frames": [{"start": 0, "end": 450}, {"start": 900, "end": 1200}] }, { "name": "construction_zone", "frames": [{"start": 600, "end": 1000}] }, { "name": "low_visibility", "frames": [{"start": 950, "end": 1200}] } ] } ``` #### Radio (temporal) ```python Python annotation theme={"theme":"css-variables"} temporal_radio_annotation = lb_types.TemporalClassificationQuestion( name="traffic_light_state", value=[ lb_types.TemporalClassificationAnswer( name="red", frames=[(0, 300)], ), lb_types.TemporalClassificationAnswer( name="green", frames=[(301, 800)], ), ], ) ``` ```json NDJSON theme={"theme":"css-variables"} temporal_radio_annotation_ndjson = { "name": "traffic_light_state", "answer": [ { "name": "red", "frames": [{"start": 0, "end": 300}] }, { "name": "green", "frames": [{"start": 301, "end": 800}] } ] } ``` #### Free-form text (temporal) ```python Python annotation theme={"theme":"css-variables"} temporal_text_annotation = lb_types.TemporalClassificationText( name="play_description", value=[ (120, 240, "Quarterback throws a deep pass"), (241, 310, "Wide receiver catches the ball"), (311, 380, "Touchdown scored"), ], ) ``` ```json NDJSON theme={"theme":"css-variables"} temporal_text_annotation_ndjson = { "name": "play_description", "answer": [ { "value": "Quarterback throws a deep pass", "frames": [{"start": 120, "end": 240}] }, { "value": "Wide receiver catches the ball", "frames": [{"start": 241, "end": 310}] }, { "value": "Touchdown scored", "frames": [{"start": 311, "end": 380}] } ] } ``` #### Nested classifications (temporal) ```python Python annotation theme={"theme":"css-variables"} nested_temporal_radio_annotation = lb_types.TemporalClassificationQuestion( name="driver_behavior", value=[ lb_types.TemporalClassificationAnswer( name="distracted", frames=[(500, 900)], classifications=[ lb_types.TemporalClassificationQuestion( name="distraction_type", value=[ lb_types.TemporalClassificationAnswer( name="mobile_phone_use", frames=[(550, 850)], classifications=[ lb_types.TemporalClassificationQuestion( name="risk_level", value=[ lb_types.TemporalClassificationAnswer( name="high", frames=[(700, 850)], ) ], ) ], ) ], ) ], ) ], ) ``` ```json NDJSON theme={"theme":"css-variables"} nested_temporal_radio_annotation_ndjson = { "name": "driver_behavior", "answer": [ { "name": "distracted", "frames": [{"start": 500, "end": 900}], "classifications": [ { "name": "distraction_type", "answer": [ { "name": "mobile_phone_use", "frames": [{"start": 550, "end": 850}], "classifications": [ { "name": "risk_level", "answer": [ { "name": "high", "frames": [{"start": 700, "end": 850}] } ] } ] } ] } ] } ] } ``` ## Tools ### Bounding box (frame-based) ```python Python annotation theme={"theme":"css-variables"} # Confidence scores are not supported for frame specific bounding box annotations and VideoObjectAnnotation class bbox_dm = { "top":617, "left":1371, "height":419, "width":505 } # Python Annotation bbox_annotation = [ lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=13, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), # x= left + width , y = top + height ) ), lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=19, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} bbox_dm = { "top":617, "left":1371, "height":419, "width":505 } bbox_annotation_ndjson = { "name" : "bbox_video", "segments" : [{ "keyframes" : [ { "frame": 13, "bbox" : bbox_dm }, { "frame": 19, "bbox" : bbox_dm } ] } ] } ``` ### Point (frame-based) ```python Python annotation theme={"theme":"css-variables"} point_annotation = [ lb_types.VideoObjectAnnotation( name = "point_video", keyframe=True, frame=17, value = lb_types.Point(x=660.134, y=407.926), ) ] ``` ```python NDJSON theme={"theme":"css-variables"} point_annotation_ndjson = { "name": "point_video", "segments": [{ "keyframes": [{ "frame": 17, "point" : { "x": 660.134 , "y": 407.926 } }] }] } ``` ### Polyline (frame-based) ```python Python annotation theme={"theme":"css-variables"} polyline_annotation = [ lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=5, segment_index=0, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=20, segment_index=0, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=24, segment_index=1, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=45, segment_index=1, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} polyline_frame_annotation_ndjson = { "name": "line_video_frame", "segments": [ { "keyframes": [ { "frame": 5, "line": [{ "x": 680, "y": 100 },{ "x": 100, "y": 190 },{ "x": 190, "y": 220 }] }, { "frame": 20, "line": [{ "x": 680, "y": 180 },{ "x": 100, "y": 200 },{ "x": 200, "y": 260 }] } ] }, { "keyframes": [ { "frame": 24, "line": [{ "x": 300, "y": 310 },{ "x": 330, "y": 430 }] }, { "frame": 45, "line": [{ "x": 600, "y": 810 },{ "x": 900, "y": 930 }] } ] } ] } ``` ### Masks (frame-based) ```python Python theme={"theme":"css-variables"} # Uploading single mask instances ## For this example we are going to to pass all the annotations payload in a single VideoMaskAnnotation (line 56) # Single mask url = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/frame_24_composite_mask.png" response = requests.get(url) img_bytes = base64.b64encode(response.content).decode('utf-8') # We are generating our frames and instances in this step, and will later add them to the VideoMaskAnnotation that will contain # all frames and instances frames_mask_single=[ lb_types.MaskFrame( index=20, im_bytes=response.content # Instead of bytes you could also pass an instance URI : instance_uri=url ) ] instances_mask_single=[ lb_types.MaskInstance(color_rgb=(76, 104, 177), name= "video_mask") ] #################################################################################################################################### # Uploading annotations with composite masks ## Add multiple masks using multiple tools in different frames - Note that only once composite mask can exist per frame frames_cp_mask_url = [ {"1": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/frame_1_composite_mask.png"}, {"24": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/frame_24_composite_mask.png"}, {"26": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/frame_26_composite_mask.png" } ] rgb_mask_tool = [(227, 135, 126) ,(169, 248, 152),(83, 152, 103)] cp_masks = [] unique_colors = set() lb_frames = [] lb_instances = [] counter = 0 for d in frames_cp_mask_url: for frame_no, v in d.items(): response = requests.get(v) colors = extract_rgb_colors_from_url(v) for color in colors: if not color in unique_colors: unique_colors.add(color) name = "video_mask" if color in rgb_mask_tool else "mask_with_text_subclass" lb_instances.append(lb_types.MaskInstance(color_rgb=color, name=name)) counter += 1 lb_frames.append( lb_types.MaskFrame( index=frame_no, im_bytes=response.content ) ) cp_masks.append(lb_types.VideoMaskAnnotation( frames=lb_frames + frames_mask_single, instances=lb_instances + instances_mask_single )) pp.pprint(lb_frames) pp.pprint(cp_masks) ``` ```python NDJSON theme={"theme":"css-variables"} url = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/color_mask.png" response = requests.get(url) img_bytes = base64.b64encode(response.content).decode('utf-8') # NDJSON - single tool video_mask_ndjson_bytes_2 = { 'masks': { 'frames': [ { "index" : 31, "imBytes": img_bytes, }, { "index" : 34, "imBytes": img_bytes, } ], 'instances': [ { "colorRGB" : [76, 104, 177], "name" : "video_mask" } ] } } ``` ### Bounding box with sub-classifications (frame-based) ```python Python annotation theme={"theme":"css-variables"} bbox_dm2 = { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } # Python Annotation frame_bbox_with_checklist_subclass_annotation = [ lb_types.VideoObjectAnnotation( name = "bbox_class", keyframe=True, frame=10, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), # x= left + width , y = top + height ) ), lb_types.VideoObjectAnnotation( name = "bbox_class", keyframe=True, frame=11, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), ), classifications=[ lb_types.ClassificationAnnotation( name="checklist_class", value=lb_types.Checklist(answer=[lb_types.ClassificationAnswer( name="first_checklist_answer")]) ) ] ), lb_types.VideoObjectAnnotation( name = "bbox_class", keyframe=True, frame=13, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), ), classifications=[ lb_types.ClassificationAnnotation( name="checklist_class", value=lb_types.Checklist(answer=[lb_types.ClassificationAnswer( name="second_checklist_answer")]) ) ] ) ] ``` ```python NDJSON theme={"theme":"css-variables"} bbox_dm2 = { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } frame_bbox_with_checklist_subclass_annotation_ndjson = { "name": "bbox_class", "segments": [{ "keyframes": [ { "frame": 10, "bbox": bbox_dm2 }, { "frame": 11, "bbox": bbox_dm2, "classifications": [ { "name": "checklist_class", "answer": [{"name": "first_checklist_answer"}] } ] }, { "frame": 13, "bbox": bbox_dm2, "classifications": [ { "name": "checklist_class", "answer": [{"name": "second_checklist_answer"}] } ] } ] } ] } ``` ### Multiple instances of bounding box annotations in the same frame ```python Python theme={"theme":"css-variables"} bbox_dm = { "top":617, "left":1371, "height":419, "width":505 } # Fist instance of bounding box ranging from frame 22 to 27 bbox_annotation_1 = [ lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=22, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), # x= left + width , y = top + height ) ), lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=27, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), ) ) ] # Second instance of bounding box ranging from frame 22 to 27 bbox_annotation_2 = [ lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=22, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), ) ), lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=27, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} bbox_dm = { "top":617, "left":1371, "height":419, "width":505 } # Fist instance of bounding box ranging from frame 22 to 27 bbox_frame_annotation_ndjson = { "name": "bbox_video", "segments": [{ "keyframes": [ { "frame": 22, "bbox": bbox_dm }, { "frame": 27, "bbox": bbox_dm2 } ] }] } # Second instance of bounding box ranging from frame 22 to 27 bbox_frame_annotation_ndjson2 = { "name": "bbox_video", "segments": [{ "keyframes": [ { "frame": 22, "bbox": bbox_dm }, { "frame": 27, "bbox": bbox_dm2 } ] }] } ``` ## Example: Import pre-labels or ground truths The steps to import annotations as pre-labels (machine-assisted learning) are similar to those to import annotations as ground truth labels. However, they vary slightly, and we will describe the differences for each scenario. ### Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid from PIL import Image import requests import base64 from io import BytesIO ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Data rows must first be uploaded to **Catalog** to attach annotations. This example shows how to create a data row in **Catalog** by attaching it to a [dataset](/horizon/developer-guides/dataset) . ```python Python theme={"theme":"css-variables"} global_key = "sample-video-jellyfish.mp4" asset = { "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "global_key": global_key, "media_type": "VIDEO" } dataset = client.create_dataset( name="video_demo_dataset", iam_integration=None # If this argument is removed, labelbox will use the default integration for your organization. ) task = dataset.create_data_rows([asset]) task.wait_till_done() print(f"Failed data rows: {task.failed_data_rows}") print(f"Errors: {task.errors}") if task.errors: for error in task.errors: if 'Duplicate global key' in error['message'] and dataset.row_count == 0: # If the global key already exists in the workspace the dataset will be created empty, so we can delete it. print(f"Deleting empty dataset: {dataset}") dataset.delete() ``` ### Step 2: Set up ontology Your project ontology should support the tools and classifications required by your annotations. To ensure accurate schema feature mapping, the value used as the `name` parameter should match the value of the `name` field in your annotation. For example, when we created an annotation above, we provided a name`annotation_name`. Now, when we set up our ontology, we must ensure that the name of our bounding box tool is also `anotations_name`. The same alignment must hold true for the other tools and classifications we create in our ontology. This example shows how to create an ontology containing all supported [annotation types](#supported-annotations) . ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_video"), lb.Tool(tool=lb.Tool.Type.POINT, name="point_video"), lb.Tool(tool=lb.Tool.Type.LINE, name="line_video_frame"), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="video_mask"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_class", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="checklist_class", scope = lb.Classification.Scope.INDEX, options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ) ] ), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask_with_text_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="sub_free_text") ] ) ], classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class", scope = lb.Classification.Scope.INDEX, ## Need to defined scope for frame classifications options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_class", scope = lb.Classification.Scope.INDEX, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_class_global", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class_global", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ) ] ) ontology = client.create_ontology("Video Annotation Import Demo Ontology", ontology_builder.asdict(), media_type=lb.MediaType.Video) ``` ### Step 3: Set Up a Labeling Project ```python Python theme={"theme":"css-variables"} project = client.create_project(name="Video Annotation Import Demo", media_type=lb.MediaType.Video) ## connect ontology to your project project.connect_ontology(ontology) ``` ### Step 4: Send Data Rows to Project ```python Python theme={"theme":"css-variables"} batch = project.create_batch( "first-batch-video-demo2", # Each batch in a project must have a unique name global_keys=[global_key], # A paginated collection of data row objects, a list of data rows or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) print("Batch: ", batch) ``` ### Step 5: Create annotation payloads For help understanding annotation payloads, see [overview](#overview). To declare payloads, you can use Python annotation types (*preferred*) or NDJSON objects. For annotations that you want to import as ground truth labels, you can also specify [benchmarks](/horizon/guides/benchmark) using the `is_benchmark_reference` flag. These examples demonstrate each format and how to compose annotations into labels attached to data rows. ```python Python annotation payload theme={"theme":"css-variables"} label = [] annotations_list = [ checklist_annotation, radio_annotation, bbox_annotation, frame_bbox_with_checklist_subclass_annotation, bbox_annotation_1, bbox_annotation_2, point_annotation, polyline_annotation, global_checklist_annotation, global_radio_annotation, nested_checklist_annotation, nested_radio_annotation, text_annotation, cp_masks ] for annotation in annotations_list: label.append( lb_types.Label( data= {"global_key": global_key}, annotations = annotation, # Optional: set the label as a benchmark # Only supported for groud truth imports is_benchmark_reference = True ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_ndjson = [] annotations_list_ndjson = [ point_annotation_ndjson, bbox_annotation_ndjson, polyline_frame_annotation_ndjson, frame_checklist_classification_ndjson, frame_radio_classification_ndjson, frame_text_classification_ndjson, nested_radio_annotation_ndjson, nested_checklist_annotation_ndjson, frame_bbox_with_checklist_subclass_annotation_ndjson, global_radio_classification_ndjson, global_checklist_classification_ndjson, text_annotation_ndjson, bbox_frame_annotation_ndjson, bbox_frame_annotation_ndjson2, video_mask_ndjson_bytes_2 ] for annotation in annotations_list_ndjson: annotation.update({ "dataRow": { "globalKey": global_key } }) label_ndjson.append(annotation) ``` ### Step 6: Import annotation payload For prelabeled (model-assisted labeling) scenarios, pass your payload as the value of the `predictions` parameter. For ground truths, pass the payload to the `labels` parameter. #### Option A: Upload as [prelabels (model assisted labeling)](/horizon/guides/model-assisted-labeling) This option is helpful for speeding up the initial labeling process and reducing the manual labeling workload for high-volume datasets. ```python MAL import theme={"theme":"css-variables"} # Upload MAL label for this data row in project upload_job = lb.MALPredictionImport.create_from_objects( client = client, project_id = project.uid, name="mal_job"+str(uuid.uuid4()), predictions=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` #### Option B: Upload to a labeling project as [ground truth](/horizon/guides/import-ground-truth) This option is helpful for loading high-confidence labels from another platform or previous projects that just need review rather than manual labeling effort. ```python Label import theme={"theme":"css-variables"} # Upload label for this data row in project upload_job = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+str(uuid.uuid4()), labels=label ) print(f"Errors: {upload_job.errors}", ) print(f"Status of uploads: {upload_job.statuses}") ``` # Import video data Source: https://docs.labelbox.com/horizon/developer-guides/import-video-data How to import video data and sample import formats. ## Specifications Supported file format: MP4 Video encoding: H.264 (also referred to as AVC), H.265 Limits: * Maximum file size: 1024 MB * Maximum frame count: 20,000 Import methods: * IAM Delegated Access * Signed URLs (`https` URLs only) * Direct upload of local files **Note**: Direct upload currently does not support adding additional metadata and attachments. Videos have to pass the **frame-count consistency** check as well. Read below for details. ### Frame-Count Consistency Please note that the Labelbox Editor only supports videos which have **frame-count consistency**. This is achieved when `Frames Per Second (FPS) x Video Duration (Number of secs) == Total Number of Frames` While real-world videos may not always satisfy this requirement due to different encoding schemas, we warn you about this on upload with the message *"Unsupported video detected"* so you can proceed with caution when labeling. **Why do we check for this?** Because while labeling, every frame unaccounted for is a potential error. Some common examples where frame-count consistency is not achieved: * Variable frame rate * Negative frame presentation times (PTS) You can utilize the [FFmpeg](https://ffmpeg.org/) command line tool to process videos into a compatible format with the following command: `ffmpeg -i -vcodec libx264 -acodec aac ` **Unsupported attributes** Please note that the Labelbox Editor does not support videos with: * Non-square sample aspect ratios (may appear stretched or squashed) Videos that contain these attributes will be flagged or blocked in the Editor, and will soon be flagged at the time of import as well. When importing video data to Labelbox, your JSON file must include the following information for each video. | Parameter | Required | Description | | - | - | - | | `row_data` | Yes | `https` path to a cloud-hosted video. For IAM Delegated Access, this URL must be in [virtual-hosted-style format](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access). For older regions, your S3 bucket may be in `https://.s3..amazonaws.com/` format. If your object URLs are formatted this way, make sure they are in the virtual-hosted-style format before importing. | | `global_key` | No | Unique user-generated file name or ID for the file. [Global keys](/horizon/developer-guides/data-row-global-keys) are enforced to be unique in your org. Data rows will not be imported if its global keys are duplicated to existing data rows. | | `media_type` | No | `"VIDEO"` (optional media type to provide better validation and error messaging) | | `metadata_fields` | No | See [Metadata](/horizon/guides/datarow-metadata). | | `attachments` | No | See [Attachments](/horizon/guides/label-data) and [Asset overlays](/horizon/guides/label-data). | ## Import format ```json Delegated Access URL theme={"theme":"css-variables"} [ { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/video-samples/sample-video-1.mp4", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/video-samples/sample-video-1.mp4", "media_type": "VIDEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" }] }, { "row_data": "https://lb-test-data.s3.us-west-1.amazonaws.com/video-samples/sample-video-2.mp4", "global_key": "https://lb-test-data.s3.us-west-1.amazonaws.com/video-samples/sample-video-2.mp4", "media_type": "VIDEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ```json Standard URL theme={"theme":"css-variables"} [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-1.mp4", "global_key": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-1.mp4", "media_type": "VIDEO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "global_key": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "media_type": "VIDEO", "metadata_fields": [{"schema_id": "cko8s9r5v0001h2dk9elqdidh", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] ``` ## Python example ```python bulk import example theme={"theme":"css-variables"} from labelbox import Client from uuid import uuid4 ## to generate unique IDs import datetime client = Client(api_key="") dataset = client.create_dataset(name="Bulk import example - Video") assets = [ { "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-1.mp4", "global_key": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-1.mp44", "media_type": "VIDEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "VIDEO", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/drone_video.mp4" }] }, { "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "global_key": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "media_type": "VIDEO", "metadata_fields": [{"name": "", "value": "tag_string"}], "attachments": [{"type": "TEXT_URL", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/text_attachment.txt"}] } ] task = dataset.create_data_rows(assets) task.wait_till_done() print(task.errors) ``` ```python local files theme={"theme":"css-variables"} local_file_paths = ['path/to/local/file1', 'path/to/local/file1'] # limit: 15k files new_dataset = client.create_dataset(name = "Local files upload") try: task = new_dataset.create_data_rows(local_file_paths) task.wait_till_done() except Exception as err: print(f'Error while creating labelbox dataset - Error: {err}') ``` # Installation Source: https://docs.labelbox.com/horizon/developer-guides/install-python-sdk Describes how to install, update, and authenticate to the Labelbox Python SDK. Many actions and workflows available in the Labelbox app can be automated using the [Python SDK](https://github.com/Labelbox/labelbox-python/), which provides access to the [Labelbox API](https://labelbox-python.readthedocs.io/en/latest/). ## Requirements * Make sure `pip` is installed and up-to-date. * Use the **Workspace settings** menu of the Labelbox app to [create an API key](/horizon/developer-guides/create-api-key). Store your key in a safe place, as you cannot view it after it has been created. * Python: 3.9 - 3.13 ## Install or upgrade SDK After you [create an API key](/horizon/developer-guides/create-api-key), you can start using our Python SDK. To install, run either `pip install labelbox` or`pip install "labelbox[data]"` in your command line. ```shell shell theme={"theme":"css-variables"} pip install "labelbox[data]" # or pip install labelbox ``` | Module | Description | | - | - | | `"labelbox[data]"` | Installs all [required dependencies](https://github.com/Labelbox/labelbox-python/blob/develop/requirements.lock) (libraries, tools to manipulate annotations, and more.) | | `labelbox` | Installs all required libraries for client-only functionality | ```python Python theme={"theme":"css-variables"} # install latest labelbox version !pip install labelbox import labelbox as lb # Enter your Labelbox API key here LB_API_KEY = None # Create Labelbox client client = lb.Client(api_key=LB_API_KEY) ``` To upgrade to the latest version, use your package manager: `pip install --upgrade labelbox` To run the Python SDK in a Docker container, use the [included Dockerfile](https://github.com/Labelbox/labelbox-python/blob/develop/libs/labelbox/Dockerfile) as a starting place. Related info: * The Python SDK [GitHub repository](https://github.com/Labelbox/labelbox-python/) * The most recent update to the Python SDK can be found at: [`https://github.com/Labelbox/labelbox-python/releases/latest/`](https://github.com/Labelbox/labelbox-python/releases/latest) * [Changelog](https://github.com/Labelbox/labelbox-python/blob/develop/libs/labelbox/CHANGELOG.md) of recent changes. ## Authentication You need an [API key](/horizon/developer-guides/create-api-key) to authenticate your client. You can: * Save your key to an environment variable: ```shell shell theme={"theme":"css-variables"} user@machine:~$ export LABELBOX_API_KEY="" user@machine:~$ python3 ``` Then, import and initialize the API Client. ```python Python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client() ``` * Paste your API key directly into your script (*not recommended*) ```python Python theme={"theme":"css-variables"} import labelbox as lb if __name__ == '__main__': API_KEY = "" client = lb.Client(API_KEY) ``` Remember that your API key authorizes access to Labelbox, your account, and your data assets; treat it as carefully as any other set of credentials, and make sure the [scope of the key](/horizon/developer-guides/create-api-key) is set according to the usage is meant for. # Issues & comments Source: https://docs.labelbox.com/horizon/developer-guides/issues Developer guide for creating and modifying issues and comments via the Python SDK. ## Issue categories ```python Python theme={"theme":"css-variables"} project = client.get_project("") # Create category = project.create_issue_category( name="Quality", description="Quality-related issues", ) # List all categories = project.get_issue_categories() # Update category = category.update(name="Renamed", description="New description") # Delete category.delete() ``` ## Issues ```python Python theme={"theme":"css-variables"} from labelbox import IssueStatus, ImageIssuePosition, VideoIssuePosition, VideoFrameRange # Create -- minimal issue = project.create_issue( content="General quality issue", data_row_id="", ) # Create -- with label, category, and position issue = project.create_issue( content="Bounding box is misaligned", data_row_id="", label_id="", category_id=category.id, position=ImageIssuePosition(x=100, y=200), ) # Also accepts DataRow/Label/IssueCategory objects directly issue = project.create_issue( content="Using objects instead of IDs", data_row_id=data_row, # DataRow object label_id=label, # Label object category_id=category, # IssueCategory object ) # Fetch with filters (returns PaginatedCollection) for issue in project.get_issues(status=IssueStatus.OPEN): print(issue.friendly_id, issue.content) # Other available filters project.get_issues( data_row_id="", category_id="", created_by_ids=[""], content="search text", ) # Fetch single issue issue = project.get_issue("") # Update (only provided fields are changed) issue = issue.update(content="Updated text") issue = issue.update(category_id="") # Resolve / Reopen issue = issue.resolve() issue = issue.reopen() # Lazy-loaded related objects (each makes an API call) data_row = issue.data_row() # Optional[DataRow] label = issue.label() # Optional[Label] category = issue.category() # Optional[IssueCategory] # Delete single issue.delete() # Bulk delete project.delete_issues(["", ""]) ``` ## Comments ```python Python theme={"theme":"css-variables"} # Create comment = issue.create_comment(content="I agree, this needs fixing") # List all comments on an issue comments = issue.comments() # Update comment = comment.update(content="Revised comment") # Delete comment.delete() ``` ## Video position variants ```python Python theme={"theme":"css-variables"} # Single frame VideoIssuePosition(frames=[ VideoFrameRange(start=5, end=5, x=100, y=200) ]) # Contiguous range (stationary pin) VideoIssuePosition(frames=[ VideoFrameRange(start=5, end=11, x=450, y=300) ]) # Contiguous range (moving pin) VideoIssuePosition(frames=[ VideoFrameRange(start=5, end=11, x=450, y=300, end_x=500, end_y=350) ]) # Multiple separated ranges VideoIssuePosition(frames=[ VideoFrameRange(start=5, end=11, x=450, y=300), VideoFrameRange(start=20, end=25, x=100, y=100), ]) ``` ## Addendum: Position formats for different data types ```python Python theme={"theme":"css-variables"} # PDF (%) PdfIssuePosition(x=0.5, y=0.75, page=2) # Text (Char range) TextIssuePosition(text_block_id="b1", start_char_index=10, end_char_index=25) # Video (Multiple frames/paths) VideoIssuePosition(frames=[ VideoFrameRange(start=5, end=11, x=450, y=300, end_x=500, end_y=350), VideoFrameRange(start=20, end=25, x=100, y=100) ]) ``` # Labeling Workforce Source: https://docs.labelbox.com/horizon/developer-guides/labeling-workforce Python SDK methods for requesting and managing your labeling workforce. You can request labeling service for your Annotate projects and manage the requested labeling workforce using the Labelbox Python SDK. ### Experimental feature This is an [experimental](/horizon/guides/product-release-phases#experimental) feature that requires you to enable experimental features before using its methods. # Methods ## Get and request labeling service Use `project.labeling_service = project.get_labeling_service()` to get the labeling service associated with the project. Then use `labeling_service.request()` to request a workforce of trained labelers for your data. ```python Python theme={"theme":"css-variables"} # Enable experimental features client.enable_experimental = True # Get labeling service labeling_service = project.get_labeling_service() # Validate all project requirements and request labeling service labeling_service.request() ``` ## Get labeling service status Use `project.get_labeling_service_status()` to check the status of your requested labeling service. ```python Python theme={"theme":"css-variables"} # Enable experimental features client.enable_experimental = True # Get status of requested labeling service project.get_labeling_service_status() ``` This method returns the following value defined in the `LabelingServiceStatus` enum, corresponding to the statuses of your requested labeling service: * `SET_UP`: Your request has been submitted, but you need to set up your request by adding data rows, ontologies, and labeling instructions to proceed the request. * `REQUESTED`: Your request has been validated and is pending approval for connecting to labeling workforce. * `ACCEPTED`: Your request has satisfied the prerequisite and been accepted. * `CALIBRATION`: Labelers are labeling a sample of your data rows that you can review and confirm alignment on the expectation. * `PRODUCTION`: Labelers are labeling all data rows. * `COMPLETE`: Labelers have finished adding labels to your projects. ## Get labeling service details Use `project.get_labeling_service_dashboard()` to retrieve the following attributes of the project for which you requested labeling service: * `id`: The project ID * `name`: The project name `status`: The [status](#get-labeling-service-status) of the labeling service * `data_rows_count`: The total number of data rows batched in the project * `tasks_completed_count`: The number of completed tasks (with the `COMPLETE` status) * `tasks_remaining_count`: The number of incomplete tasks (queued data rows with statuses other than `COMPLETE`) * `tags`: [Project tags](/horizon/guides/project-tagging) associated with the project * `media_type`: The [media type](/horizon/developer-guides/project#create-a-project) of the project * client: The Labelbox client If you are an admin, you can also use `client.get_labeling_service_dashboards()` to retrieve details of all projects with labeling service requested in your workspace. ```python Python theme={"theme":"css-variables"} # Enable experimental features client.enable_experimental = True # Get details of a project with labeling service requested project.get_labeling_service_dashboard() # Get details of all projects with labeling service requested client.get_labeling_service_dashboards() ``` # Media attributes Source: https://docs.labelbox.com/horizon/developer-guides/media-attributes Overview of Labelbox computed media attributes. Labelbox automatically computes media attributes when you create new data rows in the platform. If the data is not accessible when you create the data rows, the media attributes will not be computed for those data rows. Below is a list of the available media attributes. | Parameter | Asset type | Description | | - | - | - | | `mimeType` | Video Image Text Tiled imagery | Media type. Will be one of the following: - `video/mp4` - `image/jpeg` - `text/plain` - `application/x-tms-geo` (tiled imagery: EPSG3857) - `application/x-tms-simple` (tiled imagery: Simple) - `application/json` (other) | | `width` | Video Image | Image or frame width in pixels. | | `height` | Video Image | Image or frame height in pixels. | | `codec` | Video | Indicates the video compression standard. | | `duration` | Video | Duration of the video in seconds. | | `frameRate` | Video | Frame rate per second. | | `frameCount` | Video | Number of frames in the video. | Use this sample script to retrieve the media attributes for a data row. ```python Get media attributes theme={"theme":"css-variables"} datarow = client.get_data_row(data_row.uid) print(datarow.media_attributes) ``` Below are some sample responses by asset type. ```json Image theme={"theme":"css-variables"} { "data": { "dataRow": { "mediaAttributes": { "width": 3872, "height": 2592, "mimeType": "image/jpeg" } } } ``` ```json Video theme={"theme":"css-variables"} { "data": { "dataRow": { "mediaAttributes": { "codec": "avc1", "width": 1920, "height": 1080, "duration": 10, "mimeType": "video/mp4", "frameRate": 10, "frameCount": 100 } } } ``` ```json Text theme={"theme":"css-variables"} { "data": { "dataRow": { "mediaAttributes": { "mimeType": "text/plain" } } } ``` ```json Tiled imagery theme={"theme":"css-variables"} { "data": { "dataRow": { "mediaAttributes": { "mimeType": "application/x-tms-geo" } } } ``` # Metadata Source: https://docs.labelbox.com/horizon/developer-guides/metadata Developer guide for creating, importing, exporting, and modifying metadata fields via the Python SDK. ## Metadata types All metadata needs to be one of the following types: | Type | Descriptions | Enum value | | - | - | - | | `DateTime` | An ISO 8601 datetime field. All times must be in UTC timezone | `DataRowMetadataKind.datetime` | | `Number` | Floating-point value (max: 64-bit float) | `DataRowMetadataKind.number` | | `String` | Free text field. Max 4,096 characters. | `DataRowMetadataKind.string` | | `Enum` | Enum field with options. Multiple options can be imported. | `DataRowMetadataKind.enum` | | `Option (Enum)` | Option of an enum. Max 64 options can be created per Enum type. 128 for enterprise customers and can be further increased upon request. | `DataRowMetadataKind.enumoption` | | `Embedding` | 128 float 32 vector used for similarity | `DataRowMetadataKind.embedding` | ## Reserved fields The following field names are reserved and cannot be used as custom metadata field names: | Name | Type | Description | | - | - | - | | `tag` | `String` | The tags of the data row | | `split` | `Enum` | The split of the dataset that the data row belongs, including`train`, `valid`, and `test` | | `captureDateTime` | `DateTime` | The timestamp when the data is captured | | `skipNFrames` | `Number` | (Video data only) The number of frames to skip | | `turnInstructions` | `String` | JSON string that contains instructions for each turn in a Multi-modal chat conversation. | ## Construct metadata fields To construct a metadata field, you must provide the Schema ID for the field and the value that will be uploaded. You can do this in two ways: * Option 1: Specify the metadata using the *DataRowMetadataField* object (comes with validation for metadata fields) * Option 2: Specify the metadata fields in *dictionary* format without declaring the DataRowMetadataField objects The Metadata `value` attribute should not be `null`. ```python DataRowMetadataField object theme={"theme":"css-variables"} metadata_fields = [] ## Construct a metadata field of string kind tag_schema = metadata_ontology.get_by_name("tag") tag_metadata_field = DataRowMetadataField( schema_id=tag_schema.uid, # specify the schema id value="tag_string", # typed inputs ) metadata_fields.append(tag_metadata_field) # Construct an metadata field of datetime datetime_schema = metadata_ontology.get_by_name("captureDateTime") capture_datetime_field = DataRowMetadataField( name=datetime_schema.name, # specify the schema id value=datetime.datetime.utcnow(), # typed inputs ) metadata_fields.append(capture_datetime_field) # # Construct a metadata field of Enums options. You can import multiple options. test_schema = metadata_ontology.get_by_name("split")["test"] test_schema_field = DataRowMetadataField( schema_id=test_schema.parent, # specify the schema id value=test_schema.uid, # typed inputs ) metadata_fields.append(test_schema_field) valid_schema = metadata_ontology.get_by_name("split")["valid"] valid_schema_field = DataRowMetadataField( schema_id=valid_schema.parent, # specify the schema id value=valid_schema.uid, # typed inputs ) metadata_fields.append(valid_schema_field) ``` ```python Dictionary theme={"theme":"css-variables"} metadata_fields = [] ## Construct a metadata field of string kind tag_schema = metadata_ontology.get_by_name("tag") metadata_fields.append({"name": tag_schema.name, "value": "tag_value"}) # Construct an metadata field of datetime datetime_schema = metadata_ontology.get_by_name("captureDateTime") metadata_fields.append({"name": datetime_schema.name, "value": datetime.datetime.utcnow()}) ## Construct a metadata field of Enums options. You can import multiple options. train_schema = metadata_ontology.get_by_name("split")["valid"] metadata_fields.append({"schema_id": train_schema.parent, "value": train_schema.uid}) test_schema = metadata_ontology.get_by_name("split")["test"] metadata_fields.append({"schema_id": test_schema.parent, "value": test_schema.uid}) ``` ## Create custom metadata schema ```python Python theme={"theme":"css-variables"} import labelbox from labelbox.schema.data_row_metadata import DataRowMetadataKind client = labelbox.Client(api_key="LABELBOX_API_KEY") metadata_ontology = client.get_data_row_metadata_ontology() # create a custom metadata schema (set the kind value to a supported data type) metadata_schema = metadata_ontology.create_schema(name="metadata_name", kind=DataRowMetadataKind.string) # get the schema id schema_id = metadata_schema.uid ``` ## Upload data rows with metadata Custom metadata field limits vary according to your subscription; for details, see [Limits](/horizon/guides/limits). ```python Python theme={"theme":"css-variables"} data_row = { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg", "global_key": "metadata_tutorial", "metadata_fields": metadata_fields } dataset = client.create_dataset(name="Create data row with metadata") task = dataset.create_data_rows([data_row]) task.wait_till_done() ``` ## Get metadata schema ```python Python theme={"theme":"css-variables"} # you can look up a schema by name. metadata_schema = metadata_ontology.get_by_name("tag") metadata_schema = metadata_ontology.get_by_name("enum_metadata_name") # check the schema print(metadata_schema) schema_id = metadata_schema.uid ``` ## Get metadata fields (ontology) ```python Python theme={"theme":"css-variables"} ## Fetch metadata schema ontology. A Labelbox workspace has a single metadata ontology. metadata_ontology = client.get_data_row_metadata_ontology() # List all available fields metadata_ontology.fields ``` ## Get metadata fields ```python Python theme={"theme":"css-variables"} datarow = next(dataset.data_rows()) for metadata_field in datarow.metadata_fields: print(metadata_field['name'], ":", metadata_field['value']) ``` Result: ```json Output theme={"theme":"css-variables"} tag : custom_tag split : train captureDateTime : 2023-04-04T15:24:37.229417Z ``` ## Bulk export data rows with metadata ```python Python theme={"theme":"css-variables"} export_params= { "performance_details": True, "label_details": True, "metadata_fields": True } export_task = dataset.export(params=export_params) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` Result: ```json JSON theme={"theme":"css-variables"} { "data_row": { "id": "clflxqzty07fj077qa3dd4v27", "global_key": "metadata_tutorial", "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/basic.jpg" }, "media_attributes": { "height": 1285, "width": 2258, "mime_type": "image/jpeg" }, "metadata_fields": [{ "schema_id": "cko8s9r5v0001h2dk9elqdidh", "schema_name": "tag", "value": "tag_string" }, { "schema_id": "cko8sbczn0002h2dkdaxb5kal", "schema_name": "split", "value": [{ "schema_id": "cko8sc2yr0004h2dk69aj5x63", "schema_name": "valid" }, { "schema_id": "cko8scbz70005h2dkastwhgqt", "schema_name": "test" }] }, { "schema_id": "cko8sdzv70006h2dk8jg64zvb", "schema_name": "captureDateTime", "value": "2023-03-24T02:40:40.832576+00:00" }] } ``` ## Export metadata by data row ID You can bulk export metadata by data row with the SDK. ```python Python theme={"theme":"css-variables"} data_row_ids = [''] global_keys = [''] #The data row identifiers methods (lb.DataRowIds and lb.GlobalKeys) validate whether the provided ID is a global key or a data row ID. #Additionally, they ensure that all IDs from the list provided are unique datarow_identifiers = lb.DataRowIds(data_row_ids) global_key_identifiers = lb.GlobalKeys(global_keys) # Use one of the identifiers mdo.bulk_export(data_row_ids=global_key_identifiers) # mdo.bulk_export(data_row_ids=datarow_identifiers) ``` ## Delete metadata fields from a data row ```python Python theme={"theme":"css-variables"} global_key = '' schema_ids_to_delete =[''] data_row_id = '' deletions = [ lb.DeleteDataRowMetadata(data_row_id=lb.GlobalKey(global_key), fields=schema_ids_to_delete) ] # Delete the specified metadata on the data row mdo.bulk_delete(deletes=deletions) ``` ## Upsert metadata to existing data rows Labelbox supports individual or bulk metadata upsert of data rows. Metadata overwrites occur on a per-field basis. ```python Python theme={"theme":"css-variables"} tag_schema = metadata_ontology.get_by_name("tag") # Construct a string field field = DataRowMetadataField( schema_id=tag_schema.uid, # specify the schema id value="updated", # typed inputs ) # Completed object ready for import metadata_payload = DataRowMetadata( global_key="", # optionally, set the argument to data_row_id to use a data row ID fields=[field] ) # Provide a list of DataRowMetadata objects to upload metadata_ontology.bulk_upsert([metadata_payload]) ``` ## Update metadata schema You can update any custom metadata schema's name. However, the type cannot be modified. You also cannot modify the names of reserved fields. ```python Python theme={"theme":"css-variables"} # update a metadata schema's name metadata_schema = metadata_ontology.update_schema(name="metadata_name", new_name="metadata_name_updated") # Enum metadata schema is a bit different since it contains options. # create an Enum metadata with options enum_schema = metadata_ontology.create_schema(name="enum_metadata_name", kind=DataRowMetadataKind.enum, options=["option 1", "option 2"]) # update an Enum metadata schema's name, similar to other metadata schema types enum_schema = metadata_ontology.update_schema(name="enum_metadata_name", new_name="enum_metadata_name_updated") # update an Enum metadata schema option's name, this only applies to Enum metadata schema. enum_schema = metadata_ontology.update_enum_option(name="enum_metadata_name_option_updated", option="option 1", new_option="option 3") ``` ## Delete metadata schema You can delete a metadata schema by name. ```python Python theme={"theme":"css-variables"} status = metadata_ontology.delete_schema(name=metadata_schema.name) # returns True if successfully deleted ``` # Model Source: https://docs.labelbox.com/horizon/developer-guides/model A developer guide for creating and managing model experiments. ## Model An *experiment* is a container in Labelbox that houses all of the information related to the iterative development of a specific model. It contains the data rows for training, model error analysis metrics, model versioning, and versioned snapshots (model runs) of data rows, predictions, etc, associated with a model’s development. Experiments are designed to help you track and compare all of the iterations associated with your model development. ### Create a model ```python Python theme={"theme":"css-variables"} model = client.create_model( name="", ontology_id="" ) ``` ### Get a model ```python Python theme={"theme":"css-variables"} model = client.get_model("") # from labelbox import Model models = client.get_models(where=(Model.name == "")) ``` ## Methods ### Create a model run ```python Python theme={"theme":"css-variables"} model.create_model_run(name="" # optionally, you can supply details of the model run config model.create_model_run( name="", config={ "learning_rate": 0.001, "batch_size": 32 } ) ``` ### Delete a model, and its model runs Deleting a model also deletes its model runs. This action is permanent; it cannot be undone or rolled back. ```python Python theme={"theme":"css-variables"} model.delete() ``` ## Attributes ### Get the basics ```python Python theme={"theme":"css-variables"} # name (str) model.name ``` ### Get the model runs ```python Python theme={"theme":"css-variables"} # get the model runs (relationship to ModelRun objects) model_runs = model.model_runs() # inspect one model run next(model_runs) # inspect all model runs for model_run in model_runs: print(model_run) ``` # Model run A *model run* represents a single iteration within a model training experiment. Each model run contains a versioned snapshot of the data rows, annotations (predictions and/or ground truth), and [data splits](/horizon/guides/splits) for each iteration within a model training experiment. Model runs make it easy for you to reproduce a model training experiment using different parameters. You can also use model runs to track and compare model runs trained on different data versions. ## Get all model runs inside a Model ```python Python theme={"theme":"css-variables"} model_runs = model.model_runs() ``` ## Create a model run Creates a model run belonging to this model. ```python Python theme={"theme":"css-variables"} model_run_name = "" example_config = { "learning_rate": 0.001, "batch_size": 32, } model_run = model.create_model_run(name=model_run_name, config=example_config) ``` ## Get model run ```python Python theme={"theme":"css-variables"} model_run_id = "" model_run = client.get_model_run(model_run_id=model_run_id) model_run_data = model_run.model_run_data_rows() model_run_config = model_run.get_config()) ``` ## Add data rows to a model run Add data rows to a model run **without** any associated labels. You can use either `data_row_id` or `global_key` to specify the data rows. ```python Python theme={"theme":"css-variables"} # Using data row ids data_row_ids = ["", ""] model_run.upsert_data_rows(data_row_ids=data_row_ids) # Using global keys global_keys = ["", ""] model_run.upsert_data_rows(global_keys=global_keys) ``` ## Assign data row training, validation, and test split Note that `assign_data_rows_to_split` only works on data rows or labels that are already in a model run. You can assign them to one of "TRAINING", "VALIDATION", "TEST" split. ```python Python theme={"theme":"css-variables"} client.enable_experimental=True dataset = client.get_dataset("") # Your training dataset # using data row ids model_run.assign_data_rows_to_split( data_row_ids=data_row_ids[:100], split="TRAINING", ) model_run.assign_data_rows_to_split( data_row_ids=data_row_ids[100:150], split="VALIDATION", ) model_run.assign_data_rows_to_split( data_row_ids=data_row_ids[150:200], split="TEST", ) # using global keys model_run.assign_data_rows_to_split( global_keys=global_keys[:100], split="TRAINING", ) model_run.assign_data_rows_to_split( global_keys=global_keys[100:150], split="VALIDATION", ) model_run.assign_data_rows_to_split( global_keys=global_keys[150:200], split="TEST", ) ``` ## Add labels to a model run Adds data rows and labels to a model run. By adding labels, the associated data rows will also be upserted to the model run. ```python Python theme={"theme":"css-variables"} # upsert using label ids label_ids = ["","", ...] model_run.upsert_labels(label_ids) ``` Alternatively, you can add all labels from a project to a Model run directly. This will also add all data rows from that project to the model run. ```python Python theme={"theme":"css-variables"} # upsert using project id model_run.upsert_labels(project_id=) ``` ## Export labels from a model run See [Export for Model Runs](/horizon/guides/export-data-for-model-training) for more details and export JSON format. ```python Python theme={"theme":"css-variables"} # Set the export params to include/exclude certain fields. Make sure each of these fields are correctly grabbed export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, } export_task = model_run.export(params=export_params) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` ## Create, modify, and delete model run config to track hyperparameters. ```python Python theme={"theme":"css-variables"} example_config = { "learning_rate": 0.001, "checkpoint_path": "/path/to/checkpoint/file", "early_stopping": False, "batch_size": 32, "optimizer": { "adam": { "beta1": 0.899999976158, "beta2": 0.999000012875, "epsilon": 9.99999993923e-9 } }, "ngpu": 1, } model_run_1 = model.create_model_run(name="run 1", config=example_config) # You can also create a model with config specified, see above. # Here is how to create a model run first and update the model config field. model_run_2 = model.create_model_run(name="run 2") #The update will repace the previous model run config with the new json input. model_run_2.update_config(example_config) ``` ### Get model run config ```python Python theme={"theme":"css-variables"} model_run_parameters = model_run.get_config() ``` ### Delete the model run config ```python Python theme={"theme":"css-variables"} model_run.reset_config() ``` ### Delete data rows from a model run ```python Python theme={"theme":"css-variables"} data_row_ids = ["","", ...] model_run.delete_model_run_data_rows(data_row_ids=data_row_ids) ``` ### Delete model run ```python Python theme={"theme":"css-variables"} model_run.delete() ``` # Model run custom metrics Source: https://docs.labelbox.com/horizon/developer-guides/model-run-custom-metrics Developer guide on how to import custom model run metrics ## Upload custom metrics If the auto-generated metrics are not sufficient for your use case, you can upload custom metrics to your model run. This will help you even more precisely evaluate your model performance in Labelbox. ### Upload custom metrics to individual prediction annotations. To upload custom metrics to individual predictions, you can append the following list of dictionaries to the respective prediction. The custom metric fields are supported for all annotation types except raster segmentation. ```python Python annotation theme={"theme":"css-variables"} # Include the following list of dictionaries in your prediction annotation built with python classes custom_metrics = [ { 'name': 'iou', 'value': 0.5 }, { 'name': 'f1', 'value': 0.33 }, { 'name': 'precision', 'value': 0.55 }, { 'name': 'recall', 'value': 0.33 }, { 'name': 'tagsCount', 'value': 43 }, { 'name': 'metric_with_a_very_long_name', 'value': 0.334332 } ] ``` ```python NDJSON annotation theme={"theme":"css-variables"} # Include the following list of dictionaries in your prediction annotation built with ndjson 'customMetrics': [ { 'name': 'iou', 'value': 0.5 }, { 'name': 'f1', 'value': 0.33 }, { 'name': 'precision', 'value': 0.55 }, { 'name': 'recall', 'value': 0.33 }, { 'name': 'tagsCount', 'value': 43 }, { 'name': 'metric_with_a_very_long_name', 'value': 0.334332 } ] ``` ### Scalar custom metrics A `ScalarMetric` is a custom metric with a single scalar value. It can be uploaded at the following levels of granularity: ```python Python theme={"theme":"css-variables"} from labelbox.data.annotation_types import (ScalarMetric, ScalarMetricAggregation, ConfusionMatrixMetric) ``` ```python Python theme={"theme":"css-variables"} # custom metric on a data row data_row_metric = ScalarMetric(metric_name="iou_custom", value=0.5) # custom metric on a feature feature_metric = ScalarMetric(metric_name="iou_custom", feature_name="cat", value=0.5) # custom metric on a nested feature subclass_metric = ScalarMetric(metric_name="iou_custom", feature_name="cat", subclass_name="orange", value=0.5) ``` ### Aggregation of custom metrics This is an optional field on the `ScalarMetric` object, to control how custom metrics are aggergated. By default, the aggregation uses `ARITHMETIC_MEAN`. Aggregations occur in the following cases: * When you provide a feature or nested-feature metric, Labelbox automatically aggregates the metric across features and nested-features on the data row. *For example, say you provide a custom metric Bounding Box Width (BBW) on the features "cat" and "dog" . The data row-level metric for BBW is the average of these two values.* * When you create slices, the custom metric is aggregated across data rows of the Slice. * When you filter data inside a Model Run, the custom metric is aggregated across the filtered data rows. ```python Python theme={"theme":"css-variables"} """ If the following metrics are uploaded then in the Labelbox App, users will see: true positives dog = 4 true positives cat = 3 true positives = 7 """ feature_metric = ScalarMetric(metric_name="true_positives", feature_name="cat", value=3, aggregation=ScalarMetricAggregation.SUM) feature_metric = ScalarMetric(metric_name="true_positives", feature_name="dog", value=4, aggregation=ScalarMetricAggregation.SUM) ``` ## Add labels to a model run Adds data rows and labels to a model run. By adding labels, the associated data rows will also be upserted to the model run. ```python Python theme={"theme":"css-variables"} # upsert using label ids label_ids = ["","", ...] model_run.upsert_labels(label_ids) ``` Alternatively, you can add all labels from a project to a Model run directly. This will also add all data rows from that project to the model run. ```python Python theme={"theme":"css-variables"} # upsert using project id model_run.upsert_labels(project_id=) ``` # Model run slice Source: https://docs.labelbox.com/horizon/developer-guides/model-slice Developer guide for interacting with model slices using the Python SDK. [Model slices](/horizon/guides/slices) operate much like [Catalog slices](/horizon/guides/slices), both essentially functioning as saved searches that help filter large datasets into more manageable units. While Catalog slices function support **Catalog**, Model slices work within model runs. Model slices let you filter model run data rows into manageable units. Here, we show how to use the SDK to: * Create an experiment and a model run. * Create a slice from model run data. ## Example: create and retrieve model slice ### Before you start You must import these libraries to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ## Create a model experiment and a model run To interact with model slices, you must create a model experiment with a model run and then create a model slice through the platform. The steps below go over this process. See Slices [Slices](/horizon/guides/slices)for more information ### Create a model experiment To create a model experiment, you will need to create an ontology. See [Ontology](/horizon/developer-guides/ontology) for more information ```python Python theme={"theme":"css-variables"} #Create an ontology classification_features = [ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="Quality Issues", options=[ lb.Option(value="blurry", label="Blurry"), lb.Option(value="distorted", label="Distorted") ] ) ] ontology_builder = lb.OntologyBuilder( tools=[], classifications=classification_features ) ontology = client.create_ontology( "Ontology from new features", ontology_builder.asdict(), media_type=lb.MediaType.Image ) ``` Attach the ontology during model creation. ```python Python theme={"theme":"css-variables"} model = client.create_model( name="Model Slice Demo", ontology_id=ontology.uid ) ``` ### Create a model run from a model experiment In this step, you will need to create a dataset to attach data rows to our model run. ```python Python theme={"theme":"css-variables"} # create a sample dataset to send to your model global_key = str(uuid.uuid4()) test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "global_key": global_key } dataset = client.create_dataset(name="foundry-demo-dataset") task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print(f"Errors: {task.errors}") print(f"Failed data rows: {task.failed_data_rows}") # Create a model configuration model_run_name = "Model Slice Demo" example_config = { "learning_rate": 0.001, "batch_size": 32, } # Create a model run model_run = model.create_model_run(name=model_run_name, config=example_config) # Send the data rows to your model model_run.upsert_data_rows(global_keys=[global_key]) ``` ## Create a model slice Use the Labelbox app to create a model slice. From **Model**, select **Experiment** and select your model experiment. A filter must be active to create a slice. From the **Search** menu, select **Data row** and then set the **Condition** menu to **is not one of**. Set **Search for an id** to *test* and then press *Enter* to activate the filter. You can add additional filter conditions as needed. Select **Save slice**, name your slice, and then select **Save**. When you do this, the **Slice** menu displays the name of your slice and the number of matching data rows. From the **Slice** menu, select **Copy slice ID**. Paste the **Slice ID** into your your code: ```python Python theme={"theme":"css-variables"} SLICE_ID = "" ``` ### Get model slice ```python Python theme={"theme":"css-variables"} model_slice = client.get_model_slice(SLICE_ID) ``` ### Obtain data row IDs from model slice Data row identifiers are objects that contain both data row IDs and global keys. ```python Python theme={"theme":"css-variables"} data_row_identifiers = model_slice.get_data_row_identifiers(model_run.uid) drids = [dr for dr in data_row_identifiers] # get both global keys and data row ids # and utilize the hash method to combine both global keys and data row ids into a dictionary for dr in drids: print(f"Data row: {dr.id}, Global Key: {dr.global_key}, dr_gk: {dr.to_hash()}") ``` ### Obtain data row identifiers ``` data_rows = model_slice.get_data_row_identifiers(model_run.uid) for data_row in data_rows: print(data_row) ``` ### Model slice attributes ```python Python theme={"theme":"css-variables"} # name (str) model_slice.name # description (str) model_slice.description # updated at (datetime) model_slice.updated_at # created at (datetime) model_slice.created_at # filter (list[dict]) model_slice.filter ``` # Multimodal chat evaluation Source: https://docs.labelbox.com/horizon/developer-guides/multimodal-chat-evaluation-project Learn how to set up a multimodal chat evaluation project using the SDK. The [multimodal chat evaluation editor](/horizon/guides/multimodal-chat-evaluation-editor) allows you to evaluate generative model responses across multiple data types. This guide walks through setting up a multimodal chat evaluation project using the SDK. ## Initialize the SDK Import the Labelbox Python SDK: ```python Python theme={"theme":"css-variables"} import labelbox as lb ``` ## API key and client Provide a valid API key to authenticate the Labelbox client. See [Labelbox API keys](/horizon/developer-guides/create-api-key) to learn how to generate your key. ```python Python theme={"theme":"css-variables"} API_KEY = "" # Your API key client = lb.Client(api_key=API_KEY) ``` ## Create a multimodal chat ontology You can create ontologies for multimodal chat projects using either `client.create_ontology()` or `client.create_ontology_from_feature_schemas()`, with the `ontology_kind` parameter set to `lb.OntologyKind.ModelEvaluation`. See [Supported annotation types](/horizon/guides/multimodal-chat-evaluation-editor#supported-annotation-types) for the annotation types you can include in a multimodal chat evaluation ontology. ### Option A: `create_ontology` Use `create_ontology` to create an ontology and define its schema: ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder(ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool( tool=lb.Tool.Type.MESSAGE_SINGLE_SELECTION, name="single select feature", ), lb.Tool( tool=lb.Tool.Type.MESSAGE_MULTI_SELECTION, name="multi select feature", ), lb.Tool(tool=lb.Tool.Type.MESSAGE_RANKING, name="ranking feature"), StepReasoningTool(name="step reasoning"), FactCheckingTool(name="fact checking"), PromptIssueTool(name="prompt rating"), ], classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist feature", options=[ lb.Option(value="option 1", label="option 1"), lb.Option(value="option 2", label="option 2"), ], ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer"), ], ), ], ) # Create ontology ontology = client.create_ontology( "Example ontology", ontology_builder.asdict(), media_type=lb.MediaType.Conversational, ontology_kind=lb.OntologyKind.ModelEvaluation, ) ``` ### Option B: `create_ontology_from_feature_schemas` Use `create_ontology_from_feature_schemas` with feature schema IDs to create ontologies that reuse existing feature schemas instead of defining new ones. To obtain these IDs, go to the **Schema** tab. ```python Python theme={"theme":"css-variables"} ontology = client.create_ontology_from_feature_schemas( "Example ontology", feature_schema_ids=[" ## Create multimodal chat evaluation projects The two types of multimodal chat evaluation projects have different project creation methods and data row setups: * For **offline** multimodal chat evaluation projects, use `create_offline_model_evaluation_project` and import data rows of existing conversations. * For **live** multimodal chat evaluation projects, use `client.create_model_evaluation_project` and either: * (Recommended) Create data rows and send them to projects, like other types of projects. * Generate empty data rows upon project creation, which can't create data rows with attachments and metadata. ## Set up offline multimodal chat evaluation projects Use `client.create_offline_model_evaluation_project` to create offline multimodal chat evaluation projects. This method takes the same parameters as `client.create_project`: a `name` and an optional `description`. ```python Python theme={"theme":"css-variables"} project = client.create_offline_model_evaluation_project( name="", description="", # optional ) ``` After creating the project, import conversational version 2 data rows for further processing. For instructions, see [Import multimodal chat evaluation data](/horizon/developer-guides/import-multimodal-chat-data). To learn how to import annotations, see [Import multimodal chat annotations](/horizon/developer-guides/import-multimodal-chat-annotations). ## Set up live multimodal chat evaluation projects Use `client.create_model_evaluation_project` to create a live multimodal chat evaluation project. This method takes the same `name` and optional `description` parameters as `client.create_project`, with a few additional parameters specific to multimodal chat evaluation projects: * `data_row_count` (optional): The number of data rows to generate for your project. Defaults to 100 if a `dataset_name` or `dataset_id` is included. * `dataset_name` (optional): The name of a new dataset. Include this parameter only if you want to create a new dataset for the generated data rows. * `dataset_id` (optional): The dataset ID of an existing Labelbox dataset. Include this parameter only if you want to append generated data rows to an existing dataset. ### Option A: Create and send data rows to projects ```python Python theme={"theme":"css-variables"} # Create the project project = client.create_model_evaluation_project( name="Example live multimodal chat project", description="", # optional ) def make_data_rows(dataset_id=None): # If a dataset ID is provided, fetch the dataset using that ID. # Otherwise, create a new dataset with the specified name. if dataset_id: dataset = client.get_dataset(dataset_id) else: dataset = client.create_dataset(name="example live mmc dataset") # Helper function to generate a single data row def generate_data(ind): return { "row_data": { # The chat data format 'type': 'application/vnd.labelbox.conversational.model-chat-evaluation', 'draft': True, 'rootMessageIds': [], 'actors': {}, 'version': 2, 'messages': {} }, "global_key": f"global_key_{dataset.uid}_{ind}", "metadata_fields": [{"name": "tag", "value": "val_tag"}], "attachments": [ { "type": "IMAGE_OVERLAY", "value": "https://storage.googleapis.com/labelbox-sample-datasets/Docs/rgb.jpg" } ] } # Generate a list of 100 data rows data_list = [generate_data(ind) for ind in range(100)] # Upload the generated data rows to the dataset task = dataset.create_data_rows(data_list) print("Processing task ", task.uid) # Print the unique ID of the task task.wait_till_done() # Ensure that the task status is 'COMPLETE' to confirm success assert task.status == "COMPLETE" # Return the dataset object return dataset # Create a new data set. Alternatively, pass an existing dataset ID dataset = make_data_rows() # Retrieve the data row IDs from the dataset data_row_ids = [data_row.uid for data_row in dataset.data_rows()] # Send data rows to the project batch = project.create_batch( name="mmc-batch", # each batch in a project must have a unique name data_rows=data_row_ids, # data row IDs to include in the batch priority=1 # priority between 1(highest) - 5(lowest) ) print(f"Batch: {batch}") ``` ### Option B: Generate empty data rows ### No metadata support Only use this option if your project doesn't require metadata attachments or embeddings. ```python Python theme={"theme":"css-variables"} # Create the project and generate data rows project = client.create_model_evaluation_project( name="Example live multimodal chat project", description="", # optional dataset_name="Example live multimodal chat dataset", data_row_count=100, ) # Connect the project to the created ontology project.connect_ontology(ontology) ``` ### Set up model configurations You can create, delete, attach, and remove model configurations that evaluate your live multimodal chat responses. #### Create model configs Use `client.create_model_config` to create a model configuration. This method takes the following parameters: * `name`: The name of the model configuration. * `model_id`: The ID of the model to configure. To get this value, go to the **Model** tab, select your model, and copy the ID from the URL. * `inference_params`: Model configuration parameters in the JSON format. Each model has unique parameters. ### First-time setup If you're setting up model configurations for the first time, it's recommended to set one up using the web platform instead of the SDK to understand all the associated parameters. The following example creates a Google Gemini 1.5 Pro model configuration: ```python Python theme={"theme":"css-variables"} MODEL_ID = "270a24ba-b983-40d6-9a1f-98a1bbc2fb65" inference_params = {"max_new_tokens": 1024, "use_attachments": True} model_config = client.create_model_config( name="Example Model Config", model_id=MODEL_ID, inference_params=inference_params, ) ``` #### Attach model configurations to projects Use `project.add_model_config to attach` or `project.remove_model_config` to remove model configurations. Both methods take just a `model_config` ID. ```python Python theme={"theme":"css-variables"} project.add_model_config(model_config.uid) ``` #### Delete model configurations Use `project.delete_project_model_config()` or `client.delete_model_config` to delete model configurations. Both methods require the `model_config` ID as a parameter. You can obtain this ID in one of the following ways: * From the response when you create a model configuration * By accessing `project.project_model_configs` and iterating through the list of model configurations attached to your project ```python project.delete_project_model_config() theme={"theme":"css-variables"} model_configs = project.project_model_configs() for model_config in model_configs: project.delete_project_model_config(model_config.uid) ``` ```python client.delete_model_config theme={"theme":"css-variables"} model_configs = project.project_model_configs() for model_config in model_configs: client.delete_model_config(model_config.uid) ``` ### Mark project setup as complete Once you've completed your project setup and model configuration, use `project.set_project_model_setup_complete()` to mark the setup as complete. After that, you can add more data rows to the project, but you can't further add, modify, or delete project model configurations. ```python Python theme={"theme":"css-variables"} project.set_project_model_setup_complete() ``` ## Export multimodal chat evaluation projects The `export()` method provides a unified way to export data from all project types, including multimodal chat evaluation projects. It retrieves model responses, annotations, and related metadata in a structured format. For detailed instructions on using `export()` to export multimodal chat evaluation projects and sample export formats, see [Export multimodal chat annotations](/horizon/developer-guides/export-multimodal-chat-annotations). # Ontology Source: https://docs.labelbox.com/horizon/developer-guides/ontology Developer guide for creating, modifying, and connecting ontologies via the Python SDK. ## Client ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.data.annotation_types as lb_types client = lb.Client(api_key="") ``` ## Create an ontology Each `Tool` and `Classification` type requires a specific value to be passed when creating the feature. | Feature | Class | Value | | - | - | - | | Bounding box | `Tool` | `BBOX` | | Polygon | `Tool` | `POLYGON` | | Polyline | `Tool` | `LINE` | | Point | `Tool` | `POINT` | | Segmentation mask | `Tool` | `RASTER_SEGMENTATION` | | Relationship | `Tool` | `RELATIONSHIP` | | Entity | `Tool` | `NER` | | Message ranking | `Tool` | `MESSAGE_RANKING` | | Single message selection | `Tool` | `MESSAGE_SINGLE_SELECTION` | | Multiple message selection | `Tool` | `MESSAGE_MULTI_SELECTION` | | Message step reasoning | `Tool` | `STEP_REASONING` | | Fact-checking | `Tool` | `FACT_CHECKING` | | Prompt rating | `Tool` | `PROMPT_ISSUE` | | Radio | `Classification` | `RADIO` | | Checklist | `Classification` | `CHECKLIST` | | Text | `Classification` | `TEXT` | When you create an ontology, specify the `media_type` parameter, which represents the modality of the data rows that the ontology will be used to label. Certain features are only compatible with certain media types. ### Specify an ontology media type The `media_type` parameter of `client.create_ontology` defaults to `None`, but it's highly recommended to specify one for validation and error handling. The `media_type` parameter takes the following values: * `lb.MediaType.Audio` * `lb.MediaType.Conversational` * `lb.MediaType.Document` * `lb.MediaType.Geospatial_Tile` * `lb.MediaType.Html` * `lb.MediaType.Image` * `lb.MediaType.Simple_Tile` * `lb.MediaType.Text` * `lb.MediaType.Video` ### Create ontologies from new features You can define new features to build new ontologies. The following code sample shows how to create simple features for upserting into ontologies. To learn more about creating features, see [Features](/horizon/developer-guides/feature#create-a-feature). ```python Create features theme={"theme":"css-variables"} object_features = [ lb.Tool( tool=lb.Tool.Type.BBOX, name="regulatory-sign", color="#ff0000", ) ] classification_features = [ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="Quality Issues", options=[ lb.Option(value="blurry", label="Blurry"), lb.Option(value="distorted", label="Distorted") ] ) ] ``` You can then create an ontology with your defined features either using `ontology_builder` or `client.create_ontology_from_feature_schemas()`. ```python ontology_builder theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=object_features, classifications=classification_features ) ontology = client.create_ontology( "Ontology from new features", ontology_builder.asdict(), media_type=lb.MediaType.Image ) ``` ```python create_ontology_from_feature_schemas theme={"theme":"css-variables"} # First create the feature schema feature_schema_cat = client.create_feature_schema(feature_schema_cat_normalized) # When we create the ontology it will not re-create the feature schema print(feature_schema_cat.uid) ontology = client.create_ontology_from_feature_schemas(ontology_name, [feature_schema_cat.uid]) ``` For more complex ontology creation examples, see [Ontology examples guide](/horizon/developer-guides/ontology-examples) . ### Create from normalized JSON format * Users can create ontologies from a JSON definition of the ontology. * Each tool type requires a specific value to be passed: | Tool | Value | | - | - | | Bounding box | `rectangle` | | Polygon | `polygon` | | Polyline | `line` | | Point | `point` | | Segmentation mask | `raster-segmentation` | | Entity | `named-entity` | ```python Python theme={"theme":"css-variables"} # This automatically creates new feature schema ontology_name = "sdk-ontology" feature_schema_cat_normalized = { 'tool': 'polygon', 'name': 'cat', 'color': 'black' } ontology_normalized_json = { "tools": [feature_schema_cat_normalized], "classifications": [] } ontology = client.create_ontology(name=ontology_name, normalized=ontology_normalized_json, media_type=lb.MediaType.Image) ``` ### Create from existing features If you already have features created, you can query them by name or by schema id. Re-using an existing feature is highly recommended. ```python Python theme={"theme":"css-variables"} ## Search feature by name in your org regulatory_sign_feature_schema = next(client.get_feature_schemas("regulatory-sign")) classification_feature = next(client.get_feature_schemas("Quality Issues")) ## Get feature by feature schema ID. Alternatively you can get this from the UI regulatory_sign_feature_schema = client.get_feature_schema("FEATURE_SCHEMA_ID") ontology = client.create_ontology_from_feature_schemas("Ontology from existing features", [regulatory_sign_feature_schema.uid, classification_feature.uid], media_type=MediaType.Image) ``` ### Create a nested ontology You can create a child feature nested under a parent feature. Only classification features can be children of a nested ontology. For instance, if you want to create an ontology where a bounding box has a radio sub-classification, you will add a `classifications`field that contains a list of classification features as children. ```python Tool with subclassification theme={"theme":"css-variables"} bbox_with_sub_classification = lb.Tool(tool=lb.Tool.Type.BBOX, name="cat", classifications=[ lb.Classification(class_type=lb.Classification.Type.RADIO, name="radio", options=[ lb.Option(value="long-fur"), lb.Option(value="short-fur") ]) ]) ontology_builder = lb.OntologyBuilder(tools=[ bbox_with_sub_classification ]) ontology = client.create_ontology("Simple nested ontology", ontology_builder.asdict(), media_type=MediaType.Image) ``` ```python Classification with subclassification theme={"theme":"css-variables"} radio_with_sub_classification = Classification( class_type=Classification.Type.RADIO, instructions="radio_question_sub", options=[ Option(value="first_radio_answer", options=[ Classification( class_type=Classification.Type.RADIO, instructions="sub_radio_question", options=[ Option(value="first_sub_radio_answer"), Option(value="second_sub_radio_answer") ] ), ])], ) ontology_builder = OntologyBuilder(classifications=[ sub_radio_classification ]) ontology = client.create_ontology("Simple nested ontology", ontology_builder.asdict()) ``` ## Get ontologies ```python Python theme={"theme":"css-variables"} # get by ID ontology = client.get_ontology("") # get by name ontology = next(client.get_ontologies("")) # get multiple ontologies by keyword ontologies = client.get_ontologies(name_contains="") ``` ### Check if a feature is archived in an ontology ```python Python theme={"theme":"css-variables"} client.is_feature_schema_archived("", "") ``` ### Get all unused ontologies ```python Python theme={"theme":"css-variables"} client.get_unused_ontologies() ``` ### Delete an unused ontology ```python Python theme={"theme":"css-variables"} client.delete_unused_ontology("") ``` ## Connect an ontology to a project ```python Python theme={"theme":"css-variables"} # get (or create) a project project = client.get_project("") # get (or create) an ontology ontology = client.get_ontology("") # the argument must be an object of the Ontology class project.connect_ontology(ontology) ``` ## Methods ### Get the tools ```python Python theme={"theme":"css-variables"} tools = ontology.tools() for tool in tools: print(tool) ``` ### Get the classifications ```python Python theme={"theme":"css-variables"} classifications = ontology.classifications() for classification in classifications: print(classification) ``` ## Attributes ```python Python theme={"theme":"css-variables"} # name (str) ontology.name # description (str) ontology.description # updated at (datetime) ontology.updated_at # created at (datetime) ontology.created_at # normalized output of the ontology (JSON) ontology.normalized # count of object tools in the ontology (int) ontology.object_schema_count # count of classifications in the ontology (int) ontology.classification_schema_count # created by (relationship to User object) user = ontology.created_by() ``` # Ontology examples Source: https://docs.labelbox.com/horizon/developer-guides/ontology-examples Complex examples of creating a variety of ontologies. ## Image ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # list of classification objects lb.Classification(class_type=lb.Classification.Type.RADIO, name="radio_question", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ]), lb.Classification(class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ]), lb.Classification(class_type=lb.Classification.Type.TEXT, name="free_text"), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")]) ]) ]), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option( "first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")]) ]) ]), ], tools=[ # List of Tool objects lb.Tool(tool=lb.Tool.Type.BBOX, name="bounding_box"), lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option(value="first_sub_radio_answer")]), ]), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon"), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask"), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask_with_text_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="sub_free_text") ]), lb.Tool(tool=lb.Tool.Type.POINT, name="point"), lb.Tool(tool=lb.Tool.Type.LINE, name="polyline"), lb.Tool(tool=lb.Tool.Type.RELATIONSHIP, name="relationship") ]) ontology = client.create_ontology("Image Annotation Import Demo Ontology", ontology_builder.asdict(), media_type=lb.MediaType.Image) ``` ## Video ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_video"), lb.Tool(tool=lb.Tool.Type.POINT, name="point_video"), lb.Tool(tool=lb.Tool.Type.LINE, name="line_video_frame"), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="video_mask"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_class", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="checklist_class", scope = lb.Classification.Scope.INDEX, options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ) ] ), lb.Tool(tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask_with_text_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="sub_free_text") ] ) ], classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class", scope = lb.Classification.Scope.INDEX, ## Need to defined scope for frame classifications options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_class", scope = lb.Classification.Scope.INDEX, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_class_global", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class_global", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ) ] ) ontology = client.create_ontology("Video Annotation Import Demo Ontology", ontology_builder.asdict(), media_type=lb.MediaType.Video) ``` ## Text ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[lb.Option(value="first_radio_answer")] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ), ], ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ) ], tools=[ # List of Tool objects lb.Tool( tool=lb.Tool.Type.NER, name="named_entity" ), lb.Tool( tool=lb.Tool.Type.RELATIONSHIP, name="relationship" ) ] ) ontology = client.create_ontology("Ontology Text Annotations", ontology_builder.asdict()) ``` ## Geospatial ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.POINT, name="point_geo"), lb.Tool(tool=lb.Tool.Type.LINE, name="polyline_geo"), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo"), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo_2"), lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_geo"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_checklist_geo", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class_name", options=[ lb.Option(value="first_checklist_answer") ] ), ] ), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_text_geo", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text_geo" ), ] ) ], classifications = [ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question_geo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question_geo", options=[ lb.Option(value="first_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ), ], ), lb.Classification(class_type=lb.Classification.Type.TEXT, name="free_text"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ) ] ) ontology = client.create_ontology("Ontology Geospatial Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Geospatial_Tile) ``` ## Document ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text", scope = lb.Classification.Scope.GLOBAL ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ]) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ]) ] ), ], tools=[ # List of Tool objects lb.Tool( tool=lb.Tool.Type.BBOX,name="bounding_box"), lb.Tool(tool=lb.Tool.Type.NER, name="named_entity"), lb.Tool(tool=lb.Tool.Type.RELATIONSHIP,name="relationship"), lb.Tool(tool=lb.Tool.Type.NER, name="ner_with_checklist_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[ lb.Option(value="first_sub_checklist_answer") ] ) ]), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option( value="first_sub_radio_answer" , options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="second_sub_radio_question", options=[lb.Option("second_sub_radio_answer")] )] )] )] )] ) ontology = client.create_ontology("Document Annotation Import Demo", ontology_builder.asdict(), media_type=lb.MediaType.Document) ``` ## Audio ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="text_audio"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_audio", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_audio", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ) ] ) ontology = client.create_ontology("Ontology Audio Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Audio) ``` ## HTML ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="text_html"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_html", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_html", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ) ] ) ontology = client.create_ontology("Ontology HTML Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Html) ``` ## Conversational ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.NER,name="ner"), lb.Tool(tool=lb.Tool.Type.RELATIONSHIP,name="relationship") ], classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, scope=lb.Classification.Scope.INDEX, # Remove this line or set scope to "GLOBAL" if importing global text annotations instructions="text_convo"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, scope=lb.Classification.Scope.INDEX, # Remove this line or set scope to "GLOBAL" if importing global checklist annotations instructions="checklist_convo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, instructions="radio_convo", scope=lb.Classification.Scope.INDEX, # Remove this line or set scope to "GLOBAL" if importing global radio annotations options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ) ] ) ontology = client.create_ontology("Ontology Conversation Annotations", ontology_builder.asdict()) ``` ## Model Chat Evaluation ```python Python theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool( tool=lb.Tool.Type.MESSAGE_SINGLE_SELECTION, name="single select feature", ), lb.Tool( tool=lb.Tool.Type.MESSAGE_MULTI_SELECTION, name="multi select feature", ), lb.Tool(tool=lb.Tool.Type.MESSAGE_RANKING, name="ranking feature"), ], classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist feature", options=[ lb.Option(value="option 1", label="option 1"), lb.Option(value="option 2", label="option 2"), ], ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer"), ], ), ], ) # Create ontology ontology = client.create_ontology( "MCE ontology", ontology_builder.asdict(), media_type=lb.MediaType.Conversational, ontology_kind=lb.OntologyKind.ModelEvaluation, ) ``` # Organization Source: https://docs.labelbox.com/horizon/developer-guides/organization A developer guide for querying organization information via the Python SDK. ## Client ### Get the current organization ```python Python theme={"theme":"css-variables"} organization = client.get_organization() ``` ## Methods ### Get IAM integrations ```python Python theme={"theme":"css-variables"} # get the default IAM integration default_iam_integration = organization.get_default_iam_integration() # get all of the IAM integrations (returns a list of IAMIntegration objects) iam_integrations = organization.get_iam_integrations() for integration in iam_integrations: print(integration) ``` ### Create a project tag ```python Python theme={"theme":"css-variables"} organization.create_resource_tag( { "text": "new-tag-name", "color": "ffffff" } ) ``` ### Get the project tags ```python Python theme={"theme":"css-variables"} # get the project tags (returns a list of ResourceTag objects) tags = organization.get_resource_tags() for tag in tags: print(tag) ``` ### Invite users Depending on the level of access required, you can: * Invite users to a workspace without assigning them to any projects or user groups. * Invite users to a workspace and assign them to specific projects or user groups. ```python Organization-only theme={"theme":"css-variables"} # get and view the available roles roles = client.get_roles() print("Roles: ", roles) # create an invitation for an organization-wide role (in this case, a labeler) organization.invite_user( email="", role=roles["LABELER"] ) ``` ```python Project-based theme={"theme":"css-variables"} # get and view the available roles roles = client.get_roles() print("Roles: ", roles) # get a project project = client.get_project("") # create a ProjectRole object for a project-based role, in this case a labeler project_role = lb.ProjectRole(project=project, role=roles["LABELER"]) # create an invitation for the project-based role organization.invite_user( email="", # for a project-based role, the org-wide role must be NONE role=roles["NONE"], project_roles=[project_role] ) # Mass invite users to project emails = [] for email in emails: invite = organization.invite_user(emails, roles["NONE"], project_roles=[project_role]) ``` ```python Group-based theme={"theme":"css-variables"} from labelbox import Client, UserGroupRole from labelbox.schema.user_group import UserGroup roles = client.get_roles() # Use an existing UserGroup id (e.g. from the UI / API) user_group = UserGroup(client=client, id="") # Create a UserGroupRole object for a group-based role, in this case a reviewer user_group_role = UserGroupRole(user_group=user_group, role=roles["REVIEWER"]) invite = organization.invite_user( email="", # for a group-based role, the org-wide role must be NONE role=roles["NONE"], user_group_roles=[user_group_role] ) ``` ### Get pending invites ```python Python theme={"theme":"css-variables"} project_id = "" # For the entire organization org_invites = organization.get_invites() # For a specific project project_invites = organization.get_project_invites(project_id) ``` ### Cancel invite ```python Python theme={"theme":"css-variables"} email = """ project_id = "" # Option 1: organization invite invite = next( invite for invite in organization.get_invites() if invite.email == email ) # Option 2: project invite invite = next( invite for invite in organization.get_project_invites(project_id) if invite.email == email ) invite.cancel() ``` ### View the invite limit ```python Python theme={"theme":"css-variables"} # already accounts for users currently in the organization, thus: # used = users + invites_sent # remaining = limit - used organization.invite_limit() ``` ### Remove a user ```python Python theme={"theme":"css-variables"} # the argument must be a User object organization.remove_user(user) ``` ## Attributes ### Get the basics ```python Python theme={"theme":"css-variables"} # name (str) organization.name # created_at (datetime) organization.created_at # updated_at (datetime) organization.updated_at ``` ### Get the users ```python Python theme={"theme":"css-variables"} # get the users (relationship to User objects) users = organization.users() # inspect one user next(users) # inspect all users for user in users: print(user) # for ease of use, you can convert the paginated collection to a list list(users) ``` ### Get the projects ```python Python theme={"theme":"css-variables"} # get the projects (relationship to Project objects) projects = organization.projects() # inspect one project next(projects) # inspect all projects for project in projects: print(project) # for ease of use, you can convert the paginated collection to a list list(projects) ``` ### Get the webhooks ```python Python theme={"theme":"css-variables"} # get the webhooks (relationship to Webhook objects) webhooks = organization.webhooks() # inspect one webhook next(webhooks) # inspect all webhooks for webhook in webhooks: print(webhook) # for ease of use, you can convert the paginated collection to a list list(webhooks) ``` # Performance API Source: https://docs.labelbox.com/horizon/developer-guides/performance-api Read the numbers behind the Performance dashboard and the Monitor through the REST API or the Python SDK. The Performance API returns the numbers shown on a project's [Performance dashboard](/horizon/guides/performance-dashboard) and on the workspace [Monitor](/horizon/guides/monitor): throughput, time, and quality metrics over a period, and totals for each labeler and reviewer. Use it to bring those numbers into your own reporting. The API runs the same reports as the pages. For the same dates and filters it returns the same values, and the same rules decide what you can see. ## Before you start You need: * A Labelbox [API key](/horizon/developer-guides/create-api-key). * Access to performance data. If you can open a project's Performance dashboard, you can read that project through the API. Workspace reports need permission to view performance across your organization, as the Monitor does. * For the Python examples, Python SDK version 7.13.0 or later. All endpoints are `GET` requests under this base URL, authenticated with your API key as a bearer token: ```text theme={"theme":"css-variables"} https://api.labelbox.com/api/v1/performance ``` ## Quick start Get the number of labels created per day in a project: ```python Python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(api_key="") project = client.get_project("") series = project.get_performance_metric( "labels_created", "2026-01-01", "2026-01-31" ) for bucket in series.buckets: print(bucket.start.date(), bucket.value) ``` ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/projects/$PROJECT_ID/metrics/labels_created?startDate=2026-01-01&endDate=2026-01-31" ``` The response has one bucket per day: ```json theme={"theme":"css-variables"} { "projectId": "", "metric": "labels_created", "unit": "count", "bucketType": "time", "buckets": [ { "start": "2026-01-01T00:00:00.000Z", "end": "2026-01-02T00:00:00.000Z", "value": 412, "numerator": null, "denominator": null, "modes": null } ], "interval": "day", "startDate": "2026-01-01", "endDate": "2026-01-31", "generatedAt": "2026-02-01T09:30:00.000Z" } ``` ## Endpoints | Endpoint | Python SDK | Returns | | - | - | - | | `GET /metrics` | `client.get_performance_metrics()` | The metrics you can query | | `GET /projects/{projectId}/metrics/{metric}` | `project.get_performance_metric()` | One metric for a project, a value per interval | | `GET /projects/{projectId}/labelers` | `project.get_labeler_performance()` | Totals for each labeler | | `GET /projects/{projectId}/reviewers` | `project.get_reviewer_performance()` | Totals for each reviewer | | `GET /projects/{projectId}/download` | `project.get_performance_report_download()` | A link to the report as a file | | `GET /workspace/metrics/{metric}` | `client.get_workspace_performance_metric()` | One metric across your workspace | | `GET /workspace/labelers` | `client.get_workspace_labeler_performance()` | Totals for each labeler on each project | Every table is paged. [Filters](#filters) lists which parameters each report takes. See [Paging](#paging) and [Reliability](#reliability) before you build on the API. ## Units and dates * **Time** is in seconds. * **Percentages and agreement scores** are fractions from 0 to 1. A value of `0.9` means 90%. * **Dates** are calendar days in UTC, written as `2026-01-31`. Reports are bucketed by UTC day. `startDate` and `endDate` are the first and last day of the period, both included, and a request can cover at most 366 days. * **Wider buckets** keep their calendar bounds: a week runs from Monday, a month from the 1st. The first and last bucket of a series can reach beyond your period, but they count only the days inside it. For counts and time totals, a wider bucket is the sum of its days. Averages and percentages are worked out over the whole bucket, so they are not. In the Python SDK you can pass a `date`, a `datetime`, or a string. A `datetime` with a time zone is converted to its UTC day. ## Metrics List the metrics available to you, with their units and definitions: ```python Python theme={"theme":"css-variables"} for metric in client.get_performance_metrics(): print(metric.name, metric.unit, metric.description) ``` ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/metrics" ``` ### Throughput | Metric | Unit | What it measures | | - | - | - | | `done_datarows` | count | Data rows in the Done step of the workflow | | `accepted_datarows` | count | Data rows in the Accepted step of the workflow | | `labels_created` | count | Labels created in the period | | `annotations_created` | count | Annotations created in the period | | `approved_reviews` | count | Approve actions, through review tasks in the workflow, on labels created in the period | | `rejected_reviews` | count | Reject actions, through review tasks in the workflow, on labels created in the period | | `media_duration` | seconds | Total media duration of data rows in the Done step | | `media_duration_in_review` | seconds | Total media duration of data rows currently in a Review step | | `media_duration_accepted` | seconds | Total media duration of accepted data rows | | `total_time` | seconds | Total labeling, review, and rework time spent on labels created in the period | | `labeling_time` | seconds | Total time spent in labeling mode while the timer is on | | `review_time` | seconds | Total time spent in review mode while the timer is on | | `rework_time` | seconds | Total time spent in rework mode while the timer is on | ### Efficiency | Metric | Unit | What it measures | | - | - | - | | `avg_time_per_label` | seconds | Total labeling time divided by the number of labels worked on, including skipped and unsaved labels | | `avg_review_time` | seconds | Total review time divided by the number of data rows reviewed | | `avg_rework_time` | seconds | Total rework time divided by the number of data rows reworked | | `aht_labeled_datarows` | seconds | Average handling time per data row with at least one label | | `aht_done_datarows` | seconds | Average handling time per data row marked Done | | `aht_accepted_datarows` | seconds | Average handling time per data row marked Accepted | | `aht_all_labels` | seconds | Average handling time per label created, counting skipped, abandoned, and submitted labels | | `aht_submitted_labels` | seconds | Average handling time per submitted label | | `aht_done_labels` | seconds | Average handling time per label on done data rows | | `aht_accepted_labels` | seconds | Average handling time per label on accepted data rows | Average handling time includes creating, reviewing, and reworking time. The `aht_` metrics are listed and can be queried only when average handling time charts are enabled for your organization. Their buckets also carry `modes`, the handling time split into `creating`, `reviewing`, and `reworking`. ### Quality | Metric | Unit | What it measures | | - | - | - | | `benchmark_percentage` | percentage | Average benchmark agreement score of labels | | `benchmark_distribution` | count | Histogram of benchmark agreement scores | | `consensus_percentage` | percentage | Average consensus agreement score of labels | | `consensus_distribution` | count | Histogram of consensus agreement scores | The two distribution metrics return score ranges instead of time intervals. Their response has `"bucketType": "score"` and ten buckets, each with `rangeStart`, `rangeEnd`, and `value`, the number of labels whose score falls in that range. The ranges are fractions like the scores themselves: `0` to `0.1`, up to `0.9` to `1`. ## Filters These parameters narrow a report. Lists are comma-separated in a request, or a Python list in the SDK. | Parameter | Python SDK | Applies to | Description | | - | - | - | - | | `startDate`, `endDate` | `start_date`, `end_date` | All reports | Required. The period, as UTC days. | | `interval` | `interval` | Metrics | Bucket width: `day` (default), `week`, `month`, `quarter`, or `year`. | | `userIds` | `user_ids` | All reports | Count only work by these users. Up to 100. | | `userGroupIds` | `user_group_ids` | Project reports | Count only work by the current members of these user groups. Up to 25. With `userIds` too, only users in both are counted. | | `batchIds` | `batch_ids` | All reports | Count only these batches. Up to 100. | | `projectIds` | `project_ids` | Workspace reports | Narrow the report to these projects. Up to 50. | | `memberType` | `member_type` | Workspace reports | `internal` for members of your organization, `external` for members of other organizations. See [Shared projects and workforce providers](#shared-projects-and-workforce-providers). | | `ownerOrganizationIds` | `owner_organization_ids` | Workspace reports | Count only projects owned by these organizations. Up to 100. | | `deletedLabels` | `deleted_labels` | All reports | `include` (default), `exclude`, or `only`. | A user group filter follows the group's current membership. A group that is empty, is no longer attached to the project, or that you cannot view matches no work. A parameter that an endpoint does not have is refused by name, so a misspelled filter never runs an unfiltered report. A list that is sent must name at least one ID. An empty list is refused with `400` (`userIds=`), and the Python SDK raises `ValueError` for one (`user_ids=[]`). It is not read as "no filter", which would report on everyone. To leave a filter off, leave the parameter out, or pass `None` in the SDK. ## Labeler and reviewer totals These are the two tables on the Performance dashboard: one row per member with their totals for the period. ```python Python theme={"theme":"css-variables"} labelers = project.get_labeler_performance( "2026-01-01", "2026-01-31", sort_by="labels_created", descending=True, ) print(labelers.total) for labeler in labelers: print(labeler.email, labeler.labels_created, labeler.avg_time_per_label) for reviewer in project.get_reviewer_performance("2026-01-01", "2026-01-31"): print(reviewer.email, reviewer.labels_reviewed) ``` ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/projects/$PROJECT_ID/labelers?startDate=2026-01-01&endDate=2026-01-31&sort=labelsCreated&order=desc" curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/projects/$PROJECT_ID/reviewers?startDate=2026-01-01&endDate=2026-01-31" ``` Both tables are paged and sortable. See [Paging](#paging). The response wraps the rows in `data` and adds `page`, `perPage`, and `total`: ```json theme={"theme":"css-variables"} { "projectId": "", "data": [ { "userId": "", "projectId": "", "email": "labeler@example.com", "memberType": "internal", "labelsCreated": 128, "labelsSkipped": 3, "memberTotalTime": 21640, "labelingTimeSubmitted": 19200, "labelingTimeSkipped": 240, "reviewsReceived": 40, "reviewTimeAll": 1800, "reworkTimeAll": 600, "avgTimePerLabel": 148.4, "avgReviewTimeAll": 45, "avgReworkTimeAll": 150, "avgBenchmarkAgreement": 0.92, "avgConsensusAgreement": 0.87, "reworkPercentage": 0.03, "approvalPercentage": 0.95, "avgRating": 4.5, "isRemoved": false } ], "page": 1, "perPage": 50, "total": 37, "startDate": "2026-01-01", "endDate": "2026-01-31", "cachedAt": null, "generatedAt": "2026-02-01T09:30:00.000Z" } ``` ### Labeler fields The Python SDK uses the same names in snake case, such as `labels_created`. | Field | Description | | - | - | | `userId`, `email` | The member. See [Access and privacy](#access-and-privacy) for when the email is masked. | | `memberType` | `internal` if the member's account belongs to your organization, `external` if it belongs to another one. `null` if the account no longer exists. | | `labelsCreated` | Labels created by the member in the period. | | `labelsSkipped` | Labels skipped by the member in the period. | | `memberTotalTime` | Total time the member spent in the editor during the period. | | `labelingTimeSubmitted` | Labeling time spent by the member on submitted or unsaved labels. | | `labelingTimeSkipped` | Labeling time spent by the member on skipped labels. | | `reviewsReceived` | Approve and Reject actions, through the workflow review queue, on labels the member created. | | `reviewTimeAll` | Total review time spent on labels the member created. | | `reworkTimeAll` | Total rework time spent on labels the member created. | | `avgTimePerLabel` | Total labeling time divided by the number of labels worked on, including skipped and unsaved labels. | | `avgReviewTimeAll` | Total review time divided by the number of labels reviewed, for labels the member created. | | `avgReworkTimeAll` | Total rework time divided by the number of labels reworked, for labels the member created. | | `reworkPercentage` | Labels the member created that were reworked, divided by the labels they created. | | `approvalPercentage` | Labels the member created that received an Approve action, divided by those with any review action. | | `avgConsensusAgreement` | Average agreement with other consensus labels, for labels the member created. | | `avgBenchmarkAgreement` | Average agreement with benchmark labels, for labels the member created. | | `avgRating` | The member's average rating on the project, or `null` if they have none. | | `isRemoved` | `true` when the member is no longer on the project. | ### Reviewer fields | Field | Description | | - | - | | `userId`, `email` | The member. | | `memberType` | `internal` or `external`, as for labelers. | | `labelsReviewed` | Labels that had review time from the member in the period. | | `labelsReworked` | Labels that had rework time from the member in the period. | | `memberTotalTime` | Total time the member spent in the editor during the period. | | `reviewTime` | Total review time spent by the member. | | `reworkTime` | Total rework time spent by the member. | | `avgReviewTime` | Total review time divided by the number of labels reviewed. | | `avgReworkTime` | Total rework time divided by the number of labels reworked. | | `reworkPercentage` | Labels reviewed by the member that were reworked, divided by the labels they reviewed. | | `approvalPercentage` | Labels with an Approve action by the member, divided by labels with any review action by the member. | | `isRemoved` | `true` when the member is no longer on the project. | ## Download the report Get a link to the project's performance report as a file. This is the file the Download button on the Performance dashboard gives you. ```python Python theme={"theme":"css-variables"} report = project.get_performance_report_download("2026-01-01", "2026-01-31") print(report.url) ``` ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/projects/$PROJECT_ID/download?startDate=2026-01-01&endDate=2026-01-31" ``` The response has the `url` and `expiresInSeconds`. The link works for 24 hours. Anyone who has the link can open the file until it expires. Treat it like the data it points to. ## Workspace reports Workspace reports are the Monitor's data. They cover every project your organization owns and every project another organization shares with yours, or only the projects you name in `projectIds`. ```python Python theme={"theme":"css-variables"} series = client.get_workspace_performance_metric( "labels_created", "2026-01-01", "2026-01-31", interval="week" ) rows = client.get_workspace_labeler_performance( "2026-01-01", "2026-01-31", sort_by="labels_created", descending=True ) print(rows.total) for row in rows: print(row.project_name, row.email, row.member_type, row.labels_created) ``` ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/workspace/metrics/labels_created?startDate=2026-01-01&endDate=2026-01-31&interval=week" curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/workspace/labelers?startDate=2026-01-01&endDate=2026-01-31&sort=labelsCreated&order=desc" ``` `workspace/labelers` returns one row for each labeler on each project they worked on in the period. A row has the [labeler fields](#labeler-fields), without `avgRating`, plus: | Field | Description | | - | - | | `projectId`, `projectName` | The project. `projectName` is `null` for a project that can no longer be found. | | `projectOrganizationId`, `projectOrganizationName` | The organization that owns the project. It is not yours for a project shared with you. Both are `null` for a project that can no longer be found. | | `isProjectDeleted` | `true` when the project has been deleted. Its rows and figures stay in the report. | The table is [paged](#paging) like the project tables. Without `sort`, rows are ordered by project, then member. Workspace reports do not take a user group filter. ## Shared projects and workforce providers A project belongs to one organization and can be shared with another, for example when you add a workforce provider to it. The provider's members then work in your project, and the project appears in the provider's workspace as a shared project. The API reflects both sides of that connection. **What a workspace report covers.** The projects your organization owns, and the projects shared with it. Two fields tell them apart on every row of `workspace/labelers`: * `projectOrganizationId` is the organization that owns the project. * `memberType` is whose member did the work: `internal` for a member of your organization, `external` for a member of another organization or an Alignerr member. The Monitor calls external members **Workforce members**. Both are told from your side. To a workforce provider its own labelers are `internal`, and a customer's project has the customer's `projectOrganizationId`. Alignerr members are the exception: they are `external` to everyone, including to Alignerr's own organization. **Split a report.** Use `memberType` and `ownerOrganizationIds` on either workspace report. These are the Monitor's **Member Type** and **Owned by** filters. ```python Python theme={"theme":"css-variables"} # Labels created each week by your workforce provider's members external = client.get_workspace_performance_metric( "labels_created", "2026-01-01", "2026-01-31", interval="week", member_type="external", ) # The same for your own members internal = client.get_workspace_performance_metric( "labels_created", "2026-01-01", "2026-01-31", interval="week", member_type="internal", ) # As a workforce provider: your members' totals on one customer's projects rows = client.get_workspace_labeler_performance( "2026-01-01", "2026-01-31", owner_organization_ids=[""], member_type="internal", ) ``` ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/workspace/metrics/labels_created?startDate=2026-01-01&endDate=2026-01-31&interval=week&memberType=external" curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/workspace/labelers?startDate=2026-01-01&endDate=2026-01-31&ownerOrganizationIds=$CUSTOMER_ORGANIZATION_ID&memberType=internal" ``` A few rules apply: * **A split covers members and projects with activity in the period.** As on the Monitor, `memberType` and `ownerOrganizationIds` select from the members and projects that have rows in the report for your dates and other filters. If none match, every bucket is zero. It is never the unfiltered report. * **A split by owner leaves out projects that are gone.** A project that can no longer be found has no owner, so `ownerOrganizationIds` never selects it, and the splits by owner can add up to slightly less than the whole workspace. * **A split by owner leaves out projects you cannot report on.** Your workspace can include a project you have no role in. A metric split by `ownerOrganizationIds` does not count it, and lists it in `skippedProjectIds` so that you know. * **A split of a metric needs permission to view everyone's performance.** With `projectIds`, `memberType` and `ownerOrganizationIds` on `workspace/metrics` need permission to view all performance in every project you name. Otherwise the request returns `403`. On `workspace/labelers` they filter the rows you can already see. * **Project reports carry `memberType` too.** The labeler and reviewer tables of a project give each member's `memberType`. * **Names and emails are masked between a customer and a workforce provider.** See [Access and privacy](#access-and-privacy). IDs are never masked, so you can always group and filter by `userId`, `projectId`, and `projectOrganizationId`. * **User groups belong to the project's organization.** A `userGroupIds` filter on a project shared with you matches no work. ## Paging The three tables (`projects/{projectId}/labelers`, `projects/{projectId}/reviewers`, and `workspace/labelers`) are paged. In the Python SDK, iterating fetches one page at a time, and `total` gives the number of rows without reading them all: ```python Python theme={"theme":"css-variables"} rows = client.get_workspace_labeler_performance( "2026-01-01", "2026-01-31", page_size=100 ) print(rows.total) for row in rows: # fetches page after page as you go ... ``` In a request, use these parameters: | Parameter | Description | | - | - | | `page` | Page number, starting at 1. Written in digits only. | | `perPage` | Rows per page, up to 100. The default is 50. In the SDK, `page_size`. | | `sort` | A field of the row, such as `labelsCreated`. In the SDK, `sort_by="labels_created"`. | | `order` | `asc` (default) or `desc`. In the SDK, `descending=True`. | | `useCache` | `false` reads the report again instead of using rows read in the last few minutes. In the SDK, `use_cache=False`. Not available on reviewers. | Every response has `page`, `perPage`, and `total`, the number of rows across all pages. Request pages until you have `total` rows. A page past the end returns an empty `data`. ```bash cURL theme={"theme":"css-variables"} curl -H "Authorization: Bearer $LABELBOX_API_KEY" \ "https://api.labelbox.com/api/v1/performance/workspace/labelers?startDate=2026-01-01&endDate=2026-01-31&perPage=100&page=2" ``` Keep these in mind when you read a whole table: * **Leave the cache on.** The pages of `workspace/labelers` are cut from one reading of the report, kept for five minutes, so they agree with each other and the rows are in a fixed order. `cachedAt` says when that reading was taken. With `useCache=false` every page reads the report again. * **Sorting.** Every table sorts by the figures in its rows, such as `labelsCreated` or `avgTimePerLabel`, except `avgRating`. Without `sort`, the project tables come ordered by email, as on the Performance dashboard, and rows that tie under a sort are in that order too. A masked email keeps the place of the real one. `workspace/labelers` comes ordered by project, then member. * **Metrics are not paged.** A metric returns at most one bucket per day of the period, and the period is limited to 366 days. ## Access and privacy The API shows you what the pages show you. * **Your own performance only.** If you can only view your own performance on a project, every project report returns only your data, whatever `userIds` you pass. * **Workspace reports.** Without `projectIds`, you need permission to view performance across your organization. With `projectIds`, you need performance access to each project you name. * **Projects you cannot see.** A project you have no performance access to returns `404`, the same as a project that does not exist. * **Splitting by member type or owner.** On `workspace/metrics`, `memberType` and `ownerOrganizationIds` need permission to view all performance in every project you name in `projectIds`. A project found through its owner that you cannot report on is left out and listed in `skippedProjectIds`. * **Masked emails.** The email of a member you may not identify is replaced with a masked value, as on the pages. For example, a workforce provider's members appear masked to the project's owner, and the owner's members appear masked to the provider. * **Masked project and organization names.** A workforce provider does not see the names of the projects shared with it or of the organizations that own them. In `workspace/labelers`, `projectName` and `projectOrganizationName` are replaced with masked values for those projects. The IDs are unchanged. * **Restricted API keys.** A key created with a limited set of permissions needs a performance permission, and is limited to the ones it has. A key that only has permission to view your own performance returns only your own. ## Data freshness The API reads the same data as the Performance dashboard and the Monitor, so it is never more current than those pages. That data is rebuilt periodically, not continuously: the current day's figures can trail real activity by anything from a quarter of an hour to over an hour. A response served from the cache can be up to five minutes older again. `generatedAt` is when the response was produced. `cachedAt`, on the labeler tables, is set when the rows came from a result computed in the last few minutes. Neither tells you how recent the underlying activity data is, and no response carries an as-of time for it. Two things follow for the current day: * **Today's bucket keeps growing.** Read settled days for totals you will not revisit. * **Charts and tables do not refresh together.** For settled days a metric and the labeler table normally add up to the same total. For the current day they can differ until both have caught up. ## Limits | Limit | Value | | - | - | | Period | 366 days per request | | `userIds`, `batchIds` | 100 IDs each | | `userGroupIds` | 25 groups, with at most 5,000 members between them | | `projectIds` | 50 projects | | `ownerOrganizationIds` | 100 organizations | | `perPage` | 100 rows | Requests count toward your [API rate limit](/horizon/guides/limits), like the rest of the Labelbox API. ## Reliability * **Requests are safe to repeat.** Every endpoint is a `GET` and changes nothing, so you can retry any request. * **Retry `5xx` responses and timeouts** with a growing delay between attempts. The Python SDK does this for you, for up to four minutes, and tries once more after a request that timed out. When it stops retrying, it raises the error that kept happening. * **Do not retry `4xx` responses** without changing the request. For `429`, wait before sending more. * **Allow for long requests.** Reports are computed when you ask, and a long period over a large workspace can take a while. The Python SDK waits up to 120 seconds for a response. * **Prefer fewer, wider requests.** One request for a month at `interval=day` costs less than a request for each day. To read every member's totals for a project as a file, use [Download the report](#download-the-report). * **Expect new fields.** Responses can gain fields over time. Ignore the ones you do not know. The Python SDK does. ## Errors | Status | Meaning | Python SDK exception | | - | - | - | | `400` | A parameter is invalid. The response lists each one in `details`. | `InvalidQueryError` | | `401` | The API key is missing or invalid. | `AuthenticationError` | | `403` | Your role or API key does not include the permission, or you named a project in a split by member type or owner where you cannot view everyone's performance. | `AuthorizationError` | | `404` | The metric does not exist, or the project does not exist or is not visible to you. | `ResourceNotFoundError` | | `429` | You have made too many requests. | `ApiLimitError` | | `500` | The report could not be produced. Try again. | `InternalServerError` | | `503` | Performance reports are under maintenance. Try again later. | `InternalServerError` | The Python SDK raises `TimeoutError` when a request gets no response in time, and `NetworkError` when it cannot connect. All of these exceptions are in `lbox.exceptions`. It raises Python's `ValueError`, before sending anything, for a filter given as an empty list. A `400` response names each parameter that was refused: ```json theme={"theme":"css-variables"} { "statusCode": 400, "error": "Bad Request", "message": "Invalid query parameters", "details": [ { "parameter": "endDate", "message": "must not be before startDate" } ] } ``` # Project Source: https://docs.labelbox.com/horizon/developer-guides/project Developer guide for creating and modifying projects using the Python SDK. ## Client ```python Python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(api_key="") ``` ## Create a project When creating a project, specify a `media_type` using one of the following values: * `lb.MediaType.Audio` * `lb.MediaType.Conversational` * `lb.MediaType.Document` * `lb.MediaType.Geospatial_Tile` * `lb.MediaType.Html` * `lb.MediaType.Image` * `lb.MediaType.Simple_Tile` * `lb.MediaType.Text` * `lb.MediaType.Video` ```python Create a project theme={"theme":"css-variables"} # Create a new project project = client.create_project( name="", description="", # optional media_type=lb.MediaType.Image # specify the media type ) ``` ## Get a project ```python Python theme={"theme":"css-variables"} project = client.get_project("") # alternatively, you can get a dataset by name project = client.get_projects(where=lb.Project.name == "").get_one() ``` ## Methods ### Create a batch When creating a batch to send to a project, one of either `global_keys` or `data_rows` must be supplied as an argument. If using the `data_rows` argument, you can supply either a list of data row IDs or a list of `DataRow` class objects. Optionally, you can supply a `priority`, ranging from 1 (highest) to 5 (lowest), for which the batch should be labeled. This will determine the order in which the included data rows appear in the labeling queue compared to other batches. If no value is provided, the batch will assume the lowest priority. For more details, see [Batch](/horizon/developer-guides/batch). ```python Python theme={"theme":"css-variables"} project.create_batch( name="", global_keys=["key1", "key2", "key3"], priority=5, ) # if the project uses consensus, you can optionally supply a dictionary with consensus settings # if provided, the batch will use consensus with the specificed coverage and votes project.create_batch( name="", data_rows=["", ""], priority=1, consensus_settings={"number_of_labels": 3, "coverage_percentage": 0.1} ) ``` #### Create multiple batches The `project.create_batches()` method accepts up to 1 million data rows. Batches are chunked into groups of 100k data rows (if necessary), which is the maximum batch size. This method takes in a list of either data row IDs or `DataRow`objects into a `data_rows` argument or global keys into a `global_keys` argument, but both approaches cannot be used in the same method. Batches will be created with the specified `name_prefix` argument and a unique suffix to ensure unique batch names. The suffix will be a 4-digit number starting at `0000`. For example, if the name prefix is `demo-create-batches-` and three batches are created, the names will be `demo-create-batches-0000`, `demo-create-batches-0001`, and `demo-create-batches-0002`. This method will throw an error if a batch with the same name already exists. ```python Python theme={"theme":"css-variables"} task = project.create_batches( name_prefix="demo-create-batches-", global_keys=global_keys, priority=5 ) print("Errors: ", task.errors()) print("Result: ", task.result()) ``` #### Create batches from a dataset If you wish to create batches in a project using all the data rows of a dataset, instead of gathering global keys or IDs and iterating over subsets of data rows, you can use the `project.create_batches_from_dataset()` method. This method takes in a dataset ID and creates a batch (or batches if there are more than 100k data rows) comprised of all data rows not already in the project. The same logic applies to the `name_prefix` argument and the naming of batches as described in the section immediately above. ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") task = project.create_batches_from_dataset( name_prefix="demo-dataset-", dataset_id=dataset.uid, priority=5 ) print("Errors: ", task.errors()) print("Result: ", task.result()) ``` ### Get the batches ```python Python theme={"theme":"css-variables"} # get the batches (objects of the Batch class) batches = project.batches() # inspect one batch next(batches) # inspect all batches for batch in batches: print(batch) # for ease of use, you can convert the paginated collection to a list list(batches) ``` ### Get a batch You can retrieve the batch of a particular project with`client.get_batch()`. ```python Python theme={"theme":"css-variables"} project_id = "" batch_id = "" # returns a Batch object batch = client.get_batch(project_id, batch_id) ``` ### Connect an ontology ```python Python theme={"theme":"css-variables"} # the argument must be an object of the Ontology class project.connect_ontology(ontology) ``` ### Get the members and their roles The scope of a member is provided by the attribute `access_from` from the class `ProjectMember`. It can have one of the following values: * ORGANIZATION: project membership is derived from the organization role * PROJECT\_MEMBERSHIP: access is given specifically to the project * USER\_GROUP: access is given via a group ```python Python theme={"theme":"css-variables"} # get the members (objects of the ProjectMember class with relationships to a User and Role) members = project.members() # inspect one member member = next(members) print(member.user(), member.role(), member.access_from) # Display member info: print(member.user().uid, member.user().email, member.role().name, access_from, sep="\t") # inspect all members for member in members: print(member.user(), member.role(), access_from) # for ease of use, you can convert the paginated collection to a list list(members) ``` ### Upload labeling instructions Note that if the ontology connected to your project is connected to other projects, calling this method will attach the instructions to those projects as well. ```python Python theme={"theme":"css-variables"} # must be a PDF or HTML file project.upsert_instructions("") ``` ### Get the workflow tasks ```python Python theme={"theme":"css-variables"} # get the task queues (relationship to TaskQueue objects) task_queues = project.task_queues() # inspect all task queues for task_queue in task_queues: print(task_queue) ``` ### Move data rows to a workflow task Note that data rows need labels attached before being moved to a different workflow task. They can not be moved from "Initial Labeling." ```python Python theme={"theme":"css-variables"} project.move_data_rows_to_task_queue( data_row_ids=lb.GlobalKeys(["", ""]), # Use "lb.UniqueIds" for "" # Optional: If not included, defaults to "None", which moves data rows to the "Done" bucket task_queue_id="" ) ``` ### Modify data row priority Once a batch has been added to a project, you can set [the priority](/horizon/guides/labeling-queue) of its data rows. To do so, define a list of label parameter overrides (LPOs), which are [tuples](https://docs.python.org/3/library/ast.html?highlight=tuple#ast.Tuple) that set the priority for individual data rows. Each override has three values: an object of the `DataRow` class or a `DataRowIdentifier` object, the new priority. All values must be integers that match the range of the list. The priority is an integer between `-2,147,483,648` to `2,147,483,647`. The lowest value has the highest priority. Override lists are limited to 1,000 items; larger lists trigger an error. Once the override list is defined, pass it to `project.set_labeling_parameter_overrides` to change the priority of the corresponding data rows. Use `project.labeling_parameter_overrides` to get a list of [data row priorities](#get-data-row-priority) and `project.update_data_row_labeling_priority` to update existing data row priority. #### Set data row priority ```python Python (add LPOS) theme={"theme":"css-variables"} # Extract the global keys export_params = { "data_row_details": True } export_task = project.export(params=export_params) # Wait until the export task is complete export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] global_keys = [item["data_row"]["global_key"] for item in export_json] # Add LPOs lpos = [] priority=1 # With global keys for global_key in global_keys: lpos.append((lb.GlobalKey(global_key), priority)) priority += 1 # With data row ids # data_row_ids = ["clw7jlmav35yn0768xrpawwrc", "clw7jlmav35yo0768a5amfztu"] # for dr_id in data_row_ids: # lpos.append((lb.UniqueId(dr_id), priority)) # priority+=1 # With data row objects # data_rows = [data_row_1, data_row_2] # for data_row in data_rows: # lpos.append((data_row, priority)) # priority+=1 # Set data row priorities project.set_labeling_parameter_overrides(lpos) # Check results project_lpos = list(project.labeling_parameter_overrides()) for lpo in project_lpos: print(lpo) ``` #### Update data row priority ```python Python (update LPOs) theme={"theme":"css-variables"} # Update LPOs # With global keys global_keys = ["global_key1", "global_key2"] project.update_data_row_labeling_priority(data_rows=lb.GlobalKeys(global_keys), priority=1) # With data row ids # data_row_ids = ["clw7jlmav35yn0768xrpawwrc", "clw7jlmav35yo0768a5amfztu"] # project.update_data_row_labeling_priority(data_rows=lb.UniqueIds(data_row_ids), priority=1) # With data row objects # data_rows = [data_row_1, data_row_2] # project.update_data_row_labeling_priority(data_rows=data_rows, priority=1) # Check results project_lpos = list(project.labeling_parameter_overrides()) for lpo in project_lpos: print(lpo) ``` ### Add project tags ```python Python theme={"theme":"css-variables"} tags = project.update_project_resource_tags(["", ""]) ``` ### Get project tags ```python Python theme={"theme":"css-variables"} tags = project.get_resource_tags() ``` The `tags` variable is a list where each element is an object of type `ResourceTag` with the attributes, `uid`, `color`(ex: "008856") and`text`. ### Get the project overview With `project.get_overview(details)`you can obtain some of the data from the Project Overview tab. #### Output The boolean parameter `details` will change the output to display the distribution of data rows between the queues. When `details` is to false: | Attribute | Description | Name in the Overview tab | | - | - | - | | `to_label` | Number of data rows that are yet to be labeled | To Label | | `in_review` | Number of data rows to be reviewed | In Review | | `in_rework` | Number of data rows to be reworked | In Rework | | `skipped` | Number of skipped data rows | Skipped | | `done` | Number of data rows marked as Done | Done | | `issues` | Number of data rows with associated issues | Issues | | `labeled` | Number of data rows with one or more labels | - | | `total_data_rows` | Total number of data rows in the project | - | When `details` is set to true, the output will be the same as before, except for the following: | Attribute | Description | | - | - | | `in_review` | `data`: List of task queues in review with the associated number of data rows | | | `total`: Number of data rows to be reviewed | | `in_rework` | `data`: List of task queues in rework with the associated number of data rows | | | `total`: Number of data rows to be reworked | #### Equivalences The following are equal: | Attribute | Sum of attributes | | - | - | | `overview.labeled` | `overview.in_review + overview.in_rework + overview.done` | | `overview.total_data_rows` | `overview.to_label + overview.in_review + overview.in_rework + overview.done` | #### Example project overview ```python Python theme={"theme":"css-variables"} # Example without details overview = project.get_overview() # Selection of some attributes print(f""" To label:\t{overview.to_label / overview.total_data_rows:.2%} Labeled:\t{overview.labeled / overview.total_data_rows:.2%} """) # To label: 18.37% # Labeled: 81.63% # Example with details detailed_overview = project.get_overview(details=True) # Task queues in review print(f""" Number of data rows {detailed_overview.total_data_rows}, In review:, \tQueues {detailed_overview.in_review["data"]}, \tNumber of data rows {detailed_overview.in_review["total"]}, In rework: \tQueues {detailed_overview.in_rework["data"]}, \tNumber of data rows {detailed_overview.in_rework["total"]} """, sep="\n") # Number of data rows 23220, # In review:, # Queues [{'Initial review task': 7}], # Number of data rows 7, # In rework: # Queues [{'Rework (all rejected)': 1830}], # Number of data rows 1830 ``` ### Manage bulk imports Use the following methods to manage bulk imports: * `project.get_mal_prediction_imports()` to retrieve the list of MAL import jobs. * `project.get_label_imports()` to to retrieve the list of ground-truth import jobs. * `MALPredictionImport.delete()` to delete a MAL import. ### Delete label imports The `MALPredictionImport.delete()` method can only delete MAL imports. To delete a ground-truth label, use the [Data Rows tab](/horizon/guides/data-rows-activity) on the web platform. Deleting an import is permanent and can't be undone. ### Export a project For complete details, see [Export overview](/horizon/developer-guides/export-overview#export-data-rows-from-a-project). ```python Export theme={"theme":"css-variables"} # The return type of this method is an `ExportTask`, which is a wrapper of a`Task` # Most of `Task` features are also present in `ExportTask`. export_params= { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters= { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "label_created_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "InReview", "batch_ids": ["batch_id_1", "batch_id_2"], "data_row_ids": ["data_row_id_1", "data_row_id_2"], "global_keys": ["global_key_1", "global_key_2"] } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` ### Export issues and comments ```python Python theme={"theme":"css-variables"} import requests url = project.export_issues() issues = requests.get(url).json() # optionally, you can export only the open or resolved issues open_issues_url = project.export_issues(status="Open") resolved_issues_url = project.export_issues(status="Resolved") ``` ### Duplicate a project See the section [Duplicate a project](/horizon/guides/create-a-project#duplicate-a-project) for the scope of this method. ```python Python theme={"theme":"css-variables"} cloned_project = project.clone() ``` ### Update a project ```python Python theme={"theme":"css-variables"} project.update(name="") ``` ### Delete a project **Deleting a project cannot be undone** This method deletes the project along with all labels made in the project. This action cannot be reverted. ```python Python theme={"theme":"css-variables"} project.delete() ``` ## Attributes ### Get the basics ```python Python theme={"theme":"css-variables"} # name (str) project.name # description (str) project.description # updated at (datetime) project.updated_at # created at (datetime) project.created_at # last activity time (datetime) project.last_activity_time # number of required labels per consensus data row (int) project.auto_audit_number_of_labels # default percentage of consensus data rows per batch (float) project.auto_audit_percentage # created by (relationship to User object) user = project.created_by() # organization (relationship to Organization object) organization = project.organization() ``` ### Get the ontology ```python Python theme={"theme":"css-variables"} # get the ontology connected to the project (relationship to Ontology object) ontology = project.ontology() ``` ### Get the benchmarks ```python Python theme={"theme":"css-variables"} # get the benchmarks (relationship to Benchmark objects) benchmarks = project.benchmarks() # inspect one benchmark next(benchmarks) # inspect all benchmarks for benchmark in benchmarks: print(benchmark) # for ease of use, you can convert the paginated collection to a list list(benchmarks) ``` ### Get the webhooks ```python Python theme={"theme":"css-variables"} # get the webhooks connected to the project (relationship to Webhook objects) webhooks = project.webhooks() # inspect one webhook next(webhooks) # inspect all webhooks for webhook in webhooks: print(webhook) # for ease of use, you can convert the paginated collection to a list list(webhooks) ``` ### Get data row priority Use `project.labeling_parameter_overrides` to get a list of labeling parameter overrides (LPOs), which define the priority for each label in the override list. Use `set_labeling_parameter_overrides` and `update_data_row_labeling_priority` to [modify data row priority](#set-data-row-priority). ```python Python theme={"theme":"css-variables"} # gets the LPOs created in the project (relationship to LabelingParameterOverride objects) lpos = project.labeling_parameter_overrides() # inspect one LPO next(lpos) # inspect all LPOs for lpo in lpos: print(lpo) # for ease of use, you can convert the paginated collection to a list list(lpos) # Get the data row id for lpo in lpos: print(lpo) print("Data row:", lpo.data_row().uid) ``` ### Get the number of labels Use `project.get_label_count()` to return the sum of labels in the different task queues of a project. ```python Python theme={"theme":"css-variables"} # Return the number of project.get_label_count() ``` ## Copy data rows and labels To copy our data rows and labels to a different project from a source project, use the `client.send_to_annotate_from_catalog` method with our Labelbox client. Send to Annotate does not currently support consensus projects. ### Parameters When you send data rows with labels to our destination project, you may choose to include or exclude certain parameters inside a Python dictionary, at a minimum, a `source_project_id` will need to be provided: * `source_project_id` * The id of the project where our data rows with labels will originate. * `annotation_ontology_mapping` * A dictionary containing the mapping of the source project's ontology feature schema IDs to the destination project's ontology feature schema IDs. If left empty, only the data rows with no labels will be sent to our destination project. * `{"" : ""}` * `exclude_data_rows_in_project` * Excludes data rows that are already in the project. * `override_existing_annotations_rule` * The strategy defines how to handle conflicts in classifications between the data rows that already exist in the project and incoming labels from the source project. * Defaults to `ConflictResolutionStrategy.KeepExisting` * Options include: * `ConflictResolutionStrategy.KeepExisting` * `ConflictResolutionStrategy.OverrideWithPredictions` * `ConflictResolutionStrategy.OverrideWithAnnotations` * `param batch_priority` * The priority of the batch. ```python Python theme={"theme":"css-variables"} from labelbox.schema.conflict_resolution_strategy import ConflictResolutionStrategy send_to_annotate_params = { "source_project_id": project.uid, "annotations_ontology_mapping": annotation_ontology_mapping, # to be defined "exclude_data_rows_in_project": False, "override_existing_annotations_rule": ConflictResolutionStrategy.OverrideWithPredictions, "batch_priority": 5, } # Get task id to workflow you want to send data rows. If sent to initial labeling queue, labels will be pre-labels. queue_id = [queue.uid for queue in destination_project.task_queues() if queue.queue_type == "MANUAL_REVIEW_QUEUE" ][0] task = client.send_to_annotate_from_catalog( destination_project_id=destination_project.uid, task_queue_id=queue_id, # ID of workflow task, set ID to None if you want to send data rows with labels to the Done queue. batch_name="Prediction Import Demo Batch", data_rows=lb.GlobalKeys( global_keys # Provide a list of global keys from source project ), params=send_to_annotate_params ) task.wait_till_done() print(f"Errors: {task.errors}") ``` # Prompt and response projects Source: https://docs.labelbox.com/horizon/developer-guides/prompt-and-response-projects How to set up a prompt and response type project Prompt and response creation projects are set up differently than other Labelbox projects. They have unique methods and modifications to existing methods. This guide will showcase the differences and provide an example workflow. ## Before you start The below imports are needed to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb ``` ## API key and client Please provide a valid API key below to connect to the Labelbox client properly. For more information, please review the [Create API key guide](/horizon/developer-guides/create-api-key). ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(api_key=API_KEY) ``` ## Create a prompt and response ontology You can create ontologies for prompt and response projects in the same way as other project ontologies using two methods: `client.create_ontology` and `client.create_ontology_from_feature_schemas`. For response creation projects the only difference between other projects is the media\_type for the project needs to be set to `lb.MediaType.Text`. For prompt and prompt response creation projects you need to include their respective media type: `lb.MediaType.LLMPromptCreation` and `lb.MediaType.LLMPromptResponseCreation`. Additional you also need to provide an additional parameter of `ontology_kind`, which needs to be set to `lb.OntologyKind.ResponseCreation` this is only applicable for prompt and prompt response creation projects. ### Option A: `create_ontology` Typically, you create ontologies and generate the associated features simultaneously. Below is an example of creating an ontology for your prompt and response projects using supported classifications; for information on supported annotation types, visit our [prompt and response generation](/horizon/guides/prompt-and-response-generation-editor) guide. Depending on whether you are creating a prompt, a response, or both, certain classifications in your ontologies might not be necessary. See [supported annotation types](/horizon/guides/prompt-and-response-generation-editor#supported-prompt-formats) for more information. ```python Prompt and response creation theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[], classifications=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.PROMPT, name="prompt text", character_min = 1, # Minimum character count of prompt field (optional) character_max = 20, # Maximum character count of prompt field (optional) ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_CHECKLIST, name="response checklist feature", options=[ lb.Option(value="option 1", label="option 1"), lb.Option(value="option 2", label="option 2"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_RADIO, name="response radio feature", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_TEXT, name="response text", character_min = 1, # Minimum character count of response text field (optional) character_max = 20, # Maximum character count of response text field (optional) ) ], ) # Create ontology ontology = client.create_ontology( "Prompt and response ontology", ontology_builder.asdict(), media_type=lb.MediaType.LLMPromptResponseCreation, ) ``` ```python Prompt creation theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[], classifications=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.PROMPT, name=f"prompt text", character_min = 1, # Minimum character count of prompt field (optional) character_max = 20, # Maximum character count of prompt field (optional) ) ], ) # Create ontology ontology = client.create_ontology( "Prompt ontology", ontology_builder.asdict(), media_type=lb.MediaType.LLMPromptCreation, ) ``` ```python Response creation theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[], classifications=[ lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_CHECKLIST, name="response checklist feature", options=[ lb.Option(value="option 1", label="option 1"), lb.Option(value="option 2", label="option 2"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_RADIO, name="response radio feature", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer"), ], ), lb.PromptResponseClassification( class_type=lb.PromptResponseClassification.Type.RESPONSE_TEXT, name=f"response text", character_min = 1, # Minimum character count of response text field (optional) character_max = 20, # Maximum character count of response text field (optional) ) ], ) # Create ontology ontology = client.create_ontology( "Response ontology", ontology_builder.asdict(), media_type=lb.MediaType.Text, ontology_kind=lb.OntologyKind.ResponseCreation ) ``` ### Option B: `create_ontology_from_feature_schemas` You can also create ontologies using feature schema IDs. This makes your ontologies come with existing features instead of generating new features. You can get these features by going to the **Schema** tab inside Labelbox. ```python Prompt and response creation theme={"theme":"css-variables"} ontology = client.create_ontology_from_feature_schemas( "Prompt and response ontology", feature_schema_ids=[" ## Create response creation projects You can create response creation projects using `client.create_response_creation_project`, which uses the same parameters as `client.create_project` but provides better validation to ensure the project is set up correctly. Additionally, you need to import text data rows to be used as prompts. ```python Python theme={"theme":"css-variables"} project = client.create_response_creation_project( name="", description="", # optional ) ``` ## Create prompt response and prompt creation projects When creating a prompt response or prompt creation project using `client.create_prompt_response_generation_project`, you do not need to create data rows because they are generated automatically. This method takes the same parameters as the traditional `client.create_project` but with a few specific additional parameters. ### Parameters The `client.create_prompt_response_generation_project` method requires the following parameters: * `create_prompt_response_generation_project` parameters: * `name` (required): The name of your new project. * `description`: An optional description of your project. * `media_type` (required): The type of assets this project accepts. Can be either `lb.MediaType.LLMPromptCreation` or `MediaType.LLMPromptResponseCreation`, depending on the project type you are setting up. * `dataset_name`: The name of the dataset where the generated data rows will be located. Include this parameter only if you want to create a new dataset. * `dataset_id`: An optional dataset ID of an existing Labelbox dataset. Include this parameter if you want to append it to an existing dataset. * `data_row_count`: The number of data row assets that will be generated and used with your project. ```python Prompt and response creation theme={"theme":"css-variables"} project = client.create_prompt_response_generation_project( name="Demo prompt response project", media_type=lb.MediaType.LLMPromptResponseCreation, dataset_name="Demo prompt response dataset", data_row_count=100, ) # Setup project with ontology created above project.connect_ontology(ontology) ``` ```python Prompt creation theme={"theme":"css-variables"} project = client.create_prompt_response_generation_project( name="Demo prompt project", media_type=lb.MediaType.LLMPromptCreation, dataset_name="Demo prompt dataset", data_row_count=100, ) # Setup project with ontology created above project.connect_ontology(ontology) ``` ## Exporting prompt response, prompt or response create project Exporting from a prompt and response type project works the same as exporting from other projects. In this example, your export will be empty unless you create labels within the Labelbox platform. See [prompt and response export](/horizon/developer-guides/export-prompt-response-data) for a sample export. ```python Export theme={"theme":"css-variables"} # The return type of this method is an `ExportTask`, which is a wrapper of a`Task` # Most of `Task` features are also present in `ExportTask`. export_params = { "attachments": True, "metadata_fields": True, "data_row_details": True, "project_details": True, "label_details": True, "performance_details": True, "interpolated_frames": True, } # Note: Filters follow AND logic, so typically using one filter is sufficient. filters = { "last_activity_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "label_created_at": ["2000-01-01 00:00:00", "2050-01-01 00:00:00"], "workflow_status": "InReview", "batch_ids": ["batch_id_1", "batch_id_2"], "data_row_ids": ["data_row_id_1", "data_row_id_2"], "global_keys": ["global_key_1", "global_key_2"], } export_task = project.export(params=export_params, filters=filters) export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] print("file size: ", export_task.get_total_file_size(stream_type=lb.StreamType.RESULT)) print("line count: ", export_task.get_total_lines(stream_type=lb.StreamType.RESULT)) ``` # Quick start Source: https://docs.labelbox.com/horizon/developer-guides/quick-start Short introduction towards Labelbox Python SDK, geared mainly for new users. This short introduction to Labelbox Python SDK is designed mainly for new users. The example will introduce you to Labelbox Python SDK concepts that you will use commonly. We will demonstrate a simple but common workflow of importing an image data row, setting up a project, and exporting. ## Example: Import data rows to exporting from project ### Before you start We must first install the `Labelbox` library and then import the SDK module. It is recommended to install `"labelbox[data]"` over `labelbox` to obtain all the correct dependencies. We will also be importing the Python UUID library to generate universal unique IDs for the variety of objects that will be created with this notebook. ```shell Shell theme={"theme":"css-variables"} pip install -q "labelbox[data]" ``` ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ## Step 1: Create dataset and import data row Below, we will create a dataset and then attach a publicly hosted image data row. Typically, you would import data rows hosted on a cloud provider (*recommended*) or import them locally. For more information, visit our [import image data section](/horizon/developer-guides/image) in our developer guides. You can find your dataset inside the [*Catalog*](/horizon/guides/catalog-overview) section of Labelbox. ```python Python theme={"theme":"css-variables"} # Create dataset from client dataset = client.create_dataset(name="Quick Start Example Dataset") global_key = str(uuid.uuid4()) # Unique user specified ID # Data row structure image_data_rows = [{ "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "global_key": global_key, "media_type": "IMAGE", }] # Bulk import data row task = dataset.create_data_rows(image_data_rows) # List of data rows task.wait_till_done() print(task.errors) # Print any errors ``` ## Step 2: Creating an ontology Before sending our data row to a labeling project, we must create an ontology. The example below will create a simple ontology with a bounding box tool and a checklist classification feature. For more information, visit the [ontology section](/horizon/developer-guides/ontology) inside our developer guides. ```python Python theme={"theme":"css-variables"} # Bounding box feature object_features = [ lb.Tool( tool=lb.Tool.Type.BBOX, name="regulatory-sign", color="#ff0000", ) ] # Checklist feature classification_features = [ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="Quality Issues", options=[ lb.Option(value="blurry", label="Blurry"), lb.Option(value="distorted", label="Distorted"), ], ) ] # Builder function ontology_builder = lb.OntologyBuilder(tools=object_features, classifications=classification_features) # Create ontology ontology = client.create_ontology( "Ontology from new features", ontology_builder.asdict(), media_type=lb.MediaType.Image, ) ``` ## Step 3: Creating a project and attaching our ontology Now that we have made our ontology, we are ready to create a project where we can label our data row. ```python Python theme={"theme":"css-variables"} # Create a new project project = client.create_project( name="Quick Start Example Project", media_type=lb.MediaType.Image, ) # Attach created ontology project.connect_ontology(ontology) ``` ## Step 4: Sending our data row to our project by creating a batch With our project created, we can send our data rows by creating a batch. Our data rows will start in the initial labeling queue, where labelers are able to annotate our data row. For more information on batches, review the [batches section](/horizon/developer-guides/batch#create-a-batch) of our developer guides. ```python Python theme={"theme":"css-variables"} project.create_batch( name="Quick Start Example Batch" + str(uuid.uuid4()), global_keys=[ global_key ], # Global key we used earlier in this guide to create our dataset priority=5, ) ``` ## Step 5: Exporting from our project We have now successfully set up a project for labeling using only the SDK! From here, you can either label our data row directly inside the [labeling queue](/horizon/guides/labeling-queue) or [import annotations](/horizon/developer-guides/import-image-annotations) directly through our SDK. Below we will demonstrate the final step of this guide by exporting from our project. Since we did not label any data rows or import annotations within this guide, no labels will be presented on our data row. For a full overview of exporting, visit our [export overview](/horizon/developer-guides/export-overview) developer guide. ```python Python theme={"theme":"css-variables"} # Start export from project export_task = project.export() export_task.wait_till_done() # Stream the export using a callback function def json_stream_handler(output: labelbox.BufferedJsonConverterOutput): print(output.json) export_task.get_buffered_stream(stream_type=labelbox.StreamType.RESULT).start(stream_handler=json_stream_handler) # Collect all exported data into a list export_json = [data_row.json for data_row in export_task.get_buffered_stream()] ``` ## Clean up This section serves as an optional clean-up step to delete the Labelbox assets created within this guide. You will need to uncomment the delete methods shown. ```python Python theme={"theme":"css-variables"} # project.delete() # client.delete_unused_ontology(ontology.uid) # dataset.delete() ``` # Fundamental concepts Source: https://docs.labelbox.com/horizon/developer-guides/sdk-fundamental-concepts Overview of concepts used throughout Labelbox-Python SDK. ## DbObject The Labelbox SDK is primarily used to interact with Labelbox's database. The core abstraction used to represent a database entity is a DbObject. It has special attributes called Fields and Relationships that facilitate database queries. Most objects within the Labelbox SDK are DbOjects. You will see `Project` and `Dataset`, for example, among others throughout the documentation. ## Field caching When you fetch an object from the server, the client obtains all field values for that object. When you access that obtained field value, the cached value is returned. There is no round-trip fetch to the server to get the field value you have already fetched. Server-side updates made after the client-side fetch are not auto-propagated, meaning the values returned will still be the cached values. ## Fields Each DbObject has a set of fields. The below sections explain how to access or update one or more fields on a DbObject. ### Fetch DbObject fields must be accessed as attributes (e.g.,`project.name`). Unlike relationships, fields are cached. In this example, `client.get_projects()` returns a `PaginatedCollection` object. You can iterate through the `PaginatedCollection` object and specify the fields you want to be returned in the fetch. To fetch multiple fields from an object, you must separate them by commas. ```python Python theme={"theme":"css-variables"} from labelbox import Client client = Client() projects = client.get_projects() for project in projects: print(project.name, project.uid) ``` ### Update To update a field, use the `DbObject.update()` method to pass the field and the new value. Each data update that uses`DbObject.update()` on the client side immediately performs the same update on the server side. If the client-side update does not raise an exception, you can assume the update was successfully passed on the server side. This example uses the`project.update()` method to change a project's name and description. ```python Python theme={"theme":"css-variables"} project = client.get_project("") project.update(name="Project Name", description="New description") ``` ## Use comparison operators Paginated collections support comparisons for filtering a query. For example, you can use the `get_datasets` method with a `where` parameter with a comparison operator to filter. Any of the standard comparison operators (`==`, `!=`, `>`, `=>`, `<`, `<=`) will work. The `get_datasets` method will give you a `PaginatedCollection`. You can iterate over the `PaginatedCollection` to get your dataset. ```python Python theme={"theme":"css-variables"} from labelbox import Dataset datasets_x = client.get_datasets(where=Dataset.name == "X") for x in datasets_x: print(x) ``` ### Use combined comparisons Combine comparisons using logical expressions to filter projects. Currently, the `where` clause supports the logical AND operator. ```python Python theme={"theme":"css-variables"} from labelbox import Project, Client client = Client() projects = client.get_projects(where=(Project.name == "X") & (Project.description == "Y")) for x in projects: print(x) ``` ## Pagination Some API calls return a large number of results. To prevent too many results from being returned at once, the Labelbox API limits the number of returned objects, and the Python SDK automatically creates a `PaginatedCollection` instead. 1. For both the top-level object fetch, `client.get_projects()`, and the relationship call, `project.batches()`, a `PaginatedCollection` object is returned. This `PaginatedCollection` object takes care of the paginated fetching. ```python Python theme={"theme":"css-variables"} projects = client.get_projects() type(projects) ``` ```python Python theme={"theme":"css-variables"} project = projects[0] datasets = project.batches() type(batches) for batch in batches: batch.name ``` 2. Note that nothing is fetched immediately when the `PaginatedCollection` object is created. 3. Round-trips to the server are made only as you iterate through a `PaginatedCollection`. In this code block, that happens when a `list` is initialized with a `PaginatedCollection`, and when a `PaginatedCollection` is iterated over in a for-loop. 4. You cannot get a count of objects in the relationship from a PaginatedCollection nor access objects within it like a Python list (using squared-bracket indexing). You can only iterate over it. ### Iterate over PaginatedCollection Be careful about converting a `PaginatedCollection` into a list. This will cause all objects in that collection to be fetched from the server. In cases when you need only some objects, you can use two convenient methods: `get_one()` and `get_many(n)`. ```python Python theme={"theme":"css-variables"} data_row = dataset.data_rows().get_one() # returns first result # or data_rows = dateset.date_rows.get_many(10) # returns list of 10 first results ``` You can still iterate over a PaginatedCollection object as with a normal iterator object: ```python Python theme={"theme":"css-variables"} data_rows = dataset.data_rows() for data_row in data_rows: # your business logic pass ``` # Catalog slice Source: https://docs.labelbox.com/horizon/developer-guides/slice How to programmatically retrieve data from a Catalog slice through the SDK ## Get Catalog slices via SDK You can programmatically retrieve a slice's data rows and all associated information via our Python SDK. From there, you can use Catalog to inspect the data rows you retrieved via the SDK visually. Retrieving a slice programmatically is a convenient way to curate a new batch or a model run dataset directly from a saved slice. # End-to-end example: create and retrieve Catalog slices ### Before you start You must import these libraries to use the code examples in this section ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid ``` ### Replace with your API key ```python Python theme={"theme":"css-variables"} API_KEY = "" # To get your API key go to: Workspace settings -> API -> Create API Key client = lb.Client(api_key=API_KEY) ``` ## Create a Catalog slice Currently, we do not support creating slices through the SDK; for the purpose of this demo, we will create a catalog slice through the UI. Navigate to the **Catalog** section of the Labelbox Platform, and select **All datasets** or a particular dataset you would like to create a slice from. Navigate to **Search your data** dropdown menu or use a **similarity search** to create a filter. Hit **Enter** and select **Save slice** Give the slice name and select **Save** Copy the **Slice ID** Paste **Slice ID** ```python Python theme={"theme":"css-variables"} catalog_slice_id = "" ``` ### Get Catalog Slice ```python Get Catalog slices by ID theme={"theme":"css-variables"} catalog_slice = client.get_catalog_slice(catalog_slice_id) ``` ```python Get Catalog slices by name theme={"theme":"css-variables"} # May return a list of slices if multiple slices have the same name catalog_slice = client.get_catalog_slice(name="slice to search") ``` ```python Get all Catalog slices theme={"theme":"css-variables"} catalog_slices = client.get_catalog_slices() ``` ### Obtain Data Row IDs and Data Row objects from the Catalog slice ```python Python theme={"theme":"css-variables"} # Get a data row id slice_data_rows_ids = catalog_slice.get_data_row_ids() # Get a data row objects for data_row_id in slice_data_rows_ids: print(client.get_data_row(data_row_id)) ``` ### Obtain Data Row identifiers Data row identifiers are objects that contain both the data row ID(s) and global keys. ```python Python theme={"theme":"css-variables"} data_row_identifiers = catalog_slice.get_data_row_identifiers() drids = [dr for dr in data_row_identifiers] # get both global keys and data row ids # and utilize the hash method to combine both global keys and data row ids into a dictionary for dr in drids: print(f"Data row: {dr.id}, Global Key: {dr.global_key}, dr_gk: {dr.to_hash()}") ``` ## Curate a batch from a Catalog slice via SDK You can create a new batch from your slice or create a random sample from a slice using our Python SDK. See the Python example below to learn how to do this. ```python Python theme={"theme":"css-variables"} # Optional: sample Data rows from your Slice sampled_data_row_ids = random.sample(slice_data_rows_ids, 5) batch = project.create_batch( "test batch", # name of the batch sampled_data_row_ids, # list of Data Rows 1 # priority between 1-5 ) ``` You can append data rows to your model runs for inference from your slice. See the Python example below to learn how to do this. ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(list(slice_data_rows_ids)) ``` # Task objects Source: https://docs.labelbox.com/horizon/developer-guides/tasks Summary of task objects [`Task`](https://labelbox-python.readthedocs.io/en/latest/task.html) objects represent operations you performed using the SDK or the UI, such as importing and exporting annotations. The `Task` object can make your workflow synchronous, especially for longer operations. `DataUpsertTask` is a subclass of `Task` that represents data row upsert operations. ## Methods The main method associated with a task object is `wait_till_done`, which waits until the task is completed by periodically querying the server to update the task attributes. This method takes two parameters: 1. `timeout_seconds`: Represents the maximum time `wait_till_done` can block your code. The default value is five minutes, which should be increased for longer tasks. 2. `check_frequency`: Defines how often your task queries the server to update the task attributes, which defaults to two seconds. This is useful to limit the amount of API calls against our server in a given time frame. ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) task.wait_till_done(timeout_seconds=5_000, check_frequency=10) ``` ### Task Results The task result is a collection of details, typically information on the data rows associated with the task. ```python Python theme={"theme":"css-variables"} dataset = client.get_dataset("") task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) task.wait_till_done() print(task.result) # prints the data rows associated with the create_data_rows operation ``` ### Warning Large results (over 150,000 data rows) can take up to 10 mins to process. ### Task Errors The task errors work similarly to the task results but contain information on any associated errors. ```python errors theme={"theme":"css-variables"} dataset = client.get_dataset("") task = dataset.create_data_rows([{ "row_data": item_url, "global_key": "" }]) task.wait_till_done() print(task.errors) # prints the data rows associated with the create_data_rows operation ``` ## Get tasks You can view tasks created in the past 30 days in the UI on the [Notifications](https://app.labelbox.com/notifications) page. You can also fetch a specific task by its ID using `client.get_task_by_id()`. To retrieve tasks programmatically, you can query all tasks in an organization or filter them by status using the `where` parameter on `organization.tasks`. Task statuses are exposed via `Task.status_as_enum`. Valid statuses are: * `TaskStatus.In_Progress` * `TaskStatus.Complete` * `TaskStatus.Canceling` * `TaskStatus.Canceled` * `TaskStatus.Failed` ```python Get task by ID theme={"theme":"css-variables"} id = "" # Your task ID string client.get_task_by_id(id) ``` ```python Get tasks by status theme={"theme":"css-variables"} # All tasks tasks = organization.tasks() # Tasks in progress org_tasks_in_progress = organization.tasks( where=Task.status_as_enum == TaskStatus.In_Progress ) # Failed tasks org_task_failed = organization.tasks( where=Task.status_as_enum == TaskStatus.Failed ) ``` ### Export tasks are not supported Currently, you can't retrieve export tasks using this method. ## Cancel tasks You can cancel the following types of tasks before they complete using their task id strings: * [Streamable export tasks](/horizon/developer-guides/export-overview#cancel-export-tasks) * [Sending to annotate](/horizon/developer-guides/foundry-app-developer-guide#step-7-send-annotations-from-catalog-to-annotate) * [Sending to experiment](/horizon/developer-guides/model#add-data-rows-to-a-model-run) * Bulk-updating queue priority * Classification creation * Agreement backfill for benchmark and consensus ```python Python theme={"theme":"css-variables"} # Cancel the task before it completes success = client.cancel_task(id) assert success is True # Verify the task was cancelled cancelled_task = client.get_task_by_id(id) assert cancelled_task.status in ["CANCELING", "CANCELED"] ``` # Upload conversational text predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-conversational-text-predictions How to upload predictions on conversational text data in a model run and sample upload formats. ## Supported predictions To upload predictions in Labelbox, you need to create a predictions payload. In this section, we provide this payload for every supported prediction type. Labelbox supports two formats for the predictions payload: * Python annotation types (recommended) * NDJSON Both are described below. ### Entity ```python Python annotation theme={"theme":"css-variables"} ner_prediction = lb_types.ObjectAnnotation( name="ner", confidence=0.5, value=lb_types.ConversationEntity( start=0, end=8, message_id="4" ) ) ``` ```python NDJSON theme={"theme":"css-variables"} ner_prediction_ndjson = { "name": "ner", "confidence":0.5, "location": { "start": 0, "end": 8 }, "messageId": "4" } ``` ### Global classification: Radio ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "second_radio_answer", confidence=0.5)) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_question", "answer": {"name": "second_radio_answer", "confidence": 0.5} } ``` ### Message-based classification: Radio ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_convo", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer", confidence=0.5)), message_id="0" ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_convo", "answer": { "name": "first_radio_answer", "confidence":0.5 }, "messageId": "0", } ``` ### Message-based classification: Free-form text ```python Python annotation theme={"theme":"css-variables"} # Confidence scores are not supported for text predictions text_prediction = lb_types.ClassificationAnnotation( name="text_convo", value=lb_types.Text(answer="the answer to the text questions are right here"), message_id="0" ) ``` ```python NDJSON theme={"theme":"css-variables"} text_prediction_ndjson = { "name": "text_convo", "answer": "the answer to the text questions are right here", "messageId": "0" } ``` ### Message-based classification: Checklist ```python Python annotation theme={"theme":"css-variables"} checklist_prediction= lb_types.ClassificationAnnotation( name="checklist_convo", # must match your ontology feature"s name value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ) ] ), message_id="2" ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { "name": "checklist_convo", "answers": [ {"name": "first_checklist_answer", "confidence":0.5}, {"name": "second_checklist_answer", "confidence":0.5} ], "messageId": "2" } ``` ### Message-based nested classifications: Checklist and radio ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", message_id="10", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 # Confidence scores should be added to the answer )] )) ] )] ) ) nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5 # Confidence scores should be added to the answer ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "messageId": "10", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications" : [ { "name": "sub_checklist_question", "answer": { "name": "first_sub_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer } } ] }] } nested_radio_prediction_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.5, "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer", "confidence": 0.5} }] } } ``` ## End-to-end example: Upload predictions to a model run Here are the steps to upload predictions to a model run: ### Before you start You will need to import these libraries to use the code examples in this section: ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python Python theme={"theme":"css-variables"} global_key = "conversation-1.json" asset = { "row_data": "https://storage.googleapis.com/labelbox-developer-testing-assets/conversational_text/1000-conversations/conversation-1.json", "global_key": global_key, } dataset = client.create_dataset(name="conversational_annotation_import_demo_dataset") task = dataset.create_data_rows([asset]) task.wait_till_done() print("Errors:", task.errors) print("Failed data rows: ", task.failed_data_rows) ``` ### Step 2: Create/select an ontology for your model predictions For best results, verify your ontology before submitting a model run. Verify that: * Your ontology supports all features and classifications that might be needed during the model run. * Tool names and classification instructions match the `name` and `instructions` fields in your annotation payloads. This helps ensure that feature schemas match your intentions. ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.NER,name="ner") ], classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, scope=lb.Classification.Scope.INDEX, instructions="text_convo"), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, scope=lb.Classification.Scope.INDEX, instructions="checklist_convo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, instructions="radio_convo", scope=lb.Classification.Scope.INDEX, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", scope = lb.Classification.Scope.INDEX, options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ]) ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ]) ] ) ] ) ontology = client.create_ontology("Ontology Conversation Annotations", ontology_builder.asdict()) ``` ### Step 3: Create a model and a model run ```python Python theme={"theme":"css-variables"} # create model model = client.create_model(name="Conversational_model_run_"+ str(uuid.uuid4()), ontology_id=ontology.uid) # create model run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to the model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create the predictions payload Create the prediction payload using the code snippets in the section above. Labelbox supports two formats for the annotations payload: NDJSON and Python annotation types. Both are described below to compose your annotations into labels attached to the data rows. The resulting payload should have exactly the same content for the annotations supported by both formats. #### Python annotations ```python Python theme={"theme":"css-variables"} label_prediction = [] label_prediction.append( lb_types.Label( data= {"global_key": global_key}), annotations=[ ner_prediction, checklist_prediction, text_prediction, radio_prediction, nested_checklist_prediction, nested_radio_prediction ] ) ) ``` #### NDJSON ```python Python theme={"theme":"css-variables"} label_prediction_ndjson = [] for annotations in [ ner_prediction_ndjson, text_prediction_ndjson, checklist_prediction_ndjson, radio_prediction_ndjson, nested_checklist_prediction_ndjson, nested_radio_prediction_ndjson ]: annotations.update({ "dataRow": { "globalKey": global_key } }) label_prediction_ndjson.append(annotations) ``` ### Step 6: Upload the predictions payload to the model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_prediction) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to the model run To send annotations to a model run, we must first import them into a project, create a label payload and then send them to the model run. ```python Python theme={"theme":"css-variables"} # 7.1 Create a labelbox project project = client.create_project(name="Conversational Text Prediction Import Demo", media_type=lb.MediaType.Conversational) project.connect_ontology(ontology) # 7.2 Create a batch to send to the project project.create_batch( "batch_convo_prediction_demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create the annotations payload ner_annotation = lb_types.ObjectAnnotation( name="ner", value=lb_types.ConversationEntity( start=0, end=8, message_id="4" ) ) text_annotation = lb_types.ClassificationAnnotation( name="text_convo", value=lb_types.Text(answer="the answer to the text questions are right here"), message_id="0" ) checklist_annotation= lb_types.ClassificationAnnotation( name="checklist_convo", # must match your ontology feature"s name value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer" ), lb_types.ClassificationAnswer( name = "second_checklist_answer" ) ] ), message_id="2" ) radio_annotation = lb_types.ClassificationAnnotation( name="radio_convo", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer")), message_id="0" ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", message_id="10", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer" )] )) ] )] ) ) nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer" ) ) ) ] ) ) ) # 7.4 Create the label object label = [] label.append( lb_types.Label( data= {"global_key": global_key}, annotations=[ ner_annotation, text_annotation, checklist_annotation, radio_annotation, nested_radio_annotation, nested_checklist_annotation ] ) ) # 7.5 Upload annotations to the project using label import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="text_label_import_job"+ str(uuid.uuid4()), labels=label) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6 Send the annotations to the model run # get the labels from the project model_run.upsert_labels(project_id=project.uid) ``` # Upload document predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-document-prediction How to upload predictions on geospatial data in a model run and sample upload formats. ## Supported predictions To upload predictions in Labelbox, you need to create a predictions payload. In this section, we provide this payload for every supported prediction type. Labelbox supports two formats for the predictions payload: * Python annotation types (recommended) * NDJSON Both are described below. ### Entity The `textSelections` field is required in the payload for each entity annotation. Each `textSelections` item in the list requires the following fields: * A list of `token_ids` for each word in the group of words. * The `group_id` associated with a group of words. * The `page` of the document (1-indexed). Both the `token_ids`and the `group_id` are extracted from the text layer URL attached to the data row. Please follow the end-to-end demo to learn how to construct an entity annotation for documents. ```python Python annotation theme={"theme":"css-variables"} entities_prediction = lb_types.ObjectAnnotation( name="named_entity", confidence=0.5, value= lb_types.DocumentEntity( name="named_entity", textSelections=[ lb_types.DocumentTextSelection( token_ids=[], group_id="", page=1 ) ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} entities_prediction_ndjson = { "name": "named_entity", "confidence": 0.5, "textSelections": [ { "tokenIds": [ "", ], "groupId": "", "page": 1, } ] } ``` ### Classification: Radio ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer", confidence=0.5) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer", "confidence": 0.5} } ``` ### Classification: Checklist ```python Python annotation theme={"theme":"css-variables"} checklist_prediction = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer", confidence=0.5), lb_types.ClassificationAnswer(name = "second_checklist_answer", confidence=0.5) ]) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer", "confidence": 0.5}, {"name": "second_checklist_answer", "confidence": 0.5} ] } ``` ### Bounding box ```python Python annotation theme={"theme":"css-variables"} bbox_dim_1 = { "top": 135.3, "left": 102.771, "height": 109.843, "width": 415.8 } bbox_prediction = lb_types.ObjectAnnotation( name="bounding_box", # must match your ontology feature"s name value=lb_types.DocumentRectangle( start=lb_types.Point(x=bbox_dim_1["left"], y=bbox_dim_1["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dim_1["left"] + bbox_dim_1["width"], y=bbox_dim_1["top"]+ bbox_dim_1["height"]), # x= left + width , y = top + height page=0, unit=lb_types.RectangleUnit.POINTS ) ) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_dim_1 = { "top": 135.3, "left": 102.771, "height": 109.843, "width": 415.8 } bbox_prediction_ndjson = { "name": "bounding_box", "bbox": bbox_dim_1, "page": 0, "unit": "POINTS" } ``` ### Nested classifications: Checklist and radio ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 # Confidence scores should be added to the answer )] )) ] )] ) ) nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5 # Confidence scores should be added to the answer ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications" : [ { "name": "sub_checklist_question", "answer": { "name": "first_sub_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer } } ] }] } nested_radio_prediction_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.5, "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer", "confidence": 0.5} }] } } ``` ### Classification: Free-form text ```python Python annotation theme={"theme":"css-variables"} text_prediction = lb_types.ClassificationAnnotation( name="free_text", # must match your ontology feature"s name value=lb_types.Text(answer="sample text") ) ``` ```python NDJSON theme={"theme":"css-variables"} text_prediction_ndjson = { "name": "free_text", "answer": "sample text" } ``` ### Bounding box with nested classification ```python Python annotation theme={"theme":"css-variables"} bbox_dim = { "top": 226.757, "left": 317.271, "height": 194.229, "width": 249.386 } bbox_with_radio_subclass_prediction = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", confidence=0.5, value=lb_types.DocumentRectangle( start=lb_types.Point(x=bbox_dim["left"], y=bbox_dim["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dim["left"] + bbox_dim["width"], y=bbox_dim["top"] + bbox_dim["height"]), # x= left + width , y = top + height unit=lb_types.RectangleUnit.POINTS, page=1 ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5, classifications=[ lb_types.ClassificationAnnotation( name="second_sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="second_sub_radio_answer", confidence=0.5 ) ) ) ] ) ) ) ] ) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_with_radio_subclass_prediction_ndjson = { "name": "bbox_with_radio_subclass", "classifications": [ { "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer", "confidence": 0.5, "classifications": [ { "name": "second_sub_radio_question", "answer": { "name": "second_sub_radio_answer", "confidence": 0.5} } ] } } ], "bbox":bbox_dim, "page": 1, "unit": "POINTS" } ``` ### Entity with nested classification ```python Python annotation theme={"theme":"css-variables"} ner_with_checklist_subclass_prediction = lb_types.ObjectAnnotation( name="ner_with_checklist_subclass", confidence=0.5, value=lb_types.DocumentEntity( name="ner_with_checklist_subclass", text_selections=[ lb_types.DocumentTextSelection( token_ids=[], group_id="", page=1 ) ] ), classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer(name="first_sub_checklist_answer", confidence=0.5)] ) ) ] ) ``` ```python NDJSON theme={"theme":"css-variables"} ner_with_checklist_subclass_prediction_ndjson = { "name": "ner_with_checklist_subclass", "classifications":[ { "name": "sub_checklist_question", "answer": [{"name": "first_sub_checklist_answer", "confidence":0.5 }] } ], "textSelections": [ { "tokenIds": [ "" ], "groupId": "", "page": 1 } ] } ``` ## End-to-end example: Upload predictions to a model run Here are the steps to upload predictions to a model run: ### Before you start You will need to import these libraries to use the code examples in this section: ```python Python theme={"theme":"css-variables"} import uuid import json import requests import labelbox as lb import labelbox.types as lb_types ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python theme={"theme":"css-variables"} ## Text layer url is required for uploading entity annotations global_key = "0801.3483.pdf" img_url = { "row_data": { "pdf_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483.pdf", "text_layer_url": "https://storage.googleapis.com/labelbox-datasets/arxiv-pdf/data/99-word-token-pdfs/0801.3483-lb-textlayer.json" }, "global_key": global_key } dataset = client.create_dataset(name="pdf_demo_dataset") task = dataset.create_data_rows([img_url]) task.wait_till_done() print(f"Failed data rows: {task.failed_data_rows}") print(f"Errors: {task.errors}") if task.errors: for error in task.errors: if 'Duplicate global key' in error['message'] and dataset.row_count == 0: # If the global key already exists in the workspace the dataset will be created empty, so we can delete it. print(f"Deleting empty dataset: {dataset}") dataset.delete() ``` ### Step 2: Create/select an ontology for your model predictions Your model run should have the correct ontology setup with all the tools and classifications supported for your predictions, and the tool names and classification instructions should match the `name`/`instructions` fields in your annotation payloads to ensure the correct feature schemas are matched. ```python Python expandable theme={"theme":"css-variables"} ## Setup the ontology and link the tools created above. ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text", scope = lb.Classification.Scope.GLOBAL ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ]) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ]) ] ), ], tools=[ # List of Tool objects lb.Tool( tool=lb.Tool.Type.BBOX,name="bounding_box"), lb.Tool(tool=lb.Tool.Type.NER, name="named_entity"), lb.Tool(tool=lb.Tool.Type.NER, name="ner_with_checklist_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[ lb.Option(value="first_sub_checklist_answer") ] ) ]), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option( value="first_sub_radio_answer" , options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="second_sub_radio_question", options=[lb.Option("second_sub_radio_answer")] )] )] )] )] ) ontology = client.create_ontology("Document Annotation Import Demo", ontology_builder.asdict(), media_type=lb.MediaType.Document) ``` ### Step 3: Create a model and a model run ```python Python theme={"theme":"css-variables"} # create model model = client.create_model(name="PDF_model_run_"+ str(uuid.uuid4()), ontology_id=ontology.uid) # create model run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to the model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create the predictions payload To import ner annotations, you can either pass a`text_layer_url` or use Labelbox-generated `text_layer_url`. To extract the generated text layer url we first need to export the data row ```python Python theme={"theme":"css-variables"} task = lb.DataRow.export(client=client,global_keys=[global_key]) task.wait_till_done() if task.has_result(): stream = task.get_buffered_stream() text_layer = "" for output in stream: output_json = output.json text_layer = output_json['media_attributes']['text_layer_url'] print(text_layer) ``` ```python theme={"theme":"css-variables"} import requests import json # Helper method def update_text_selections(annotation, group_id, list_tokens, page): return annotation.update({ "textSelections": [ { "groupId": group_id, "tokenIds": list_tokens, "page": page } ] }) # Fetch the content of the text layer res = requests.get(text_layer) # Phrases that we want to annotate obtained from the text layer url content_phrases = ["Metal-insulator (MI) transitions have been one of the", "T. Sasaki,* N. Yoneyama, and N. Kobayashi"] # Parse the text layer text_selections = [] text_selections_ner = [] for obj in json.loads(res.text): for group in obj["groups"]: if group["content"] == content_phrases[0]: list_tokens = [x["id"] for x in group["tokens"]] # build text selections for Python annotations document_text_selection = lb_types.DocumentTextSelection(groupId=group["id"], tokenIds=list_tokens, page=1) text_selections.append(document_text_selection) # build text selection for the NDJson annotations update_text_selections(annotation=entities_prediction_ndjson, group_id=group["id"], # id representing group of words list_tokens=list_tokens, # ids representing individual words from the group page=1) if group["content"] == content_phrases[1]: list_tokens_2 = [x["id"] for x in group["tokens"]] # build text selections for Python annotations ner_text_selection = lb_types.DocumentTextSelection(groupId=group["id"], tokenIds=list_tokens_2, page=1) text_selections_ner.append(ner_text_selection) # build text selection for the NDJson annotations update_text_selections(annotation=ner_with_checklist_subclass_prediction_ndjson, group_id=group["id"], # id representing group of words list_tokens=list_tokens_2, # ids representing individual words from the group page=1) #re-write the entity annotation with text selections entities_prediction_document_entity = lb_types.DocumentEntity(name="named_entity",confidence=0.5, textSelections = text_selections) entities_prediction = lb_types.ObjectAnnotation(name="named_entity",value=entities_prediction_document_entity) # re-write the entity annotation + subclassification with text selections classifications = [ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer(name="first_sub_checklist_answer", confidence=0.5)] ) ) ] ner_annotation_with_subclass = lb_types.DocumentEntity(name="ner_with_checklist_subclass",confidence=0.5, textSelections= text_selections_ner) ner_with_checklist_subclass_annotation = lb_types.ObjectAnnotation(name="ner_with_checklist_subclass", confidence=0.5, value=ner_annotation_with_subclass, classifications=classifications) # Final NDJSON and python annotations print(f"entities_annotations_ndjson={entities_prediction_ndjson}") print(f"entities_annotation={entities_prediction}") print(f"nested_entities_annotation_ndjson={ner_with_checklist_subclass_prediction_ndjson}") print(f"nested_entities_annotation={ner_with_checklist_subclass_annotation}") ``` ### Step 5: Create the predictions payload Create the predictions payload using the snippets of code shown [above](#supported-predictions). Labelbox supports two formats for the annotations payload: NDJSON and Python annotation types. Both approaches are described below with instructions to compose annotations into Labels attached to the data rows. The resulting `label_predictions_ndjson` and `label_predictions` payloads should have exactly the same prediction content (with the exception of the uuid strings that are generated). ```python Python annotation payload theme={"theme":"css-variables"} label_predictions = [] label_predictions.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ entities_prediction, checklist_prediction, nested_checklist_prediction, text_prediction, radio_prediction, nested_radio_prediction, bbox_prediction, bbox_with_radio_subclass_prediction, ner_with_checklist_subclass_prediction ] ) ) ``` ```json NDJSON payload theme={"theme":"css-variables"} label_predictions_ndjson = [] for annot in [ entities_prediction_ndjson, checklist_prediction_ndjson, nested_checklist_prediction_ndjson, text_prediction_ndjson, radio_prediction_ndjson, nested_radio_prediction_ndjson, bbox_prediction_ndjson, bbox_with_radio_subclass_prediction_ndjson, ner_with_checklist_subclass_prediction_ndjson ]: annot.update({ "dataRow": {"globalKey": global_key}, }) label_predictions_ndjson.append(annot) ``` ### Step 6: Upload the predictions payload to the model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_predictions) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to the model run To send annotations to a model run, we must first import them into a project, create a label payload and then send them to the model run. ```python Python theme={"theme":"css-variables"} # 7.1 Create a labelbox project project = client.create_project(name="Document Prediction Import Demo", media_type=lb.MediaType.Document) project.connect_ontology(ontology) # 7.2 Create a batch to send to the project project.create_batch( "batch_text_prediction_demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create the annotations payload entities_annotation = lb_types.ObjectAnnotation( name="named_entity", value= lb_types.DocumentEntity( name="named_entity", textSelections=text_selections ) ) radio_annotation = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer") ) ) checklist_annotation = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer"), lb_types.ClassificationAnswer(name = "second_checklist_answer"), ]) ) bbox_dim_1 = { "top": 135.3, "left": 102.771, "height": 109.843, "width": 415.8 } bbox_annotation = lb_types.ObjectAnnotation( name="bounding_box", # must match your ontology feature"s name value=lb_types.DocumentRectangle( start=lb_types.Point(x=bbox_dim_1["left"], y=bbox_dim_1["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dim_1["left"] + bbox_dim_1["width"], y=bbox_dim_1["top"]+ bbox_dim_1["height"]), # x= left + width , y = top + height page=0, unit=lb_types.RectangleUnit.POINTS ) ) nested_checklist_annotation = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", )] )) ] )] ) ) nested_radio_annotation = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", ) ) ) ] ) ) ) text_annotation = lb_types.ClassificationAnnotation( name="free_text", value=lb_types.Text(answer="sample text") ) bbox_dim = { "top": 226.757, "left": 317.271, "height": 194.229, "width": 249.386 } bbox_with_radio_subclass_annotation = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", value=lb_types.DocumentRectangle( start=lb_types.Point(x=bbox_dim["left"], y=bbox_dim["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dim["left"] + bbox_dim["width"], y=bbox_dim["top"] + bbox_dim["height"]), # x= left + width , y = top + height unit=lb_types.RectangleUnit.POINTS, page=1 ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", classifications=[ lb_types.ClassificationAnnotation( name="second_sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="second_sub_radio_answer" ) ) ) ] ) ) ) ] ) ner_with_checklist_subclass_annotation = lb_types.ObjectAnnotation( name="ner_with_checklist_subclass", value=lb_types.DocumentEntity( name="ner_with_checklist_subclass", text_selections=text_selections_ner ), classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer(name="first_sub_checklist_answer")] ) ) ] ) # 7.4 Create the label object labels = [] labels.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ entities_annotation, checklist_annotation, nested_checklist_annotation, text_annotation, radio_annotation, nested_radio_annotation, bbox_annotation, bbox_with_radio_subclass_annotation, ner_with_checklist_subclass_annotation ] ) ) # 7.5 Upload annotations to the project using label import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="text_label_import_job"+ str(uuid.uuid4()), labels=labels) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6 Send the annotations to the model run # get the labels id from the project model_run.upsert_labels(project_id=project.uid) ``` # Upload geospatial predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-geospatial-predictions How to upload predictions on geospatial data in a model run and sample upload formats. ## Supported predictions To upload predictions in Labelbox, you need to create the prediction payload. This section provides this payload for every prediction type. Labelbox supports two formats for the predictions payload: * Python annotation types (recommended) * NDJSON Both are described below. ### Uploading confidence scores is optional If you do not specify a confidence score, the prediction will be treated as if it had a confidence score of 1. ### Point ```python Python annotation theme={"theme":"css-variables"} point_prediction = lb_types.ObjectAnnotation( name = "point_geo", confidence=0.4, value = lb_types.Point(x=-99.20647859573366, y=19.40018029091072), ) ``` ```python NDJSON theme={"theme":"css-variables"} point_prediction_ndjson = { "name": "point_geo", "confidence": 0.4, "point": { "x": -99.20647859573366, "y": 19.40018029091072 } } ``` ### Polyline ```python Python annotation theme={"theme":"css-variables"} # Coordinates coords = [ [ -99.20842051506044, 19.40032196622975 ], [ -99.20809864997865, 19.39758963475322 ], [ -99.20758366584778, 19.39776167179227 ], [ -99.20728325843811, 19.3973265189299 ] ] line_points = [] for sub in coords: line_points.append(lb_types.Point(x=sub[0], y=sub[1])) # Python Annotation polyline_prediction = lb_types.ObjectAnnotation( name = "polyline_geo", confidence=0.5, value = lb_types.Line(points=line_points), ) ``` ```python NDJSON theme={"theme":"css-variables"} coords = [ [ -99.20842051506044, 19.40032196622975 ], [ -99.20809864997865, 19.39758963475322 ], [ -99.20758366584778, 19.39776167179227 ], [ -99.20728325843811, 19.3973265189299 ] ] line_points_ndjson = [] for sub in coords: line_points_ndjson.append({"x":sub[0], "y":sub[1]}) polyline_prediction_ndjson = { "name": "polyline_geo", "confidence": 0.5, "line": line_points_ndjson } ``` ### Polygon ```python Python annotation theme={"theme":"css-variables"} polygon_points = [] for sub in coords_polygon: polygon_points.append(lb_types.Point(x=sub[0], y=sub[1])) polygon_prediction = lb_types.ObjectAnnotation( name = "polygon_geo", confidence=0.5, value = lb_types.Polygon(points=polygon_points), ) ``` ```python NDJSON theme={"theme":"css-variables"} polygon_points_ndjson = [] for sub in coords_polygon: polygon_points_ndjson.append({"x":sub[0], "y":sub[1]}) polygon_prediction_ndjson = { "name": "polygon_geo", "confidence": 0.5, "polygon": polygon_points_ndjson } ``` ```python coords_polygon expandable theme={"theme":"css-variables"} coords_polygon = [ [ -99.21042680740356, 19.40036244486966 ], [ -99.2104160785675, 19.40017017124035 ], [ -99.2103409767151, 19.400008256428897 ], [ -99.21014785766603, 19.400008256428897 ], [ -99.21019077301027, 19.39983622176518 ], [ -99.21022295951845, 19.399674306621385 ], [ -99.21029806137086, 19.39951239131646 ], [ -99.2102873325348, 19.399340356128437 ], [ -99.21025514602663, 19.399117722085677 ], [ -99.21024441719057, 19.39892544698541 ], [ -99.2102336883545, 19.39874329141769 ], [ -99.21021223068239, 19.398561135646027 ], [ -99.21018004417421, 19.398399219233365 ], [ -99.21011567115785, 19.39822718286836 ], [ -99.20992255210878, 19.398136104719125 ], [ -99.20974016189577, 19.398085505725305 ], [ -99.20957922935487, 19.398004547302467 ], [ -99.20939683914186, 19.39792358883935 ], [ -99.20918226242067, 19.39786286996558 ], [ -99.20899987220764, 19.397822390703805 ], [ -99.20891404151918, 19.397994427496787 ], [ -99.20890331268312, 19.398176583902874 ], [ -99.20889258384706, 19.398368859888045 ], [ -99.20889258384706, 19.398540896103246 ], [ -99.20890331268312, 19.39872305189756 ], [ -99.20889258384706, 19.39890520748796 ], [ -99.20889258384706, 19.39907724313608 ], [ -99.20889258384706, 19.399259398329956 ], [ -99.20890331268312, 19.399431433603585 ], [ -99.20890331268312, 19.39961358840092 ], [ -99.20890331268312, 19.399785623300048 ], [ -99.20897841453552, 19.399937418648214 ], [ -99.20919299125673, 19.399937418648214 ], [ -99.2093861103058, 19.39991717927664 ], [ -99.20956850051881, 19.39996777770086 ], [ -99.20961141586305, 19.40013981222548 ], [ -99.20963287353517, 19.40032196622975 ], [ -99.20978307724, 19.4004130431554 ], [ -99.20996546745302, 19.40039280384301 ], [ -99.21019077301027, 19.400372564528084 ], [ -99.21042680740356, 19.40036244486966 ] ] ``` ### Bounding box ```python Python annotation theme={"theme":"css-variables"} bbox_top_left = lb_types.Point(x= -99.20746564865112, y=19.39799442829336) bbox_bottom_right = lb_types.Point(x=-99.20568466186523, y=19.39925939999194) bbox_prediction = lb_types.ObjectAnnotation( name = "bbox_geo", confidence=0.5, value = lb_types.Rectangle(start=bbox_top_left, end=bbox_bottom_right) ) ``` ```python NDJSON theme={"theme":"css-variables"} coord_object = { "coordinates" : [[ [ -99.20746564865112, 19.39799442829336 ], [ -99.20746564865112, 19.39925939999194 ], [ -99.20568466186523, 19.39925939999194 ], [ -99.20568466186523, 19.39799442829336 ], [ -99.20746564865112, 19.39799442829336 ] ]] } bbox_prediction_ndjson = { "name" : "bbox_geo", "confidence": 0.5, "bbox" : { 'top': coord_object["coordinates"][0][1][1], 'left': coord_object["coordinates"][0][1][0], 'height': coord_object["coordinates"][0][3][1] - coord_object["coordinates"][0][1][1], 'width': coord_object["coordinates"][0][3][0] - coord_object["coordinates"][0][1][0] } } ``` ### Classification: Radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_question_geo", value=lb_types.Radio(answer=lb_types.ClassificationAnswer(name="first_radio_answer", confidence=0.5)) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_question_geo", "answer": { "name": "first_radio_answer", "confidence": 0.5} } ``` ### Bounding box with nested checklist classification ```python Annotation Types theme={"theme":"css-variables"} bbox_with_checklist_subclass = lb_types.ObjectAnnotation( name="bbox_checklist_geo", confidence=0.5, value=lb_types.Rectangle( start=lb_types.Point(x=-99.210266, y=19.39540372195134), # Top left end=lb_types.Point(x=-99.20621067903966, y=19.396901), # Bottom right ), classifications=[ lb_types.ClassificationAnnotation( name="checklist_class_name", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer(name="first_checklist_answer", confidence=0.5)] ) ) ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} coord_object_checklist = { "coordinates": [ [ [ -99.210266, 19.39540372195134 ], [ -99.210266, 19.396901 ], [ -99.20621067903966, 19.396901 ], [ -99.20621067903966, 19.39540372195134 ], [ -99.210266, 19.39540372195134 ] ] ] } bbox_with_checklist_subclass_ndjson = { "name": "bbox_checklist_geo", "confidence": 0.5, "classifications": [{ "name": "checklist_class_name", "answer": [ { "name":"first_checklist_answer", "confidence": 0.5} ] }], "bbox": { 'top': coord_object_checklist["coordinates"][0][1][1], 'left': coord_object_checklist["coordinates"][0][1][0], 'height': coord_object_checklist["coordinates"][0][3][1] - coord_object_checklist["coordinates"][0][1][1], 'width': coord_object_checklist["coordinates"][0][3][0] - coord_object_checklist["coordinates"][0][1][0] } } ``` ### Bounding box with nested free-text classification ```python Python annotation theme={"theme":"css-variables"} bbox_with_free_text_subclass = lb_types.ObjectAnnotation( name="bbox_text_geo", value=lb_types.Rectangle( start=lb_types.Point(x=-99.21019613742828, y=19.397447957052933), # Top left end=lb_types.Point(x=-99.20986354351044, y=19.39772119262215), # Bottom right ), classifications=[ lb_types.ClassificationAnnotation( name="free_text_geo", value=lb_types.Text(answer="sample text") ) ] ) ``` ```python NDJSON theme={"theme":"css-variables"} coord_object_text ={ "coordinates": [ [ [ -99.21019613742828, 19.397447957052933 ], [ -99.21019613742828, 19.39772119262215 ], [ -99.20986354351044, 19.39772119262215 ], [ -99.20986354351044, 19.397447957052933 ], [ -99.21019613742828, 19.397447957052933 ] ] ] } bbox_with_free_text_subclass_ndjson = { "name":"bbox_text_geo", "confidence": 0.5, "classifications": [{ "name": "free_text_geo", "confidence": 0.5, "answer": "sample text" }], "bbox": { 'top': coord_object_text["coordinates"][0][1][1], 'left': coord_object_text["coordinates"][0][1][0], 'height': coord_object_text["coordinates"][0][3][1] - coord_object_text["coordinates"][0][1][1], 'width': coord_object_text["coordinates"][0][3][0] - coord_object_text["coordinates"][0][1][0] } } ``` ### Classification: Checklist (multi-choice) ```python Python annotation theme={"theme":"css-variables"} checklist_prediction = lb_types.ClassificationAnnotation( name="checklist_question_geo", confidence = 0.5, value=lb_types.Checklist(answer = [ lb_types.ClassificationAnswer(name = "first_checklist_answer", confidence = 0.5), lb_types.ClassificationAnswer(name = "second_checklist_answer", confidence = 0.5), lb_types.ClassificationAnswer(name = "third_checklist_answer", confidence = 0.5) ]) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { 'name': 'checklist_question_geo', "confidence": 0.5, 'answer': [ {'name': 'first_checklist_answer', "confidence": 0.5}, {'name': 'second_checklist_answer', "confidence": 0.5}, {'name': 'third_checklist_answer', "confidence": 0.5}, ] } ``` ### Classification: Nested radio ```python Python annotation theme={"theme":"css-variables"} nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5, classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.2 ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_prediction_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.2, "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer", "confidence": 0.3} }] } } ``` ### Classification: Nested checklist ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 )] )) ] )] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "confidence":0.5, "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer", "confidence": 0.5} } ] }] } ``` ## End-to-end example: Upload predictions to a model run Follow the steps below to upload predictions to a model run. ### Before you start You will need to import these libraries to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import os import uuid import numpy as np from PIL import Image import cv2 import labelbox as lb import labelbox.types as lb_types ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python Python theme={"theme":"css-variables"} top_left_bound = lb_types.Point(x=-99.21052827588443, y=19.400498983095076) bottom_right_bound = lb_types.Point(x=-99.20534818927473, y=19.39533555271248) epsg = lb_types.EPSG.EPSG4326 bounds = lb_types.TiledBounds(epsg=epsg, bounds=[top_left_bound, bottom_right_bound]) global_key = "mexico_city" tile_layer = lb_types.TileLayer( url="https://s3-us-west-1.amazonaws.com/lb-tiler-layers/mexico_city/{z}/{x}/{y}.png" ) tiled_image_data = lb_types.TiledImageData(tile_layer=tile_layer, tile_bounds=bounds, zoom_levels=[17, 23]) asset = { "row_data": tiled_image_data.asdict(), "global_key": global_key, "media_type": "TMS_GEO" } dataset = client.create_dataset(name="geo_demo_dataset") task= dataset.create_data_rows([asset]) print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ### Step 2: Create/select an ontology for your model predictions Your model run should have the correct ontology set up with all the tools and classifications supported for your predictions. Here is an example of creating an ontology programmatically for all the example predictions above: ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.POINT, name="point_geo"), lb.Tool(tool=lb.Tool.Type.LINE, name="polyline_geo"), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo"), lb.Tool(tool=lb.Tool.Type.POLYGON, name="polygon_geo_2"), lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_geo"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_checklist_geo", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class_name", options=[ lb.Option(value="first_checklist_answer") ] ), ] ), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_text_geo", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text_geo" ), ] ) ], classifications = [ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question_geo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question_geo", options=[ lb.Option(value="first_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ), ], ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ) ] ) ontology = client.create_ontology("Ontology Geospatial Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Geospatial_Tile) ``` ### Step 3: Create a Model and model run ```python Python theme={"theme":"css-variables"} # create Model model = client.create_model(name="geospatial_model_run_" + str(uuid.uuid4()), ontology_id=ontology.uid) # create model run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to the model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create the predictions payload Create the annotations payload using the snippets of code shown [above](#supported-predictions). Labelbox supports two formats for the annotations payload: NDJSON and Python annotation types. Both approaches are described below with instructions to compose annotations into Labels attached to the data rows. The resulting `labels` and `ndjson_labels` from each approach will include every annotation (created above) supported by the respective method. ```python Python annotation payload theme={"theme":"css-variables"} labels =[] labels.append( lb_types.Label( data={ "global_key": global_key, "tile_layer": tile_layer, "tile_bounds":bounds, "zoom_levels": [12, 20] }, annotations = [ point_prediction, polyline_prediction, polygon_prediction, bbox_prediction, radio_prediction, bbox_with_checklist_subclass, bbox_with_free_text_subclass, checklist_prediction, nested_checklist_prediction, nested_radio_prediction ] ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_ndjson = [] for prediction in [ radio_prediction_ndjson, checklist_prediction_ndjson, bbox_with_free_text_subclass_ndjson, bbox_with_checklist_subclass_ndjson, bbox_prediction_ndjson, point_prediction_ndjson, polyline_prediction_ndjson, polygon_prediction_ndjson, nested_checklist_prediction_ndjson, nested_radio_prediction_ndjson ]: prediction.update({ 'dataRow': {'globalKey': global_key}, }) label_ndjson.append(prediction) ``` ### Step 6: Upload the predictions payload to the Model Run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=labels) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to the Model Run (Optional) ```python Python theme={"theme":"css-variables"} # 7.1. Create a labelbox project # Create a Labelbox project project = client.create_project(name="geospatial_prediction_demo", queue_mode=lb.QueueMode.Batch, media_type=lb.MediaType.Geospatial_Tile) project.connect_ontology(ontology) # 7.2. Create a batch to send to the project project.create_batch( "batch_geospatial_prediction_demo", # Each batch in a project must have a unique name global_keys=[global_key], # A list of data rows or data row ids priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create the annotations payload as explained in: # https://docs.labelbox.com/horizon/developer-guides/import-geospatial-annotations#supported-annotations point_annotation... polyline_annotation... polygon_annotation... bbox_annotation... radio_annotation... bbox_with_checklist_subclass... bbox_with_free_text_subclass... checklist_annotation... nested_radio_annotation... nested_checklist_annotation... # 7.4 Create the label object labels =[] labels.append( lb_types.Label( data={ "global_key": global_key, "tile_layer": tile_layer, "tile_bounds":bounds, "zoom_levels": [12, 20] }, annotations = [ point_annotation, polyline_annotation, polygon_annotation, bbox_annotation, radio_annotation, bbox_with_checklist_subclass, bbox_with_free_text_subclass, checklist_annotation, nested_checklist_annotation, nested_radio_annotation ] ) ) # 7.5 Upload annotations to the project using Label Import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="geospatial_annotations_import_" + str(uuid.uuid4()), labels=labels) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6. Send the annotations to the Model Run # get the labels id from the project model_run.upsert_labels(project_id=project.uid) ``` # Upload HTML predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-html-predictions How to upload predictions on HTML data in a model run and sample upload formats. ## Supported annotations To import annotations in Labelbox, you need to create the annotations payload. This section shows how to declare the payloads for each supported annotation type. ### Classification: Radio (single-choice) ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "second_radio_answer", confidence=0.5)) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer"} } ``` ### Classification: Nested radio ```python Python annotation theme={"theme":"css-variables"} nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5,# Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_prediction_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer", "confidence": 0.5 } }] } } ``` ### Classification: Nested checklist ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 )] )) ] )] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer", "confidence": 0.5 } } ] }] } ``` ### Classification: Checklist (multi-choice) ```python Python annotation theme={"theme":"css-variables"} checklist_prediction = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "third_checklist_answer", confidence=0.5 ) ]) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer", "confidence": 0.5} ] } ``` ### Classification: Free-form text ```python Python annotation theme={"theme":"css-variables"} text_prediction = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text", confidence=0.5) ) ``` ```python NDJSON theme={"theme":"css-variables"} text_prediction_ndjson = { "name": "free_text", "answer": "sample text", "confidence":0.5 } ``` ## Example: Upload predictions to model run To upload predictions to a model run: ### Before you start You will need to import these libraries to use the code examples in this section: ```python Library Import theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid import numpy as np ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python Python theme={"theme":"css-variables"} global_key ="sample_html_2.html" test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/html_sample_data/sample_html_2.html", "global_key": global_key, } dataset = client.create_dataset( name="html prediction demo dataset", iam_integration=None # Removing this argument will default to the organization"s default IAM integration ) task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ### Step 2: Set up ontology Your project should include an ontology that supports your annotations. To ensure feature schema matches, the tool names and classification names should match the `name` field in your annotations. ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", # name matching the tool used in the annotation options=[lb.Option(value="first_radio_answer")] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ) ], ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ) ] ) ontology = client.create_ontology("Ontology HTML Predictions", ontology_builder.asdict(), media_type=lb.MediaType.Html) ``` ### Step 3: Create model and model run ```python Python theme={"theme":"css-variables"} #create Model model = client.create_model(name="HTML_model_run_" + str(uuid.uuid4()), ontology_id=ontology.uid) #create Model Run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create prediction payload Use the examples in [Supported annotations](#supported-annotations) to create your annotation payloads; you can use declare them as Python dictionaries or NDJSON objects. Examples of each type are shown here; they also show how to compose annotations into labels attached to the data rows. The resulting `label_prediction` and `label_prediction_ndjson` from each approach demonstrates each supported annotation type. ```python Python annotation payload theme={"theme":"css-variables"} label_prediction = [] label_prediction.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ radio_prediction, checklist_prediction, text_prediction, nested_checklist_prediction, nested_radio_prediction ] ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_prediction_ndjson = [] for annot in [ radio_prediction_ndjson, nested_radio_prediction_ndjson, checklist_prediction_ndjson, text_prediction_ndjson, nested_checklist_prediction_ndjson ]: annot.update({ "dataRow": {"globalKey": global_key}, }) label_prediction_ndjson.append(annot) ``` ### Step 6: Upload predictions payload to model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_prediction) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to model run *(Optional)* To send annotations to a model run: 1. Import them into a project 2. Create a label payload 3. Send Send them to the model run ```python Python theme={"theme":"css-variables"} #7.1. Create a labelbox project project = client.create_project(name="HTML prediction import demo", queue_mode=lb.QueueMode.Batch, media_type=lb.MediaType.Html) project.connect_ontology(ontology) # 7.2. Create a batch to send to the project project.create_batch( "batch_prediction_html", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create the annotations payload radio_annotation... nested_radio_annotation... nested_checklist_annotation... checklist_annotation... text_annotation... # 7.4 Create the label object label.append( lb_types.Label( data={"global_key" : global_key }, annotations=[ text_annotation, checklist_annotation, radio_annotation, nested_checklist_annotation, nested_radio_annotation ] ) ) #7.5. Upload annotations to the project using Label Import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="html_annotation_import" + str(uuid.uuid4()), labels=label) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6 Send the annotations to the Model Run model_run.upsert_labels(project_id=project.uid) ``` # Upload image predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-image-predictions How to upload predictions on image data in a model run and sample upload formats. ## Overview To upload predictions in Labelbox, you need to create a prediction payload. In this section, we provide this payload for every supported annotation type. ### Predictions Payload Types Labelbox supports two formats for the annotations payload: * Python annotation types (recommended) * Provides a seamless transition between third-party platforms, machine learning pipelines, and Labelbox. * Allows you to build annotations locally with local file paths, numpy arrays, or URLs * Easily convert Python Annotation Type format to NDJSON format to quickly import annotations to Labelbox * Supports one-level nested classification (radio, checklist, or free-form text) under a tool or classification annotation. * JSON * Skips formatting annotation payload in the Labelbox Python annotation type * Supports any levels of nested classification (radio, checklist, or free-form text) under a tool or classification annotation. ### Confidence Score You can include confidence scores and custom metrics when you upload your model predictions to a model run. However, given the predictions and annotations in a model run, Labelbox will automatically calculate some [auto-generated metrics](/horizon/guides/model-runs#auto-generated-metrics) upon upload. ### Uploading confidence scores is optional If you do not specify a confidence score, the prediction will be treated as if it had a confidence score of 1. ### Supported Prediction The following predictions are supported for an image data row: * Radio * Checklist * Free-form text * Bounding box * Point * Polyline * Polygon * Segmentation masks ## Classification ### Radio (single-choice) ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer( name = "second_radio_answer", confidence=0.5 ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_question", "answer": {"name": "second_radio_answer", "confidence": 0.5} } ``` ### Classification: Nested radio ```python Python annotation theme={"theme":"css-variables"} nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5, classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5 ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_prediction_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.5, "classifications": [{ "name": "sub_radio_question", "answer": { "name": "first_sub_radio_answer", "confidence": 0.5 } }] } } ``` ### Classification: Nested checklist ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 )] )) ] )] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, "classifications": [{ "name": "sub_checklist_question", "answer": { "name": "first_sub_checklist_answer", "confidence": 0.5 } }] }] } ``` ### Checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_prediction = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ) ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer" , "confidence": 0.5}, {"name": "second_checklist_answer", "confidence": 0.5} ] } ``` ### Bounding box ```python Python annotation theme={"theme":"css-variables"} bbox_prediction = lb_types.ObjectAnnotation( name = "bounding_box", confidence=0.5, value=lb_types.Rectangle( start=lb_types.Point(x=1690, y=977), # x = left, y = top end=lb_types.Point(x=1915, y=1307), # x= left + width , y = top + height ) ) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_prediction_ndjson = { "name": "bounding_box", "confidence": 0.5, "bbox": { "top": 977, "left": 1690, "height": 330, "width": 225 } } ``` ### Bounding box with nested classification ```python Python annotation theme={"theme":"css-variables"} bbox_with_radio_subclass_prediction = lb_types.ObjectAnnotation( name="bbox_with_radio_subclass", confidence=0.5, value=lb_types.Rectangle( start=lb_types.Point(x=541, y=933), # x = left, y = top end=lb_types.Point(x=871, y=1124), # x= left + width , y = top + height ), classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio(answer=lb_types.ClassificationAnswer(name="first_sub_radio_answer", confidence=0.5)) ) ] ) ``` ```python NDJSON theme={"theme":"css-variables"} bbox_with_radio_subclass_prediction_ndjson = { "name": "bbox_with_radio_subclass", "confidence": 0.5 "classifications": [{ "name": "sub_radio_question", "answer": { "name":"first_sub_radio_answer", "confidence": 0.5} }], "bbox": { "top": 933, "left": 541, "height": 191, "width": 330 } } ``` ### Polygon ```python Python annotation theme={"theme":"css-variables"} polygon_prediction = lb_types.ObjectAnnotation( name = "polygon", confidence = 0.5, value=lb_types.Polygon( points=[lb_types.Point(x=1489.581,y=183.934), lb_types.Point(x=2278.306,y=256.885), lb_types.Point(x=2428.197,y=200.437), lb_types.Point(x=2560.0,y=335.419), lb_types.Point(x=2557.386,y=503.165), lb_types.Point(x=2320.596,y=503.103), lb_types.Point(x=2156.083, y=628.943), lb_types.Point(x=2161.111,y=785.519), lb_types.Point(x=2002.115, y=894.647), lb_types.Point(x=1838.456,y=877.874), lb_types.Point(x=1436.53,y=874.636), lb_types.Point(x=1411.403,y=758.579), lb_types.Point(x=1353.853,y=751.74), lb_types.Point(x=1345.264, y=453.461), lb_types.Point(x=1426.011,y=421.129)] ), ) ``` ```python NDJSON theme={"theme":"css-variables"} polygon_prediction_ndjson = { "name": "polygon", "confidence": 0.5, "polygon": [ {"x": 1489.581, "y": 183.934}, {"x": 2278.306, "y": 256.885}, {"x": 2428.197, "y": 200.437}, {"x": 2560.0, "y": 335.419}, {"x": 2557.386, "y": 503.165}, {"x": 2320.596, "y": 503.103}, {"x": 2156.083, "y": 628.943}, {"x": 2161.111, "y": 785.519}, {"x": 2002.115, "y": 894.647}, {"x": 1838.456, "y": 877.874}, {"x": 1436.53, "y": 874.636}, {"x": 1411.403, "y": 758.579}, {"x": 1353.853, "y": 751.74}, {"x": 1345.264, "y": 453.461}, {"x": 1426.011, "y": 421.129}, {"x": 1489.581, "y": 183.934} ] } ``` ### Classification: free-form text ```python Python Annotation theme={"theme":"css-variables"} text_annotation = lb_types.ClassificationAnnotation( name="free_text", value=lb_types.Text(answer="sample text", confidence=0.5) ) ``` ```python NDJSON theme={"theme":"css-variables"} text_annotation_ndjson = { "name": "free_text", "confidence": 0.5, "answer": "sample text", } ``` ### Segmentation mask ```python Python annotation theme={"theme":"css-variables"} # We recommend to use byte array url = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/raster_seg.png" response = requests.get(url) mask_data = lb.types.MaskData(im_bytes=response.content) # You can also use "url" instead of img_bytes to pass the PNG mask url. mask_prediction = lb_types.ObjectAnnotation( name="mask", confidence=0.5, value=lb_types.Mask( mask=mask_data, color=(255, 255, 255)) ) ``` ```python NDJSON theme={"theme":"css-variables"} # using instanceURI, bytes array is not fully supported. mask_prediction_ndjson = { "name": "mask", "confidence": 0.5, "classifications": [], "mask": {"instanceURI": url, "colorRGB": (255, 255, 255)} } ``` ### Segmentation mask with nested classification ```python Python theme={"theme":"css-variables"} url_2 = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/raster_seg_with_subclass.png" response_2 = requests.get(url_2) mask_data_2 = lb_types.MaskData(im_bytes=response_2.content) # Python annotation mask_with_text_subclass_prediction = lb_types.ObjectAnnotation( name = "mask_with_text_subclass", # must match your ontology feature"s name value=lb_types.Mask( mask=mask_data_2, confidence=0.5, color=(255, 255, 255)), classifications=[ lb_types.ClassificationAnnotation( name="sub_free_text", value=lb_types.Text(answer="free text answer") )] ) ``` ```python NDJSON theme={"theme":"css-variables"} mask_with_text_subclass_prediction_ndjson = { "name": "mask_with_text_subclass", "mask": {"instanceURI": url_2, "colorRGB": (255, 255, 255)}, "classifications":[{ "name": "sub_free_text", "answer": "free text answer" }] } ``` ### Point ```python Python annotation theme={"theme":"css-variables"} point_prediction = lb_types.ObjectAnnotation( name = "point", confidence=0.5, value = lb_types.Point(x=1166.606, y=1441.768), ) ``` ```python NDJSON theme={"theme":"css-variables"} point_prediction_ndjson = { "name": "point", "confidence": 0.5, "classifications": [], "point": {"x": 1166.606, "y": 1441.768} } ``` ### Polyline ```python Python annotation theme={"theme":"css-variables"} polyline_prediction = lb_types.ObjectAnnotation( name = "polyline", confidence=0.5, value=lb_types.Line( points=[lb_types.Point(x=2534.353, y=249.471), lb_types.Point(x=2429.492, y=182.092), lb_types.Point(x=2294.322, y=221.962), lb_types.Point(x=2224.491, y=180.463), lb_types.Point(x=2136.123, y=204.716), lb_types.Point(x=1712.247, y=173.949), lb_types.Point(x=1703.838, y=84.438), lb_types.Point(x=1579.772, y=82.61), lb_types.Point(x=1583.442, y=167.552), lb_types.Point(x=1478.869, y=164.903), lb_types.Point(x=1418.941, y=318.149), lb_types.Point(x=1243.128, y=400.815), lb_types.Point(x=1022.067, y=319.007), lb_types.Point(x=892.367, y=379.216), lb_types.Point(x=670.273, y=364.408), lb_types.Point(x=613.114, y=288.16), lb_types.Point(x=377.559, y=238.251), lb_types.Point(x=368.087, y=185.064), lb_types.Point(x=246.557, y=167.286), lb_types.Point(x=236.648, y=285.61), lb_types.Point(x=90.929, y=326.412)] ), ) ``` ```python NDJSON theme={"theme":"css-variables"} polyline_prediction_ndjson = { "name": "polyline", "confidence":0.5, "classifications": [], "line": [ {"x": 2534.353, "y": 249.471}, {"x": 2429.492, "y": 182.092}, {"x": 2294.322, "y": 221.962}, {"x": 2224.491, "y": 180.463}, {"x": 2136.123, "y": 204.716}, {"x": 1712.247, "y": 173.949}, {"x": 1703.838, "y": 84.438}, {"x": 1579.772, "y": 82.61}, {"x": 1583.442, "y": 167.552}, {"x": 1478.869, "y": 164.903}, {"x": 1418.941, "y": 318.149}, {"x": 1243.128, "y": 400.815}, {"x": 1022.067, "y": 319.007}, {"x": 892.367, "y": 379.216}, {"x": 670.273, "y": 364.408}, {"x": 613.114, "y": 288.16}, {"x": 377.559, "y": 238.251}, {"x": 368.087, "y": 185.064}, {"x": 246.557, "y": 167.286}, {"x": 236.648, "y": 285.61}, {"x": 90.929, "y": 326.412} ] } ``` ## Example: Upload predictions to model run To upload predictions to a model run: ### Before you start These examples require the following libraries: ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.types as lb_types import uuid import requests ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python Python theme={"theme":"css-variables"} # send a sample image as batch to the project global_key = "2560px-Kitano_Street_Kobe01s5s4110.jpeg" test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg", "global_key": global_key } dataset = client.create_dataset(name="image_prediction_demo") task = dataset.create_data_rows([test_img_url]) print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ### Step 2: Set up ontology for predictions Your model run ontology should support all tools and classifications used in your predictions. This example shows how to create an ontology with all supported [prediction types](#supported-predictions) . ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ), ], tools=[ # List of tools lb.Tool( tool=lb.Tool.Type.BBOX, name="bounding_box"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_with_radio_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ), lb.Tool( tool=lb.Tool.Type.POLYGON, name="polygon"), lb.Tool( tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask"), lb.Tool( tool=lb.Tool.Type.RASTER_SEGMENTATION, name="mask_with_text_subclass", classifications=[ lb.Classification( class_type=lb.Classification.Type.TEXT, name="sub_free_text") ] ), lb.Tool( tool=lb.Tool.Type.POINT, name="point"), lb.Tool( tool=lb.Tool.Type.LINE, name="polyline")] ) ontology = client.create_ontology("Image Prediction Import Demo", ontology_builder.asdict(), media_type=lb.MediaType.Image ) ``` ### Step 3: Create model and model run Create a Model using the ontology and a model run. ```python Python theme={"theme":"css-variables"} # create Model model = client.create_model(name="image_model_run_" + str(uuid.uuid4()), ontology_id=ontology.uid) # create Model Run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create prediction payloads For help creating prediction payloads, see [supported predictions](#supported-predictions). You can declare payloads as Python annotation types (*preferred*) or as NDJSON objects. This example demonstrates each format and shows how to compose annotations into labels attached to the data rows. The resulting `label_prediction_ndjson` and `label_prediction` payloads should have exactly the same prediction content (except for the value of generated `uuid` string values). ```python Python annotation theme={"theme":"css-variables"} label_prediction = [] label_prediction.append(lb_types.Label( data={"global_key" : global_key }, annotations = [ radio_prediction, nested_radio_prediction, checklist_prediction, nested_checklist_prediction, bbox_prediction, bbox_with_radio_subclass_prediction, polyline_prediction, polygon_prediction, mask_prediction, mask_with_text_subclass_prediction, point_prediction, text_annotation ] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} label_prediction_ndjson = [] for annot in [ radio_prediction_ndjson, checklist_prediction_ndjson, bbox_prediction_ndjson, bbox_with_radio_subclass_prediction_ndjson, polygon_prediction_ndjson, mask_prediction_ndjson, mask_with_text_subclass_prediction_ndjson, point_prediction_ndjson, polyline_prediction_ndjson, text_annotation_ndjson, nested_radio_prediction_ndjson, nested_checklist_prediction_ndjson ]: annot.update({ "dataRow": {"globalKey": global_key} }) label_prediction_ndjson.append(annot) ``` ### Step 6: Upload prediction payloads to model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_prediction) # Errors will appear for prediction uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to a model run This step is optional. This example creates a project with ground truth annotations to visualize both annotations and predictions in the model run. To send annotations to a model run: ```python Python theme={"theme":"css-variables"} # 7.1 Create a labelbox project project = client.create_project(name="Image Prediction Demo", media_type=lb.MediaType.Image) project.connect_ontology(ontology) # 7.2 Create a batch project.create_batch( "batch_predictions_demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create your annotation payload as explained in: # https://docs.labelbox.com/horizon/developer-guides/import-image-annotations#supported-annotations radio_annotation ... nested_radio_annotation ... nested_checklist_annotation ... checklist_annotation ... bbox_annotation ... bbox_with_radio_subclass_annotation ... polygon_annotation ... text_annotation ... mask_annotation ... mask_with_text_subclass_annotation ... point_annotation ... polyline_annotation ... # 7.4 Create a Label object by identifying the applicable data row in Labelbox # and providing a list of annotations labels = [] annotations = [ radio_annotation, nested_radio_annotation, checklist_annotation, nested_checklist_annotation, text_annotation, bbox_annotation, bbox_with_radio_subclass_annotation, polygon_annotation, mask_annotation, mask_with_text_subclass_annotation, point_annotation, polyline_annotation ] labels.append( lb_types.Label( data={"global_key" : global_key }, annotations=annotations)) # 7.5 Upload annotations to the project using Label import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="annotation_import_" + str(uuid.uuid4()), labels=labels) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6 Send the annotations to the model run model_run.upsert_labels(project_id=project.uid) ``` # Upload LLM response evaluation Source: https://docs.labelbox.com/horizon/developer-guides/upload-llm-response-evaluation-predictions How to upload predictions on LLM response evaluation data in a model run and sample upload formats. ## Supported predictions To upload predictions in Labelbox, you need to create a prediction payload. This section shows how to create a payload for each supported prediction type. Samples are show for the following supported supported payload formats: * Python annotation types (*recommended*) * NDJSON Both are described below. ### Entity (Message-based) ```python Python annotation theme={"theme":"css-variables"} ner_prediction = lb_types.ObjectAnnotation( name="ner", confidence=0.5, value=lb_types.ConversationEntity( start=0, end=8, message_id="message-1" ) ) ``` ```python NDJSON theme={"theme":"css-variables"} ner_prediction_ndjson = { "name": "ner", "confidence": 0.5, "location": { "start": 0, "end": 8 }, "messageId": "message-1" } ``` ### Classification: Radio (Single-choice) ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="Choose the best response", value=lb_types.Radio(answer=lb_types.ClassificationAnswer( name="Response B", confidence=0.5))) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "Choose the best response", "answer": { "name": "Response B", "confidence": 0.5 } } ``` ### Classification: Checklist (Multi-choice, Message-based) ```python Python annotation theme={"theme":"css-variables"} checklist_prediction= lb_types.ClassificationAnnotation( name="checklist_convo", # must match your ontology feature"s name value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ) ] ), message_id="message-1" # Message specific annotation ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { "name": "checklist_convo", "answers": [ {"name": "first_checklist_answer","confidence":0.5}, {"name": "second_checklist_answer","confidence":0.5} ], "messageId": "message-1" } ``` ### Classification: Free-form text ```python Python annotation theme={"theme":"css-variables"} text_prediction = lb_types.ClassificationAnnotation( name="Provide a reason for your choice", value=lb_types.Text(answer="the answer to the text questions right here", confidence=0.5) ) ``` ```python NDJSON theme={"theme":"css-variables"} text_prediction_ndjson = { "name": "Provide a reason for your choice", "answer": "This is the more concise answer", "confidence": 0.5 } ``` ### Classification: Nested checklist (Message-based) ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", message_id="message-1", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 # Confidence scores should be added to the answer )] )) ] )] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "messageId": "message-1", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications" : [ { "name": "sub_checklist_question", "answer": { "name": "first_sub_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer } } ] }] } ``` ### Classification: Nested radio ```python Python annotation theme={"theme":"css-variables"} nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5 # Confidence scores should be added to the answer ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_prediction_ndjson = { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.5, "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer", "confidence": 0.5} }] } } ``` ## Example: Upload predictions to a model run Here are the steps to upload predictions to a model run: ### Before you start You must import these libraries to use the code examples in this section. ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows Upload your data rows to Catalog. To learn how to format your import JSON files, see [Import conversation & model response data](/horizon/developer-guides/import-conversation-model-response-data). Note the additional `modelOutputs` object required. For descriptions, see [Model outputs object definitions](/horizon/developer-guides/import-conversation-model-response-data#model-outputs-object). ```python Python theme={"theme":"css-variables"} global_key = "pairwise_shooping_asset" convo_data = { "row_data": "https://storage.googleapis.com/labelbox-datasets/conversational-sample-data/pairwise_shopping_2.json", "global_key": global_key } # Create a dataset dataset = client.create_dataset(name="pairwise_prediction_demo") # Create datarows task = dataset.create_data_rows([convo_data]) print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ```python Sample row_data theme={"theme":"css-variables"} pairwise_shopping_2 = """ { "type": "application/vnd.labelbox.conversational", "version": 1, "messages": [ { "messageId": "message-0", "timestampUsec": 1530718491, "content": "Hi! How can I help?", "user": { "userId": "Bot 002", "name": "Bot" }, "align": "left", "canLabel": false }, { "messageId": "message-1", "timestampUsec": 1530718503, "content": "I just bought a vacuum cleaner from you guys like a week ago and it's already broken!!", "user": { "userId": "User 00686", "name": "User" }, "align": "right", "canLabel": true } ], "modelOutputs": [ { "title": "Response A", "content": "I'm really sorry to hear that your vacuum cleaner is not working as expected. We certainly understand how frustrating this can be, especially with a recent purchase. I assure you, we're here to help!\n\n To proceed with resolving this issue, could you please provide some details about the problem you're experiencing with the vacuum cleaner? Additionally, if you could share your order number or receipt details, that would be very helpful. We will definitely look into the available options for you, which may include a replacement or a refund, depending on the specific circumstances and our return policy.\n\n Your satisfaction is our priority, and we'll do our best to resolve this matter promptly and to your satisfaction.", "modelConfigName": "GPT-3.5 with temperature 0" }, { "title": "Response B", "content": "I'm sorry about the issue with your vacuum cleaner. Please send us the order number or receipt details so we can quickly assist you with a replacement. Your satisfaction is important to us!", "modelConfigName": "Fine Tuned GPT-3.5 with demo data" } ] } """ ``` ### Step 2: Create/select an ontology for your model predictions Your model run should have the correct ontology set up with all the tools and classifications supported for your predictions. Here is an example of creating an ontology programmatically for all the example predictions above: ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.NER,name="ner"), ], classifications=[ lb.Classification( class_type=lb.Classification.Type.RADIO, scope=lb.Classification.Scope.GLOBAL, name="Choose the best response", options=[lb.Option(value="Response A"), lb.Option(value="Response B"), lb.Option(value="Tie")] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="Provide a reason for your choice" ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, scope=lb.Classification.Scope.INDEX, name="checklist_convo", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", scope = lb.Classification.Scope.INDEX, options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ]) ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", scope = lb.Classification.Scope.GLOBAL, options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ]) ] ) ] ) ontology = client.create_ontology("Pairwise comparison ontology", ontology_builder.asdict(), media_type=lb.MediaType.Conversational) ``` ### Step 3: Create a model and a model run Create a Model using the ontology and a model run. ```python Python theme={"theme":"css-variables"} # create model model = client.create_model(name="Comparison_model_run_"+ str(uuid.uuid4()), ontology_id=ontology.uid) # create model run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to the model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create the predictions payload Create the predictions payload using the snippets of code shown [above](#supported-predictions). Labelbox supports two formats for the annotations payload: NDJSON and Python annotation types. Both approaches are described below with instructions to compose annotations into Labels attached to the data rows. The resulting `label_ndjson` and `label_prediction `payloads should have exactly the same prediction content. ```python Python theme={"theme":"css-variables"} label_prediction = [] label_prediction.append(lb_types.Label( data= {"global_key": global_key}, annotations= [ ner_prediction, text_prediction, checklist_prediction, radio_prediction, nested_radio_prediction, nested_checklist_prediction ] )) ``` ```python NDJSON theme={"theme":"css-variables"} label_ndjson = [] for annotations in [ ner_prediction_ndjson, text_prediction_ndjson, checklist_prediction_ndjson, radio_prediction_ndjson, nested_checklist_prediction_ndjson, nested_radio_prediction_ndjson ]: annotations.update({ "dataRow": { "globalKey": global_key } }) label_ndjson.append(annotations) ``` ### Step 6: Upload predictions payload to the model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_prediction) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to the model run (optional) We will create a project with ground truth annotations to visualize both annotations and predictions in the model run. To send annotations to a model run, we must first import them into a project, create a label payload, and then send them to the model run. ```python Python theme={"theme":"css-variables"} # 7.1 Create a labelbox project project = client.create_project(name="Conversational Human Evaluation Demo", media_type=lb.MediaType.Conversational) project.connect_ontology(ontology) # 7.2 Create a batch to send to the project project.create_batch( "batch_convo_prediction_demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create the annotations payload ( # See here for more details: # https://labelbox-group.readme.io/reference/import-conversational-text-annotations#supported-annotations ner_annotation ... text_annotation ... checklist_annotation ... radio_annotation ... nested_radio_annotation ... nested_checklist_annotation ... # 7.4 Create the label object label_annotation = [] label_annotation.append(lb_types.Label( data= {"global_key": global_key}, annotations= [ ner_annotation, text_annotation, checklist_annotation, radio_annotation, nested_radio_annotation, nested_checklist_annotation ] )) #7.5 Upload annotations to the project using label import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="label_import_job"+ str(uuid.uuid4()), labels=label_annotation) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6 Send the annotations to the model run model_run.upsert_labels(project_id=project.uid) ``` # Upload text predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-text-predictions How to upload predictions on text data in a model run and sample upload formats. ## Supported prediction types To upload predictions in Labelbox, you need to create a predictions payload. This section shows how to declare payloads for each supported prediction type. You can declare payloads using Python annotation types (*preferred*) or as NDJSON objects. Confidence scores are optional. If you do not include confidence scores in your prediction payloads, the prediction is treated as if it had a confidence value of one (`1`). ### Entity ```python Python annotation theme={"theme":"css-variables"} named_entity = lb_types.TextEntity(start=10, end=20) entities_prediction = lb_types.ObjectAnnotation(value=named_entity, name = "named_entity", confidence=0.5) ``` ```python NDJSON theme={"theme":"css-variables"} entities_prediction_ndjson = { "name": "named_entity", "confidence": 0.5, "location": { "start": 10, "end": 20 } } ``` ### Classification: radio (single choice) ```python Python annotation theme={"theme":"css-variables"} radio_prediction = lb_types.ClassificationAnnotation( name="radio_question", value=lb_types.Radio(answer = lb_types.ClassificationAnswer(name = "first_radio_answer", confidence=0.5) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} radio_prediction_ndjson = { "name": "radio_question", "answer": {"name": "first_radio_answer", "confidence": 0.5} } ``` ### Classification: radio nested ```python Python annotation theme={"theme":"css-variables"} nested_radio_prediction = lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5 ) ) ) ] ) ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_prediction_ndjson= { "name": "nested_radio_question", "answer": { "name": "first_radio_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications": [{ "name":"sub_radio_question", "answer": { "name" : "first_sub_radio_answer", "confidence": 0.5} }] } } ``` ### Classification: checklist nested ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5, # Confidence scores should be added to the answer classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5, )] )) ] )] ) ) ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, # Confidence scores should be added to the answer "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer", "confidence": 0.5} } ] }] } ``` ### Classification: checklist (multiple choice) ```python Python annotation theme={"theme":"css-variables"} checklist_prediction = lb_types.ClassificationAnnotation( name="checklist_question", value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "third_checklist_answer", confidence=0.5 ) ]) ) ``` ```python NDJSON theme={"theme":"css-variables"} checklist_prediction_ndjson = { "name": "checklist_question", "answer": [ {"name": "first_checklist_answer", "confidence": 0.5} ] } ``` ### Classification: free-form text ```python Python annotation theme={"theme":"css-variables"} text_prediction = lb_types.ClassificationAnnotation( name = "free_text", value = lb_types.Text(answer="sample text", confidence=0.5) ) ``` ```python NDJSON theme={"theme":"css-variables"} text_prediction_ndjson = { "name": "free_text", "answer": "sample text", "confidence": 0.5 } ``` ## Example: Upload predictions to model run To upload predictions to a model run: ### Before you start These examples require the following libraries: ```python Python theme={"theme":"css-variables"} import labelbox as lb import labelbox.data.annotation_types as lb_types import labelbox.data.serialization as lb_serializers import uuid ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python Python theme={"theme":"css-variables"} global_key = "lorem-ipsum.txt" test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-sample-datasets/nlp/lorem-ipsum.txt", "global_key": global_key } dataset = client.create_dataset( name="text prediction demo dataset", iam_integration=None # Removing this argument will default to the organziation's default iam integration ) task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print("Errors:",task.errors) print("Failed data rows:", task.failed_data_rows) ``` ### Step 2: Set up ontology for predictions Your model run ontology should support all tools and classifications required by your predictions. this example shows how to create an ontology containing all [supported prediction types](#supported-prediction-types). ```python Python theme={"theme":"css-variables"} ## Set up the ontology and link the tools created above. ontology_builder = lb.OntologyBuilder( classifications=[ # List of Classification objects lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_question", options=[lb.Option(value="first_radio_answer")] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option(value="first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[ lb.Option(value="first_sub_radio_answer") ] ), ] ) ], ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_question", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer"), lb.Option(value="third_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ) ], tools=[ # List of Tool objects lb.Tool(tool=lb.Tool.Type.NER, name="named_entity") ] ) ontology = client.create_ontology("Ontology Text Predictions", ontology_builder.asdict() , media_type=lb.MediaType.Text) ``` ### Step 3: Create model and model run ```python Python theme={"theme":"css-variables"} model = client.create_model(name="text_model_run_"+ str(uuid.uuid4()), ontology_id=ontology.uid) model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create prediction payloads See [supported prediction types](#supported-prediction-types) for help creating prediction payloads. You can declare predictions as Python annotation types (*preferred*) or NDJSON objects. These examples show each type and describe how to compose predictions into labels attached to the data rows. The resulting `label_ndjson_predictions` and `label_predictions` payloads should have exactly the same prediction content (except for the `uuid` string values). ```python Python annotation payload theme={"theme":"css-variables"} label_predictions = [] label_predictions.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ entities_prediction, nested_radio_prediction, radio_prediction, checklist_prediction, nested_checklist_prediction, text_prediction, ] ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} label_ndjson_predictions= [] for annot in [ entities_prediction_ndjson, radio_prediction_ndjson, checklist_prediction_ndjson, text_prediction_ndjson, nested_radio_prediction_ndjson, nested_checklist_prediction_ndjson ]: annot.update({ "dataRow": {"globalKey": global_key} }) label_ndjson_predictions.append(annot) ``` ### Step 6: Upload payload to model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_predictions) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to model run This step is optional. ```python Python theme={"theme":"css-variables"} # 7.1. Create a labelbox project project = client.create_project(name="Text Prediction Import Demo", media_type=lb.MediaType.Text) project.connect_ontology(ontology) # 7.2. Create a batch to send to the project project.create_batch( "batch_text_prediction_demo", # Each batch in a project must have a unique name global_keys=[global_key], # Paginated collection of data row objects, list of data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 Create your annotation payload as explained in: # /horizon/developer-guides/import-text-annotations#supported-annotations entities_annotation ... nested_radio_annotation ... radio_annotation ... checklist_annotation ... nested_checklist_annotation ... text_annotation ... # 7.4. Create the label object label = [] label.append( lb_types.Label( data={"global_key" : global_key }, annotations = [ entities_annotation, nested_radio_annotation, radio_annotation, checklist_annotation, nested_checklist_annotation, text_annotation, ] ) ) # 7.5 Upload annotations to the project using Label Import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="text_label_import_job"+ str(uuid.uuid4()), labels=label) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # 7.6 Send the annotations to the model run # get the labels id from the project model_run.upsert_labels(project_id=project.uid) ``` # Upload video predictions Source: https://docs.labelbox.com/horizon/developer-guides/upload-video-predictions How to upload predictions on video data in a model run and sample upload formats. ## Supported annotations To upload predictions in Labelbox, you need to create a predictions payload. In this section, we provide this payload for every supported prediction type. ### Uploading confidence scores is optional If you do not specify a confidence score, the prediction will be treated as if it had a confidence score of 1. Note that confidence scores are not supported for `VideoObjectAnnotation` objects. Nested `ClassificationAnnotation` objects inside of a `VideoObjectAnnotation` can still have a confidence score. ### Bounding box ```python Python annotation theme={"theme":"css-variables"} # bbox dimensions bbox_dm = { "top":617, "left":1371, "height":419, "width":505 } bbox_prediction = [ lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=13, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), # x= left + width , y = top + height ) ), lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=15, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), ) ), lb_types.VideoObjectAnnotation( name = "bbox_video", keyframe=True, frame=19, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm["left"], y=bbox_dm["top"]), end=lb_types.Point(x=bbox_dm["left"] + bbox_dm["width"], y=bbox_dm["top"] + bbox_dm["height"]), ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} # bbox dimensions bbox_dm = { "top":617, "left":1371, "height":419, "width":505 } bbox_prediction_ndjson = { "name" : "bbox_video", "segments" : [{ "keyframes" : [ { "frame": 13, "bbox" : bbox_dm }, { "frame": 15, "bbox" : bbox_dm }, { "frame": 19, "bbox" : bbox_dm } ] } ] } ``` ### Point ```python Python annotation theme={"theme":"css-variables"} point_prediction = [ lb_types.VideoObjectAnnotation( name = "point_video", keyframe=True, frame=17, value = lb_types.Point(x=660.134, y=407.926), ) ] ``` ```python NDJSON theme={"theme":"css-variables"} point_prediction_ndjson = { "name": "point_video", "confidence": 0.5, "segments": [{ "keyframes": [{ "frame": 17, "point" : { "x": 660.134 , "y": 407.926 } }] }] } ``` ### Polyline ```python Python annotation theme={"theme":"css-variables"} polyline_prediction = [ lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=5, segment_index=0, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=12, segment_index=0, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=20, segment_index=0, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=24, segment_index=1, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ), lb_types.VideoObjectAnnotation( name = "line_video_frame", keyframe=True, frame=45, segment_index=1, value=lb_types.Line( points=[lb_types.Point(x=680, y=100), lb_types.Point(x=100, y=190)] ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} polyline_prediction_ndjson = { "name": "line_video_frame", "segments": [ { "keyframes": [ { "frame": 5, "line": [{ "x": 680, "y": 100 },{ "x": 100, "y": 190 },{ "x": 190, "y": 220 }] }, { "frame": 12, "line": [{ "x": 680, "y": 280 },{ "x": 300, "y": 380 },{ "x": 400, "y": 460 }] }, { "frame": 20, "line": [{ "x": 680, "y": 180 },{ "x": 100, "y": 200 },{ "x": 200, "y": 260 }] } ] }, { "keyframes": [ { "frame": 24, "line": [{ "x": 300, "y": 310 },{ "x": 330, "y": 430 }] }, { "frame": 45, "line": [{ "x": 600, "y": 810 },{ "x": 900, "y": 930 }] } ] } ] } ``` ### Classification: Radio (global) ```python Python annotation theme={"theme":"css-variables"} global_radio_prediction = [lb_types.ClassificationAnnotation( name="radio_class_global", value=lb_types.Radio(answer = lb_types.ClassificationAnswer( name = "first_radio_answer", confidence=0.5 )) )] ``` ```python NDJSON theme={"theme":"css-variables"} global_radio_classification_ndjson = { "name": "radio_class_global", "answer": { "name": "first_radio_answer", "confidence": 0.5} } ``` ### Classification: Radio (frame-based) ```python Python annotation theme={"theme":"css-variables"} radio_prediction = [ lb_types.VideoClassificationAnnotation( name="radio_class", frame=9, segment_index=0, value=lb_types.Radio(answer = lb_types.ClassificationAnswer( name = "first_radio_answer", confidence=0.5 )) ), lb_types.VideoClassificationAnnotation( name="radio_class", frame=15, segment_index=0, value=lb_types.Radio(answer = lb_types.ClassificationAnswer( name = "first_radio_answer", confidence=0.5 )) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} frame_radio_classification_prediction_ndjson = { "name": "radio_class", "answer": { "name": "first_radio_answer", "frames": [{"start": 9, "end": 15}]} } ``` ### Classification: Checklist (global) ```python Python annotation theme={"theme":"css-variables"} global_checklist_prediction=[lb_types.ClassificationAnnotation( name="checklist_class_global", value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ), lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ) ] ) )] ``` ```python NDJSON theme={"theme":"css-variables"} global_checklist_classification_ndjson = { "name": "checklist_class_global", "answer": [ { "name": "first_checklist_answer" , "confidence": 0.5}, { "name": "second_checklist_answer", "confidence": 0.5} ] } ``` ### Classification: Checklist (frame-based) ```python Python annotation theme={"theme":"css-variables"} checklist_prediction= [ lb_types.VideoClassificationAnnotation( name="checklist_class", frame=29, segment_index=0, value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ) ] ) ), lb_types.VideoClassificationAnnotation( name="checklist_class", frame=35, segment_index=0, value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "first_checklist_answer", confidence=0.5 ) ] ) ), lb_types.VideoClassificationAnnotation( name="checklist_class", frame=39, segment_index=1, value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ) ] ) ), lb_types.VideoClassificationAnnotation( name="checklist_class", frame=45, segment_index=1, value=lb_types.Checklist( answer = [ lb_types.ClassificationAnswer( name = "second_checklist_answer", confidence=0.5 ) ] ) ) ] ``` ```python NDJSON theme={"theme":"css-variables"} frame_checklist_classification_prediction_ndjson = { "name": "checklist_class", "answer": [ { "name": "first_checklist_answer" , "frames": [{"start": 29, "end": 35 }]}, { "name": "second_checklist_answer", "frames": [{"start": 39, "end": 45 }]} ] } ``` ### Classification: Nested radio (global) ```python Python annotation theme={"theme":"css-variables"} nested_radio_prediction =[lb_types.ClassificationAnnotation( name="nested_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_radio_answer", confidence=0.5 , classifications=[ lb_types.ClassificationAnnotation( name="sub_radio_question", value=lb_types.Radio( answer=lb_types.ClassificationAnswer( name="first_sub_radio_answer", confidence=0.5 ) ) ) ] ) ) )] ``` ```python NDJSON theme={"theme":"css-variables"} nested_radio_prediction_ndjson = { "name": "nested_radio_question", "answer": {"name": "first_radio_answer", "confidence": 0.5, "classifications" : [ {"name": "sub_radio_question", "answer": {"name": "first_sub_radio_answer", "confidence": 0.5}} ] } } ``` ### Classification: Nested checklist (global) ```python Python annotation theme={"theme":"css-variables"} nested_checklist_prediction = [lb_types.ClassificationAnnotation( name="nested_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5 , classifications=[ lb_types.ClassificationAnnotation( name="sub_checklist_question", value=lb_types.Checklist( answer=[lb_types.ClassificationAnswer( name="first_sub_checklist_answer", confidence=0.5 )] )) ] )] ) )] ``` ```python NDJSON theme={"theme":"css-variables"} nested_checklist_prediction_ndjson = { "name": "nested_checklist_question", "answer": [{ "name": "first_checklist_answer", "confidence": 0.5, "classifications" : [ { "name": "sub_checklist_question", "answer": {"name": "first_sub_checklist_answer", "confidence": 0.5} } ] }] } ``` ### Bounding box with sub-classifications (frame-based) ```python Python annotation theme={"theme":"css-variables"} bbox_dm2 = { "top": 146.0, "left": 98.0, "height": 382.0, "width": 341.0 } frame_bbox_with_checklist_subclass_prediction = [ lb_types.VideoObjectAnnotation( name = "bbox_class", keyframe=True, frame=10, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), # x = left, y = top end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), # x= left + width , y = top + height ) ), lb_types.VideoObjectAnnotation( name = "bbox_class", keyframe=True, frame=11, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), ), classifications=[ lb_types.ClassificationAnnotation( name='checklist_class', value=lb_types.Checklist(answer=[lb_types.ClassificationAnswer( name="first_checklist_answer", confidence=0.5 )]) ) ] ), lb_types.VideoObjectAnnotation( name = "bbox_class", keyframe=True, frame=13, segment_index=0, value = lb_types.Rectangle( start=lb_types.Point(x=bbox_dm2["left"], y=bbox_dm2["top"]), end=lb_types.Point(x=bbox_dm2["left"] + bbox_dm2["width"], y=bbox_dm2["top"] + bbox_dm2["height"]), ), classifications=[ lb_types.ClassificationAnnotation( name='checklist_class', value=lb_types.Checklist(answer=[lb_types.ClassificationAnswer( name="second_checklist_answer", confidence=0.5 )]) ) ] ) ] ``` ```python NDJSON theme={"theme":"css-variables"} frame_bbox_with_checklist_subclass_prediction_ndjson = { "name": "bbox_class", "segments": [{ "keyframes": [ { "frame": 10, "bbox": bbox_dm2 }, { "frame": 11, "bbox": bbox_dm2, "classifications": [ { "name": "bbox_radio", "answer": [{"name": "first_checklist_answer", "confidence": 0.5}] } ] }, { "frame": 13, "bbox": bbox_dm2, "classifications": [ { "name": "bbox_radio", "answer": [{"name": "second_checklist_answer", "confidence": 0.5}] } ] } ] } ] } ``` ### Text ```python Python annotation theme={"theme":"css-variables"} text_prediction = [lb_types.ClassificationAnnotation( name="free_text", # must match your ontology feature's name value=lb_types.Text(answer="sample text", confidence=0.5) )] ``` ```python NDJSON theme={"theme":"css-variables"} text_prediction_ndjson = { 'name': 'free_text', 'confidence': 0.5, 'answer': 'sample text', } ``` ### Masks Raster segmentation masks are not yet supported in the Model product. ## End-to-end example: Upload predictions to a Model Run Follow the steps below to upload predictions to a model run. ### Before you start You will need to import these libraries to use the code examples in this section: ```python Python theme={"theme":"css-variables"} import labelbox as lb import uuid ``` Replace the value of `API_KEY` with a valid [API key](/horizon/developer-guides/create-api-key) to connect to the Labelbox client. ```python Python theme={"theme":"css-variables"} API_KEY = None client = lb.Client(API_KEY) ``` ### Step 1: Import data rows into Catalog ```python Python theme={"theme":"css-variables"} global_key = "sample-video-2.mp4" test_img_url = { "row_data": "https://storage.googleapis.com/labelbox-datasets/video-sample-data/sample-video-2.mp4", "global_key": global_key } dataset = client.create_dataset( name="Video prediction demo", iam_integration=None # Removing this argument will default to the organziation's default iam integration ) task = dataset.create_data_rows([test_img_url]) task.wait_till_done() print("Errors: ",task.errors) print("Failed data rows: ",task.failed_data_rows) ``` ### Step 2: Create/select an ontology for your model predictions Your project should have the correct ontology setup with all the tools and classifications supported for your predictions and the tool and classifications `name` should match the `name` fields in your predictions to ensure the correct feature schemas are matched. ```python Python expandable theme={"theme":"css-variables"} ontology_builder = lb.OntologyBuilder( tools=[ lb.Tool(tool=lb.Tool.Type.BBOX, name="bbox_video"), lb.Tool(tool=lb.Tool.Type.POINT, name="point_video"), lb.Tool(tool=lb.Tool.Type.LINE, name="line_video_frame"), lb.Tool( tool=lb.Tool.Type.BBOX, name="bbox_class", classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class", scope = lb.Classification.Scope.INDEX, ## defined scope for frame classifications options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ) ] ) ], classifications=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class", scope = lb.Classification.Scope.INDEX, ## defined scope for frame classifications options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_class", scope = lb.Classification.Scope.INDEX, options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="nested_radio_question", options=[ lb.Option("first_radio_answer", options=[ lb.Classification( class_type=lb.Classification.Type.RADIO, name="sub_radio_question", options=[lb.Option("first_sub_radio_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="nested_checklist_question", options=[ lb.Option("first_checklist_answer", options=[ lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="sub_checklist_question", options=[lb.Option("first_sub_checklist_answer")] ) ] ) ] ), lb.Classification( class_type=lb.Classification.Type.RADIO, name="radio_class_global", options=[ lb.Option(value="first_radio_answer"), lb.Option(value="second_radio_answer") ] ), lb.Classification( class_type=lb.Classification.Type.CHECKLIST, name="checklist_class_global", options=[ lb.Option(value="first_checklist_answer"), lb.Option(value="second_checklist_answer") ] ), lb.Classification( class_type=lb.Classification.Type.TEXT, name="free_text" ) ] ) ontology = client.create_ontology("Ontology Video Annotations", ontology_builder.asdict(), media_type=lb.MediaType.Video ) ``` ### Step 3: Create a Model and model run ```python Python theme={"theme":"css-variables"} # create Model model = client.create_model(name="video_model_run_" + str(uuid.uuid4()), ontology_id=ontology.uid) # create model run model_run = model.create_model_run("iteration 1") ``` ### Step 4: Send data rows to the model run ```python Python theme={"theme":"css-variables"} model_run.upsert_data_rows(global_keys=[global_key]) ``` ### Step 5: Create the predictions payload Create the annotations payload using the snippets of code here. ```python Python annotation payload theme={"theme":"css-variables"} label_predictions = [] annotations_list = [ point_prediction, bbox_prediction, polyline_prediction, checklist_prediction, radio_prediction, nested_radio_prediction, nested_checklist_prediction, frame_bbox_with_checklist_subclass_prediction, global_radio_prediction, global_checklist_prediction, text_prediction ] flatten_list_annotations = [ann for ann_sublist in annotations_list for ann in ann_sublist] label_predictions.append( lb_types.Label( data={"global_key" : global_key }, annotations = flatten_list_annotations ) ) ``` ```python NDJSON payload theme={"theme":"css-variables"} # Create a Label object by identifying the applicable data row in Labelbox and providing a list of annotations label_prediction_ndjson = [] for annotation in [ point_prediction_ndjson, bbox_prediction_ndjson, polyline_prediction_ndjson, frame_checklist_classification_prediction_ndjson, frame_radio_classification_prediction_ndjson, nested_radio_prediction_ndjson, nested_checklist_prediction_ndjson, frame_bbox_with_checklist_subclass_prediction_ndjson, global_radio_classification_ndjson, global_checklist_classification_ndjson, video_mask_prediction_ndjson, text_prediction_ndjson ]: annotation.update({ "dataRow": { "globalKey": global_key } }) label_prediction_ndjson.append(annotation) ``` ### Step 6: Upload the predictions payload to the model run ```python Python theme={"theme":"css-variables"} # Upload the prediction label to the Model Run upload_job_prediction = model_run.add_predictions( name="prediction_upload_job"+str(uuid.uuid4()), predictions=label_predictions) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_prediction.errors) print("Status of uploads: ", upload_job_prediction.statuses) ``` ### Step 7: Send annotations to the model run (optional) ```python Python theme={"theme":"css-variables"} # 7.1 Create a labelbox project # Create a Labelbox project project = client.create_project(name="video_prediction_demo", media_type=lb.MediaType.Video) project.connect_ontology(ontology) # 7.2 Create a batch to send to the project project.create_batch( "batch_video_prediction_demo", # Each batch in a project must have a unique name global_keys=[global_key], # A list of data rows, data row ids or global keys priority=5 # priority between 1(Highest) - 5(lowest) ) # 7.3 create the annotations payload # See here for more details: # /horizon/developer-guides/import-video-annotations#supported-annotations checklist_annotation ... radio_annotation ... bbox_annotation ... frame_bbox_with_checklist_subclass ... point_annotation ... polyline_annotation ... global_checklist_annotation ... global_radio_annotation ... nested_checklist_annotation ... nested_radio_annotation ... text_annotation ... # 7.4 Create a Label object by identifying the applicable data row in Labelbox and providing a list of annotations labels = [] annotations_list = [ checklist_annotation, radio_annotation, bbox_annotation, frame_bbox_with_checklist_subclass, point_annotation, polyline_annotation, global_checklist_annotation, global_radio_annotation, nested_checklist_annotation, nested_radio_annotation, text_annotation ] flatten_list_annotations = [ann for ann_sublist in annotations_list for ann in ann_sublist] labels.append( lb_types.Label( data={"global_key" : global_key }, annotations = flatten_list_annotations ) ) # 7.5 Upload annotations to the project using Label Import upload_job_annotation = lb.LabelImport.create_from_objects( client = client, project_id = project.uid, name="video_annotations_import_" + str(uuid.uuid4()), labels=labels) upload_job_annotation.wait_until_done() # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) print("Status of uploads: ", upload_job_annotation.statuses) # Errors will appear for annotation uploads that failed. print("Errors:", upload_job_annotation.errors) # 7.6 Send the annotations to the Model Run # get the labels id from the project model_run.upsert_labels(project_id=project.uid) ``` # User Source: https://docs.labelbox.com/horizon/developer-guides/user Developer guide for managing team members using the Python SDK. # Client ```python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(api_key="") ``` # Get the current user ```python theme={"theme":"css-variables"} user = client.get_user() ``` # Methods ## Update an organization role ```python theme={"theme":"css-variables"} # get the available roles roles = client.get_roles() # update the role (in this case, the user is granted labeler permissions) user_to_update.update_org_role(roles["LABELER"]) ``` ## Upsert a project-based role ```python theme={"theme":"css-variables"} # get the available roles roles = client.get_roles() # get the project project = client.get_project("") # update the project-based role (in this case, the user is granted reviewer permissions) user_to_update.upsert_project_role( project=project, role=roles["REVIEWER"] ) ``` ## Remove from a project ```python theme={"theme":"css-variables"} # get the project project = client.get_project("") # remove from the project user_to_remove.remove_from_project(project) ``` # Attributes ## Get the basics ```python theme={"theme":"css-variables"} # name (str) user.name # nickname (str) user.nickname # email (str) user.email # created at (datetime) user.created_at # updated at (datetime) user.updated_at # role (relationship to OrgRole object) role = user.org_role() # organization (relationship to Organization object) organization = user.organization() ``` ## Get the projects ```python theme={"theme":"css-variables"} # get the projects (relationship to Project objects) projects = user.projects() # inspect one project next(projects) # inspect all projects for project in projects: print(project) # for ease of use, you can convert the paginated collection to a list list(projects) ``` ## Get the created tasks ```python theme={"theme":"css-variables"} # get the tasks (relationship to Task objects) tasks = user.created_tasks() # get the tasks in progress user_task_in_progress = user.created_tasks(where=Task.status_as_enum == TaskStatus.In_Progress) # inspect one project next(tasks) # inspect all projects for task in tasks: print(task) # for ease of use, you can convert the paginated collection to a list list(tasks) ``` # Manage user groups `users` replaced by `members` Starting with version 7.0.0 of the SDK, the `users` attribute is now deprecated. Please use `members` instead in order to specify a user account and a role. Managing user groups requires you to import additional classes: ```python theme={"theme":"css-variables"} from labelbox.schema.user_group import UserGroup, UserGroupColor, UserGroupMember ``` ## Create user groups ```python theme={"theme":"css-variables"} # Retrieve roles roles = client.get_roles() # Define a user group with individual roles (members) user_group = UserGroup( client=client, name="User Group with Explicit Roles", color=UserGroupColor.GREEN, members={ UserGroupMember(user=user1, role=roles["LABELER"]), UserGroupMember(user=user2, role=roles["REVIEWER"]), UserGroupMember(user=user3, role=roles["LABELER"]) }, projects={project1}, description="Basic test" ) # Create the defined user group created_group = user_group.create() ``` ## Update a user group ```python theme={"theme":"css-variables"} # Define the user group properties to be updated user_group.name = "Updated User Group Name" user_group.color = UserGroupColor.GREEN user_group.description = "New group" # Add new projects to the group user_group.projects.add(project1) ## or user_group.projects.update({project1, project2}) # Add new users to the group user_group.members.add(UserGroupMember(user, roles["TEAM_MANAGER"])) ## or ugm = { UserGroupMember(user1, roles["LABELER"]), UserGroupMember(user2, roles["TEAM_MANAGER"]) } user_group.members.update(ugm) # Push the changes to the group user_group.update() ``` ## Search for and get info on user groups ```python From name theme={"theme":"css-variables"} group_name = "example_name" # Get all user groups in your workspace user_groups = UserGroup.get_user_groups(client) # Search for a user group by its name example_group = next( (group for group in user_groups if group.name == group_name), None ) if example_group: print(f"Found user group 'example_name' with ID: {example_group.id}") else: print("No user group named 'example_name' found.") # Get info of the user group found example_group.get() ``` ```python From id theme={"theme":"css-variables"} # Search for a user group by its id group_id = "" # Example af4ff010-cb4c-11f0-a929-8300a55d380 group = UserGroup(client, id=group_id).get() ``` # Webhooks Source: https://docs.labelbox.com/horizon/developer-guides/webhook Developer guide on how to set up Labelbox webhooks through Python SDK See [Webhooks](/horizon/guides/webhooks) guide page for details on how to use webhooks to receive notifications from activities in Labelbox. ## Example configurations to receive requests ```python Python (Flask) theme={"theme":"css-variables"} from flask import Flask, request import json import hmac import hashlib import threading from werkzeug.serving import run_simple # This can be any secret that matches your webhook config (we will set later) secret = b"" # Example for server-side code to receive webhook events app = Flask(__name__) @app.route("/webhook-endpoint", methods=["POST"]) def print_webhook_info(): payload = request.data computed_signature = hmac.new( secret, msg=payload, digestmod=hashlib.sha1 ).hexdigest() if request.headers["X-Hub-Signature"] != "sha1=" + computed_signature: print( "Error: computed_signature does not match signature provided in the headers" ) return "Error", 500 print("=========== New Webhook Delivery ============") print("Delivery ID: %s" % request.headers["X-Labelbox-Id"]) print("Event: %s" % request.headers["X-Labelbox-Event"]) print( "Payload: %s" % json.dumps(json.loads(payload.decode("utf8")), indent=4) ) return "Success" thread = threading.Thread( target=lambda: run_simple("0.0.0.0", 3001, app) ) thread.start() ``` ```python Python (AWS Lambda) theme={"theme":"css-variables"} import hmac import hashlib import re secret = "" # Example of a AWS Lambda to receive webhook events def lambda_handler(event, context): webhook = re.sub( r"^sha1=", "", event["headers"]["x-hub-signature"], ) digest = hmac.new( key=bytes(secret, "utf-8"), msg=event["body"].encode("utf-8"), digestmod=hashlib.sha1, ) if webhook != digest.hexdigest(): return { "statusCode": 400, "body": "Error: computed_signature does not match", } print("=========== New Webhook Delivery ============") print("Delivery ID: %s" % event["headers"]["x-labelbox-id"]) print("Event: %s" % event["headers"]["x-labelbox-event"]) print("=========== Payload =============") print(event["body"]) return "Success" ``` ```python Python (GC Function) theme={"theme":"css-variables"} import functions_framework import json import hmac import hashlib secret = b"" # Example of a Google Cloud Function to receive webhook events @functions_framework.http def hello_http(request): payload = request.data computed_signature = hmac.new( secret, msg=payload, digestmod=hashlib.sha1 ).hexdigest() if request.headers["X-Hub-Signature"] != "sha1=" + computed_signature: print( "Error: computed_signature does not match signature provided in the headers" ) return "Error", 500 print("=========== New Webhook Delivery ============") print("Delivery ID: %s" % request.headers["X-Labelbox-Id"]) print("Event: %s" % request.headers["X-Labelbox-Event"]) print( "Payload: %s" % json.dumps(json.loads(payload.decode("utf8")), indent=4) ) return "Success" ``` ```python Python (Azure Function) theme={"theme":"css-variables"} import azure.functions as func import json import hmac import logging import hashlib secret = b"" # Example of an Azure Function to receive webhook events app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION) @app.route(route="http_trigger") def http_trigger(req: func.HttpRequest) -> func.HttpResponse: payload = req.get_body() computed_signature = hmac.new( secret, msg=payload, digestmod=hashlib.sha1 ).hexdigest() if req.headers["X-Hub-Signature"] != "sha1=" + computed_signature: logging.warning( "Error: computed_signature does not match signature provided in the headers" ) return func.HttpResponse("Error", status_code=500) logging.info("=========== New Webhook Delivery ============") logging.info("Delivery ID: %s" % req.headers["X-Labelbox-Id"]) logging.info("Event: %s" % req.headers["X-Labelbox-Event"]) logging.info( "Payload: %s" % json.dumps(json.loads(payload.decode("utf8")), indent=4) ) return func.HttpResponse("Success", status_code=200) ``` ## Create webhook ### Webhook v2 setup You can create a v2 webhook through the [Labelbox UI](/horizon/guides/webhooks#manage-webhooks-in-the-ui). The v2 webhook mimics the [export](/horizon/developer-guides/export-overview) payload. ### Webhook v1 setup As shown below, you can create a v1 webhook with the Python SDK or the Labelbox UI, which is similar to v2 webhooks. ```python Python theme={"theme":"css-variables"} from labelbox import Client, Webhook public_url = "https://example.com/webhook-endpoint" # Your server's public url - where the messages will be sent to secret = b"CHANGE-ME" # Use to verify Labelbox is sending the message client = Client(api_key="") project = client.get_project("") print([topic.value for topic in Webhook.Topic]) # See all available topics secret = b"example_secret" # This can be any secret that matches your webhook config (we will set later) webhook = Webhook.create(client, topics=["LABEL_CREATED"], url="public_url", secret=secret.decode(), project=project) ``` ## Get webhooks ```python Python theme={"theme":"css-variables"} # Fetch all webhooks org = client.get_organization() webhooks = org.webhooks() #paginated # Fetch project webhooks project = client.get_project("") webhooks = project.webhooks() #paginated webhook = next(webhooks) status = webhook.status server_url = webhook.url topics = webhook.topics project = webhook.project() ``` ## Update webhook ```python Python theme={"theme":"css-variables"} # url, topics, and status can all be updated updated_url = f"{public_url}/webhook-endpoint" webhook.update(url=updated_url, topics=[Topic.LABEL_DELETED], status=Webhook.Status.INACTIVE.value) ``` ## Delete webhook ```python Python theme={"theme":"css-variables"} webhook.delete() ``` ### Sample response #### v2 ```json Label created expandable theme={"theme":"css-variables"} { "data_row": { "id": "clvmpaetz76ct0706730rfcq9", "external_id": null, "global_key": "TEST-ID-157128604429964085398241585654637789196d", "row_data": "https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_0001.jpeg", "metadata_fields": [], "details": { "dataset_id": "clvmpa9et010j0750rlgs8qdi", "created_at": "2024-04-30T18:07:10.82Z", "updated_at": "2024-04-30T18:07:14.199Z", "created_by": null } }, "projects": { "clvo7ajpc014907zxagiw9hry": { "project_name": "test_lambda", "labels": [ { "label_kind": "Default", "id": "clvo7b6h501ic07fdbg62c32w", "label_details": { "created_at": "2024-05-06T17:18:48Z", "updated_at": "2024-05-06T17:18:48Z", "created_by": "[email protected]" }, "performance_details": { "seconds_to_create": 5759.536, "seconds_to_review": 0, "skipped": false, "benchmark_reference_label": null, "benchmark_score": null, "consensus_score": null }, "annotations": { "classifications": [], "objects": [ { "bbox": { "height": 359, "left": 24, "top": 85, "width": 480 }, "color": "#1976d2", "feature_id": "clvv876mw00023b6qr6mk2o4s", "instance_uri": "https://api.labelbox.com/masks/feature/clvv876mw00023b6qr6mk2o4s", "schema_id": "clvcnmppk0aq507z11haa4x66", "title": "person", "value": "person" } ], "relationships": [] } } ], "project_details": { "ontology_id": "clvcnmpor0aq207z14jesh8w7", "batch_id": "11EF07D4F9E60FC08820734F207E70B1", "priority": 5, "consensus_expected_label_count": { "Int32": 1, "Valid": true } } } }, "media_attributes": { "asset_type": "image", "content_length": 122898, "exif_rotation": 1, "height": 825, "mime_type": "image/jpeg", "sub_type": "jpeg", "super_type": "image", "width": 550 } } ``` ```json Label updated expandable theme={"theme":"css-variables"} { "data_row": { "id": "clvmpaetz76ct0706730rfcq9", "external_id": null, "global_key": "TEST-ID-157128604429964085398241585654637789196d", "row_data": "https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_0001.jpeg", "metadata_fields": [], "details": { "dataset_id": "clvmpa9et010j0750rlgs8qdi", "created_at": "2024-04-30T18:07:10.82Z", "updated_at": "2024-04-30T18:07:14.199Z", "created_by": null } }, "projects": { "clvo7ajpc014907zxagiw9hry": { "project_name": "test_lambda", "labels": [ { "label_kind": "Default", "id": "clvo7b6h501ic07fdbg62c32w", "label_details": { "created_at": "2024-05-06T17:18:48Z", "updated_at": "2024-05-06T17:18:48Z", "created_by": "[email protected]" }, "performance_details": { "seconds_to_create": 5759.536, "seconds_to_review": 0, "skipped": false, "benchmark_reference_label": null, "benchmark_score": null, "consensus_score": null }, "annotations": { "classifications": [], "objects": [ { "bbox": { "height": 359, "left": 24, "top": 85, "width": 480 }, "color": "#1976d2", "feature_id": "clvv876mw00023b6qr6mk2o4s", "instance_uri": "https://api.labelbox.com/masks/feature/clvv876mw00023b6qr6mk2o4s", "schema_id": "clvcnmppk0aq507z11haa4x66", "title": "person", "value": "person" } ], "relationships": [] } } ], "project_details": { "ontology_id": "clvcnmpor0aq207z14jesh8w7", "batch_id": "11EF07D4F9E60FC08820734F207E70B1", "priority": 5, "consensus_expected_label_count": { "Int32": 1, "Valid": true } } } }, "media_attributes": { "asset_type": "image", "content_length": 122898, "exif_rotation": 1, "height": 825, "mime_type": "image/jpeg", "sub_type": "jpeg", "super_type": "image", "width": 550 } } ``` ```json Label deleted expandable theme={"theme":"css-variables"} { "data_row": { "id": "clvmpaetz76cp0706m3jw8ww4", "external_id": null, "global_key": "TEST-ID-157128695542350976802229818230180675596d", "row_data": "https://storage.googleapis.com/labelbox-datasets/People_Clothing_Segmentation/jpeg_images/IMAGES/img_0004.jpeg", "metadata_fields": [], "details": { "dataset_id": "clvmpa9et010j0750rlgs8qdi", "created_at": "2024-04-30T18:07:10.819Z", "updated_at": "2024-04-30T18:07:14.196Z", "created_by": null } }, "projects": { "clvo7ajpc014907zxagiw9hry": { "project_name": "test_lambda", "labels": [ { "label_kind": "Default", "id": "clvo7b02m01p807znd5bwfr2v", "label_details": { "created_at": "2024-05-01T19:19:31Z", "updated_at": "2024-05-06T17:22:06Z", "created_by": "[email protected]" }, "performance_details": { "seconds_to_create": 3.75, "seconds_to_review": 0, "skipped": false, "benchmark_reference_label": null, "benchmark_score": null, "consensus_score": null }, "annotations": { "classifications": [], "objects": [ { "bbox": { "height": 540, "left": 130, "top": 82, "width": 311 }, "color": "#1976d2", "feature_id": "clvo7b6qr00023b6q4xe4nnff", "instance_uri": "https://api.labelbox.com/masks/feature/clvo7b6qr00023b6q4xe4nnff", "schema_id": "clvcnmppk0aq507z11haa4x66", "title": "person", "value": "person" } ], "relationships": [] } } ], "project_details": { "ontology_id": "clvcnmpor0aq207z14jesh8w7", "batch_id": "11EF07D4F9E60FC08820734F207E70B1", "priority": 5, "consensus_expected_label_count": { "Int32": 1, "Valid": true } } } }, "media_attributes": { "asset_type": "image", "content_length": 167155, "exif_rotation": 1, "height": 825, "mime_type": "image/jpeg", "sub_type": "jpeg", "super_type": "image", "width": 550 } } ``` ```json Workflow expandable theme={"theme":"css-variables"} { "organizationId": "cloordigw0091073kc6pohyab", "projectId": "clvo7ajpc014907zxagiw9hry", "action": "MOVE", "dataRowIds": ["clvmpaetz76ct0706730rfcq9"], "originTaskId": "b363420e-1ea9-09dd-914c-d105efc5785a", "originTaskName": "Initial labeling task", "destinationTaskId": "c7a8a45d-c37d-4dba-bde7-792581597b10", "destinationTaskName": "Initial review task", "actorId": "cloordihe0092073kcorncj7g", "timestamp": "2024-05-06T17:18:48.797Z" } ``` #### v1 ```json Label created expandable theme={"theme":"css-variables"} { "agreement": null, "benchmarkAgreement": null, "createdAt": "2023-02-13T18:22:46Z", "dataRow": { "createdAt": "2023-01-25T17:54:20Z", "deletedAt": null, "externalId": "multi_thread_update_4cfdcb29-be33-4e07-b586-849829385210", "id": "cldbyu7c7n4jp072scuqt66ll", "labelCountInProject": 1, "rowData": "https://labelbox.s3-us-west-2.amazonaws.com/datasets/mapillary_traffic/images/F1xqHzDSi1qP3qSLBXVyJQ.jpg", "updatedAt": "2023-01-25T20:42:57Z" }, "dataset": { "createdAt": "2023-01-25T17:53:58Z", "deleted": false, "description": "", "id": "cldbytq0w2uw8074hgl3hbxr2", "name": "Datarow Update Test - 5K", "updatedAt": "2023-01-25T17:54:21Z" }, "deleted": false, "id": "cle357uex0g5307z996y28ic0", "label": "{\"objects\":[{\"featureId\":\"cle357vrp00013b6jsi6dfei0\",\"schemaId\":\"cldan3u0701sc07yi6xyw6up6\",\"color\":\"#ff0000\",\"title\":\"bounding_box\",\"value\":\"bounding_box\",\"bbox\":{\"top\":453,\"left\":951,\"height\":670,\"width\":952},\"instanceURI\":\"https://api.labelbox.com/masks/feature/cle357vrp00013b6jsi6dfei0\"}],\"classifications\":[],\"relationships\":[]}", "project": { "createdAt": "2023-02-13T18:20:52Z", "deleted": false, "description": "", "id": "cle355i370h86070hf1v7ata1", "name": "Webhooks", "updatedAt": "2023-02-13T18:22:17Z" }, "secondsToLabel": 0, "skipped": false, "updatedAt": "2023-02-13T18:22:46Z", "user": { "email": "[email protected]", "id": "clbpgxfaehww6076tedo5ejwx" } } ``` ```json Label updated expandable theme={"theme":"css-variables"} { "id": "cl4g555g81ltr07cka1a2b1wv", "createdAt": "2022-06-16T19:41:49Z", "updatedAt": "2022-06-16T19:58:25Z", "secondsToLabel": 6.002, "label": "{\"objects\":[{\"featureId\":\"cl4hg2phm00013b6j2thsko0u\",\"schemaId\":\"cl2i6hbju1o2y10a321w3cusv\",\"title\":\"grape\",\"value\":\"grape\",\"color\":\"#1CE6FF\",\"bbox\":{\"top\":298,\"left\":445,\"height\":520,\"width\":930},\"instanceURI\":\"https://api.labelbox.com/masks/feature/cl4hg2phm00013b6j2thsko0u\"}],\"classifications\":[],\"relationships\":[]}", "agreement": null, "benchmarkAgreement": null, "deleted": false, "skipped": false, "labelCountInProject": 2, "project": { "id": "cl2i6gssn1o2210a3gq549fq0", "createdAt": "2022-04-27T22:57:43Z", "updatedAt": "2022-06-15T22:19:36Z", "name": "grapes", "description": "", "deleted": false }, "dataRow": { "id": "cl2hzfg8g18kw0ztwbtnq0b6q", "createdAt": "2022-04-27T19:40:43Z", "updatedAt": "2022-04-27T20:13:51Z", "deletedAt": null, "externalId": "CFR_1620.jpg", "rowData": "https://storage.labelbox.com/ckhmnux5zeutm0825bt0kj34y%2F5375dc13-6442-22d7-f523-e9d252e715ea-CFR_1620.jpg?Expires=1656619105&KeyName=labelbox-assets-key-3&Signature=iOg3tbKe4j7xWZHw1r5rQD4iJIk=" }, "dataset": { "id": "cl2hz9qay0unl10a3f4wmcsfi", "createdAt": "2022-04-27T19:36:16Z", "updatedAt": "2022-04-27T19:40:43Z", "name": "data", "description": "", "deleted": false }, "user": { "id": "ckhmnux6hi0p907898jext199", "email": "[email protected]" } } ``` ```json Label deleted expandable theme={"theme":"css-variables"} { "id": "cl2i6hegh1x660z9i9fzz01a0", "createdAt": "2022-04-27T22:58:35Z", "updatedAt": "2022-06-16T20:12:41Z", "secondsToLabel": 31.448, "label": "{\"objects\":[{\"featureId\":\"cl2i6hhgd00013f6ghfnrcn6h\",\"schemaId\":\"cl2i6hbju1o2y10a321w3cusv\",\"title\":\"grape\",\"value\":\"grape\",\"color\":\"#1CE6FF\",\"bbox\":{\"top\":701,\"left\":402,\"height\":316,\"width\":238},\"instanceURI\":\"https://api.labelbox.com/masks/feature/cl2i6hhgd00013f6ghfnrcn6h\"},{\"featureId\":\"cl2i6hp3h00033f6gaofl29lx\",\"schemaId\":\"cl2i6hbju1o3010a30ksn2yiy\",\"title\":\"grape mask\",\"value\":\"grape_mask\",\"color\":\"#FF34FF\",\"instanceURI\":\"https://api.labelbox.com/masks/feature/cl2i6hp3h00033f6gaofl29lx\"}],\"classifications\":[],\"relationships\":[]}", "agreement": null, "benchmarkAgreement": null, "deleted": true, "skipped": false, "labelCountInProject": 1, "project": { "id": "cl2i6gssn1o2210a3gq549fq0", "createdAt": "2022-04-27T22:57:43Z", "updatedAt": "2022-06-16T20:06:25Z", "name": "grapes", "description": "", "deleted": false }, "dataRow": { "id": "cl2hzfg8g18kk0ztwg22ke4vz", "createdAt": "2022-04-27T19:40:43Z", "updatedAt": "2022-04-27T20:13:52Z", "deletedAt": null, "externalId": "SYH_2017-04-27_1324.jpg", "rowData": "https://storage.labelbox.com/ckhmnux5zeutm0825bt0kj34y%2F1d883928-046f-97d5-e3f7-e6c296967c7e-SYH_2017-04-27_1324.jpg?Expires=1656619961&KeyName=labelbox-assets-key-3&Signature=0ma5Yw5qxdJE6_x7ACR7aPEHePM=" }, "dataset": { "id": "cl2hz9qay0unl10a3f4wmcsfi", "createdAt": "2022-04-27T19:36:16Z", "updatedAt": "2022-04-27T19:40:43Z", "name": "data", "description": "", "deleted": false }, "user": { "id": "ckhmnux6hi0p907898jext199", "email": "[email protected]" } } ``` ```json Workflow expandable theme={"theme":"css-variables"} { "organizationId": "cl81imdtk0cie0yx4g39i0du8", "projectId": "clbxofp0h05d2074475g14f9v", "action": "APPROVE", "dataRowIds": ["cl9elx44f001i076k2pz84tw6"], "originTaskId": "92665e1f-a507-4308-bdfc-d6be3c629ec7", "originTaskName": "My custom review step", "destinationTaskId": null, "destinationTaskName": "Done", "actorId": "cl81imdtx0cif0yx4ak0ndq8a", "timestamp": "2023-01-20T12:06:01.421Z" } ``` # What's new Source: https://docs.labelbox.com/horizon/developer-guides/whats-new See the latest version updates of the Labelbox SDK ## Added * Add performance report methods that return the numbers behind a project's Performance dashboard and the workspace Monitor: `Client.get_performance_metrics()`, `Client.get_workspace_performance_metric()`, `Client.get_workspace_labeler_performance()`, `Project.get_performance_metric()`, `Project.get_labeler_performance()`, `Project.get_reviewer_performance()`, and `Project.get_performance_report_download()`. See the [Performance API](/horizon/developer-guides/performance-api) guide * Add `allowed_ip_cidrs` to `Client.create_api_key()` and `ApiKey.create_api_key()` to restrict an API key to a list of IP addresses or CIDR ranges, and expose `ApiKey.allowed_ip_cidrs` ## Changed * `Client.is_feature_schema_archived()` now includes the HTTP status and response body in the error it raises when the archived status cannot be read ## Fixed * Route embedding metadata operations (`Client.create_embedding()`, `Client.get_embeddings()`, `Client.get_embedding_by_id()`, `Embedding.delete()`) through GraphQL so they work with opaque `lbx_` API keys instead of raising `JSONDecodeError` ## Added * Add `User.last_login_at` to expose GraphQL `lastLoginAt` (nullable datetime of last session in the organization) ## Added * Add `Tool.Type.MARKER` for video marker tool ontologies — fetching an ontology containing a marker tool no longer raises `ValueError` ## Removed * Remove LLM prompt/response and response-creation project support: `Client.create_prompt_response_generation_project()`, `Client.create_response_creation_project()`, `MediaType.LLMPromptCreation`, `MediaType.LLMPromptResponseCreation`, `OntologyKind.ResponseCreation`, `EditorTaskType.ResponseCreation`, and `Project.is_prompt_response()` ## Fixed * Fix Foundry app lookups to raise `ResourceNotFoundError` for apps that do not exist, instead of a generic `LabelboxError` ## Added * Optional `batch_ids` parameter to `Project.get_overview()` to return workflow state counts scoped to one or more batches ## Added * `ModelRun.total_cost` and `ModelRun.total_data_rows` properties to retrieve inference cost and data row count for a model run * `ModelRun.refresh_cost_and_usage()` to re-fetch live cost and usage data ## Added * Add `Project.bulk_assign_data_rows()` method for bulk assigning data rows to a user ## Fixed * Reorder `create_api_key` validation to check input params before making API calls * Fix mypy type error in `DataRowUpsertItem.build` ## Added * Add CRUD support for `Issues`, `Comments` and `Issue Categories` ## Added * Add support for text subclasses under global text subclasses ## Added * Add support of groups to [invite\_user()](/horizon/developer-guides/organization#invite-users) * Add group classification type * Add model config response count support ## Fixed * Workflow Management allow multiple inputs ## Added * Add support for Audio Temporal Annotations ## Updated * DataRowMetadata: Remove enforcement of character limit ## Fixed * Project Setup: Remove project setup complete restriction * API key creation: fix support of role with a space in the name * Workflow cloning: Fix validation for some nodes ## Fixed * `reset_to_initial_nodes()` and `clone_workflow_from()` will not update initial node IDs anymore ## Added * Add the ability to specify relationship constraints ## Fixed * Minor fixes to CRUD operations when specifying user groups ## Added * Add support for Likert scale * Add creation of IAM integrations * Add workflow management * Add model app ID to model run creation and return it * Add support for project ontology change * Remove `mypy` from dependencies ## Updated * **BREAKING CHANGE** `UserGroup` no longer supports the `users` field - it's been replaced by the `members` field. * Update `UserGroup` to fix project membership ## Fixed * `ApiKey.created_by` now correctly displays the `user` for old API keys ## Added * [API key management](/horizon/developer-guides/create-api-key#manage-api-keys-from-the-sdk) using the SDK * Methods for [getting](/horizon/developer-guides/organization#get-pending-invites) and [canceling](/horizon/developer-guides/organization#cancel-invite) invitations to workspaces or projects * The `organization.tasks()` method for getting organization tasks and the `TaskStatus` enum for filtering both [organization](/horizon/developer-guides/organization#get-tasks) and [user](/horizon/developer-guides/user#get-the-created-tasks) tasks ## Added * [Task cancellation](/horizon/developer-guides/tasks#cancel-tasks) for supported task types * `AUDIO` as a [supported attachment type](/horizon/developer-guides/attachments#supported-attachment-types) ## Added * An optional `source_ontology_name` field to `Relationship` class. You can now specify relationships using `source_ontology_name` instead of a concrete source annotation. This approach is especially useful for [PDF document annotations](/horizon/developer-guides/import-document-annotations#relationships-page-specific) because it allows you to reference ontology classes directly when you create relationships. * Validation ensures that either `source` or `source_ontology_name` is provided, but not both * Support for [searching by name](/horizon/developer-guides/slice#get-catalog-slice) for `get_catalog_slice` * `get_catalog_slices` to [get all slices](/horizon/developer-guides/slice#get-catalog-slice) ## Added * (Beta) MAL support for PDF [relationships](/horizon/developer-guides/import-document-annotations#relationships-between-classifications-and-objects) * (Beta) `readonly` [setting](/horizon/developer-guides/import-document-annotations#read-only-relationships) for relationships ## Added * Support for python 3.13 * Note: minimum numpy version that supports python 3.13 is 2.1 * Include predictions in slice export ## Fixed * GraphQL upload SSL configuration ## Added * Support including `predictions` in catalog exports * Add feature schema attributes to support the auto OCR tool in ontologies * `task_queue_id` is now optional and defaults to `None` for better type support when [moving data rows to a workflow task](/horizon/developer-guides/project#move-data-rows-to-a-workflow-task) ## Added * Group member upload * Group member export - For security reasons, the `Invite` class doesn't contain actual invite id (uid) any more. This change affects all versions. ## Added * Multimodal chat evaluation [ontology support](/horizon/developer-guides/multimodal-chat-evaluation-project#option-a-create_ontology) for two new tools: * Prompt rating: [Example export payload](/horizon/developer-guides/export-multimodal-chat-annotations#prompt-rating) * Fact-checking: [Example export payload](/horizon/developer-guides/export-multimodal-chat-annotations#fact-checking) * Added the following new import methods that replace the deprecated `BulkImportRequest` class: * `project.get_mal_prediction_imports()` to retrieve the list of MAL import jobs. * `project.get_label_imports()` to to retrieve the list of ground-truth import jobs. * `MALPredictionImport.delete()` to delete a MAL import. * Deprecation warning to `wait_until_done` methods * Deprecation warning to `get_data_row_ids` ## Fixed * Fixed relationship label bug * Fixed the `Import lb-client` exceptions for labelbox * Fixed `get_roles` return type to `Dict[str, Role]` (instead of `List[Role]`) * Adjusted deprecation message to a new standard * Improved support for `Client.enable_experimental` ## Added * Added the [step reasoning ontology tool](/horizon/developer-guides/multimodal-chat-evaluation-project#option-a-create_ontology) . ## Fixed * Use valid `lbox.exception` in client IAMIntegration validation * Improve SDK method name detection for Datadog dashboard * Limit labeling front-end visibility and mentions of a custom editor * Remove all unused python imports * Remove bulk import requests ## Fixed * Updated `labelbox.exceptions` to `lbox.exceptions` for IAM Integration check ## Added * Added deprecation messages to all `export_v2` methods * Added logic to extract SDK methods to pass to Datadog dashboard * Added the `lbox.request_client` Graphql client as a separate component ## Updated * Updated the minimum requirement of `numpy` version from 1.24 to 1.25 and cleaned up `_TypedArray` validations * Refactored client `create_project` inputs to be named parameters instead of \*\*kwargs * Updated pydantic v2 deprecation warnings * Removed experimental from `UserGroup` * Improved labeling service error reporting * Switched to ruff linter ## Fixed * Labeling dashboard serialization * Handling of global key in `ModelEvaluationTemplate` ## Removed * Discontinued the support for Python 3.8 * Removed various deprecated classes, methods and attributes, including: * `QueueMode` for project * LabelGenerator `assign_feature_schema_ids` * Label `assign_feature_schema_ids` * AttachmentType `attachment_type.TEXT` * DataRowMetadata bulk\_delete `deletes` parameter - support for string for data row ids or global keys, use `UniqueId` or `GlobalKey` class instead * Dataset `create_data_rows_sync`. Use create\_data\_rows instead * Slice `get_data_row_ids`. Use get\_data\_row\_identifiers instead * LabelingParameterOverrideInput remove instance of `DataRow` as a type of input * Project `_update_queue_mode` and `get_queue_mode` * `Project.setup` and `Project.setup_editor`. Use `project.connect_ontology` instead * Removed deprecated get stream - related classes from ExportTask * `JsonConverter`, `JsonConverterOutput`, `FileConverter`, `FileConverterOutput` * Removed label `Data` classes as Label data attribute. Use `GenericDataRowData` instead `MaskData` is NOT removed * Removed NDJsonConverter `deserialize` * Removed the COCO package and `BulkImportRequest` package, including: * `BulkImportRequest` * `BulkImportRequestState` * `project.upload_annotations` * `project.bulk_import_requests` ## Fixed * Exporter encoding ## Added * Support data row / batch for live mmc projects([#1856](https://github.com/Labelbox/labelbox-python/pull/1856)) ## Added * Support self-signed SSL certs([#1811](https://github.com/Labelbox/labelbox-python/pull/1811)) * Rectangle units now correctly support percent inputs([#1848](https://github.com/Labelbox/labelbox-python/pull/1848)) ## Updated * Set `tasks_remaining_count`attribute of [labeling service dashboard](/horizon/developer-guides/labeling-workforce#get-labeling-service-details) to `None` if labeling has not started. * Improve error messaging when creating LLM project with invalid dataset id parameter. ## Removed * **BREAKING CHANGE**: Export v1 SDK methods are all removed. Use the latest [export methods](/horizon/developer-guides/export-overview) instead. * **BREAKING CHANGE**: Unused labelbox\_v1 serialization package is removed. ## Fixed * Fixed CUID dependencies that cause a crash if numpy is not installed. ## Added * **BREAKING CHANGE** for Pydantic v1 users: Labelbox Python SDK now uses Pydantic v2. * Added automation test support for multiple SDK versions. ## Fixed * Fixed flaky tests. ## Fixed * Fixed labeling dashboard query for tags. ## Added * Added `project.get_labeling_service_dashboard()` and `client.get_labeling_service_dashboards()`, which can retrieve information of projects with labeling service requested. See [Get labeling service details](/horizon/developer-guides/labeling-workforce#get-labeling-service-details). * Added `client.get_task_by_id()`, which fetches tasks by their IDs. See [Get tasks](/horizon/developer-guides/tasks#get-tasks) . * Added support for [importing multimodal chat annotations](/horizon/developer-guides/import-multimodal-chat-annotations) . ## Fixed * Restored `client.headers`. ## Added * Added `labeling_service.request()`, which validates all project requirements and requests a labeling service. See [Labeling Workforce](/horizon/developer-guides/labeling-workforce). * Added the `is_benchmark_reference` flag that supports importing ground truth labels as benchmarks. See import annotations guides for example payloads. ## Updated * `get_labeling_service()` now automatically requests labeling service if none has been used on a project. See [Labeling Workforce](/horizon/developer-guides/labeling-workforce). ## Removed * **BREAKING CHANGE**: Removed `Project.labeling_frontend.disconnect()`. Now you can only have one default labeling front end per project and can't disconnect it. * **BREAKING CHANGE**: Removed the experimental method `project.request_labeling_service()`. See [Labeling Workforce](/horizon/developer-guides/labeling-workforce) for the latest approach. ## Added * Added experimental methods `Project.get_labeling_service()`, `request_labeling_service()` and `get_labeling_service_status()` that support requesting and checking the status of labeling service. See [Labeling Workforce](/horizon/developer-guides/labeling-workforce). * Added `Client.create_prompt_response_generation_project()` and `create_response_creation_project()` methods that support project and ontology creation for prompt response projects. See [Import prompt and response annotations](/horizon/developer-guides/import-prompt-and-response-annotations) and [Prompt and response projects](/horizon/developer-guides/prompt-and-response-projects) to learn how to use these methods to set up prompt and response projects. * Added `is_benchmark_enabled` and `is_consensus_enabled` flags that specify whether a project supports [benchmarks](/horizon/guides/benchmark) and [consensus](/horizon/guides/consensus) . ## Updated * Made Project quality modes a list to allow combining more than 1 quality mode per project. ## Notebooks * Added back the [python notebook](https://colab.research.google.com/github/Labelbox/labelbox-python/blob/develop/examples/exports/export_v1_to_v2_migration_support.ipynb) on migrating from Export V1 to the latest `export()`. * Added correct data param to the video notebooks. ## Other * Use connection pool for all http and graphql requests ## Removed * Project` media_type` no longer supports JSON ## Added * Added `project.set_project_model_setup_complete()` method * Added [user group management](/horizon/developer-guides/user#manage-user-groups) methods. * Refactored `dataset.create_data_rows_sync` to `upsert` * Added `upload_type` to Project * Added prompt classification for python object * Alias `wait_xxx` functions ## Fixed * Predictions missing during Catalog slice Export * Prevented adding batches to live chat evaluation projects * Added missing media types * Deprecate `project.setup_editor` in favor of `project.connect_ontology` * Bumped dateutil max version * Bumped version rye * Updated create ontology for project setup ## Added * Include predictions in export * Adds upsert label feedback method `client upsert_label_feedback()` ## Removed * Removed deprecated class `LabelList` ## Added * Conversational data row checks * UI ontology mode support * Empty data row validation ## Fixed * Numpy semver locked to \< 2.0.0 ## Added * SLSA provenance generation ## Fixed * Fix `client.get_project()` for LLM projects * Throw user-friendly errors when creating a custom embedding ## Added * Update Dataset `create_data_rows` to allow upload of unlimited number of data rows * New Dataset methods for iam\_integration: `add_iam_integration`, `remove_iam_integration` ## Notebooks * Added model evaluation SDK method notebook * Added quick start notebook geared towards new users ## Added * `project.get_overview()` to be able to retrieve project details * `project.clone()` to be able to clone projects * Support for Rye 0.34 * `Requirements.lock`, `Requirements-dev.lock` to latest dependencies * `ExportTask.get_buffered_stream` to replace `ExportTask.get_stream` ## Fixed * `ExportTask.result` / `ExportTask.errors` parsing content incorrectly * Lack of exceptions related to updating model config ## Added * Added [chat model evaluation](/horizon/developer-guides/multimodal-chat-evaluation-project) support: * `client.create_model_config()` * `project.project_model_configs()` * `project.add_model_config()` * `project.delete_project_model_config()` * `ProjectModelConfig.delete()` * `client.create_model_evaluation_project()` * Update existing methods to support chat model evaluation project * `client.create_ontology()` * `client.create_ontology_from_feature_schemas()` * Coco deprecation message ## Fixed * Fixed error reporting for client.create\_project() * Do not retry http 422 errors ## Notebooks * `Send_to_annotate_from_catalog` functionalities outside Foundry ## Fixed in Notebooks * Fixed meta notebook * Modified queue\_management.ipynb to remove some parameters * Update\_huggingface.ipynb * Corrected\_HF.ipynb # Workflow Source: https://docs.labelbox.com/horizon/developer-guides/workflow Developer guide for creating and modifying workflows via the Python SDK. # Client ```python theme={"theme":"css-variables"} import labelbox as lb client = lb.Client(api_key="") ``` # Fundamentals Workflows are connected to the `Project` class and are generated automatically during project creation. Workflows help organize and control the flow of labeling tasks through different stages. Key concepts: * Workflows are composed of **nodes** and **edges**. * Each node can have only one input connection, except when both `Initial labeling task` and `Rework (All Rejected)` nodes serve as inputs to a single downstream node. * No changes are pushed to the platform until you call `update_config()`. * All nodes must be connected for the workflow to be valid. ## Access a workflow ```python theme={"theme":"css-variables"} project.get_workflow() ``` ## Clone a workflow from a different project ```python theme={"theme":"css-variables"} project_source_id = "" project_target_id = "" project_source = client.get_project(project_source_id) project_target = client.get_project(project_target_id) project_target.clone_workflow_from(project_source.uid) ``` ## Reset a workflow Use `reset_to_initial_nodes()` to only have the InitialLabeling and InitialRework nodes and start from scratch. The method will return an object `InitialNodes` with two attributes `labeling` and `rework`. You can also pass parameters to configure the initial nodes. ```python theme={"theme":"css-variables"} # Get initial nodes with basic status initial_nodes = workflow.reset_to_initial_nodes() initial_labeling = initial_nodes.labeling initial_rework = initial_nodes.rework # Configure initial nodes # All parameters are optional from labelbox.schema.workflow.config import LabelingConfig, ReworkConfig from labelbox.schema.workflow.enums import IndividualAssignment initial_nodes = workflow.reset_to_initial_nodes( labeling_config=LabelingConfig( instructions="Instructions for labeling", max_contributions_per_user=10 ), rework_config=ReworkConfig( instructions="Instructions for reworking", individual_assignment=[IndividualAssignment.LabelCreator], max_contributions_per_user=5 ) ) ``` ## Commit changes To push changes made to a workflow, you must use `update_config()`. In case of validation issue, it will throw a `ValueError` exception. ```python theme={"theme":"css-variables"} # Commit changes without changing node locations workflow.update_config() # Commit changes and attempt to realign nodes workflow.update_config(reposition=True) ``` # Nodes ## Add a node Types of nodes are listed in the enum `NodeType`: **Initial nodes:** * `NodeType.InitialLabeling` - Entry point for new labeling tasks * `NodeType.InitialRework` - Entry point for tasks that need to be reworked **Step nodes:** * `NodeType.Review` - Review completed labels * `NodeType.Logic` - Apply filters to route tasks conditionally * `NodeType.CustomRework` - Custom rework step with configurable settings **Terminal nodes:** * `NodeType.Done` - Marks tasks as completed * `NodeType.Rework` - Sends tasks back to the rework queue * `NodeType.CustomRework` can be used as a terminal node or be connected to another node. `NodeType.CustomRework` can be used as a terminal node or be connected to another node. ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import NodeType review_node = workflow.add_node(type=NodeType.Review) ``` ## Delete a node This automatically removes connected edges. Initial nodes can't be deleted. ```python theme={"theme":"css-variables"} # Get nodes to delete nodes_to_delete = [ node for node in workflow.get_nodes() if node.name == "NodeToDelete" ] workflow.delete_nodes(nodes_to_delete) ``` # Edges ## Add an edge Edges connect the output of a source node to the input of a target node. All nodes must be connected in the workflow. Types of outputs are listed in the enum `NodeOutput`: * `NodeOutput.If` (default value, can be omitted) * `NodeOutput.Else` * `NodeOutput.Approved` * `NodeOutput.Rejected` ### Outputs per node | Node | Available outputs | | - | - | | `InitialLabeling` | `NodeOutput.If` | | `InitialRework` | `NodeOutput.If` | | `Review` | `NodeOutput.Approved`, `NodeOutput.Rejected` | | `Logic` | `NodeOutput.If`, `NodeOutput.Else` | | `CustomRework` | Optional `NodeOutput.If` | | `Done` | | | `Rework` | | ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import NodeOutput # Connect nodes with appropriate outputs workflow.add_edge(initial_labeling, initial_review) # Default NodeOutput.If workflow.add_edge(initial_rework, initial_review) workflow.add_edge(initial_review, logic, NodeOutput.Approved) workflow.add_edge(initial_review, rework_node, NodeOutput.Rejected) workflow.add_edge(logic, done, NodeOutput.If) # NodeOutput.If can be omitted workflow.add_edge(logic, custom_rework_1, NodeOutput.Else) ``` ## Node attributes The following attributes can be configured for each node type: | **Node** | **Configuratble attributes** | | :- | :- | | `InitialLabeling` | `instructions`, `max_contributions_per_user` | | `InitialRework` | `instructions`, `individual_assignment`, `max_contributions_per_user` | | `Review` | `instructions`, `group_assignment`, `max_contributions_per_user` | | `Logic` | `name`, `match_filters`, `filters` | | `CustomRework` | `name`, `instructions`, `group_assignment`, `individual_assignment`, `max_contributions_per_user` | | `Done` | `name` | | `Rework` | `name` | **Common attributes:** * `max_contributions_per_user`: Positive integer representing the maximum number of labels per task queue (empty for no limit) * `instructions`: Custom instructions for labelers working on this node * `group_assignment`: List of user group IDs assigned to this node * `individual_assignment`: Individual assignment strategy (you can use the `IndividualAssignment` enum) # Filters ## Logic node The Logic node contains filters that determine how tasks flow through the workflow. The `match_filters` attribute controls how multiple filters are evaluated: * `MatchFilters.Any`: Match any of the filters (OR logic) * `MatchFilters.All`: Match all of the filters (AND logic) ## Add and remove filters ```python theme={"theme":"css-variables"} from labelbox.schema.workflow.enums import WorkflowDefinitionId from labelbox.schema.workflow.enums import FilterField workflow = project.get_workflow() logic = next( node for node in workflow.get_nodes() if node.definition_id == WorkflowDefinitionId.Logic ) # logic = workflow.get_node_by_id("0359113a-6081-4f48-83d1-175062a0259b") # Remove a filter based on its type logic.remove_filter(FilterField.ModelPrediction) # Apply changes #workflow.update_config() # Add a filter logic.add_filter( model_prediction([ mp_condition.is_none() ]) ) # Apply changes #workflow.update_config() ``` ## Manage filters on Logic nodes ```python theme={"theme":"css-variables"} from labelbox.schema.workflow.enums import WorkflowDefinitionId, FilterField from labelbox.schema.workflow import mp_condition, model_prediction workflow = project.get_workflow() # Get the Logic node logic = next( node for node in workflow.get_nodes() if node.definition_id == WorkflowDefinitionId.Logic ) # Alternative: get by node ID # logic = workflow.get_node_by_id("0359113a-6081-4f48-83d1-175062a0259b") # Remove a filter based on its type logic.remove_filter(FilterField.ModelPrediction) # Add a filter logic.add_filter( model_prediction([ mp_condition.is_none() ]) ) # Apply changes workflow.update_config() ``` ## Available filters Each filter type can be used at most once per Logic node. The enum `FilterField` provides available filters for search or deletion: * `FilterField.Annotation` * `FilterField.Batch` * `FilterField.ConsensusAverage` * `FilterField.Dataset` * `FilterField.FeatureConsensusAverage` * `FilterField.IssueCategory` * `FilterField.LabelingTime` * `FilterField.LabeledAt` * `FilterField.LabeledBy` * `FilterField.Metadata` * `FilterField.ModelPrediction` * `FilterField.NlSearch` * `FilterField.ReviewTime` * `FilterField.Sample` ### annotation Filter by the presence of specific annotations. `schema_node_ids` is a list of schema node IDs that correspond to tools or classifications defined in the project's ontology schema. **Operators:** None (direct list filter) ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import annotation # Using named parameter annotation(schema_node_ids=[""]) # Using positional parameter annotation([""]) ``` ### batch Filter by batch membership. \*\*Operators: \*\* * `is_one_of` * `is_not_one_of` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import batch # Using named parameter batch.is_one_of(values=[""]) # Using positional parameter batch.is_one_of([""]) ``` ### consensus\_average Filter by overall consensus score. **Operators:** None (range filter with min/max) ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import consensus_average # Using named parameters consensus_average(min=0.17, max=0.61) # Using positional parameters consensus_average(0.17, 0.61) ``` ### dataset Filter by dataset membership. **Operators:** None (direct list filter) ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import dataset # Using named parameter dataset(dataset_ids=[""]) # Using positional parameter dataset([""]) ``` ### feature\_consensus\_average Filter by consensus score for specific features. `annotations` is a list of schema node IDs that correspond to tools or classifications defined in the project's ontology schema. **Operators:** None (range filter with min/max and annotation list) ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import feature_consensus_average # Using named parameters feature_consensus_average(min=0.17, max=0.67, annotations=[""]) # Using positional parameters feature_consensus_average(0.17, 0.67, [""]) ``` ### issue\_category Filter by issue categories flagged during review. **Operators:** * `is_one_of` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import issue_category # Using named parameter issue_category.is_one_of(category_ids=[""]) # Using positional parameter issue_category.is_one_off([""]) ``` ### labeling\_time Filter by how long it took to create the label. The value represents a number of seconds. **Operators:** * `greater_than` * `less_than` * `greater_than_or_equal` * `less_than_or_equal` * `between` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import labeling_time # Using named parameter labeling_time.greater_than(seconds=1000) # Using positional parameter labeling_time.greater_than(1000) ``` ### labeled\_at Filter by when the label was created. **Operators:** * `between` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import labeled_at from datetime import datetime # Using named parameters labeled_at.between( start=datetime(2024, 3, 9, 5, 5, 42), end=datetime(2025, 4, 28, 13, 5, 42) ) # Using positional parameters labeled_at.between( datetime(2024, 3, 9, 5, 5, 42), datetime(2025, 4, 28, 13, 5, 42) ) ``` ### labeled\_by Filter by the user who created the label. **Operators:** * `is_one_of` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import labeled_by # Using named parameter labeled_by.is_one_of(user_ids=["", ""]) # Using positional parameter labeled_by.is_one_of(["", ""]) ``` ### metadata Filter by data row metadata values. Each condition `m_condition`) takes a metadata schema ID `key`) and list of strings as parameters `value`). **Operators:** * `contains` * `starts_with` * `ends_with` * `does_not_contain` * `is_any` * `is_not_any` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import metadata, m_condition # Using named parameters metadata(conditions=[m_condition.contains(key="", value=["test"])]) # Using positional parameters metadata([m_condition.contains("", ["test"])]) ``` ### model\_prediction Filter by model predictions. Model predictions use a list of conditions named `mp_condition`. Each condition takes a list of model IDs, followed by an integer for the minimum score `min_score`) and an integer for the optional maxim score `max_score`). `is_none` takes precedence over other operators. **Operators:** * `is_one_of` * `is_not_one_of` * `is_none` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import model_prediction, mp_condition # Using named parameter model_prediction(conditions=[ mp_condition.is_one_of(models=[""], min_score=1), mp_condition.is_not_one_of(models=[""], min_score=2, max_score=6), mp_condition.is_none() ]) # Using positional parameter model_prediction([ mp_condition.is_one_of([""], 1), mp_condition.is_not_one_of([""], 2, 6), mp_condition.is_none() ]) ``` ### natural\_language Filter using semantic search. The `content` (or prompt) follows this format: `"Find this / more of this / not this / bias_value"` where `bias_value` is a decimal between 0 and 1. **Operators**: None (semantic search with score range) ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import natural_language # Using named parameters natural_language( content="Birds in the sky/Blue sky/clouds/0.5", min_score=0.178, max_score=0.768 ) # Using positional parameters natural_language("Birds in the sky/Blue sky/clouds/0.5", 0.178, 0.768) ``` ### review\_time Filter by how long it took to review the label. **Operators:** * `greater_than` * `less_than` * `greater_than_or_equal` * `less_than_or_equal` * `between` ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import review_time # Using named parameter review_time.less_than_or_equal(seconds=100) # Using positional parameter review_time.less_than_or_equal(100) ``` ### sample Filter by percentage sampling. The percentage is entered as an integer. **Operators:** None (percentage value) ```python theme={"theme":"css-variables"} from labelbox.schema.workflow import sample # Using named parameter sample(percentage=23) # Using positional parameter sample(23) ``` # Example: Create a minimal workflow The following example will create a basic workflow with three nodes: * Initial labeling task * Rework (all rejected) * Done ```python theme={"theme":"css-variables"} import labelbox as lb from labelbox.schema.workflow import NodeType # Initialize client and project client = lb.Client(api_key="") project_id = "" project = client.get_project(project_id) # Get workflow workflow = project.get_workflow() # Create nodes initial_nodes = workflow.reset_to_initial_nodes() done = workflow.add_node(type=NodeType.Done) # Connect nodes workflow.add_edge(initial_nodes.labeling, done) workflow.add_edge(initial_nodes.rework, done) # Validate and commit changes try: updated_workflow = workflow.update_config(reposition=True) print("Workflow updated successfully!") except ValueError as e: print(f"Validation failed: {e}") ``` # Example: Showcase complete workflow The following example shows all the nodes and filters. ```python theme={"theme":"css-variables"} import labelbox as lb from labelbox.schema.workflow import ( NodeType, NodeOutput, ProjectWorkflowFilter, LabelingConfig, ReworkConfig, labeled_by, metadata, sample, labeled_at, mp_condition, m_condition, labeling_time, review_time, issue_category, batch, dataset, annotation, consensus_average, model_prediction, natural_language, feature_consensus_average ) from labelbox.schema.workflow.enums import IndividualAssignment, MatchFilters from datetime import datetime # Initialize client and project client = lb.Client(api_key="") project_id = "" project = client.get_project(project_id) # Get workflow workflow = project.get_workflow() # Reset config to start a fresh workflow initial_nodes = workflow.reset_to_initial_nodes( labeling_config=LabelingConfig( instructions="This is the entry point", max_contributions_per_user=10 ), rework_config=ReworkConfig( individual_assignment=[IndividualAssignment.LabelCreator] ) ) initial_labeling = initial_nodes.labeling initial_rework = initial_nodes.rework initial_review = workflow.add_node( type=NodeType.Review, name="Initial review task", group_assignment=["", ""] ) logic = workflow.add_node( type=NodeType.Logic, name="Logic node", match_filters=MatchFilters.Any, filters=ProjectWorkflowFilter([ labeled_by.is_one_of(["", "", ""]), metadata([m_condition.contains("", ["test"])]), sample(23), labeled_at.between( datetime(2024, 3, 9, 5, 5, 42), datetime(2025, 4, 28, 13, 5, 42) ), labeling_time.greater_than(1000), review_time.less_than_or_equal(100), issue_category.is_one_of([""]), batch.is_one_of([""]), dataset.is_one_of([""]), annotation.is_one_of([""]), consensus_average(0.17, 0.61), model_prediction([ mp_condition.is_one_of([""], 1), mp_condition.is_not_one_of([""], 2, 6), mp_condition.is_none() ]), natural_language("Birds in the sky/Blue sky/clouds/0.5", 0.178, 0.768), feature_consensus_average(0.17, 0.67, [""]) ]) ) # Terminal and step nodes done = workflow.add_node(type=NodeType.Done) rework = workflow.add_node(type=NodeType.Rework, name="To rework") custom_rework_1 = workflow.add_node( type=NodeType.CustomRework, name="Custom Rework 1", individual_assignment=IndividualAssignment.LabelCreator, group_assignment=["", ""] ) review_2 = workflow.add_node( type=NodeType.Review, name="Review 2" ) custom_rework_2 = workflow.add_node( type=NodeType.CustomRework, name="Custom Rework 2", instructions="Additional rework instructions" ) done_2 = workflow.add_node( type=NodeType.Done, name="Ready for final review" ) # Create edges between nodes workflow.add_edge(initial_labeling, initial_review) workflow.add_edge(initial_rework, initial_review) workflow.add_edge(initial_review, logic, NodeOutput.Approved) workflow.add_edge(initial_review, rework, NodeOutput.Rejected) workflow.add_edge(logic, review_2, NodeOutput.If) workflow.add_edge(logic, custom_rework_1, NodeOutput.Else) workflow.add_edge(review_2, done, NodeOutput.Approved) workflow.add_edge(review_2, custom_rework_2, NodeOutput.Rejected) workflow.add_edge(custom_rework_2, done_2) # Commit the workflow try: updated_workflow = workflow.update_config(reposition=True) print("Workflow updated successfully!") except ValueError as e: print(f"Validation failed: {e}") ``` # Access, storage & security Source: https://docs.labelbox.com/horizon/guides/access-storage Information on account and data accessibility, storage, and security. This guide provides information on your options for storing data when using Labelbox, as well as details on our security and privacy measures. ## Data storage configurations You have three options for connecting your data to Labelbox. The method you choose determines where your assets are stored. * **IAM delegated access (recommended)**: This is the recommended method for connecting your data to Labelbox. It allows you to host your data in your own cloud storage ([AWS S3](/horizon/guides/import-aws-s3-data), [Google Cloud Storage](/horizon/guides/using-google-cloud-storage), or [Azure Blob Storage](/horizon/guides/microsoft-azure-blob-storage)) and grant Labelbox access using native Identity and Access Management (IAM) roles. * **Pre-signed URLs**: You can create a JSON file containing pre-signed or public URLs that point to files in your own cloud storage. You are responsible for [generating these signed URLs](/horizon/guides/signed-urls). Once you have the JSON file, you can upload it to Labelbox. * **Direct file upload**: If you choose to upload your data directly, it will be stored in private buckets on Labelbox's Google Cloud Services. Labelbox will have access to your data to generate signed URLs for rendering assets in the browser. The standard expiration value for these signed URLs is one day. **A note on data location and performance** To ensure the best performance and fastest loading times for your labeling team, we recommend that you host your cloud data in a location that is geographically close to your team members. Labelbox uses Google Cloud Services for cloud storage and all data is stored in the US. If your data is hosted on our servers, we use a CDN that provides geo-loading as close to the location as possible. Labelbox does not store data in the EU and will not do so on request. ## Security and compliance We are committed to ensuring that your data is secure and that we meet our obligations under privacy and security laws. **Data encryption**\ All data hosted by Labelbox, including labeled data, assets, and private user information, is encrypted at rest using AES-256. We use Google Cloud for storage, which means your data is also encrypted on the server-side using GCP's default encryption keys. Data is automatically decrypted when read by an authorized user. **User authentication**\ To ensure that only authorized users can access your data, we use Auth0 for authentication. We also support [multi-factor authentication (MFA)](/horizon/guides/multifactor-authentication) and [Single Sign-On (SSO)](/horizon/guides/single-sign-on) for Enterprise accounts. **Regulatory compliance**\ Labelbox has a comprehensive privacy program to meet our obligations under regulations such as [CCPA](https://en.wikipedia.org/wiki/California_Consumer_Privacy_Act), [GDPR](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation), [SOC 2](https://en.wikipedia.org/wiki/System_and_Organization_Controls) Type II, and [HIPAA](https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act). For more details, please see our [Privacy FAQ](https://labelbox.com/company/privacy-faq). ## Data privacy and portability **Data usage**\ Labelbox does not sell customer or end-user personal data. We share personal data with third-party service providers for business purposes only, but we do not share this information for monetary value or other valuable consideration. For more information, please see our [Privacy Notice](/page/privacy-notice). **Data portability**\ Upon request, we can export your data and provide it to you. We can also permanently delete all of your data from our servers. ## More information # AI critic Source: https://docs.labelbox.com/horizon/guides/ai-critic AI Critic helps you ensure the quality of your data by allowing you to define validation rules using natural language. The AI is contextually aware of all elements on the data row—including messages, features, and rubrics—enabling you to create highly specific critiques. AI critic is supported for the Multi-modal chat editor, the Audio editor, and the Video editor. ## How to create a new critic 1. Go to your **Project settings** → **Advanced** and enable **AI critic.** You can also choose which Foundry model to power the AI critic by setting the **AI critic model**. 2. In the editor, select the **AI critic setup** icon in the top navbar. Then, click **Add new critic**. 3. Add a short descriptive **title** for your critic. Then, in the text box, **describe your critic** using natural language. Define which elements you want to critique and the specific criteria they must meet. The more specific your description, the better the critic performs. For the best results, make sure your prompt references these element names: | Element | Description | | - | - | | Actor | A participant in the conversation - either a "human" (the prompter) or a "model" (an AI responder). Each actor has metadata like a display name or model configuration name. | | Checklist classification | A multi-select classification where labelers choose one or more options from a list. Like radios, options can contain nested sub-questions. | | Display rules | Conditional visibility logic that controls when a classification appears. A criterion with display rules only shows when other criteria have specific values, enabling branching evaluation workflows. | | Feature | The actual value a labeler selected or entered for a classification. Contains the answer data, including which option was chosen (for radio/checklist) or what text was entered. | | Feature schema | The definition or blueprint for a classification. Describes its type (radio, checkbox, text), available options, scope, and display rules. Think of it as the question template. | | Index data | Binds a scoped classification to a specific element in the conversation. For example, a response-message-scoped classification's index data contains the ID of the response it applies to. | | Prompt message | A message sent by the human participant that initiates a conversation turn. In multi-turn conversations, each new prompt builds on the previous exchange. | | Radio classification | A single-select classification where labelers choose exactly one option from a list. Options can have nested sub-questions that appear when selected. | | Response message | A message generated by an AI model in reply to a prompt. In multi-model evaluation, a single prompt can have multiple responses from different models. | | Rubric criterion | A structured evaluation question used to assess model responses. Can be a radio (single-select), checkbox (multi-select), or text (free-form) question. Each criterion is associated with a specific prompt message and evaluates the responses to that prompt. | | Rubric group | A container that organizes related rubric criteria under a common header. Groups can have min/max constraints on how many criteria they contain. | | Scope | Determines where a classification applies. "Global" applies to the entire conversation. Other scopes target a specific prompt message, response message, rubric criterion, or turn. | | Text classification | A free-form text input annotation where labelers enter arbitrary text. | | Turn | A complete prompt-response exchange in the conversation. Each turn contains one prompt message and one or more response messages from model actors. | 5. Set the **Enforcement**. This setting determines what happens when a critique fails. Select from the following options. * **Block**: Prevents the user from submitting the label until the failing critique is resolved. This is the strictest quality-control option. * **Acknowledge**: When the user clicks submit, a notification appears detailing the quality failures. The user must acknowledge the message to proceed, but they are not blocked from submitting. * **None**: Displays the critique result as a visual aid directly in the editor but does not interfere with the submission process. 6. Click **Update Critic** to save it. The AI will immediately begin critiquing elements based on your new rule. ## How to write effective critiques You can create critiques for various elements within the editor. The AI understands the context and relationships between different elements, such as a classification and the message it is attached to. To verify that a classification's value is correct based on its label and the message it's associated with, you can use a prompt like this: `Look at the instructions or label for each classification. Make sure the value for the classification is correct based on its label and all of the contextual information. If it's a scoped classification, make sure to read the associated message in order to make your critique.` The AI critic will then: 1. Target all classifications in the editor. 2. Read the classification's label. 3. If the classification is attached to a message, it will read the message content. 4. Determine if the selected value is appropriate based on the context. ### Example prompts * *"All responses must be at least 3 sentences long and written in formal English. Responses that are too short or use casual language should fail."* * *"Each response must directly address the question asked in the prompt message. If the response is off-topic or does not answer the question, it should fail."* * *"For all checklist classifications, ensure that no contradictory options have been selected together."* * *"For all radio classifications scoped to response messages, verify the selected option accurately reflects the tone of the response."* * *"Review the entire conversation holistically. The overall exchange must feel natural and coherent. Flag data rows where the conversation feels disjointed or artificially constructed."* * *Ensure that no personally identifiable information (PII) appears anywhere in the conversation — in prompts, responses, or classifications."* ### Important considerations **Be specific**, vague prompts produce inconsistent results. **Use standard vocabulary** — terms like "turn," "rubric criterion," "global scope," "response message scope" help the AI target the right elements. **Experiment** with your prompts — writing a good critic prompt is an art; test different phrasings and check results in the editor to find what works. # Alignerr Source: https://docs.labelbox.com/horizon/guides/alignerr # Overview Source: https://docs.labelbox.com/horizon/guides/annotate-overview Collaboratively annotate data with the internal team, your own vendor, or Labelbox data labeling service. Annotate is the data labeling platform within Labelbox. It allows your organization to label data with any human workforce at any scale. ## How Annotate works When it comes to deciding how to label your data, you have the following options: * Outsource this task to a labeling service — these external teams receive training on the specific labeling tasks required and quickly proceed to label large datasets (see [Workforce](/horizon/guides/labeling-services)). * Import your model predictions as pre-labels to speed up the labeling process (see [Model-assisted labeling](/horizon/guides/model-assisted-labeling). * Rely on your own internal team of labelers to label your dataset (see [Labeling editors](/horizon/guides/label-data)). Regardless of your labeling method, Labelbox Annotate is a central place where you can manage all your labeling projects, customize your labeling & quality workflows, and monitor your labeling team's performance. ## Customizable labeling editor The editor is the labeling interface purposefully designed to be highly configurable. The editor is the primary tool for creating, viewing, and editing annotations. The labeling editor supports the following media types out of the box: ## Model Assisted Labeling with Foundry Users can use Foundry to add tools or classifications onto data rows as an auto-labeling feature. This is set up when a project has data to label and an editor with a set ontology. Once completed, users can enable the Model Assisted Labeling tool and configure an LLM to create annotations. ## Customizable labeling & review settings You can set up customized review steps based on your decided quality strategy in your project's [Workflow](/horizon/guides/workflows) tab. As you work with large, complex projects, having to review all labeled data rows becomes increasingly time-consuming and expensive. You can leverage workflows to create a highly-customizable, step-by-step review pipeline to drive efficiency and automation into your review process. To learn more, read [Workflows](/horizon/guides/workflows). Additionally, you can leverage the following quality tools: ## Labeling workforce Powered by Labelbox’s data engine, you can leverage Labelbox labeling services to collaborate with an external labeling workforce in real time and produce high-quality data while leveraging AI and automation techniques to keep human labeling costs at a minimum. To learn more, see [Labeling services](/horizon/guides/labeling-services) ## Getting started with Annotate # Send project updates Source: https://docs.labelbox.com/horizon/guides/annotate-project-updates Learn how to send mass communication to members in your organization or external orgs with project updates. Project updates in Labelbox are a great way to keep everyone on your team informed. This feature allows you to communicate and collaborate directly within the Labelbox platform, which is especially helpful when working with an external workforce. You can find the **Updates** section in the **Notifications** tab. This is where you can coordinate with all stakeholders, including your team, a labeling service admin, and the labeling workforce. To keep things organized, Labelbox allows you to create different types of updates for common scenarios. This makes it easier to find important information later. ## How to create an update Follow these steps to create a project update: 1. **Open a new update**: Select a project and go to **Notifications** → **Updates**. Click **+ New update**. 2. **Select the update type**: Select the type of update from the dropdown menu and add a message to provide more details. The text field for creating and editing updates supports markdown, allowing you to easily format your text. Here are the types of updates you can choose from: | Update type | What it does | | - | - | | Progress update | Shares updates on the project's progress. | | ETA updated | Communicates any changes to the project's expected completion date. | | Instructions updated | Notifies the labeling team of any changes to the project instructions. | | Issue | Informs the labeling team about any labeling issues you find in the project. | | General | For any communication that doesn't fit into the other categories. | 3. **Add a message**: You may optionally add a message to make your update more descriptive. Markdown formatting is supported in your message description. 4. **Share with**: You can specify which organizations will receive this update. * **All**: This will make it available for all organizations added to the project (it will enable this update for external organizations added in the future) * **Internal only**: This will always make it private for your organization, meaning no other organizations (even those added later) will have access to this update. * **Specific organization**: You can select specific organizations that will have access to it (your organization is always included). This option is only available if your project is shared with external orgs. 5. **Notify with emails**: To support engagement, you may select to notify users of the update via email. Check the box to enable email notifications. 6. **Add attachments**: You may optionally include local files in your update. Click **Add** under **Attachments** to do so. 7. When you are done, click **Create update**. For easy retrieval, you can filter your updates in the **Notifications** tab with two filters: * **Author:** filters by the email address of the party creating the update * **Type:** filters by the update type Once a new update is created, you can reply to the same update. This will add it as an additional reply to the thread. You can reply to updates to close the thread or ask additional clarifying questions. ## Automatic email notifications Once you are added as a project admin, you will automatically receive email notifications for every new update. These emails will come from [support@labelbox.com](mailto:support@labelbox.com), and the subject line will include the project name, making it easy to sort notifications from different projects into unique email threads. # Audio Source: https://docs.labelbox.com/horizon/guides/audio-editor Guide for labeling audio data. With the audio editor, you can add annotations to audio files, like classifying natural language conversations and music, to train conversational AI and audio-based ML models. The editor supports automatic speech-to-text recognition with the Whisper model, enabling you to transcribe any audio segment. ## Set up audio annotation projects To set up an audio annotation project: 1. [Create an audio dataset](/horizon/guides/datasets-datarows). 2. On the [Annotate projects page](https://app.labelbox.com/projects), click the **+ New project** button. 3. Select **Audio**. Provide a **name** and an optional **description** for your project. 4. Click **Save**. The system then creates the project and redirects you to the project overview page. 5. Click **Add data**. Then select your audio dataset. Click **Sample** to sample your dataset, or you can manually select data rows and click **Queue batch**. ### Data row size limit To view the maximum size allowed for a data row, see [limits](/horizon/guides/limits). ## Set up ontologies After setting up an audio annotation project, you can [add an ontology](/horizon/guides/labelbox-ontology#create-a-new-ontology) based on how you want to label the data. The audio editor supports the following annotation types that you can include in your ontology: | Feature | Import annotations | Export annotations | | - | - | - | | **Radio classification** | [See payload](/horizon/developer-guides/import-audio-annotations#radio-single-choice) | [See payload](/horizon/developer-guides/export-audio-annotations#classification---radio) | | **Checklist classification** | [See payload](/horizon/developer-guides/import-audio-annotations#checklist-multiple-choice) | [See payload](/horizon/developer-guides/export-audio-annotations#classification---checklist) | | **Free-form text classification** | [See payload](/horizon/developer-guides/import-audio-annotations#free-form-text) | [See payload](/horizon/developer-guides/export-audio-annotations#classification---free-form-text) | ### Classification scopes You can apply classifications as **global classifications** at the file level, **temporal classifications** at the frame level, or **nested classifications** under other annotations. ## Use the audio editor After adding data and setting up an ontology for your audio annotation project, you can add labels to data rows using the audio editor. Each data row displays in the editor with: * **A waveform** visualizing pattern of sound pressure variation. * **A spectrogram** showing the range of sound frequencies and their strengths over time. * **A timeline** of audio split into 500 millisecond intervals by default and a **Timeline Resolution** slider that allows you to adjust the time intervals on the timeline. * **Basic player controls**, such as the play/pause button, back/forward 10-second buttons, and the playback speed. You can also click anywhere on the waveform to instantly move to your desired location. To add a global classification. select the classification and enter the value. To add a temporal classification, select the classification, choose the interval on the timeline or waveform for when the classification starts, and add the classification value. You will see a circle representing the classification value on the timeline. ### Timeline resolution differences for labels If you set a lower resolution with the timeline resolution slider, the classification label you add may not align exactly with the current timeline resolution. This indicates that the classification was placed at a timestamp with a higher resolution than the one currently being used. You can adjust the timeline resolution to a higher resolution to see the exact position of the classification. ### Enable speech recognition The in-editor automatic speech-to-text support allows you to recognize and extract text from audio segments using free-form text classifications. To enable it: 1. When [creating the ontology](/horizon/guides/labelbox-ontology#step-1-create), add a temporal **Text** classification feature. 2. Select the temporal text classification, and then select a starting frame on the timeline. 3. Click **START TRANSCRIBING**. 4. Click **END TRANSCRIBING** at your desired ending frame. If the system detects speech, it automatically generates a transcript in the text annotation. ### Keyboard shortcuts | Function | Hotkey | Description | | - | - | - | | Play/Pause | `Space` | Play or pause the audio playback | | Move backward one frame | `←` | Move backward one frame | | Move forward one frame | `→` | Move forward one frame | | Select frames | `Shift` + `Mouse` | Select frames for adding temporal classifications | | Advance to the previous keyframe | `⇧` + `←` | Advance to the previous keyframe | | Advance to the next keyframe | `⇧` + `→` | Advance to the next keyframe | | Jump to objects | `Down` | Jump to objects | | Next object | `Down` | Move to the next object | | Previous object | `Up` | Move to the previous object | | Toggle | `⌘` + `/` | Toggle the keyboard shortcuts menu | # Monitor activity with cloud audit logs Source: https://docs.labelbox.com/horizon/guides/audit-logs How to configure data access audit logs. This guide demonstrates how to use your cloud provider's native logging tools to get full transparency into how and when Labelbox accesses your data via IAM Delegated Access. By monitoring these logs, you can maintain a complete, unchangeable record of every interaction, which is essential for security compliance and internal audits. ## Gain visibility through logs When you grant Labelbox access using the IAM Delegated Access method, every action our platform takes is authenticated by your cloud provider. Every time Labelbox temporarily assumes the IAM role to access a file for labeling, your cloud provider records that event in an audit log. These logs answer critical security questions: * **Who** made the request? (The specific Labelbox IAM role) * **What** resource was accessed? (The specific file/object in your bucket) * **When** did the access occur? (A precise timestamp) * **From where** was the request made? (The source IP address, which will be one of Labelbox's servers) By analyzing these logs, you can have complete confidence that Labelbox is only accessing data as needed for your users to perform their labeling tasks. ## Step-by-step monitoring guides One of the benefits of using IAM delegated access is that security-conscious teams can choose to access audit logs of when their raw data is read by Labelbox through their cloud provider. # Send a batch to a labeling project Source: https://docs.labelbox.com/horizon/guides/batches Instructions for sending a batch of data rows from Catalog to a labeling project via the app UI. In Labelbox, a "batch" is a group of data rows that you send from your Catalog to a project for labeling. Using batches gives you more control and flexibility over your labeling workflow. ## How to send a batch to a labeling project Follow these steps to send a group of data rows to a labeling project: 1. Navigate to the **Catalog** and use the filters to select the data rows you want to include in your batch. 2. Once you have selected the data rows, click the **Manage selection** dropdown menu and select **Send to Annotate**. 3. In the **Configure batch** window, you will need to: 1. Choose the project you want to send the batch to. 2. Set a priority for the batch. A priority of 1 is the highest and 5 is the lowest. This will determine the order the data rows are placed in the labeling queue. 3. Enable or disable **Consensus** for the batch. If you enable Consensus, you will need to set a **% Coverage** to indicate the percentage of data rows that will be queued for labeling by multiple labelers, and a **# Labels** value to indicate how many labels can be added to the data row. 4. Click **Submit**. Any data rows that have already been submitted to the labeling project will be excluded from the batch. **Limits** See [this page](/horizon/guides/limits) to learn the limits for sending batches to a project. ## How to create a batch by sampling Sampling is a useful technique for creating a batch when you are working with a large amount of data. Instead of manually selecting data rows, you can use sampling to create a batch of a specific size. 1. In the **Catalog**, use the filters to find the data rows you want to sample from. 2. Click the **Sample** button in the top right corner. 3. In the batch creation window, you can choose how many data rows to sample and the sampling method. Labelbox supports two sampling methods: * **Random**: This method will randomly select the specified number of data rows from your filtered results. * **Ordered**: This method will select the specified number of data rows based on the sorted order of the results. You can sort the results by the **Created At** timestamp in ascending or descending order. The sequence generated by ordered sampling does not influence the sequence of the data rows in the labeling queue. 4. Fill out the project, batch name, and priority details. 5. Click **Submit batch**. ## How to send predictions as pre-labels To speed up the labeling process, you can include model predictions as pre-labels in your batch. 1. In the batch creation window, toggle the **Include predictions** option on. 2. From the dropdown menu, select the model run and the specific predictions you want to include in the batch. 3. Confirm that the model run is compatible with the labeling project. For the predictions to be compatible, the features in the model run ontology and the labeling project ontology must be the same. If the model run and the labeling project do not share any features, you will not be able to send the predictions as pre-labels. 4. Click **Submit batch**. ## Important considerations * **Media type compatibility**: You can only send a batch to a project that has the same media type. For example, you cannot send a batch of text data to a project that is set up for labeling images. * **Appending to batches**: Once a batch has been sent to a project, you cannot add more data rows to it. * **Data rows in multiple batches**: A data row cannot be in more than one batch in the same project at the same time. * **Sharing batches**: A batch cannot be shared between projects. However, you can create a new batch with the same data rows and send it to a different project. # Benchmark Source: https://docs.labelbox.com/horizon/guides/benchmark Learn how to set up benchmarks to analyze the quality of your labels. Benchmarks serve as the gold standard for other labels. You can designate specific data rows with annotations as benchmarks, and all other annotations on these data rows are automatically compared to these benchmark reference labels to calculate benchmark agreement scores. ### Benchmarks in the queue Labelbox ensures that the first five data rows in your labeling queue are benchmark labels. After these initial five, the order of the remaining benchmarks is not guaranteed. Beyond the first five, there is a 10% chance that any subsequent data row you encounter will be a benchmark label. If you have fewer than five benchmarks, they are the first data rows in your queue, and no additional benchmarks appear unless you add more. ## Supported types Currently, benchmarking is supported for the following asset and annotation types: | Asset type | Bounding box | Polygon | Polyline | Point | Segmentation mask | Entity | Radio | Checklist | | - | - | - | - | - | - | - | - | - | | Images | | | | | | N/A | | | | Videos | | - | | | - | N/A | | | | Audio | N/A | N/A | N/A | N/A | N/A | N/A | | | | Text | N/A | N/A | N/A | N/A | N/A | | | | | Tiled imagery | | | | | - | N/A | | | | Documents | | N/A | N/A | N/A | N/A | | | | | HTML | N/A | N/A | N/A | N/A | N/A | N/A | | | | Conversational text | N/A | N/A | N/A | N/A | N/A | | | | | Human-generated responses | N/A | N/A | N/A | N/A | N/A | N/A | | | ## Set up benchmarks To set an individual data row as a benchmark reference: 1. In the editor, label the data row and click **Submit**. 2. Navigate back to the project homepage. 3. Go to the **Data rows** tab. 4. Select the labeled data row from the list. This will open the Data row browser. 5. Click on the three dots next to the data row and select **Add as benchmark** from the dropdown. To bulk-assign labeled data rows as benchmark references: 1. On the **Data rows** tab, select the data rows that you want to set as benchmarks. 2. Click the **selected** dropdown and select **Assign labels as benchmarks**. 3. If you have permissions to access benchmark scores, you can select from the following two options: * **Infer automatically**: Set the labels created within the benchmark quality mode as benchmark references. If the project also uses consensus as the quality mode, this option also sets consensus winners as benchmark references. * **Target label by score**: Select or set a range of benchmark scores to set labeled data rows matching the filter as benchmark references. If you don't have permissions to access benchmark scores or if no benchmark scores are available, you can only see and use the **Infer automatically** option. 4. Click **Submit**. Once a label is designated as a benchmark, the data row is automatically moved to **Done**. Benchmarked data rows will be served to all labelers in a project. Benchmarked data rows can't be moved to any other step unless the benchmark is removed. For a benchmark agreement to be calculated, one benchmark-reference label and at least one non-benchmark label need to be on the data row. Whenever annotations are added or updated, the benchmark agreement is recalculated as long as at least one non-benchmark label exists on the data row. ## Search and filter data using benchmark scores The **Benchmark agreement** filter helps you find qualified data rows based on benchmark scores. You can apply this filter in the following locations: * The [Data Rows](/horizon/guides/data-rows-activity) tab * The [Workflow](/horizon/guides/workflows) tab * The [Catalog](/horizon/guides/search) page When using the filter, you can configure the following options: * **Scope**: Specify the type of agreement to measure: * **Feature-level** measures the alignment between annotators' labels and the predefined benchmark reference labels for each data row. If you select this option, further specify one or more feature schemas in the ontology using the dropdown menu. * **Label-level** evaluates the overall agreement of all annotations within a single data row compared to the benchmark reference label. * **Calculation**: Choose whether to calculate the agreement as an absolute or average score. * **Range (0-1)**: Set the score range from 0 to 1, 0 indicates no agreement with the benchmark reference label and 1 represents complete agreement. # A guide to Labelbox's LBUs Source: https://docs.labelbox.com/horizon/guides/billing Learn how to perform common billing tasks and how Labelbox charges works. ## What are Labelbox (LBUs)? A Labelbox Unit (LBU) is a normalized unit of data in the Labelbox platform. Think of it as a credit that you consume as you use Labelbox's products. Each LBU represents a specific amount of work done, which can involve one or more data rows (e.g., images, text files, videos). Since each of Labelbox's products—Catalog, Annotate, and Model—is designed for different tasks, the way you consume LBUs will vary depending on the product you're using, the type and amount of data you're working with, and the specific actions you take. ## How LBU consumption is calculated The table below shows how many LBUs are consumed for different types of data across the three Labelbox products. | Data row asset type | Catalog (monthly) | Annotate (one-time) | Model (one-time) | | - | - | - | - | | Image | 1 LBU per 60 data rows | 1 LBU per data row | 1 LBU per 5 data rows | | Text | 1 LBU per 60 data rows | 1 LBU per data row | 1 LBU per 5 data rows | | Chat (Conversation) & Offline Multi Modal | 1 LBU per 60 data rows | 1 LBU per data row | 1 LBU per 5 data rows | | Audio | 1 LBU per 60 data rows | 1 LBU per data row | 1 LBU per 5 data rows | | Document (PDF), per page | 1 LBU per 60 data rows + 1 LBU per 60 pages | 1 LBU per page | 1 LBU per 5 pages | | Video | 1 LBU per 60 data rows + 1 LBU per 5,000 frames | 1 LBU per data row + 1 LBU per 150 frames | 1 LBU per 5 data rows + 1 LBU per 1,500 frames | | Geospatial & Medical Tiled Imagery | 1 LBU per 6 data rows | 4 LBU per data row | 1 LBU per data row | | Live Multi Modal/LLM | 1 LBU per 60 data rows | 20 LBU per data row | 1 LBU per 5 data rows | Free accounts have 500 free LBU credits each month. If you reach this limit, you can access and export your data. However, you cannot add data rows, labels, or predictions until the next billing period. (You can [**upgrade your account**](https://docs.labelbox.com/horizon/guides/update-account-plan#upgrade-current-plan) at any time.) ## How LBUs are consumed by each product Different actions within Labelbox consume LBUs. Here's how consumption works for each Labelbox product: ### Catalog consumption In **Catalog**, you consume LBUs when you upload and store data, regardless of the data type. To help you avoid accidental charges, there is a *seven-day grace period* that starts when you first upload data. If you remove any unused data rows before this grace period ends, you won't be charged any LBUs for them. However, if you take any action that adds a label to a data row (like annotating it), that row will consume LBUs, even if it's within the grace period. Once the seven-day grace period is over, all data rows in your Catalog will start consuming LBUs. Here’s an example: * You upload 600 images to a dataset. * For the first 7 days, no LBUs are consumed. You can delete any of these images without being charged. * If you label 60 of those images during the grace period, you will consume 1 Catalog LBU for the current billing period, even if you delete them later. * Once the grace period ends, the remaining 540 data rows will consume 9 LBUs for the current billing period. * If you then decide to delete 300 rows after the grace period, you will still be charged a total of 10 Catalog LBUs for the current billing period. In the next billing period, your LBU consumption will drop to 5 LBUs to reflect the new data row count. ### Annotate consumption In **Annotate**, you consume LBUs when a data row is first submitted to a project and labeled. This is a *one-time charge per data row*; submitting the same data row to additional projects does not consume additional LBUs. For videos, LBUs are consumed for the entire video as a single unit, no matter how many frames you label. If you label just one frame, the entire video is charged once. If you go back and label more frames in the same video later, you won't be charged any additional LBUs. **Skipped data rows** For Annotate projects, "skipped" data rows are considered labeled and will contribute to LBU consumption. ### Model consumption In **Model**, you consume LBUs when you do any of the following: * Add data rows to a model run. * Use Foundry to create and submit model predictions. * Use data rows for model-assisted labeling (MAL), either through the app or the SDK. These are *one-time charges* for each data row. Once a data row has been used in Model and consumed LBUs, you can use it again for other Model tasks without incurring additional LBU charges. However, new data rows will consume more Model LBUs. Be aware that Foundry may incur additional inference costs based on the number of rows processed, the complexity of the task, the model used, and other factors. To learn more, see the **Add-on charges** section below. ## Examples of LBU consumption Here are a couple of examples to illustrate how LBU consumption works in common workflows. **Example 1: Data labeling with model assisted pre-labels** Let's say you need to label 1,000 images, and you use Foundry to generate pre-labels for half of them (500 images). Here's how you would be charged: * **Catalog**: 17 LBUs per month to store the 1,000 data rows. * **Model**: 100 LBUs (one-time charge) for generating predictions on 500 of the data rows. * **Annotate**: 1,000 LBUs (one-time charge) for the 1,000 labeled data rows. If this activity occurred within a single billing period, you would be charged 1,117 LBU once and 1.67 LBU each month the data rows remain in Catalog. **Example 2: Data curation and labeling with enrichment** Now, imagine you have 60,000 images that all have Foundry predictions, and you end up labeling 10,000 of them. Here’s the breakdown: * **Catalog**: 1,000 LBUs per month to store the 60,000 data rows (or 12,000 LBU annually). * **Model**: 12,000 LBUs (one-time charge) for the 60,000 data rows with predictions. * **Annotate**: 10,000 LBUs (one-time charge) for the 10,000 labeled data rows. If this activity occurred within the same billing period, you would be charged 23,000 LBU once and 1,000 LBU each month the data remains in **Catalog**. ## Charges for add-on services Your account may also be billed for add-on services. These charges are billed separately from your subscriptions and may require you to add a credit card to your account. Available add-ons include: * **Foundry**: This service uses foundational models to predict labels and enrich your data. When you use Foundry, you will incur *inference costs*, which are based on the amount of data processed, the model used, and the complexity of the task. * **Labeling services**: You can connect with a team of professional labelers with specialized knowledge to annotate and review large amounts of data for you. * **Single sign-on (SSO)**: This allows Enterprise teams to use their company's single sign-on service to sign in to Labelbox. * **HIPAA compliance**: This helps Enterprise subscribers comply with the Health Insurance Portability and Accountability Act of 1996. If you are an Enterprise customer, you should contact your technical account manager to verify the terms, payment details, and other information about your add-ons. ## Where can I find more information? See our [Limits](/horizon/guides/limits) page to learn more about the platform limitations per tier. # Bulk classification for zero-shot learning Source: https://docs.labelbox.com/horizon/guides/bulk-classification You can use bulk classification in Labelbox to perform zero-shot learning. After curating a subset of data that shares common traits, you can classify them all at once, directly from the Catalog. ## What is zero-shot learning? Zero-shot learning (ZSL) is a machine learning technique that allows an AI model to recognize and categorize objects or concepts it has never seen before. In a typical supervised learning model, an AI is trained on a large dataset of labeled examples. For instance, a model might be trained on thousands of images of cats and dogs, with each image labeled as either "cat" or "dog." The model then learns to distinguish between cats and dogs based on the patterns it observes in the training data. Zero-shot learning, on the other hand, does not require any labeled examples of the categories it is being asked to classify. Instead, it relies on auxiliary information, such as textual descriptions or attributes, to make predictions. For example, a zero-shot learning model could be told that a "zebra" is a "striped horse." Even if the model has never seen a zebra before, it can use its existing knowledge of horses and stripes to recognize a zebra in an image. Labelbox's bulk classification using zero-shot learning is a great way to speed up your classification projects, as you can quickly generate classifications without having to label each asset one-by-one. This allows you to integrate off-the-shelf neural networks as zero-shot classifiers. ## How to bulk classify data Here is a step-by-step guide on how to bulk classify data in Labelbox. 1. **Select a subset of data to classify:** Use the search features in Labelbox to select a subset of data. You can select the top results of a natural language search or all assets that look similar to each other using similarity search. 2. **Add a classification:** Click on the number of items selected in the top right corner and choose **Add classification**. 3. **Pick the destination labeling project:** Select the destination labeling project from the dropdown menu. Only projects with a global classification question in their ontology will appear. 4. **Provide classification values:** Answer the classification questions that appear. These values will apply to all data rows in the bulk classification job. You must answer all required classifications and subclassifications. You can search for a classification question by typing its name. 5. **Specify the workflow step:** Choose which step of the labeling and review workflow the data rows should be sent to. For example, if you pick the **Initial labeling task**, then the classifications will be sent as [*pre-labels*](/horizon/guides/model-assisted-labeling). If you pick any other task - such as **Rework**, **Initial review task**, or **Done** - then the classifications will be sent as [*labels*](/horizon/guides/import-ground-truth). 6. **Include or exclude data rows that already have labels:** If a data row already has a label in the destination project, you can either *overwrite* the existing label or *exclude* the data row from the job. * **Overwrite previous value with new value**: Only classification questions that you answer -- as part of the bulk classification job -- will overwrite already-existing classifications. * **Exclude from job**: Excludes these data rows from the bulk classification job to preserve the already-existing labels. 7. **Submit the bulk classification:** Click **Submit batch** to launch the job. It may take a few moments for the data to be sent to the labeling project. 8. **Track the progress:** You can track the progress of the job in the notification panel and will be notified by a pop-up message when it's complete. ## Limitations There are a few limitations to keep in mind when using bulk classification: * There are limits to the number of data rows you can classify at once. You can work around this by classifying data in chunks. For example, you can leverage the **Annotation** filter with the attributes **is not one of** and \[your classification question] to filter out already classified data rows and proceed with the next bulk classification job. * You cannot use bulk classification with a consensus project. * Benchmark data rows are excluded from bulk classification jobs. Since benchmark labels are a gold standard label, they should not be overwritten by bulk classifications. The data rows are sent to the labeling project as a [batch](/horizon/guides/batches). You can control the name of the batch in the top-left corner, as well as the data row priority in the **Advanced settings**. # Search and view compatibility Source: https://docs.labelbox.com/horizon/guides/catalog-search-compatibility A summary of search and view capabilities available in Catalog by data type. Below are the supported search filters that Catalog supports out of the box. You can extend the usability of these core offerings by uploading your own [custom metadata](/horizon/guides/datarow-metadata#custom-fields) and [embeddings](/horizon/guides/similarity#supported-embeddings), then combining them with the supported filters below to accomplish your search and curation objectives. ## Supported search filters (basic) To learn more about the supported filters, see [Filters](/horizon/guides/search). | Asset type | Annotation | Dataset | Metadata | Project | Media attribute | Batch | Data row | | - | - | - | - | - | - | - | - | | **Image** | | | | | | | | | **Video** | | | | | | | | | **Text** | | | | | Only mime type | | | | **HTML** | | | | | Only mime type | | | | **Document** | | | | | Only mime type | | | | **Tiled imagery** | | | | | Only mime type | | | | **Audio** | | | | | | | | | **Conversational** | | | | | Only mime type | | | ### Enrich your data with custom metadata As noted above, Labelbox supports metadata on any data type. To learn how to use custom metadata and these Catalog filters to enrich your data, read this blog post on [how to make your data queryable using foundation models](https://labelbox.com/blog/make-videos-queryable-using-foundation-models/). ## Supported search filters (advanced) | Asset type | [Find text](/horizon/guides/find-text) | [Natural language](/horizon/guides/natural-language-search) | | - | - | - | | **Image** | - | \* | | **Video** | - | (beta, add-on feature) | | **Text** | | \*\* | | **HTML** | | \*\* | | **Document** | | \*\*\* | | **Tiled imagery** | - | \* | | **Audio** | - | - | | **Conversational** | | \*\* | \* Uses the off-the-shelf [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) vision model (512 dimensions). \*\* Uses the off-the-shelf [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) text model (768 dimensions), based on the first 64k characters. \*\*\* Users can pick between off-the-shelf [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) vision model (512 dimensions) and [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) text model (768 dimensions, based on the first 64k characters). ## Similarity (embeddings) | Asset type | Off-the-shelf embeddings | Custom embeddings | | - | - | - | | **Image** | [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) (512 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **Video** | [Google Gemini Pro Vision](https://ai.google.dev/gemini-api/docs/models/gemini) . First two (2) minutes of content is embedded. Audio signal is not used currently. This is a paid add-on feature available upon request. | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **Text** | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **HTML** | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **Document** | [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) (512 dimensions) and[all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **Tiled imagery** | [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) (512 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **Audio** | Audio is transcribed to text.[all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | | **Conversational** | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | Up to 2048 dimensions per embedding; up to 100 custom embeddings per workspace. | ### Enhance similarity search with custom embeddings As noted above, Labelbox supports custom embeddings on any data type. Powerful embeddings can be generated using foundational models and easily uploaded to Labelbox. You can then use these embeddings in combination with any of the above filters to accomplish your data search goals. For an example of how to get started, check out this guide on [how to generate custom embeddings using foundational models and upload them to Labelbox](https://labelbox.com/guides/using-labelbox-and-hugging-face-to-generate-custom-embeddings-and-curate-impactful-data/). ## Options for viewing your data | Asset type | Thumbnail view | Detail view | Annotations overlay (thumbnail) | Annotations overlay (detail) | | - | - | - | - | - | | **Image** | | | | | | **Video** | | | - | Classifications only | | **Text** | | | | | | **HTML** | | | - | | | **Document** | | | Classifications only | Classifications only | | **Tiled imagery** | | | | | | **Audio** | - | - | - | | | **Conversational** | | | | | # Import data Source: https://docs.labelbox.com/horizon/guides/connect-to-cloud-storage Learn how to import your data by connecting your cloud storage to Labelbox. ## Overview To start labeling your data, you first need to grant our platform secure access to the files stored in your private cloud (AWS, GCP, or Azure). This guide explains the two methods for connecting your data, helping you choose the best one for your project's security and workflow needs. The two methods are * **IAM Delegated Access**: A robust, long-term connection method. * **Signed URLs**: A flexible method using temporary, secure links to your data. **Our Recommendation**: For most use cases, especially long-term projects, we recommend **IAM Delegated Access** for its superior security and lower maintenance. | Feature | IAM Delegated Access | Signed URLs | | - | - | - | | Setup complexity | **High.** Requires a one-time configuration within your cloud provider's IAM console. | **Low.** No initial cloud configuration is needed in Labelbox. | | Maintenance | **Low.** "Set it and forget it." Works for all data in the configured location. | **High.** Requires a continuously running service on your end to generate new URLs. | | Data Freshness | **Real-time.** New data added to your bucket is immediately available for labeling. | **Delayed.** New data requires new signed URLs to be generated and uploaded to Labelbox. | | Ideal for | Long-term projects, enterprise-scale data operations, and stringent security environments. | Quick-start projects, proof-of-concepts, or when you cannot create IAM roles. | *** ## IAM delegated access IAM (Identity and Access Management) Delegated Access is the most secure and scalable method for connecting your data. You create a trust relationship by setting up a dedicated role within your own cloud account that Labelbox is permitted to assume. This gives Labelbox temporary, read-only credentials to access your data when your users are labeling. ### How it works 1. **You**: Create an IAM role in your AWS, GCP, or Azure account that has read-only permissions to your data bucket. 2. **You**: Provide Labelbox with the unique identifier (ARN/ID) of that role. 3. **Labelbox**: When a user needs to view an image or document, Labelbox uses the provided identifier to request temporary access credentials from your cloud provider. 4. **Your Cloud Provider**: Validates the request and grants Labelbox a short-lived token to access only the specified data. ### Key advantages * **Superior Security**: Your secret keys are never shared with Labelbox. Access is easily auditable and can be revoked at any time from your cloud console. * **Low Maintenance**: After the initial setup, you never have to worry about managing credentials or access again. It just works. * **Simplified Workflow**: Data scientists and annotators can easily browse and import data without needing to handle URLs. ### Step-by-step guides *** ## Signed URLs A signed URL is a web link that provides temporary access to a specific file in your storage bucket. Each URL is "signed" with cryptographic keys that validate the request and expire after a set time (e.g., 7 days). You are responsible for generating these URLs and providing them to Labelbox. ### How it works 1. **You**: Write and run a script or service that generates a unique signed URL for each data asset you want to label. 2. **You**: Create a JSON file containing these URLs and upload it to Labelbox. 3. **Labelbox**: When a user accesses a task, Labelbox uses the corresponding signed URL from your JSON file to fetch and display the data. 4. **Your Cloud Provider**: Validates the signature on the URL and serves the file. Access is denied if the URL has expired. ### Key advantages * **Fast to Start**: Bypasses the need for complex IAM configuration, making it ideal for quick tests or proof-of-concepts. * **Granular Control**: You have explicit, file-level control over what data is accessible and for how long. ### Step-by-step guide *** ## Automate imports with the Python SDK Labelbox provides a [Python SDK](/horizon/developer-guides/install-python-sdk) to help automate data setup. You can download sample code from the app or use the online docs to learn more. To download samples from the app: 1. From the dataset default screen, select **Use Python SDK**. 2. From the **Create data rows** prompt, select the tab appropriate for your data type. 3. Use the **Copy** button to copy the code to the Clipboard or the **Download** button to save it locally. Once you have a copy of the sample script, you can customize for your needs. More information is available for each supported data type, including: *** ## Upload local files To upload local files directly to the Labelbox platform, go to **Catalog**, click **+New**, then select **Choose files to upload**. **Direct upload not recommended** Uploading your files to Labelbox is NOT recommended. We recommend using IAM delegated access or Signed URLs instead (see sections above). # Consensus Source: https://docs.labelbox.com/horizon/guides/consensus Learn how to set up consensus scoring to analyze the quality of your labels. Consensus represents the agreement between your labeling workforce. Consensus agreement scores are calculated in real-time for features and labels with multiple annotations by different labelers. Whenever an annotation is created, updated, or deleted, the consensus score is recalculated for data rows with two or more labels. ## Supported types Currently, consensus scoring is supported for the following asset and annotation types: | Asset type | Bounding box | Polygon | Polyline | Point | Segmentation mask | Entity | Relationship | Radio | Checklist | Free-form text | | - | - | - | - | - | - | - | - | - | - | - | | Image | | | | | | N/A | N/A | | | - | | Video | | - | | | - | N/A | N/A | | | - | | Text | N/A | N/A | N/A | N/A | N/A | | - | | | - | | Chat | N/A | N/A | N/A | N/A | N/A | | - | | | - | | Audio | N/A | N/A | N/A | N/A | N/A | N/A | N/A | | | - | | Geospatial | | | | | N/A | | N/A | | | - | | Documents | | N/A | N/A | N/A | N/A | N/A | N/A | | N/A | - | | HTML | N/A | N/A | N/A | N/A | N/A | N/A | | | N/A | - | | Human-generated responses | N/A | N/A | N/A | N/A | N/A | N/A | | | N/A | | ## Set up consensus scoring When adding data rows to an **Annotate** project, use the **Queue batch** option to enable consensus scoring and configure additional settings. You can't change these settings after submission. | Consensus setting | Description | | - | - | | **Data row priority** | The position in the labeling queue these data rows will be slotted based on priority. | | **% coverage** | The percentage of the data rows in the batch that will enter the labeling queue as consensus data rows for multi-labeling. Defaults to 0. | | **# labels** | The number of labels that each consensus data row will be added. Defaults to 2. Must be less than or equal to the number of labelers on the project. | ### Consensus calculation can take up to five minutes ## Select consensus winners After a data row is labeled and enters the review stage, the first set of annotations entered for a data row represents consensus by default. Reviewers can reassign consensus to another set of annotations once the data row has more than one label. If your data row has been labeled more than once, you'll view all of the label entries on that data row in the data row browser. The following example shows a data row with two sets of labels. The green trophy icon indicates that the first set of annotations is considered "consensus." To change consensus, click the trophy icon next to the preferred annotations. ### Recalculation of consensus agreement scores The consensus score reflects agreement among labelers, so changing the winning label might lead to a recalculation of the score based on the new consensus. ### Set consensus winners as benchmark references You can designate consensus winners as benchmarks. See [Set up benchmarks](/horizon/guides/benchmark#set-up-benchmarks). ## Search and filter data using consensus scores The **Consensus agreement** filter helps you find qualified data rows based on consensus scores. You can apply this filter in the following locations: * The [Data Rows](/horizon/guides/data-rows-activity) tab * The [Workflow](/horizon/guides/workflows) tab * The [Catalog](/horizon/guides/search) page When using the filter, you can configure the following options: * **Scope**: Specify the type of agreement to measure: * **Feature-level** measures the agreement on a specific feature schema in the ontology for each data row. If you select this option, further specify one or more feature schemas in the ontology using the dropdown menu. * **Label-level** evaluates the overall agreement across all annotations within a single data row. * **Calculation**: Choose whether to calculate the agreement as an absolute or average score. * **Range (0-1)**: Set the score range from 0 to 1, where 0 indicates no agreement among annotators and 1 indicates complete agreement. # Customer support Source: https://docs.labelbox.com/horizon/guides/contacting-customer-support How to work with Labelbox support to report issues and feedback. The Labelbox Support Team is available to help you troubleshoot issues and get the most out of the platform. ## Support availability Our support team is available from 9:00 AM to 8:00 PM Eastern Time, Monday through Friday, excluding major U.S. holidays. ## Status page Before submitting a support ticket, please check our [Status page](https://status.labelbox.com/ "https://status.labelbox.com/") to see if there are any ongoing incidents or scheduled maintenance that might be affecting the platform. This can often provide immediate answers and updates regarding widespread issues. ## Labelbox support bot Our AI-powered support bot in the Labelbox platform is your first stop for quick questions and information retrieval. We recommend using the bot to scan our documentation, look up SDK functions, or learn about different data export methods. To get the most accurate answers, it's best to ask clear and specific questions. For example, instead of asking a general question like "How do I script?", try asking something more precise, such as "What is the SDK function to create a new project?" Well-formulated questions will help the bot give you the most insightful response. Additionally, you can use the **Ask a question** chat bot from anywhere in the *Labelbox docs*. This AI-powered chat bot allows you quickly retrieve specific content from our docs (e.g., SDK functions, export methods, etc). ## How to file a support ticket To file a support ticket, you'll need to use the Labelbox support portal. Access to the portal is by invitation only. If you don't have an account, please contact your sales representative. To help our support team resolve your issue as quickly as possible, please include the following information in your ticket: | Field | Description | | - | - | | On behalf of | The email address entered in this field should belong to the primary stakeholder of the concern being reported. The ticket will be associated with the Jira portal identity for this user and the email address should be associated with a Labelbox account. Other users can be added as `Request participants` after the creation of the ticket in order to view and participate in the thread. | | Affected email address | The email address(es) entered in this field should represent the Labelbox account(s) in which the relevant issue is occurring. Typically, the entry here will be the same as the above field, but this is not always the case when reporting a team-related concern. | | Issue urgency | Select the urgency level that best describes your issue.
  • **Critical**: *Labelbox will not load or I cannot log in.* (Example: Labelbox will not load for any team members; team members are unable to log in).
  • **High**: *I'm mostly blocked from using Labelbox.* (Example: I cannot access a specific feature or functionality of Labelbox).
  • **Medium**: *I'm finding it difficult to use Labelbox.* (Example: The pen tool is not working properly for a large number of team members).
  • **Low**: *I'm running into minor issues using Labelbox.* (Example: The usage report on the Account page is not working properly).
  • **No Urgency**: Utilize this option for general questions or any concern that is not a bug.
| Additionally, you will need to include the following information in your ticket: ### Workspace ID Click your initials in the left panel, select **Workspace settings**, and the copy the **Workspace ID**. ### Project ID Open a project and copy the ID from the URL. The Project ID will be the string of characters after `projects/` and before the next `/`. ### Dataset ID Go to **Catalog**, select your dataset, and copy the ID from the URL. The Dataset ID will be the string of characters after `catalog/dataset/`. ### Data Row ID and Label ID Go to **Catalog**, click on a data row to open the detailed view, and find the Data row ID on the right side of the panel. You can also find the data row ID and label ID by selecting a project and navigating the **Data Rows** tab. Click on any data row to open the **Data browser**. * In the URL, the data row ID will be located immediately after `data-rows/` and span until the next `/` in the URL ( *in red*), followed by the label ID (*in green*). * The label ID will appear in the left panel (*in green*). Data rows that have not been labeled will not have a label ID. Asset ID and Data row ID are used synonymously and refer to the Labelbox-generated IDs assigned to each data row in an organization. A Label ID is only generated once annotations have been submitted on a data row. Keep in mind, when utilizing Benchmarks or Consensus, there may be more than one Label ID associated with a single data row. ## How to view your support tickets You can view your support tickets in two ways: 1. **From your email:** After you create a ticket, the email address entered in the **on behalf of** field will receive an email from Labelbox's Customer Help Portal. The subject of the email will follow the format of `LS-#### `, where `Summary` is the summary you entered when filing the ticket. When you receive the email, click on **View request** to open the ticket in the Customer Help Portal. If someone else raised the ticket on your behalf and you have never used the portal, you may need to complete a brief sign-up procedure. 2. **From the support portal:** Log in to the customer support portal and click **Requests** in the top right corner to see all tickets created by you and your team. Once here, you can filter your tickets, monitor their status, and select a ticket directly from the portal. ## Best practices for getting a fast resolution Here are some tips to help us resolve your tickets as quickly as possible: * **Include attachments:** Screenshots, screen recordings, JSON files, Python snippets, or any other resources that provide context are always helpful. * **Create separate tickets for separate issues:** If you have multiple, unrelated questions and concerns, please open separate tickets. When possible, please refrain from raising a new issue on an existing ticket. Keeping issues separate helps us reach resolutions faster as a team and allows us to better monitor trends so that we can continue to improve our Support overall. * **Comment to re-open a ticket:** If a ticket has been closed by the Support team and the same issue emerges -- or perhaps you have more feedback to add -- you can always comment on the ticket and it will automatically re-open. * **Use the customer support portal:** Communication on tickets will be clearer, more efficient, and easier to digest on the Jira portal compared to over email. In addition, you can use the portal to view all tickets raised by your team, which can illuminate trends and enable the consolidation of your efforts. # Conversational text Source: https://docs.labelbox.com/horizon/guides/conversational-editor Guide for conversation or thread-based text data. When creating a project, select **Conversations**.

## Import conversational text To learn how to import conversational text data, visit our documentation on [importing conversational text](/horizon/developer-guides/import-conversational-text-data). ### Data row size limit To view the maximum size allowed for a data row, visit our [limits](/horizon/guides/limits) page. ## Supported annotation types Below are all of the annotation types you may include in your ontology when you are labeling conversational or thread data. Classification-type annotations can be applied globally and/or nested within an object-type annotation. | Feature | Import annotations | Export annotations | | - | - | - | | **Entity** | [See payload](/horizon/developer-guides/import-conversational-text-annotations#entity-message-based) | [See payload](/horizon/developer-guides/export-conversational-text-annotations#text-entity-named-entity) | | **Relationships** | [See payload](/horizon/developer-guides/import-conversational-text-annotations#relationship-with-entity-message-based) | [See payload](/horizon/developer-guides/export-conversational-text-annotations#relationship) | | **Radio classification** (Global or message-based) | [See payload](/horizon/developer-guides/import-conversational-text-annotations#classification-radio-single-choice-message-based) | [See payload](/horizon/developer-guides/export-conversational-text-annotations#classification---radio) | | **Checklist classification** (Global) | [See payload](/horizon/developer-guides/import-conversational-text-annotations#classification-checklist-multi-choice-message-based) | [See payload](/horizon/developer-guides/export-conversational-text-annotations#classification---checklist) | | **Free-form text classification** (Global) | [See payload](/horizon/developer-guides/import-conversational-text-annotations#classification-free-form-text-message-based) | [See payload](/horizon/developer-guides/export-conversational-text-annotations#classification---free-form-text) | ### Entity To create an entity, simply choose the entity tool in your ontology and select the text string by clicking the desired starting character and dragging to select a sequence of characters in the unstructured text. Please note that in both the conversational and thread-based UI, the annotator will only be able to label messages that have been marked as `canLabel` in the import file. The messages that can be annotated will have a white background while those that cannot be annotated will have a grey background. ### Message-based radio classifications One unique feature of our conversation editor is the ability to label specific messages in a conversation with a radio classification value. This enables you to annotate messages with values such as intent or user sentiment. In order to configure your radio classification as a message-based classification, you must configure your radio classification task as a **Frame/pixel-based** classification value during ontology configuration. If this value is not configured correctly, the radio classification will apply to the entire conversation rather than a single message. ### Relationship With annotation relationships, you can create and define relationships between entity annotations in the conversational text editor. You can then use these annotation relationships to consolidate labeling workflows and potentially reduce the number of language models needed. Follow these steps to create a relationship between two entity annotations: 1. In the editor, create two entity annotations. 2. Select the relationship annotation from the Tools menu, then click on one of the entity annotations. To create the relationship, move your cursor to another entity and click to create the relationship. 3. Add an optional subclassification to the relationship. ## Text-specific hotkeys | Function | Hotkey | Description | | - | - | - | | Create a relationship | `Option` + Click (source) + Click (target) | Select an entity to be the source of a relationship, then connect it to another entity to be the target. | # Create a project Source: https://docs.labelbox.com/horizon/guides/create-a-project Instructions for creating and modifying a labeling project in Labelbox. This guide will walk you through the essential steps of creating a new project, as well as managing its lifecycle through duplication and deletion. ## How to create a new project 1. Navigate to the **Projects** tab in the main Labelbox application. 2. Click the **"New Project"** button in the top right corner. 3. **Choose the data modality or task type**. Note that you will only be able to attach data rows that match the data type you set here. For example, you cannot select video as the data type upon project creation and then send image data rows to this project later. 4. Give your project a clear, descriptive **Name**. You may optionally add a **Description** and project **tags**. Then, click **Save**. 5. From the project overview, click **Add data** to select the data rows to include in this project. Refer to our [Catalog docs](/horizon/guides/catalog-overview) to learn how to import data. 6. In this step, you can [filter and search](/horizon/guides/search) your data rows to curate a batch. Select the data rows you wish to send to the project, then click **Queue batch of *n*** to add the data rows to the labeling queue. 7. In the **Advanced batch settings**, you can optionally set the **Data row priority** and toggle on **Consensus** mode. Select **Submit** to send this batch to your labeling project. 8. The final step is to **Set up ontology and labeling experience**. You may either create a new ontology or reuse an existing one. 9. Once you complete these steps, the **Start labeling** button will be activated in your project overview. ## How to duplicate a project Duplicating a project is a great way to reuse a complex setup without starting from scratch. When you duplicate a project, you copy its ontology, labeling interface settings, and all other configurations. 1. From the **Projects** tab, find the project you wish to duplicate. Open the project. 2. Click on the project name at the top. From the dropdown menu, select **Duplicate project**. 3. A new project will be created with the same settings. Below are the settings that can get copied when you duplicate a project: | Setting | Copied to duplicate version | | - | - | | Project Name | | | Project Tags | | | Project Description | | | Ontology | | | Quality | | | Batches | | | Data rows | | | Workflow instructions/setup | | | Performance | | | Issues | | | Issue Categories | | | Notifications | | | Import labels | | | Export | | | Members (settings) | | | External workforces | | | Models (for Live multimodal chat projects) | | ## How to delete a project If a project is no longer needed, you can delete it to keep your workspace organized. 1. From the **Projects** tab, find the project you wish to delete. Open the project. 2. Click on the project name at the top. From the dropdown menu, select **Delete project**. 3. A confirmation dialog will appear. Type the name of the project to confirm and click **"Delete"**. Deleting a project is a permanent action and cannot be undone. ## How to create project tags As you create more projects in Labelbox, keeping them organized becomes crucial. Tags are a simple yet powerful way to categorize, filter, and manage your projects. Benefits of using project tags: * **Categorization:** Group projects by team (e.g., `team-alpha`, `team-beta`), status (`in-progress`, `completed`), or data type (`images-night`, `text-legal`). * **Easy filtering:** Quickly find all projects associated with a specific tag, saving you time from manually searching. * **Improved visibility:** Get a clear overview of your labeling initiatives by filtering your project list by tag. **To create and apply tags** 1. From the **Projects** tab, select a project. 2. From the project overview, hover over the **Tags** section and click the **Edit** icon. 3. You can either *select an existing tag* from the dropdown list or *type a new tag* and press **Enter** to create it. A project can have multiple tags. **Filtering by tags** Use the filter bar at the top of the **Projects** page to filter by tag. Click on the tag you want to filter by, and the view will instantly update to show only the projects with that tag. You can select multiple tags to further refine your search. **To delete a tag** To manage existing tags, select a project, click the **Edit** icon next to **Tags** in the project overview. Click **Manage tags** and click the **Delete** icon next to the tag name. Deleting a tag removes it from all associated projects, and modifying a tag updates it across all associated projects. # Configure CORS Source: https://docs.labelbox.com/horizon/guides/create-cors-headers Instructions for configuring Cross-Origin Resource Sharing (CORS) for AWS S3, GCS, and Microsoft Azure. This guide explains what a CORS policy is and how to configure it correctly in AWS, GCP, and Azure. Completing this step ensures that your data assets will render correctly within the Labelbox application. ### What is CORS and why is it necessary? * **The Problem:** Modern web browsers enforce a "Same-Origin Policy" to protect you from malicious websites. This policy prevents a web page (like the Labelbox app at `https://app.labelbox.com`) from making requests to a different domain (like your storage bucket at `your-bucket.s3.amazonaws.com`). * **The Solution:** A CORS policy is a small configuration file you add to your storage bucket. It acts as a permission slip, telling the browser, "It's okay to allow requests from `https://app.labelbox.com` to access files in this bucket." When configuring CORS for your cloud storage bucket, you will need to include both of these Labelbox origins: `https://app.labelbox.com` and `https://editor.labelbox.com`. ## AWS S3 1. Navigate to your S3 bucket in the AWS Console. 2. Click the bucket name. 3. Go to the **Permissions** tab. 4. Scroll to the **Cross-origin resource sharing (CORS)** section, click **Edit**. 5. Paste the provided policy into the editor and save. ```json JSON theme={"theme":"css-variables"} [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET" ], "AllowedOrigins": [ "https://app.labelbox.com", "https://editor.labelbox.com" ], "ExposeHeaders": [] } ] ``` 7. For more details on setting up CORS for your AWS S3 bucket, see [these AWS docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html). ## Google Cloud Storage (GCS) The configuration must allow requests from both `https://app.labelbox.com` and `https://editor.labelbox.com` to ensure data loads correctly across the entire platform. You can set this policy using one of the two methods below. ### Option 1: Using the gcloud CLI on your local machine This method is ideal if you have the `gcloud` command-line tool installed on your computer. 1. Create a new file on your computer named `cors-config.json`. 2. Copy and paste the following JSON content into the file. Note: The `"*"` for `responseHeader` allows all necessary headers and is recommended for robust compatibility. ```json theme={"theme":"css-variables"} [ { "origin": ["https://app.labelbox.com", "https://editor.labelbox.com"], "method": ["GET"], "responseHeader": ["*"], "maxAgeSeconds": 3600 } ] ``` 3. Open your terminal or command prompt and run the following command. Be sure to replace `[BUCKET_NAME]` with the actual name of your GCS bucket. ```shellscript theme={"theme":"css-variables"} gcloud storage buckets update gs://[BUCKET_NAME] --cors-file=cors-config.json ``` ### Option 2: Using the Google Cloud Shell This method is convenient if you prefer to work directly within the Google Cloud Console without creating local files. 1. Open the [Google Cloud Shell](https://shell.cloud.google.com/ "https://shell.cloud.google.com/") from your GCP console. 2. Run the following command in the Cloud Shell terminal. This command creates the `cors-config.json` file and writes the correct configuration to it in a single step. ```shellscript theme={"theme":"css-variables"} echo '[{"origin": ["https://app.labelbox.com", "https://editor.labelbox.com"], "method": ["GET"], "responseHeader": ["*"], "maxAgeSeconds": 3600}]' > cors-config.json ``` 3. Next, run the command below to apply the configuration to your bucket. Remember to replace `[BUCKET_NAME]` with your bucket's name. ```shellscript theme={"theme":"css-variables"} gcloud storage buckets update gs://[BUCKET_NAME] --cors-file=cors-config.json ``` ## Microsoft Azure 1. Navigate to the your Storage Account in your Azure Portal. 2. Under **Settings**, find **Resource sharing (CORS)**. 3. Select the **Blob Service** tab. 4. Fill in the fields: Allowed origins (`https'//app.labelbox.com` and `https://editor.labelbox.com`). Allowed methods (`GET`), etc. and save. ## Troubleshooting | Error message | Troubleshooting | | - | - | | Unable to detect proper CORS configuration | Ensure your cloud storage bucket has CORS configured with the following origins: `https://app.labelbox.com` and `https://editor.labelbox.com`. For more troubleshooting help, see: - [AWS troubleshooting CORS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/cors-troubleshooting.html) - [GCS troubleshooting CORS requests](https://cloud.google.com/storage/docs/configuring-cors#troubleshooting) - [Cross-Origin Resource Sharing (CORS) support for Azure Storage](https://docs.microsoft.com/en-us/rest/api/storageservices/cross-origin-resource-sharing--cors--support-for-the-azure-storage-services) | # Custom model integration Source: https://docs.labelbox.com/horizon/guides/custom-model-integration Describes how to set up and integrate a custom model so that it can be used with Foundry. If you are on an enterprise plan, you can integrate custom models with [Foundry](/horizon/guides/foundry) to use them to predict labels, enrich data, and generate responses for evaluation purposes. To upgrade to the enterprise plan, please [contact sales](https://labelbox.com/sales/). ## Host custom models Before integrating your custom model, you need to deploy it on an HTTP endpoint accessible via the Internet that accepts HTTP POST calls with a JSON payload. You can host it either on your own infrastructure or through any model hosting vendor, such as [Vertex AI](https://cloud.google.com/vertex-ai/docs/general/deployment), [Databricks](https://docs.databricks.com/en/machine-learning/model-serving/create-manage-serving-endpoints.html), [Huggingface](https://huggingface.co/inference-endpoints), [Replicate](https://replicate.com/docs/how-does-replicate-work#private-models), [OpenAI](https://platform.openai.com/docs/guides/fine-tuning/use-a-fine-tuned-model). ## Create model integrations Once you have a public HTTP endpoint for your custom model, you can create the integration: 1. On the [Models](https://app.labelbox.com/mea) page, click **Create** and select **Custom Model**. 2. Select the data type for the model. 3. Add custom model information, including: * **Name**: A unique identifier for the model. * **HTTP endpoint**: The URL of the HTTP endpoint hosting your model. * **Secret** (optional): The authentication token for secret-secured endpoints only. * **Description** (optional): The descriptive context of the model. 4. Click **Create model**. On the **Settings** tab, you can review and edit the model information. You can add a rate limit and a Readme. To send data to your model for label prediction, click **+ Model run**. From there, you can define and [preview your model run](/horizon/guides/foundry-define-model-run), [view prediction and details](/horizon/guides/foundry-view-predictions), and [send predictions to Annotate](/horizon/guides/foundry-annotate-predictions). ### Bounding box and mask tasks not supported Currently, this model integration flow doesn't support tasks involving bounding box and mask annotations. To integrate a custom model for these tasks, see [Create model integrations for bounding box and mask tasks](#create-model-integrations-for-bounding-box-and-mask-tasks). ## Create model integrations for bounding box and mask tasks For a custom model predicting bounding box and mask labels, you need to create a model manifest file and [contact customer solutions](https://labelbox.atlassian.net/servicedesk/customer/portal/2/group/3/create/214) to manually establish the integration. The Labelbox solutions team can help you manage the job queuing, track status, and process predictions using the Labelbox platform. ### Create manifest files To integrate your model into the Foundry workflow, you need to specify and provide a `model.yaml` manifest file. This file stores metadata about the model, including its name, description, inference parameters, model output ontology, API endpoint, and other details. You need to create the `model.yaml` file in the following format: ```yaml Example YAML manifest file theme={"theme":"css-variables"} name: My custom model inference_endpoint: my_inference_endpoint # Deploy your service to an API endpoint that can be accessed secrets: my_secret # Your secret, API keys to be authenticated with your endpoint requests_per_second: 0.1 # Your estimate of requests per second description: My awesome custom model for object recognition readme: | # optional readme in markdown format ### Intended Use Object recognition model on my custom classes. ### Limitations My custom model has limitations, such as ... ### Citation ... allowed_asset_types: [image] # list of allowed asset types, one or more of [image, "text", "video", "html", "conversational"] allowed_feature_kinds: [text, radio, checklist] # list of allowed feature kinds. One or more of [text, radio, checklist, rectangle, raster-segmentation, named-entity, polygon, point, edge] # Only needed if your model has a predefined set of classes for classification or object detection. If your model is an LLM or takes any text input, you can remove this section. ontology: media_type: IMAGE # This example ontology has two classification classes and two object detection classes. classifications: - instructions: label name: label type: radio options: - label: tench value: tench position: 0 - label: goldfish value: goldfish position: 1 tools: - name: person tool: rectangle - name: bicycle tool: rectangle inference_params_json_schema: # hyperparmeters configured in the app and passed to your API endpoint. properties: # Examples follow, each with different types and defaults. prompt: description: "Prompt to use for text generation" type: string default: "" confidence: description: object confidence threshold for detection type: number default: 0.25 minimum: 0.0 maximum: 1.0 max_new_tokens: description: Maximum number of tokens to generate. Each word is generally 2-3 tokens. type: integer default: 1024 minimum: 100 maximum: 4096 use_image_attachments: description: Set to true if model should also process datarow attachments. type: boolean default: False required: # Use to specify hyperparameters that must have values for each model run. - prompt max_tokens: 1024 # only relevant for LLM to control maximum token size ``` ## Endpoint requests for model tasks Every time you use your integrated custom model to predict labels or run other tasks, it sends a JSON request to your model's endpoint. The request payload provides the data row for prediction and includes the ontology and inference parameter values you selected. Here's an example request body: ```json Example JSON request theme={"theme":"css-variables"} { "prompt": [ { "role": "system", "parts": [ { "text": "Start each sentence with three equal signs ===" } ] }, { "role": "user", "parts": [ { "text": "what is in this text and image?" }, { "text": "Hello. This is a user-provided txt file content." }, { "image": "base64_encoded_image_string" } ] } ] } ``` Here are descriptions of fields in the request body: * `prompt`: Contains the current conversation with the model. For single-turn queries, it’s a single instance. For multi-turn queries, it includes conversation history and the latest request. Each `prompt` has a message structure with two properties: `role` and `parts`. * `role`: A string indicating the individual producing the message content. Possible values include: * `system`: Instructions to the model. * `user`: User-generated message sent by a real person. * `assistant`: Model-generated message, used to insert responses from the model during multi-turn conversations. * `parts`: A list of ordered parts that make up a multi-part message content. It can contain the following segments of data: * `text`: Text prompt or code snippet. * `image`: Base64 encoded image. ### Response Responses are expected to match the format of the labels predicted by the custom model, such as a string containing the raw model response or a JSON object for NER and classifications. Here's an example JSON response with keys corresponding to feature names in the ontology: ```json JSON theme={"theme":"css-variables"} // Object Detection { "cat": { // coordinate order: left, top, width, height "boxes": [[0, 0, 10, 10], [40, 40, 8, 10]], "scores": [0.9, 0.7], }, "dog": { "boxes": [[20, 20, 5, 5]], "scores": [0.8], }, } // Classification { "summary": "Tom and Bob are happy to work at IBM", // Free Text "sentiment": "positive", // Radio classification "emotion": ["joy", "fear"], // Checklist classification } // Segmentation { "cat": { // Can use pycocotools.mask.encode for RLE encoding "masks": [ { "size": [, ], "counts": "" } ] } } // Named Entity { "person": [ {"start": 0, "end": 3, "text": "Tom"}, {"start": 5, "end": 8, "text": "Bob"}, ] } ``` # Manage your project's data rows Source: https://docs.labelbox.com/horizon/guides/data-rows-activity The Data Rows tab enables you to easily view, filter, and manage the data rows in your project. The **Data rows** tab in your project dashboard is your central hub for viewing, filtering, and managing all the data rows within a specific project. Whether a data row is labeled, waiting in the queue, or undergoing review, you can find it here. ## Filtering your data rows Each data row displays key information, including how much time has been spent on labeling and review, which dataset it belongs to, and whether there are any open issues. There are several ways you can filter your data. ### Use search filters To find specific data rows, you can use a powerful and flexible filtering system. Click on **Search your data** to get started. You can combine multiple filters using AND/OR conditions to create highly specific queries. For example, you could search for all data rows in a specific `batch` AND that have been `reworked by` a particular labeler. This allows you to precisely target the data you need. Here are the available filters: | Filter | What it does | | - | - | | Dataset | Find data rows belonging to a specific dataset. | | Metadata | Filter data rows based on their metadata fields and values. | | Media attribute | Query data rows based on attributes of the asset itself (e.g., image height or video duration). | | Batch | Find data rows that are part of a particular batch. | | Data row | Search for data rows by their ID, Global Key, creation date, or last activity date. | | Find text | Perform a text search within your data rows. | | Natural language | Use natural language to search for data rows (e.g., "show me all the pictures of cats"). | | Task | Find data rows that are currently in a specific workflow task. | | Issue | Filter data rows based on any issues associated with their labels. | | Label actions | Find data rows based on actions taken on their labels, such as who labeled them, when they were labeled, or if they were skipped. **Note:** Bulk moving a data row to a different step is not considered a review or rework, so it won't appear in filters like `Reworked at`, `Reworked by`, `Reviewed by`, or `Reviewed at`. | | Annotation | Filter data rows based on the number of annotations on their labels. | | Benchmark agreement | Find data rows based on benchmark agreement metrics. | | Consensus agreement | Find data rows based on consensus agreement metrics. | ### Filter data rows by status The status of a data row tells you where it is in the labeling and review process. The following statuses are available by default for every project. You can see a count of data rows for each status on the left panel. Clicking on a status will filter the table to show only the data rows with that status. | Status | What it means | | :- | :- | | To label | These data rows need to be labeled. For a standard data row, this means it has no labels. For a consensus or benchmark data row, this means it has fewer than the required number of labels (e.g., it has two labels but needs three). | | In review | These data rows are currently in a review task within the workflow. | | In rework | These data rows have been sent back for corrections and are in the rework task. | | Done | These data rows have all the required labels and have successfully passed all review steps in the workflow. | ## Updating your data rows You have several options to update your data rows in bulk or at the individual level ### Task browser: Update individual data rows To view or edit the labels on a single data row, click on the data row in the table. This will open the **Task browser**. From here, you can: * **Edit the label**: Click the **Edit** button to create or edit annotations. Press **Save** to save your changes. * **View all labels (for consensus or benchmark)**: If you are using consensus or benchmarks, you can view all the different labels created for that data row. Use the `>` icon in the left-side panel to see all labels, view their IDs, and select a "winner" label. Click on the options menu next to a data row to access more actions you can take at the data row level. From this menu, you can: * **Copy link:** Copy the link to the data row. You can send the link to the data row to other members in your organization. * **Delete labels & requeue:** Remove all existing labels from a selected group of data rows and then send those data rows back to the beginning of the labeling queue to be worked on again. * **Move to step:** Choose to move the selected data row to **Initial review task**, **Rework**, or **Done**. * **Add as benchmark:** Select this label as a winning benchmark label. ### Bulk update multiple data rows If you need to update the configurations for many data rows at one time, you can use the Bulk actions feature. Select multiple data rows to perform the following actions in bulk: | Action | What it does | | - | - | | **Change priority** | Assign a new priority to the selected data rows to change their order in the labeling queue. | | **Delete and requeue** | Allows you to remove all existing labels from a selected group of data rows and then send those data rows back to the beginning of the labeling queue to be worked on again. You can select to preserve the existing labels or delete them. | | **Export data** | Export the selected data rows and their labels. | | **Hide/unhide from labelers** | Control which data rows are visible to labelers. Hidden data rows will have an "Authorized only" tag. Admins can still view the row details, but labelers will only be able to copy the external ID and will see an "Unauthorized to view this data row" message if they try to access it. | | **Move to step** | Choose to move the selected data rows to: **Initial review task**, **Rework**, or **Done**. | | **Assign labels as benchmarks** | Designate the labels on the selected data rows as benchmark reference labels. | Bulk **Move to step** actions are not considered a review or rework. Therefore, these actions will not show up when applying **Reworked at**, **Reworked by**, **Reviewed by**, or **Reviewed at** filters. ## Updating your batches From the Data Rows tab, you can also update the batches of data rows in your project. ### View and update batches Click on **Batches** → **View batches** to view and update all the existing batches in your project. From here, you can: * **Rename the batch**. * **Copy batch ID**. * **Change priority**: When you initially create the batch, you have the option to select the data row priority (value between 1 - 5). Leaving it as-is will default to batch value to 5. From the **View batches** panel you can update the priority for the batch. * **Remove queued data rows**: This will remove any data rows from the batch that have not yet been labeled. * **Delete labels**: This will delete the labels from the data rows in the batch and give you the option to requeue them, either with or without the old label as a template. * **View history**: See a log of when data rows were added or removed from the batch. ### Add more data to batch Click on **Batches** → **Add data from Catalog** to select more data rows to add to the batch. # Metadata Source: https://docs.labelbox.com/horizon/guides/datarow-metadata Instructions for adding, filtering, and modifying metadata in the app UI. **Developer guide**: [Metadata](/horizon/developer-guides/metadata) Metadata is non-annotation information about an asset. You can use metadata to search and filter your data rows in Labelbox. The metadata schema lives at the organization level. This allows you to apply the same metadata fields across multiple datasets. You can use reserved metadata fields and add custom metadata fields. ## Data types All metadata needs to be one of the following types: | Type | Descriptions | Filtering | | - | - | - | | `DateTime` | An ISO 8601 datetime field. All times must be in UTC timezone | Equals, greater than, less than, between | | `Number` | Floating-point value (max: 64-bit float) | Greater than & less than | | `String` | free text field. Max 4,096 characters. | Equals & prefix matching | | `Enum` | Enum field with options. Multiple options can be imported. | Equals | | `Option (Enum)` | Option of an enum. Max 64 options can be created per Enum type. 128 for enterprise customers and can be further increased upon request. | Equals | | `Embedding` | 128 float 32 vector used for similarity | | ## Reserved fields By default, Labelbox defines several metadata fields on your data rows. You don't have to use these fields and can change the field values, but you can't delete or rename these fields. Each metadata field has a unique schema ID used to upload data to Labelbox. | Name | Type | Description | | - | - | - | | `tag` | `String` | The tags of the data row | | `split` | `Enum` | The split of the dataset that the data row belongs, including`train`, `valid`, and `test` | | `captureDateTime` | `DateTime` | The timestamp when the data is captured | | `skipNFrames` | `Number` | (Video data only) The number of frames to skip | | `turnInstructions` | `String` | JSON string that contains instructions for each turn in a Multi-modal chat conversation. | ## Custom fields To create custom metadata fields via the UI, go to the [**Schema**](https://app.labelbox.com/schema/metadata) tab. Select **Create**. Each metadata field must have a unique name and a type. The max number of fields per organization is determined by account tier. To view your metadata schema, go to the [**Schema tab**](https://app.labelbox.com/schema/metadata) and then select the **Metadata** subtab. ### Metadata field limits You can modify the names of custom metadata schema by clicking the **Edit** button in the detail view of each schema, but you can't change the type once you create a field,. You also can't modify the names of reserved fields. See [limits](/horizon/guides/limits) to learn the maximum number of allowed metadata fields of your account. ## Update custom metadata schema via the UI Go to the Schema tab to update custom metadata fields via the UI. Then, find the metadata you want to modify and click the **Edit** button. Only the following can be updated: * The name of custom metadata for [non-reserved](#reserved_fields) metadata * The name of options for Enum metadata ## Bulk add metadata Follow these steps to bulk add metadata to your data rows in **Catalog**. See [limits](/horizon/guides/limits) to learn the limits for bulk adding metadata to data rows in Catalog. ### Step 1: Select data rows You will need to select a curated subset of data. For example, you may select a cluster of data from the [projector view](/horizon/guides/model-runs). Another option could be to select the top results of a [natural language search](/horizon/guides/natural-language-search). This way, you can use neural networks like CLIP as zero-shot classifiers. A third possibility is to select all assets that look similar to each other - thanks to [Labelbox similarity search](/horizon/guides/similarity). Similarity search powered by embeddings allows you to leverage any off-the-shelf neural network as a zero-shot classifier. There are three ways to select data rows: * Option 1: Select all filtered data rows by choosing **Select all**. * Option 2: Use the selection icon (checkbox) to manually select individual data rows. * Option 3: Bulk select data rows by selecting the first data row, press and hold `Shift`, and then select the last data row. All data rows between the first and last ones are selected. ### Step 2: Add metadata After you select your data rows, select **Add metadata** from the selection menu. ### Step 3: Pick a metadata field From the selection menu, select the metadata field you want to apply. You can search for metadata fields by typing their name if you don't see them in the dropdown. Metadata fields must exist in Labelbox before they appear in the menu. ### Step 4: Provide metadata values Enter a metadata value. This metadata field and value will apply to all selected data rows. Choose **Save** to apply the metadata in bulk. Changes can take time to be reflected in the data row metadata. ## View metadata You can surface data rows with the new metadata values, by [searching on metadata in Labelbox](/horizon/guides/search#supported-attributes-for-search-and-filter). The newly created metadata tag will also show up in the detailed view. ## Filter metadata Once you upload your metadata, you can easily filter and view metadata in Catalog. If you want to label this set of data rows, you can filter by that metadata field and send them to a labeling project as a [batch](/horizon/guides/batches). ### Metadata viewing access Labelers cannot filter and view metadata in Catalog. However, labelers will be able to view metadata information in the data row information panel. Select a data row to open its detailed view. The **Metadata** panel shows all current metadata. ## Export metadata When you export your metadata, you can sync the newly created metadata with any outside system such as a cloud bucket, data lake, data warehouse, or database. To learn how to export data rows containing metadata, see [Export from catalog](/horizon/guides/export-from-catalog). ## Delete metadata You can delete metadata for a data row through the SDK. Visit [this guide](/horizon/developer-guides/metadata) for instructions. # Manage datasets Source: https://docs.labelbox.com/horizon/guides/datasets-datarows Instructions for managing datasets in the Labelbox UI. This guide covers how to create, modify, and manage your datasets directly within the Labelbox user interface. ## Before you start Before you can label data, you need to import it into Labelbox. Take a look at our guides for connecting your cloud data to Labelbox, then come back to this page. ## Key definitions The following are fundamental concepts for organizing your data in Labelbox. Understanding these terms is the first step to building powerful ML models. All data in Labelbox is organized into **Datasets**, which are made up of individual **Data Rows**. | Term | Definition | | :- | :- | | Asset | The file you want to label (e.g., an image, a video, a text file). This is your core, raw data. | | Data row | The container for a single asset and all its related information. This includes the asset itself, its metadata, attachments, and any annotations (labels) created for it. | | Dataset | A collection of data rows, typically from a single source or domain. For example, you might create one dataset for "Medical Images from Device X" and another for "Customer Support Transcripts." | | Attachment | Supplementary information you can add to an asset to provide extra context for your labelers. For example, you could attach a PDF with instructions or a reference image. | | Global key | An optional but highly recommended unique ID for each data row. Using global keys helps prevent duplicate data uploads and makes it easy to map data rows in Labelbox back to your own external databases or file systems. | ## Create a new dataset 1. Navigate to the **Catalog** tab. 2. Click the **+ New** button to open the "Create a new dataset" dialog. 3. Give your dataset a clear, descriptive name (e.g., `q1-2026-night-driving-images`). 4. Optionally, add a description for more context. 5. Choose an import method: * [Connect to your cloud storage (RECOMMENDED)](/horizon/guides/connect-to-cloud-storage) * [Signed URLs](/horizon/guides/signed-urls) * [Direct upload](/horizon/guides/upload-local-files) 6. Click **Create dataset**. Best practices * Naming: Dataset names can be up to 256 characters and include letters, numbers, spaces, and `_-.,()/`. Use names that clearly explain the data's source and purpose. * Organization: Group data from a single domain or source into its own dataset. This simplifies labeling workflow setup. You can use Metadata to further organize and filter data rows within a dataset. ## Append data to an existing dataset You can add more data to a dataset at any time. 1. Go to **Catalog** and select your dataset from the list on the left. 2. Click the **Append to dataset** button. 3. You will be prompted to choose an import method to add your new data rows. ## Delete a dataset 1. In **Catalog**, select the dataset you wish to delete. 2. Click the three-dot menu and select **Delete dataset**. 3. A confirmation dialog will appear. Type `delete` and click the **Delete dataset** button to confirm. Deleting a dataset is a permanent, irreversible action. All associated data rows, annotations, metadata, and classifications will be lost. # Deprecations Source: https://docs.labelbox.com/horizon/guides/deprecations A list of active and completed deprecations ## Active deprecations This section lists all products and features which are currently in a deprecation period. *** ## Completed deprecations This section lists all products and features which have been decommissioned. These products and features are no longer available. ### Model experiments As of July 15th, the Model experiments feature is no longer available. Note: This update does ***not*** impact any of the “Foundry” models or capabilities. ### Code runner Code Runner was a Labelbox feature that allowed users to execute small code snippets directly within MMC conversations via Cloud Run functions. Code runner was sunset on July 7, 2026 due to lack of usage. ### Prompt and response editor The Prompt and response editor in the Annotate product was sunset on June 30, 2026. ### Public Demo organization The public demo workspace in the Labelbox platform was created to provide sample projects for users to explore the platform's features and capabilities. It was sunset on April 3, 2026. ### Census integration As of June 30, 2025, we no longer support the Census integration. ### Export v2 nonstreamable methods In May 2025, we introduced changes to the Python SDK that broke compatibility with the following Export v2 non-streamable methods in version 3.67 and earlier: * `catalog.export_v2()` * `data_row.export_v2()` * `dataset.export_v2()` * `model_run.export_v2()` * `project.export_v2()` * `slice.export_v2()` If you're using any of these methods with SDK version 3.67 or earlier, please update to a newer version or migrate to the `export()` method, which provides a more scalable and performant way to export data across the Labelbox platform. To learn more, see [Export overview](/horizon/developer-guides/export-overview). ### Automation efficiency score On April 16, 2025, we removed Automation efficiency score from the Labelbox platform. ### Catalog cluster view On February 28, 2025, we deprecated Catalog cluster view for all customers due to a lack of usage. ### Smart select In February 2025, we deprecated the **Smart select** button for selecting datasets on the Catalog page. ### Reporting (Enterprise Dashboard) In February 2025, we deprecated the Reporting page (Enterprise Dashboard) for all customers. The [Monitor tab](/horizon/guides/monitor) offers all of the same metrics and filters (plus more). Unlike the Reporting page, the Monitor page is a native solution that is built into the Labelbox platform, so it offers a more seamless and robust user experience. To learn more, read [this guide](/horizon/guides/migration-guide-reporting-page-to-monitor). ### DICOM editor Due to a lack of usage, we sunsetted the DICOM editor on November 25, 2024. ### YOLO models in Foundry On November 7th 2024, Labelbox disabled YOLO models in Foundry due to a lack of usage. Foundry still supports other object detection and segmentation model alternatives (OWL-ViT, Rekognition, GroundingDINO, GroundingDINO + SAM). If you would like to set up your own YOLO model for inferencing, refer to our [custom model integration](/horizon/guides/custom-model-integration#create-model-integrations-for-bounding-box-and-mask-tasks) docs. ### Fine-tuning On November 7th 2024, Labelbox disabled the model fine-tuning feature. This means that image fine-tuning is no longer usable. We may re-enable the fine-tuning feature in the future. ### Export V1 In early September, we disabled Export v1 for all remaining customers. All users should use the `export()` method instead. ### Data connector libraries All data connector libraries, including `labelbase`, `labelspark`, `labelpandas`, `labelsnow`, and `labelbox-bigquery` libraries, have been publicly archived and are no longer maintained. ### Custom editor We sunsetted the custom editor for all customers on June 30, 2024. Custom editor projects and their associated labels are no longer accessible. Please use one of our native labeling editors instead. ### Dropdown schema Dropdown is a deprecated classification type (the other classification types are radio, checklist, and free-form text). Labelbox implemented a UI solution that provides a dropdown view for radio and checklist classifications. Therefore, we removed the capability to add the dropdown schema to new ontologies. We recommend using radio or checklist classifications and selecting the dropdown toggle instead. ### Labels tab Prior to the rollout of the [Data Rows tab](/horizon/guides/data-rows-activity), the Labels tab was a dashboard view in Annotate. The Labels tab was replaced by the Data Rows tab as part of the shift to our new data row-centric paradigm. Effective August 2023, no more customers are using the Labels tab. ### Review step Prior to the rollout of [Workflows](/horizon/guides/workflows), the review step was the mechanism for reviewing labels in Annotate. The review step was replaced by Workflows in order to offer more flexibility in customizing the review flow for labeling tasks. Effective August 2023, no more customers are using the Review step. ### Dataset-based queueing Prior to the rollout of [batch-based queueing](/horizon/guides/batches), the only way to send data rows to a labeling project was by sending the entire dataset. This was replaced by batch-based queueing to enable customers to send a curated subset of data rows across multiple data rows to a labeling project. Effective August 2023, no more customers are using dataset-based queueing. ### On-prem At the end of 2022, Labelbox decided to sunset our on-premise offering. All customers are encouraged to use our SaaS product instead. As of August 2023, no more customers are using the Labelbox on-prem offering. ### Superpixel In 2023, Labelbox released the [auto-segment tool](/horizon/guides/label-data#auto-segment), which replaced the Superpixel feature. Auto-segment is embedded into the segmentation mask tool. You can use it to auto-generate segmentation masks. As of January 2023, no more customers are using the Superpixel feature. # Connect to Discord Source: https://docs.labelbox.com/horizon/guides/discord Follow these instructions to set up your Alignerr account with Discord. As part of our onboarding and communication process, all Alignerrs are required to sign up for Discord. Going forward, Discord will be our official platform for project updates, team communications, and key announcements. # Refresh membership If you *already have* an Alignerr Discord account, follow these steps: 1. Log onto [app.alignerr.com](http://app.alignerr.com). 2. Go to **Settings** → **Discord** → click **Refresh Discord membership**. 3. Sign into Discord using your existing credentials. # Create/link account If you *do not have* an Alignerr Discord account, follow these steps: 1. Log onto [app.alignerr.com](http://app.alignerr.com). 2. Go to **Settings** → **Discord** → select **Link Discord account**. 3. Select **Authorize** in the Alignerr Manager modal. 4. Create your Discord account using the email address you use for Alignerr projects. 5. You will receive an email from Discord. Open the email and click **Verify email**. 6. When you enter Discord, you’ll see a modal that says **Create Your First Discord Server**. Exit out of that modal. Do not select anything. # Documents Source: https://docs.labelbox.com/horizon/guides/document-editor Guide for labeling document (PDF) data. The **Documents** editor lets you annotate and review PDF documents for tasks such as document review, text extraction, and data labeling for machine learning. You can navigate multi-page documents, manage annotation layers, and track progress efficiently. The editor also supports automatic optical character recognition (OCR) with the ChatGPT o1 model to detect and extract text from selected areas. ## Set up document annotation projects To set up a document annotation project: 1. [Create a document (PDF) dataset](/horizon/guides/datasets-datarows). 2. On the [Annotate projects page](https://app.labelbox.com/projects), click the **+ New project** button. 3. Select **Documents**. Provide a **name** and an optional **description** for your project. 4. Click **Save**. The system then creates the project and redirects you to the project overview page. 5. Click **Add data**. Then select your audio dataset. Click **Sample** to sample your dataset, or you can manually select data rows and click **Queue batch**. To learn how to import documents using the SDK, see [importing document data](/horizon/developer-guides/import-document-data). ### Data row size limit To view the maximum size allowed for a data row, see [limits](/horizon/guides/limits). ### Image encoding If your PDF files contain images, use JPEG encoding and RGB colorspace for color images. ## Supported annotation types Below are the annotation types you may include in your ontology for labeling document data. Classification-type annotations can be applied globally or nested within a bounding box or entity annotation. | Feature | Import annotation | Export annotation | | - | - | - | | **Bounding box** | [See payload](/horizon/developer-guides/import-document-annotations#bounding-box) | [See payload](/horizon/developer-guides/export-document-annotations#bounding-box) | | **Entity** | [See payload](/horizon/developer-guides/import-document-annotations#entity) | [See payload](/horizon/developer-guides/export-document-annotations#text-entity-named-entity) | | **Relationship** | [See payload](/horizon/developer-guides/import-document-annotations#relationship) | [See payload](/horizon/developer-guides/export-document-annotations#relationship) | | **Radio classification** | [See payload](/horizon/developer-guides/import-document-annotations#classification---radio-single-choice) | [See payload](/horizon/developer-guides/export-document-annotations#classification---radio) | | **Checklist classification** | [See payload](/horizon/developer-guides/import-document-annotations#classification---checklist-multi-choice) | [See payload](/horizon/developer-guides/export-document-annotations#classification---checklist) | | **Free-form text classification** | [See payload](/horizon/developer-guides/import-document-annotations#classification---free-form-text) | [See payload](/horizon/developer-guides/export-document-annotations#classification---free-form-text) | All PDF documents support bounding box annotations. To create other annotations, PDF documents *must* have text boxes before you upload them to Catalog. For best results, verify text layers *before* uploading PDFs. ### Bounding box To create a bounding box, use your cursor to create the shape around a character, word(s), or section in the document. To reposition the bounding box on the document, click + hold, then use your mouse or trackpad to reposition the annotation on the document. You can also click + drag the corners to resize the bounding box. ### Entity To create an entity annotation, click the desired starting character and drag to select a sequence of characters in the text. Characters are not restricted to a single class; entity annotations may overlap completely or partially. Entities may also span multiple pages. To edit an entity's class, right-click the entity and select **Change class**. Shortcut: In the **Tools** panel, you will see a numerical hotkey next to the name of the annotation. Use the specified number hotkey (e.g., `1`, `2`, `3`) to activate the entity tool. To create another entity, press the number hotkey again to activate the tool, then create another entity. Once all entities have been created, press `E` to submit your label. #### Token selection We also support tokenization, so you can create and highlight entities at both word and character levels, which is determined by the data in your JSON upload. Clicking on a specific word will highlight the entire word. This is helpful when labeling text, as it can be easy to accidentally miss certain characters or words when highlighting. ### Relationships To create a relationship between annotations: 1. Select a relationship tool and hover over the source annotation of the relationship to reveal the annotation's anchor points. 2. Click an anchor point to create the starting point of the relationship, then bring your mouse over to the annotation you want to relate it to, hovering over it to reveal its anchor points. 3. Click one of the anchor points to complete the relationship. Right-click a relationship to change its direction, make it bi-directional, or delete it altogether. #### Relationships for annotations across pages If you want to create an annotation relationship for annotations that exist on different pages, you will need to follow a slightly different workflow: 1. Select the relationship tool. 2. Go to the annotation where you want to start the relationship, right-click, and click **Select relationship start**. 3. Scroll to your destination annotation, right-click, and click **Select relationship end**. After you have selected both the starting and end annotation of the relationship, your relationship will be established. ### Radio classification Create a radio classification by activating the classification question and inputting the answer value. In the below example, press `8`, `k`, and `esc` to complete the radio classification. Once all classifications have been completed, press `e` to submit your label. ### Checklist classification Create a checklist classification by activating the classification question and inputting the answer value(s). In the below example, pressing `7` and pressing `Down` + `Enter` on the answer values completes the checklist classification. Once all classifications have been completed, press `e` to submit your label. ### Free text classification Create a free text classification by activating the classification question and inputting the answer value. In the below example, pressing `6`, typing the answer value, and pressing `Enter` completes the free text classification. Once all classifications have been completed, press `e` to submit your label. ## Enable auto-OCR The in-editor automatic OCR support allows you to recognize and extract text from PDFs using bounding boxes. To enable it, add a bounding box feature and toggle on the Automatic OCR option. When you draw a bounding box on the document, OCR automatically detects and extracts text within the selected area. The extracted text is stored as a text subclassification. ## Custom text layers A unique aspect of our document editor is the ability to view text layers. You can toggle the text layer on and it will appear whenever you want to highlight an entity. ## Navigate the document Use your mouse scroll wheel or trackpad to move forward and backward through the pages of the document. To jump to a specific page, highlight the current page number in the top navigation bar, type your desired page number, and press `Enter`. To zoom in, press `Z` and click on the section of the page you want to zoom in on. To zoom out, press `Opt` + `Z` and click on the page, or press `Shift` + `Z` to return the page to its original zoom level. ## Document-specific hotkeys | Function | Hotkey | Description | | - | - | - | | Show Text Layer | `Shift` + `T` | Show or hide the text layer. | # Similarity search with embeddings Source: https://docs.labelbox.com/horizon/guides/embeddings Learn how to use embeddings to perform a similarity search in Labelbox. Embeddings are numerical representations of your data that make it possible to find items that are visually or contextually similar. This is especially useful for unstructured data like images, text, and video, where you can't use traditional search methods. In Labelbox, this is called a similarity search. By using similarity search, you can quickly find more examples of rare edge cases, identify and remove bad data, and ultimately build a higher-quality dataset for training your models. ## How to use similarity search The easiest way to get started with similarity search is to use the *pre-computed embeddings* that Labelbox provides for common data types. **To perform a similarity search:** 1. **Find an anchor**: In the Catalog, hover over a data row you want to find more examples of and click the **Find similar data** icon. This initial data row will be your "anchor". 2. **Add more anchors**: You can optionally select multiple data rows to use as anchors and then select **Add selection as anchors**. 3. **Refine your results**: * **Add or remove anchors**: To add more examples and refine the search, select more data rows and click "Add selection to anchors". To remove an anchor, click on "Anchors (n)" to view all anchors, and click the "—" icon on the one you want to remove. * **Adjust the similarity score**: You can adjust the range of the similarity score (from 0 to 1) to broaden or narrow your search. A higher score means the results will be more similar to your anchors. | Asset type | Pre-computed embedding | | - | - | | Image | [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) (512 dimensions) | | Video | [Google Gemini Pro Vision](https://ai.google.dev/gemini-api/docs/models). First two (2) minutes of content is embedded. Audio signal is not used currently. This is a paid add-on feature available upon request. | | Text | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | | HTML | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | | Document | [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) (512 dimensions) and [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | | Tiled imagery | [CLIP-ViT-B-32](https://huggingface.co/sentence-transformers/clip-ViT-B-32) (512 dimensions) | | Audio | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions). Audio is transcribed to text. | | Conversational | [all-mpnet-base-v2](https://huggingface.co/sentence-transformers/all-mpnet-base-v2) (768 dimensions) | ## How to use your own embeddings If you have your own embeddings, you can upload them to Labelbox to use in similarity searches. View the [limits](https://docs.labelbox.com/horizon/guides/limits) page to learn the custom embedding limits per workspace and the maximum dimensions allowed per custom embedding. **To create and upload a custom embedding:** 1. Create an [API key](https://docs.labelbox.com/horizon/developer-guides/create-api-key). 2. Navigate to **Schema > Embeddings**. 3. Click **+ Create** and give your embedding a name and specify the number of dimensions. 4. Once created, you can use this [Google Colab notebook](https://colab.research.google.com/drive/159lWZzY3wtGacLjwfPuiqdz7eaQ8TfXj?usp=sharing) to upload your custom embeddings. You can view all of the embedding fields in JSON format by clicking the **\** button. To delete a custom embedding, you can do so from the same **Schema > Embeddings** page by selecting the embedding and clicking the gear icon. To learn more about custom embeddings, see our [Custom embeddings developer guide](/horizon/developer-guides/custom-embeddings). ## Advanced search techniques To further refine your data curation workflow, you can combine a similarity search with other filters in the Catalog, such as metadata, annotations, or datasets. Once you have a set of filters that you want to reuse, you can save them as a "slice". Slices are dynamic, so any new data that matches your filter criteria will automatically be added to the slice. # Explore your data in Catalog Source: https://docs.labelbox.com/horizon/guides/explore-your-data-in-catalog Welcome to Labelbox Catalog, your command center for understanding, curating, and preparing unstructured data for your machine learning workflows. Before you can build a high-performing model, you need to deeply understand the data you're working with. Catalog is designed to move you from raw data to curated, label-ready datasets with confidence and speed. Think of Catalog as an interactive, searchable index of all your training data. It provides the tools to not just see your data, but to interact with it, ask questions of it, and organize it in powerful ways. ## What can you do with Catalog? * **Visualize and explore your data to uncover insights:** Instead of guessing what’s in your dataset, you can directly visualize it. Spot imbalances, identify outliers, find rare edge cases, and understand the distribution of your data before it ever touches a model. Use the gallery view for a visual survey, the list view for metadata analysis, and the analytics view to see statistical breakdowns. * **Find specific data with powerful search and filtering:** Move beyond simple filename searches. Catalog allows you to build complex queries to find the exact data you need. You can filter by a rich set of attributes including metadata, annotation-class, dataset, project, and even the content of the data itself using AI-powered search methods. * **Curate and organize datasets for any workflow:** Your raw data is just the beginning. Catalog helps you organize it for specific tasks. You can create static **batches** of data to send to a labeling project or define dynamic **slices** that automatically track specific subsets of your data over time, like "all images flagged for review." * **Take targeted action on your data:** Finding data is only half the battle. Catalog is fully integrated with the rest of the Labelbox platform, allowing you to take immediate action on your findings. Select a group of data rows and, with a few clicks, you can add metadata, export them for analysis, or send them directly to a labeling project. By providing a single, unified interface to explore and manage all your unstructured data, Catalog empowers you to make smarter, data-driven decisions throughout the entire model development lifecycle. ## Key concepts To effectively navigate and use Catalog, it's important to understand its core components. These are the fundamental building blocks you'll encounter as you explore and manage your data. | Term | Definition | | - | - | | Data rows | **What it is:** The most basic unit in Labelbox, representing a single item of your data. A *data row* is a pointer to a single data asset (e.g., an image, a video, a text file, a medical image) along with all its associated information, including metadata, attachments, and annotations.
**Why it's important:** Every action in Catalog—from filtering to labeling—is performed on one or more data rows. | | Datasets | **What it is:** A *dataset* is a top-level container that holds a collection of data rows, often grouped by a specific project, data source, or collection period. You organize your data into datasets when you upload it to Labelbox.
Why it's important: Datasets provide the initial organization for your data and serve as the starting point for exploration in Catalog. | | Slices | **What it is:** A *slice* is a dynamic, saved query that represents a subset of your data. Think of it as a "smart folder" or a saved search that always stays up-to-date.
**Why it's important:** Slices let you continuously monitor specific subsets of your data without re-applying filters. When new data is added to the dataset that matches the slice's filters, it automatically appears in the slice. This is perfect for tracking data quality issues or monitoring for specific edge cases. | | Batches | **What it is:** A *batch* is a static, fixed group of data rows that you can send to a labeling project.
**Why it's important:** Batches are the primary mechanism for queuing up work for human labelers. Once created, a batch does not change unless you manually add or remove items, providing a stable workload for your labeling projects. | | Embeddings | \*\*What it is: \*\**Embeddings* are powerful numerical representations (vectors) of your data that capture its semantic meaning. They are the engine behind Catalog's AI-powered search features. Labelbox can generate these for you, or you can provide your own.
**Why it's important:** Embeddings allow you to search for data based on meaning and similarity, not just metadata. They power features like **similarity search** ("find more images like this one") and **natural language search** ("find images of a dog playing in a park"), making it possible to find relevant data in a more intuitive and powerful way. | ## Gallery view The gallery view is the default, providing a visual grid of your data. It's designed for rapid visual scanning and exploration. * **Best for:** Getting a high-level visual sense of your dataset, spotting visual outliers, discovering trends, and making quick selections based on what the data looks like. * **How to use it:** Simply scroll through the grid to explore. You can click on any data row to open the detailed view or use `Shift + Click` to select a range of items. ## List view The list view organizes your data rows in a familiar table format, emphasizing metadata over visual appearance. * **Best for:** Analyzing and sorting your data by specific metadata attributes, comparing values across data rows, and finding data with specific metadata characteristics. * **How to use it:** 1. Switch to the list view using the view-selector icon. 2. Click on any column header to sort the data by that attribute. 3. Use the **Manage columns** button to customize which metadata fields are displayed in the table, tailoring the view to your specific task. ## Analytics view The analytics view provides interactive charts and graphs that summarize the distribution of data in your current selection. * **Best for:** Understanding the composition of your dataset, identifying class imbalances, and seeing how your data is distributed across different metadata values. * **How to use it:** 1. Switch to the analytics view using the view-selector icon. 2. The view will display histograms and charts for your data's attributes. 3. You can click on a bar in any chart (e.g., a specific annotation class) to automatically filter your dataset down to only the data rows with that attribute. ## Detailed view The detailed view allows you to perform a deep dive into a single data row. * **Best for:** Inspecting an individual data asset, viewing all of its associated metadata, examining its annotations, and accessing all related information in one place. * **How to use it:** Simply click on any data row from the gallery, list, or cluster view to open the detailed view. Here you can see the asset itself, edit metadata, view annotation history, and more. The display panel allows you to customize these display settings: | Display setting | Description | | - | - | | Pin data row details | Pins key details in the gallery view. | | Black color fill image | Replace the data row with black pixels. This is useful to see annotations and predictions stand out | | Show objects | Show the objects in the preview. Supported for predictions and ground truth. | | Show segmentation masks | Show segmentation masks in the preview. Supported for predictions and ground truth. | | Show classifications | Show classifications in the preview. Supported for predictions and ground truth. | | Conversational text formatting - Raw | Display conversational text data rows in raw, unformatted version. | | Conversational text formatting - Markdown | Display conversational text data rows in markdown. | | Segmentation colors: Agree, Disagree | When **Show segmentation masks** is toggled ON, display the agree/disagree in different colors. | # Export model run data Source: https://docs.labelbox.com/horizon/guides/export-data-for-model-training Instructions for exporting model run data from the app UI to train a model in your desired computing environment. Labelbox offers three primary methods for exporting your data: from the Catalog, from a labeling project, or directly from a model run. *This guide specifically focuses on how to export data from a model run.* Exporting model run data is typically done when you want to train a machine learning model in your own custom computing environment outside of Labelbox. By exporting this data, you extract your assets (such as images, video, text, documents, audio, geospatial imagery, or HTML) along with their corresponding annotations so your external models can learn from them. ## How to export your model run data To export annotations from a model run via the Labelbox user interface (UI), follow these initial navigation steps: 1. Go to the **Model** tab and open the **Custom** subtab. 2. Select your desired model from this view. 3. Click on the **Model runs** tab and select a specific model run. Tip: You can use filters to query data rows based on their label status, metadata, batch, annotations, and workflow history. Excluding unneeded fields from your export will make the export process faster and the resulting file much smaller. Once you have opened your model run, you have four options for choosing which data rows to export: ### Option 1: Export all data rows If you want to export the entirety of the data in the current model run, locate and click on **All data** (this will also display the total number of data rows). From the drop-down menu that appears, click **Export data**. ### Option 2: Export a specific split or slice In machine learning, data is often divided into "splits" (**Training**, **Validation**, **Test**, or **Unlabeled**) to train and evaluate models, or categorized into custom "slices." To export one of these specific groups, look to the left side of the Model Run page and click on your desired **Slice** or **Split**. Next, click on the slice or split name at the top of the data view, and select **Export data** from the drop-down menu. ### Option 3: Export filtered data rows If you only need data that meets highly specific criteria, you can build custom queries using the **Model filters**. Once your filtered results appear: 1. Click **Select all** on the right side of the screen. 2. Click the **Manage selection** dropdown. 3. Select **Export data** to export those specific rows. ### Option 4: Export hand-selected data rows For the most granular control, you can manually select specific data rows using the checkboxes next to each row. After you have checked the boxes for your desired data: 1. Click the **Manage selection** dropdown. 2. Select **Export data**. ## Downloading your exported file After you trigger the export (for example, by clicking **Export JSON**), a notification banner will appear letting you know the job has started. You can track the progress of your export in the Labelbox **Notifications center**. Once the system finishes processing the job, simply click the **Download** link in your notifications to save the export file to your computer. ## Export specifications Click through these links to view the export specifications based on data type. # Export data from Catalog Source: https://docs.labelbox.com/horizon/guides/export-from-catalog Instructions for exporting your data from the Labelbox Catalog UI. Labelbox offers three main ways to export your data: from the Catalog, from a model run, or from a labeling project. *This guide focuses specifically on how to select and export data directly from your Catalog.* Exporting from the Catalog is highly flexible. It allows you to use various filters (like similarity, natural language search, annotations, and metadata) to pinpoint exactly the data rows you need. Additionally, Catalog exports give you the powerful option to bundle in associated project labels and model run predictions alongside your data. ## How to export data from Catalog Follow these steps to export your data rows from the Catalog interface. Tip: Only include the optional fields you truly need. Excluding unnecessary fields will make your export process significantly faster and keep your resulting file size smaller. ### Step 1: Select data rows 1. Navigate to the **Catalog** tab in Labelbox. 2. Apply one or more filters to narrow down the specific data rows you want to export. 3. Click **Select all** in the top-right corner of your screen. 4. Open the dropdown menu under **Manage selection** (this will display the number of data rows you selected) and click **Export data**. ### Step 2: Select fields to include in your export Choose any optional data fields you wish to include in your final export file. If you are unsure what a specific field means, you can refer to the [Labelbox Export Glossary](/horizon/developer-guides/export-glossary) for definitions. ### Step 3: Include project data (optional) If you need project-specific labels included in your export file: 1. Select the **Export labels from project** option. 2. A dropdown will appear, allowing you to **select projects**. *(Note: Only projects where at least one of your selected data rows has been labeled will appear in this list).* 3. For the projects you select, all associated labels will be included in the resulting NDJSON export file for each respective data row. ### Step 4: Include model run data (optional) If you need model predictions and labels included in your export: 1. Select the **Export labels and predictions from model runs** option. 2. A dropdown will appear for you to **select model runs**. *(Note: Only model runs that contain your selected data rows will appear here).* 3. For the selected model runs, all labels and predictions will be included in the resulting NDJSON export file for each respective data row. ## How to specify your data row source Before you export, you need to decide where your data rows are coming from. Depending on how you want to curate your data, you can choose from one of the four source options below: ### Option 1: Export from all datasets 1. Select **All datasets** in the top-left corner of the Catalog. 2. Apply your desired filter or combination of filters to search across everything. 3. Click **Select all** in the top-right corner. 4. Click **Manage selection**, choose **Export data** from the dropdown menu, and select your optional fields. ### Option 2: Export from a single dataset 1. Select a specific dataset from the list of datasets on the left-side menu. 2. Apply your desired filters to narrow the data down further within that dataset. 3. Click **Select all** in the top-right corner. 4. Click **Manage selection**, choose **Export data** from the dropdown menu, and select your optional fields. ### Option 3: Export from a slice 1. Toggle to **Slices** on the left-side menu. 2. Select an existing slice you want to work with. 3. Modify or add to the filters that make up the slice, if needed. 4. Click **Select all** in the top-right corner. 5. Click **Manage selection**, choose **Export data** from the dropdown menu, and select your optional fields. ### Option 4: Export specific, hand-picked data rows 1. Manually select the specific data rows you want to export by checking the boxes in the top-left corner of each data row's thumbnail. 2. Click **Manage selection** in the top-right corner. 3. Choose **Export data** from the dropdown menu, and select any optional fields you wish to include. ## Export specifications Click through these links to view the export specifications based on data type. ## Developer guide To learn how to export data from Catalog programmatically, visit this developer guide. # Export labels from project Source: https://docs.labelbox.com/horizon/guides/export-labels Instructions for exporting your annotations from a labeling project via the UI. Once you and your team have labeled your data in Labelbox, you will likely need to export those labels to train your machine learning models, run analytics, or simply keep secure backups. While Labelbox allows you to export data from multiple places (like the Catalog or a model run), this guide focuses specifically on how to export your annotations directly from a **labeling project** using the Labelbox user interface. ## Step 1: Select data rows for export To get started, you must select a project that has at least one label in the "Done" column. Navigate to **Annotate**, open your desired project, and go to the **Data Rows** tab. From here, you have three flexible options for deciding exactly which data rows you want to include in your export file: ### Option 1: Export all data rows If you want to download your entire project's dataset at once, you have two quick ways to do this: * **The quick button:** Click **All (X data rows)**, then simply select **Export data** from the dropdown menu. * **The checkbox method:** Click **All (X data rows)**, then check the box in the top-left corner of the table header to select all visible rows. Finally, click the **"X selected"** dropdown menu and choose **Export data**. ### Option 2: Quick filter by label status Sometimes you only need labels that are in a specific stage of your workflow, such as ones that are fully "Done" or currently "In review." 1. Locate the **"Search your data"** field. Right next to it, select a specific label status. *Note: You can only filter by one status at a time.* 2. Once the table updates, manually check the boxes next to the rows you want to export, or check the box in the top-left of the table header to select them all. 3. Click the **"X selected"** menu and choose **Export data**. ### Option 3: Filter by specific data row attributes For a highly customized export, you can build specific queries to find rows matching certain attributes—like a specific dataset, batch, or metadata value. 1. Click the **Search your data** field to reveal the advanced filter options. 2. Add your desired filters (e.g., *Dataset, Batch, Find text, Annotation, Metadata*) to narrow down your results. 3. Select the data rows from the filtered list that you want to include. 4. Click the **"X selected"** menu and choose **Export data**. ## Step 2: Choose your export fields and download After narrowing down your data rows and clicking **Export data**, you get to customize exactly what information is packaged into your final file. 1. **Select your fields:** A panel will appear featuring date range queries and various data fields. Choose exactly which fields you want to include or exclude, or click **Select all** if you need everything. *(Tip: If you are unsure what a specific field means, consult the Labelbox export specifications).* 2. **Generate the export:** Once your fields are selected, click the **Export JSON** button. A notification banner will pop up to let you know your export job is processing in the background. 3. **Open notifications:** Hover over the bell icon at the bottom of your left navigation bar and click on **Notifications**. 4. **Download your file:** In the Notifications center, locate your export task (you can use filters if you have a lot of alerts). Click the **Download icon** to save the JSON file directly to your computer. ## Export specifications Click through the links below to view the export specifications for each data type. # Export model predictions Source: https://docs.labelbox.com/horizon/guides/export-predictions Describes how to export Foundry predictions and the options you can choose while doing so. Exporting predictions from Foundry allows you to use your model's output outside of the Labelbox platform. This is useful for a variety of tasks, such as: * **Offline analysis:** Analyze model performance in a Jupyter notebook, Excel, or other data analysis tools. * **Integration with other systems:** Feed predictions into downstream applications or custom MLOps pipelines. * **Custom reporting:** Create detailed reports and visualizations on model accuracy and behavior. You can start an export from three main areas in the Labelbox platform, depending on what you need: | Location | Use case | | - | - | | Catalog | Best for when you want to search for and select specific data rows that have predictions you want to export. | | Model | Ideal for when you need to export predictions from a particular model run. | | Annotate | Use this when you want to export predictions for data rows that are part of a specific project. | ## Step-by-step instructions Follow these simple steps to export your predictions: ### Step 1: Select your data First, navigate to the **Catalog**, **Model**, or **Annotate** section and select the data rows with the predictions you want to export. ### Step 2: Start the export From the **Manage selection** menu that appears, choose **Export data**. This will open the **Export** panel where you can customize your export. ### Step 3: Choose your export options In the **Export** panel, you can select what information you want to include in your export file. Depending on where you export your data from (i.e., Catalog, Annotate, Model), you can choose to include these details in your export: * Data row details * Metadata * Attachments * MMC code executions * Project details * Performance details * Label details * Interpolated frames * **Model run details** * **Predictions** * Embeddings * Model type override * Export labels from projects * **Export labels and predictions from model runs** ### Step 4: Generate the export file Once you've selected your options, click **Export JSON**. Labelbox will then start building your export file. ### Step 5: Download your results When your export is ready, you'll see a **Download** link in the **Notifications Center**. Clicking this will give you two options for downloading your results: | Download method | Description | | - | - | | Browser download | This is the quickest way to get your file.
Click the **Download** button to save the results directly to your computer. **Important:** Don't close your browser until the download is complete, or the file will be incomplete. | | Python SDK script | For large datasets, it's better to download using a Python script. The download panel will provide you with a sample script you can copy. This allows the download to happen in the background, so you don't have to worry about closing your browser. You'll need a Labelbox API key to run the script.
Click the **Code sample** button to view a sample Python script to download your export results based on the selected options. | # Find text Source: https://docs.labelbox.com/horizon/guides/find-text Search for text and uncover assets in Catalog. When working with text data such as [text](/horizon/developer-guides/import-text-annotations), [conversational text](/horizon/developer-guides/export-conversational-text-annotations), [documents](/horizon/developer-guides/import-document-annotations), or [HTML](/horizon/guides/html-editor), you can use the **find text** filter in Catalog to surface data rows that contain specific keywords. ## How the find text filter works When you use the **find text** filter, you are allowed to specify multiple keywords in the expression. Labelbox will then surface data rows that contain those words. ### Character/word limits To view the minimum & maximum characters/words for the *find text* filter, visit our [limits](/horizon/guides/limits) page. If multiple words are provided in the expression, Labelbox will surface data rows in this order: 1. Data rows containing all words 2. Data rows containing all words except one 3. Data rows containing all words except two ... and so on until all results containing at least one of the provided words are returned. ## Supported media types The filter is supported for the following data types: | Media type | Limit | | - | - | | [Text](/horizon/guides/text-editor) | First 64k characters | | [Conversational text](/horizon/guides/conversational-editor) | First 64k characters | | [HTML](/horizon/guides/html-editor) | First 64k characters | | [Documents](/horizon/guides/document-editor) | First 64k characters | The limits above indicate the last character in the media file that the filter will reach. For example, with the current limits, if the keyword you are searching for appears after the 64,000th character in the media file, the data row will not appear in the filter results. ## Combine find text with other filters You can combine the **find text** filter with other filters in Catalog. Some filters are best used for targeting *unstructured* data and others are best for targeting *structured* data. Combine the **find text** with the following filters to target data rows by structured data: Combine **find text** with the following filters to target data rows by unstructured data: ## Automate data curation with slices After you populate the Catalog filters, you can save these filters as a [slice](/horizon/guides/slices) of data. When you save a filter as a slice, you will not need to populate the same filters over and over again. Also, slices are dynamic, so any new incoming data rows will automatically appear in the relevant slices. Read through the following resources to learn how to take action on the filtered data. # Labelbox Foundry Source: https://docs.labelbox.com/horizon/guides/foundry Welcome to Labelbox Foundry, your integrated environment for leveraging foundation models and custom AI to accelerate your data labeling and model development workflows. Foundry is designed to help you move from raw, unlabeled data to high-quality training data with speed and efficiency. With Foundry, you can: * **Generate high-quality predictions:** Use state-of-the-art foundation models to automatically generate labels for your data. * **Compare model performance:** Run multiple models on the same data to evaluate and compare their performance, helping you choose the best model for your use case. * **Automate labeling workflows:** Set up Foundry Apps to create repeatable, automated labeling pipelines that can be triggered from the Catalog or via API. * **Active learning workflows:** Identify the most valuable data to label by finding where your model is least confident, and send those assets directly to a human labeling project. * **Integrate custom models:** Bring your own models into the Labelbox ecosystem to take advantage of Foundry's powerful workflow tools. Foundry helps you close the gap between your model's capabilities and your production needs, streamlining the path to building better AI. ## Key concepts | Concept | Description | | - | - | | Model run | The core process in Foundry. A model run is a single execution of a model on a selected batch of data rows. It generates predictions for each data row. | | Foundry app | A saved model run configuration. Foundry Apps allow you to save a specific model and its settings so you can easily and repeatedly run it on new batches of data, automating your workflows. | | Predictions | The output generated by a model run. Predictions are the labels or values that the model "predicts" for your data (e.g., bounding boxes, classifications). | | Ontology | The schema that defines the features your model can predict. You will map the model's output to your project's ontology to ensure consistency. | ## Before you begin To ensure a smooth experience with Foundry, please complete the following setup steps: 1. [**Connect your cloud data**](/horizon/guides/connect-to-cloud-storage)**:** Foundry works directly with your data stored in the cloud. Make sure you have successfully connected your AWS, GCP, or Azure cloud storage to Labelbox. 2. [**Create a project**](/horizon/guides/create-a-project)**:** You'll need a Labelbox project set up with a defined ontology. This project will be the destination for predictions that need human review. 3. **Select your data:** In the Labelbox Catalog, identify and select the data rows you wish to use for your model run. ## Add Foundry to your workspace Foundry is available for all plans except for Educational subscriptions. * **Self-Service (Free, Starter):** Go to your workspace settings to enable Foundry as an add-on. You will be prompted to agree to the terms and add a payment method if needed. * **Enterprise:** Please contact your account manager to enable Foundry. ### How billing works Foundry billing has two components: Inference Costs and Labelbox Units (LBUs). | Cost type | Description | | - | - | | Inference costs | This is a direct cost for using a Labelbox-hosted model, charged in USD. It varies by model and the amount of data processed. These fees are passed on to you and charged immediately after a model run. | | Labelbox units (LBUs) | Your Labelbox subscription includes a set number of LBUs. Foundry consumes LBUs when you run a model and when you send predictions to a labeling project. This usage is deducted from your subscription's LBU balance. | For example: 1. Suppose you import 1,000 images into Catalog; this consumes 17 LBUs. 2. You select 500 images and use a Foundry model run to generate predictions. Based on the model you selected and the parameters of your model run, this generates a \$2.00 inference fee. 3. When the Foundry model run is complete, 500 images and their predictions are now available in Model. This consumes 100 LBUs. 4. To verify the predictions, you send them to Annotate for human review. This consumes another 500 LBUs. Overall, you've generated \$2 in inference costs (Step 2); this is charged immediately. You've also used 600 LBUs as one time charges (Model and Annotate) and generate a 17 LBU charge each month your data remains in **Catalog**. The LBU consumption is charged against the terms of your subscription at the end of the current billing cycle. Compute fees depend on the specific model used, the amount of data processed, and other factors. For details, consult the model card. To learn more about LBUs, see [Labelbox Units (LBUs)](https://docs.labelbox.com/horizon/guides/billing#labelbox-units-lbus). You can review the specific costs for any model by selecting it in the Model gallery and viewing the **Pricing** details on its overview tab. The total cost of any completed model run can be found in the run's details. ## Remove Foundry from workspace You can unsubscribe to Foundry and remove it from your Labelbox subscription at any time. To do so: 1. Select **Workspace Settings** from the Labelbox main menu to open the **Organization Settings**. 2. From the **Billing tab**, locate the **Add-ons** section and then select **Remove**. 3. A confirmation prompt asks you to confirm your request. Select **Unsubscribe** to do so. Removing Foundry from your subscription doesn't affect your data. Any predictions created during the subscription remain. # Create a Foundry app Source: https://docs.labelbox.com/horizon/guides/foundry-apps Foundry apps help you automate Foundry workflows. Learn how to create and manage Foundry apps. As you move from experimenting with models to building production-grade AI systems, you need tools that provide consistency, scalability, and automation. **Foundry Apps** are the key to achieving this in Labelbox. A Foundry App is more than just a saved model; it's a *saved, reusable, and executable workflow configuration*. It captures the entire logic of a model run—the specific model, the ontology mapping, the confidence threshold, and naming conventions—and packages it into a single, on-demand tool. ## Create a Foundry app There are two main ways to create a Foundry app in Labelbox: * **From the Model section**: This is the most direct way to create a new app. * **From a model run configuration**: If you've already configured a model run that you'd like to reuse, you can save it as an app. ### Option 1: Create an app from the Model tab 1. Sign in to Labelbox and select **Model** from the main menu. 2. In the Model gallery, click the **Create** menu and select **App**. 3. Choose the foundational model you want to use for your app. 4. Give your app a name and an optional description, then click **Proceed**. 5. Define the parameters for your model run. If you have a previous model run with settings you'd like to reuse, you can use the **Load model run config** option. 6. Optionally, you can select up to ten data rows to preview how the app will work. To do this, click **Select data for preview**, choose your data rows, and then click **Import selected**. 7. When you're finished, click **Save & Create**. ### Option 2: Save a model run configuration as an app 1. Start a regular Foundry model run by selecting your data rows and a model. 2. Configure your model run settings as you normally would. 3. Before submitting the model run, click the **Save as App** button, which appears near the model's name. 4. You'll be prompted to enter a name and description for your new app. 5. Click **Proceed** to create the app. The **Save as App** button will not be displayed for models that are not compatible with Foundry apps. ## Update a Foundry app To make changes to an existing Foundry app: 1. Sign in to Labelbox and go to the **Model** section. 2. Click on the **Apps** tab and select the app you want to update. 3. Adjust the app's settings as needed. 4. When you're done, click **Save & Update**. ## Delete a Foundry app If you no longer need a Foundry app, you can delete it. Please be careful, as this action cannot be undone. 1. Sign in to the Labelbox app and go to the **Model** section. 2. From the **Apps** tab, select the app you want to delete. 3. Go to the **Settings** tab and click the **Delete app** button. 4. A confirmation prompt will appear. Type the word "delete" and then click **Delete**. Use care when deleting apps. Deleted apps cannot be recovered or restored. # Create pre-labels with Foundry model-assisted labeling Source: https://docs.labelbox.com/horizon/guides/foundry-model-assisted-labeling Use an off-the-shelf Foundry model to pre-label your data rows in your project. Model-assisted labeling is a feature in Labelbox that uses AI to automatically generate labels on your data. This can help you label your data faster and more accurately. Here's how it works: * **You provide a model**: You can choose from a list of pre-trained models in Labelbox or bring your own. * **The model predicts labels**: The model will analyze your data and suggest labels based on what it has learned. * **You review and approve**: You can then review the suggested labels and make any necessary corrections. This process can significantly speed up your labeling workflow, especially for large datasets. ## How to enable model assisted labeling Here's a step-by-step guide to using model-assisted labeling in your Labelbox project. 1. **Set up a new project**: To get started, create a new project in Labelbox. Make sure your project has an ontology and a batch of data that is compatible with Foundry. Once your project is set up, the **Model assisted labeling** button next to **Start labeling** will be enabled. 2. **Choose your model**: Click on the **Model assisted labeling** button. You will be taken to a page where you can choose the model you want to use. Labelbox offers a variety of models to choose from. The models at the top of the list are the easiest to set up, but you can also use one of the other models if you prefer. 3. **Configure your model**: After you have selected a model, you will need to configure it. This involves two main steps: * **Align the model with your ontology**: This means mapping the labels that the model can predict to the labels in your project's ontology. * **Set the confidence threshold:** This is a value between 0 and 1 that tells the model how certain it needs to be before it suggests a label. A higher confidence threshold will result in fewer, but more accurate, suggestions. If your ontology is open-ended, you can also provide your own criteria to the model, similar to prompt engineering. 4. **Preview the selected model**: Before you apply the model to your entire dataset, you can generate a preview to see how it performs on a small sample of your data. This is a great way to test your model and make sure it is performing as you expect. During the preview, you can: * **Adjust the confidence threshold** to find the optimal setting for your use case. * **Highlight annotations** to see which tool was used and the model's confidence for each annotation. If the model is not performing well, you can go back and choose a different model. If you are happy with the results, click **Submit**. 5. **Submit the model run**: After you submit the model, it will start running on your data. You can track the progress of the model in the notification tab at the bottom left of the page. When the model has finished running, you will see a confirmation in the Notifications tab and the results will be shown in your project. 6. **Review the pre-labels**: Once the model has finished running, the suggested annotations will be superimposed on your data rows. You can then review the suggestions and either submit them as they are or remove them and create your own annotations. # HTML Source: https://docs.labelbox.com/horizon/guides/html-editor A guide for using the native HTML editor to label data. When creating a project, select HTML.
You can use the native HTML editor to customize the way your data rows render in the labeling interface. You can use this guide to understand how the HTML editor works, how to set up an HTML labeling project, and common configurations for the HTML editor. ## Import HTML data To learn how to prepare and format your HTML data for importing to Labelbox, see [Import HTML data](/horizon/developer-guides/import-html-annotations). You have two options for uploading to Labelbox: 1. Store your HTML files in your cloud storage and send Labelbox a JSON file containing URL links to the HTML files (recommended) 2. Upload the HTML files directly to Labelbox Note: To label a webpage, you must save it as an HTML file. Direct linking to web pages via a URL is not supported. Please take a look at the Common HTML editor configurations section below to explore ways to configure your import file to render your data in the editor. ## Supported annotation types Create (or reuse) an ontology for your labeling project. The table below shows the annotation types that are supported for labeling HTML data. Click through the links in the table to view the import and export formats for each classification type. Note: You can use [Model-assisted labeling](/horizon/guides/model-assisted-labeling) to import classifications as prelabels to your HTML data rows. | Feature | Import format | Export format | | - | - | - | | **Radio classification** | [See payload](/horizon/developer-guides/import-html-annotations#classification-radio-single-choice) | [See payload](/horizon/developer-guides/export-html-annotations#classification---radio) | | **Checklist classification** | [See payload](/horizon/developer-guides/import-html-annotations#classification-checklist-multi-choice) | [See payload](/horizon/developer-guides/export-html-annotations#classification---checklist) | | **Free-form text classification** | [See payload](/horizon/developer-guides/import-html-annotations#classification-free-form-text) | [See payload](/horizon/developer-guides/export-html-annotations#classification---free-form-text) | Note: HTML files are rendered in Labelbox as an iframe with the sandbox attribute. This means that scripts, pop-ups, and third-party API calls are blocked. ## Custom HTML editor configurations You can leverage the HTML format to customize the way your data appears in the labeling interface. Here are some common configurations our customers set up using the HTML editor. ### Compare and rank products Add CSS to your HTML file to display objects side-by-side in a grid container. This is helpful for ranking or comparing multiple products in the labeling interface. To do this, ensure your CSS display property is set to `grid`. # Hubstaff timer Source: https://docs.labelbox.com/horizon/guides/hubstaff-timer This guide explains how to configure your Hubstaff app for labeling. Use the instructions below as a guide for navigating the Hubstaff timer for your labeling tasks. ## Step 1: Create or join your Hubstaff account Follow these steps to set up your Hubstaff account and install the desktop app. 1. You should have received an email containing an invitation to Hubstaff. Click the link in the email to create your Hubstaff account (You can also set up your Hubstaff account via the onboarding checklist in your Alignerr app dashboard). 2. If you already have a Hubstaff account, the Labelbox team will add you to our organization. You'll receive an email inviting you to join the Alignerr organization in Hubstaff. ## Step 2: Install the Hubstaff desktop app 1. Download and install the Hubstaff desktop app on your computer. 2. Sign in using the credentials from the invitation email. ## Step 3: Open your project in Labelbox 1. Go to your assigned project in Labelbox. 2. Review the labeling instructions. 3. In the project overview tab, click the **Start timer** button. This will open the Hubstaff browser extension (you will need to install the Hubstaff browser extension, if you haven't already). 4. In the pop-up window, select **Always allow** and click **Open Hubstaff**. 5. When the Labelbox app has detected that the Hubstaff timer is enabled, you will be able to start labeling ## Step 4: Start labeling 1. Click **Start working** to open the labeling task. ### Case 1: Labeling in an external platform 1. When you click **Start working**, you may be directed to another platform to complete your labeling task. 2. Labelbox will automatically detect that you have opened the labeling task and will start the Hubstaff timer. 3. Complete the labeling tasks. 4. When you are done labeling, return to the Labelbox app and click **Stop timer**. ### Case 2: Labeling in the Labelbox platform 1. When you click **Start working**, you may be directed to the Labelbox editor to complete the Labeling task. 2. Before you can begin labeling, you’ll be prompted to acknowledge that two timers are running (Labelbox & Hubstaff) and that all work will be paid according to the Labelbox timer. 3. If the Hubstaff timer is not running, the submit button in the Labelbox app will be disabled. You will see a prompt asking you to confirm when the Hubstaff timer is enabled. 4. Complete the labeling tasks. 5. When you are done labeling, you do not need to explicitly start or stop any timer if you are labeling in the Labelbox editor. The app will start and stop your timer automatically. 6. When you have completed the task, submit your work in the Labelbox editor. # Images Source: https://docs.labelbox.com/horizon/guides/image-editor Guide for labeling image data. The image editor lets you label images with various supported annotation types, attachments, and editor settings. ## Set up image annotation projects To set up an image annotation project: 1. [Create an image dataset](/horizon/guides/datasets-datarows) and load the images you want to label. Alternatively, use the SDK to [import images](/horizon/developer-guides/image). 2. On the [Annotate projects page](https://app.labelbox.com/projects), click the **+ New project** button. 3. Select **Image**. Add a **Name** and an optional **Description** for your project. 4. Click **Save**. The system then creates the project and redirects you to the project overview page. 5. Click **Add data**. Then select your image dataset. Click **Sample** to sample your dataset, or you can manually select data rows and click **Queue batch**. ### Data row size limit To view the maximum size allowed for a data row, see [limits](/horizon/guides/limits). ## Set up an ontology After setting up an audio annotation project, you can [add an ontology](/horizon/guides/labelbox-ontology#create-a-new-ontology) based on how you want to label the data. The image editor supports the following annotation types that you can include in your ontology: | Feature | Import annotation | Export annotation | | - | - | - | | **Bounding box** | [See payload](/horizon/developer-guides/import-image-annotations#bounding-box) | [See payload](/horizon/developer-guides/export-image-annotations#bounding-box) | | **Segmentation mask** | [See payload](/horizon/developer-guides/import-image-annotations#segmentation-mask) | [See payload](/horizon/developer-guides/export-image-annotations#mask) | | **Polygon** | [See payload](/horizon/developer-guides/import-image-annotations#polygon) | [See payload](/horizon/developer-guides/export-image-annotations#polygon) | | **Polyline** | [See payload](/horizon/developer-guides/import-image-annotations#polyline) | [See payload](/horizon/developer-guides/export-image-annotations#polyline) | | **Point** | [See payload](/horizon/developer-guides/import-image-annotations#point) | [See payload](/horizon/developer-guides/export-image-annotations#point) | | **Relationship** | [See payload](/horizon/developer-guides/import-image-annotations#relationship-with-bounding-box) | [See payload](/horizon/developer-guides/export-image-annotations#relationship) | | **Radio classification** | [See payload](/horizon/developer-guides/import-image-annotations#classification-radio-single-choice) | [See payload](/horizon/developer-guides/export-image-annotations#classification---radio) | | **Checklist classification** | [See payload](/horizon/developer-guides/import-image-annotations#classification-checklist-multi-choice) | [See payload](/horizon/developer-guides/export-image-annotations#classification---checklist) | | **Free-form text classification** | [See payload](/horizon/developer-guides/import-image-annotations#classification-free-form-text) | [See payload](/horizon/developer-guides/export-image-annotations#classification---free-form-text) | ### Classification scopes Classification annotations can be applied globally or nested within an object-type annotation. ### Audio-to-text for text classification You can enable free-text classification to allow labelers to record audio instead of typing text manually. The system transcribes the recorded audio into text using the integrated OpenAI Whisper model. To set up and use audio-to-text for text classification: 1. When [creating the ontology](/horizon/guides/labelbox-ontology#step-1-create), add a **Text** classification feature and enable **Record audio for transcription**. 2. In the editor, select the text classification annotation and click **Record audio**. Each recording can last up to 30 minutes and automatically stops when it reaches the limit. 3. Click **Stop recording** when you finish speaking. The system transcribes the audio into text and adds the recording as an attachment to the annotation. 4. Edit the transcribed text, remove the attached audio file, or click **Record audio** to re-record if needed. 5. Click **Submit** to save the annotation. ### Bounding box Create a bounding box by starting at one corner and dragging your cursor to create the shape around an object in the image. You can also click and drag to reposition the bounding box on the image. ### Cuboid Create a cuboid by starting at one corner and dragging your cursor to create a box shape around an object in the image. Once you release it, it will automatically become a cuboid. You can use the various levers on the tool to adjust its rotation along the x, y, and z axes. Along the center of the top bar, you will find buttons to switch to **Rotate** mode,**Move** mode, or **Scale** mode. You can also directly input the rotation into the modal that appears in the ontology pane once the cuboid is initially drawn or selected for editing. The fields available to edit are: * **Scale:** This controls the cuboid size in each dimension. The unit is in pixels. * **Rotate:** This controls the rotation of the cuboid along the three axes. ### Segmentation mask The segmentation mask annotation supports different tools based on the ontology creation time. ### Empty mask error If you receive an error message that reads, “The object is missing selected pixels. You should relabel or delete this object”, it means that the mask is considered empty. By definition, a segmentation mask cannot have overlapping pixels. Thus, drawing a second mask fully within the first creates an empty one, resulting in this warning message. #### AutoSegment 2.0 Ontologies created after June 13, 2023, by default, use a new *AutoSegment 2.0* tool powered by Meta's Segment Anything model. You can use the AutoSegment 2.0 tool to generate mask predictions for individual objects in your image. The Auto Segment 2.0 tool is designed for instance segmentation, meaning you can quickly label individual objects as it predicts one class at a time. Adjusting the contrast and brightness of the image **does not** affect the accuracy of the SAM model. When selecting a segmentation tool, choose the AutoSegment option from the top navigation bar. There are two modes available: * **Box mode**: allows you to draw a box around an object to generate a mask on it. * **Hover and Click mode**: allows you to visualize masks as you move the cursor around, click to generate, and further modify by adding positive and negative points. #### AutoSegment (Legacy) Only ontologies created before June 13, 2023 use this is the older version of AutoSegment. When a segmentation tool is selected, choose the AutoSegment option from the top navigation bar, and draw a box around the object you want to label. For best results, draw the boundary of the box close to the object edges. You can also adjust the brightness and contrast settings in the **Adjustments** menu in the top navigation bar to further improve results. Creating more contrast between the object you are labeling and its background will help the model detect the object more accurately. #### Brush tool Ontologies created after June 13, 2023 can access the brush tool. Use the brush tool to draw a freehand mask as though you are painting the canvas. You can choose from a circle or square-shaped brush and adjust the size in pixels. Click the brush icon with the minus sign to use the eraser tool. It behaves similarly but removes mask pixels as you move the mouse. #### Pen tool Use the pen tool to outline the item in the image. Hold the cursor down to draw freehand or let the cursor go to draw straight lines between points. The pen tool is only available when creating segmentation annotations. Click the pen icon with the plus sign to use the pen tool. #### Erase tool You can use the erase tool to clean up the edges of a segmentation mask. Click the pen icon with the minus sign to use the erase tool. #### Fill tool You can use the fill tool to easily label backgrounds or, otherwise, assign a segmentation annotation to all other pixels in the image that have not already been labeled. The fill tool is only enabled for segmentation annotations. Click the droplet icon to use the fill tool. ### Polygon Create a polygon annotation by clicking to create each point in the shape. Click the first point to close the polygon. ### Polyline Use the polyline tool to label lines in an image. Click on the last point to complete the shape. ### Point Use the point tool to label precise locations on the image. ### Relationship To create a relationship between annotations, select a relationship tool and hover over the annotation where you want the relationship to start to reveal the annotation's anchor points. Click an anchor point to create the starting point of the relationship, then bring your mouse over to the annotation you want to relate it to, hovering over it to reveal its anchor points. Finally, click one of the anchor points to complete the relationship. Right-click a relationship to change its direction, make it bi-directional, or delete it from the asset. ## Editor settings The image editor has the following settings for object-type annotations: * **Overlay object titles**: Displays the names of annotation objects on the image. * **Show annotations**: Displays created annotation objects on the image. * **Enable annotations occlusion**: Allows annotation objects to overlap based on z-order (front objects occlude those behind them). * **Polygon snapping**: Allows points on lines, polygons, and bounding boxes to align or attach to polygon edges. ### Setting object positions When the **Enable annotations occlusion** setting is active, you can adjust the z-order of annotation objects to control how they stack visually. The **OBJECTS** panel provides two modes for managing object positions: * **Universal arrangement**: Adjust the stacking order incrementally by selecting and repositioning individual objects. * **List-based arrangement**: Use a drag-and-drop interface to reorder objects directly in a list. ### Z-order settings limit All object-type annotations have a z-order value that you can [export](/horizon/developer-guides/export-image-annotations#sample-project-export), but you can't change the z-order value for cuboids and segmentation masks because they can't occlude other objects. # Connect AWS S3 to Labelbox via IAM Delegated Access Source: https://docs.labelbox.com/horizon/guides/import-aws-s3-data Step-by-step guide for importing your S3 bucket data to Labelbox via IAM delegated access. This guide provides a complete set of step-by-step instructions for securely connecting your Amazon S3 bucket data to Labelbox using IAM delegated access. ## Prerequisites Before you begin, ensure you have the following: * You have permissions to create IAM roles and policies in your AWS account. * You know the name of the S3 bucket you want to connect. * You have configured [Cross-Origin Resource Sharing (CORS)](/horizon/guides/create-cors-headers) on your S3 bucket to allow Labelbox to request resources from your cloud storage. ## Step 1: Begin integration in Labelbox First, you'll start the integration process in the Labelbox UI to obtain the necessary credentials. 1. In Labelbox, navigate to **Settings** > **Integrations**. 2. Under **Add integrations**, select **Cloud provider**. 3. Select **AWS** as your source. 4. From the **Create AWS integration page**, copy the Labelbox AWS account ID and the External ID. 5. Leave this page open in your browser: you will return to it in a later step. ## Step 2: Create a role for Labelbox in AWS Next, you will create a role in your AWS account that Labelbox can assume to access your S3 bucket. ### Part A: Create a permission policy 1. In your AWS account, navigate to the **IAM Management Console > Policies** page. 2. Click **Create policy** and select the **JSON** policy editor. 3. Paste the following JSON policy, which grants read-only access to a specific S3 bucket. ```json theme={"theme":"css-variables"} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation" ], "Resource": [ "arn:aws:s3:::CustomerBucketARN/*", "arn:aws:s3:::CustomerBucketARN" ] } ] } ``` Remember to replace `CustomerBucketARN`with the actual ARN of your S3 bucket. 4. Add Add a name for the policy (for example, `LabelboxReadAccess`) and click **Create policy**. ### Part B: Create a role 1. From the **Roles** page in the IAM Management Console, click **Create role**. 2. Select **Custom trust policy** and paste the following policy. Be sure to enter the **External ID** you obtained from Labelbox in Step 1. ```json theme={"theme":"css-variables"} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::340636424752:role/lb-aws-delegated-access-role" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "" } } } ] } ``` 3. In the **Add Permissions** step, attach the permission policy you created in Part A (e.g., `LabelboxReadAccess`). 4. Add a name for the role (e.g., `LabelboxS3Access`) and click **Create role**. 5. Click on the role you just created and copy the **Role ARN** from the **Summary** tab. ## Step 3: Complete the integration setup in Labelbox Now, you will add the Role ARN to the new integration you added in Labelbox in Step 1. 1. Go back to the **Create AWS integration** page in Labelbox. 2. In the **Provider ARN and name** section: * Set the **integration name**. * Enter the **AWS bucket name**. * Paste the **AWS Role ARN**. 3. Click **Save integration**. ## Step 4: Validate the integration After you complete the setup in Labelbox, the system will automatically run a validation check on the integration. You can check the status on the **Integrations > Manage integrations** page. If the integration fails, you can click the refresh icon to view error messages and troubleshoot your setup. Here are possible error messages and our suggestions for troubleshooting your integration setup. | Error | Troubleshooting | | :- | :- | | *Role cannot be assumed* | Ensure that the integration’s role ARN is correct and that the Labelbox External ID is properly configured in your AWS account. Additionally, your AWS account admin must [activate STS](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html) in the `us-east-2` region using the IAM console | | *External ID configured insecurely* | Ensure that the Labelbox External ID is properly configured in your AWS account. | ## Step 5: Create, upload, and validate the dataset Finally, you need to create and validate your dataset. 1. When creating your import file, use virtual-hosted-style URLs that follow this format: `https://.s3..amazonaws.com/`. To learn how to format your import file, visit these guides: 2. If you created your integration and imported your dataset using the Labelbox UI, Labelbox automatically runs validation checks to determine whether the CORS setup was configured properly. It also checks if Labelbox can successfully fetch data from your S3 bucket and properly sign the URLs. Your dataset should now be set up with IAM delegated access. Labelbox will use the AWS role you created to generate temporary signed URLs every time it accesses data in your S3 bucket. # Import errors & warnings Source: https://docs.labelbox.com/horizon/guides/import-errors-and-warnings Learn about the warnings and errors you may encounter when importing a dataset to Labelbox. This guide explains the processing issues you may encounter when importing data and how to resolve them. When data is ingested, Labelbox processes it to generate embeddings, extract media attributes, and standardize formats. If an issue occurs, it will be flagged as an error or a warning. ## Processing states * **Processing**: The initial state when a data row is ingested. * **Success**: The data row was processed successfully. * **Failure**: The data row could not be processed due to an error or warning. It will not appear in the Catalog until the issue is fixed. ## View and fix failed data rows 1. **Find failures**: In Catalog, an issue icon will appear next to datasets with processing issues. Click this icon to go to the "Processing Issues" view. 2. **Filter issues**: You can filter the view to see data rows with specific errors or warnings. 3. **Fix the source issue**: Use the error descriptions below to diagnose the problem (e.g., fix a broken URL, correct cloud storage permissions). 4. **Re-process**: Once you've fixed the underlying issue, select the failed data rows in the "Processing Issues" view and click **Re-process**. ### Large files Large files such as TIF, Video, and Audio can take a few seconds to process. ## Common errors (failures) An error is a serious issue that prevents a data row from being used. | Error | Description & action | | - | - | | ConversionFailed | The file could not be converted to a format required for labeling.
**Action:** Ensure the file is not corrupt and meets format requirements. | | FetchFailed | The data row URL could not be reached.
**Action:** Check that the URL is correct and that the Labelbox backend has access. | | FetchTimeout | The data row could not be fetched due to a timeout error.
**Action**: Re-process the data rows. | | Forbidden | Access to the URL was denied.
**Action:** Check your cloud storage permissions or pre-signed URL validity. | | InternalError | An internal error prevented the data row from being processed.
**Action**: Re-process the data rows. | | InprocessableContent | The data row could not be processed due to malformed or invalid content.
**Action**: Review the structure of your import file. | | InvalidEspg | The data row has invalid ESPG metadata. | | NotFound | The file does not exist at the specified URL.
**Action:** Verify the URL is correct. | | TooManyBands | The geospatial data row has too many bands. | | TooManyRequests | The data row could not be fetched due to a rate-limiting error. | ## Warnings A warning indicates a non-critical issue. The data row can be used, but your experience may be degraded. | Warning | Description & action | | - | - | | InvalidCors | The file is served with invalid CORS headers, which may impact the labeling experience.
**Action:** Check that the URL is being served with the correct CORS headers. | **Processing issues and the Python SDK** * If you export data rows from Catalog using the SDK, all data rows are exported, including those with processing issues. * If you send data rows to a labeling project using the SDK, all data rows are sent to the labeling project, including those with processing issues. # Import ground truth annotations into a project Source: https://docs.labelbox.com/horizon/guides/import-ground-truth Learn how to import your ground truth data from internal or third-party tools into Labelbox. This guide will walk you through the process of importing your existing ground truth annotations from other internal or third-party tools into Labelbox. ## What is ground truth? In machine learning, "ground truth" refers to the data that we consider to be the "true" or accurate labels for a given dataset. This data is often created by human annotators and is used to train and evaluate machine learning models. By importing your ground truth annotations into Labelbox, you can consolidate all of your labeled data in one place, creating a single source of truth for your machine learning projects. This is particularly useful when you are migrating from another labeling platform to Labelbox. ## How to import your ground truth annotations? You can generate Python SDK code snippets directly from your project's "Import labels" tab to help you with the import process. These snippets will serve as a great starting point for the steps below. 1. **Import your data rows**: Before you can import your annotations, you need to have the corresponding data rows (the raw data you want to label, such as images, videos, or text) in Labelbox. If you haven't already, you'll need to [create a dataset](/horizon/guides/datasets-datarows) and import your data rows into the Catalog. 2. **Create or select an ontology**: An ontology (also known as a taxonomy) defines the set of classes and attributes that can be used to label your data. For example, an ontology for an image classification project might include classes like "cat," "dog," and "car." You'll need to either [create a new ontology](/horizon/guides/labelbox-ontology) or select an existing one that matches the annotations you want to import. 3. **Create a labeing project**: Your annotations need to be associated with a labeling project in Labelbox. If you don't already have a project set up with the correct ontology, you will need to [create a project](/horizon/guides/create-a-project). 4. **Send a batch of data rows to your project**: Now that your project and ontology are configured, you need to [send a batch of data rows](/horizon/guides/batches) to the project's labeling queue. This will be the specific set of data rows that you will be attaching your imported annotations to. 5. **Create the annotations payload**: This is where you'll prepare the annotations themselves for import. Each annotation in your payload needs to reference a specific annotation class from your ontology and a specific data row ID. Labelbox supports two formats for the annotation payload: **Python Annotation Types** and **NDJSON**. Go to the **Import labels** tab and select **Import ground truth**. There you will find a sample code snippet you can use to import your ground truth annotations. 6. **Import your annotation payload**: Once you have created your annotation payload, the final step is to submit the import job. After the job is complete, your ground truth annotations will appear in your project, and the corresponding data rows will be marked as "Done." Under **Import labels** → **Import ground truth**, copy the code snippet for **Import annotations payload**. Check out these end-to-end developer guides to learn how to import ground truth annotations based on your data type. ## Best practices * Before starting a new import job, ensure that there are no existing model-assisted labeling (MAL) annotations on the data rows. Duplicate import jobs can overwrite existing labels or cause unexpected behavior. * The activity page in Labelbox will not show any changes until the entire import job is complete. # Import annotations as pre-labels into a project Source: https://docs.labelbox.com/horizon/guides/import-prelabels Learn how to import your annotations as pre-labels into Labelbox. This guide will walk you through the process of importing your annotations from other internal or third-party tools as pre-labels into Labelbox. ## What are pre-labels? Pre-labels are suggested annotations that are programmatically imported into a Labelbox project. Often generated by a machine learning model, these annotations serve as a starting point to accelerate the labeling process. Instead of creating every annotation from scratch, human labelers are presented with these pre-populated suggestions in the labeling editor. The primary goal of using pre-labels is to boost labeling efficiency. A labeler's task shifts from manual creation to a faster workflow of reviewing, correcting, and confirming the suggested annotations. A pre-label is only converted into a finalized, ground truth annotation after a human labeler has reviewed and submitted the asset, ensuring that the final data maintains a high standard of quality. ## How to import annotations as pre-labels You can reference Python SDK code snippets directly from the **Import labels** tab in your project dashboard. These snippets will serve as a great starting point for the steps below. 1. **Import your data rows**: To import annotations as pre-labels, you'll need to have a set of data rows to attach the annotations to. If you do not already have a set of data rows, you'll need to [create a dataset](/horizon/guides/datasets-datarows) by importing data rows into Catalog. 2. **Create/select an ontology**: When you import a set of annotations, you'll need to specify the ontology (also called taxonomy) that corresponds to the set of annotations. If the project ontology already exists in Labelbox, you may select the ontology that fits your annotations. If the ontology does not exist in Labelbox yet, you'll need to [create an ontology](/horizon/guides/labelbox-ontology). 3. **Create a labeling project**: Before you can import your annotations, you'll need to make sure you have a project to connect these annotations to. You cannot simply import annotations without specifying which project they'll be associated with. Oftentimes, you will already have a project set up with the correct ontology for your set of annotations. However, If you do not already have a project, you will need to [create a project](/horizon/guides/create-a-project) and attach the ontology that fits your annotations. 4. **Send a batch of data rows to the project**: Now that you have your project and ontology configured, you'll need to send a subset of data Rows (i.e., a [batch](/horizon/guides/batches)) to the project's labeling queue. You will be attaching your annotations to this batch of data rows. 5. **Create the annotations payload**: This is where you’ll prepare the annotations themselves for import. Each annotation in your payload needs to reference a specific annotation class from your ontology and a specific data row ID. Labelbox supports two formats for the annotation payload: **Python Annotation Types** and **NDJSON**. Go to the **Import labels** tab and select **Import pre-labels**. There you will find a sample code snippet you can use to import your ground truth annotations. 6. **Import your annotation payload**: Once you have created your annotation payload, the final step is to submit the import job. After the job is complete, your ground truth annotations will appear in your project, and the corresponding data rows will be marked as “Done.” Under **Import labels** → **Import pre-labels**, copy the code snippet for **Import annotations payload**. Check out these end-to-end developer guides to learn how to import ground truth annotations based on your data type. ## Best practices * Make sure the annotations are in the proper format. Use the charts above to determine whether an annotation type is supported for the data type you are labeling. * Before you begin a new import job, make sure there are no existing MAL annotations on the data row. * To override an imported annotation on a data row, import the annotation again using the same `uuid`. # Labeling instructions Source: https://docs.labelbox.com/horizon/guides/instructions-and-quizzes Your instructions are the single most important resource for your labeling team. This is where you move beyond simple definitions and provide the context, examples, and rules needed to handle real-world complexity. Well-crafted instructions are the first line of defense against inconsistent or inaccurate annotations. ## Create labeling instructions To add labeling instructions: 1. Go to your project overview. 2. Click on **Labeling instructions**. 3. In the **Instructions** section, you may write the instructions directly in the text box, upload a PDF or HTML file, or add a video link. What to include in your instructions: * **Detailed Definitions:** Go beyond the feature name. What exactly constitutes a "partially occluded vehicle" versus a "fully visible vehicle"? * **Visual Examples (The Good and The Bad):** Show clear examples of correctly labeled data. Just as importantly, show examples of common mistakes or edge cases that should be labeled differently or ignored. * **Edge Case Guidance:** Your data will never be perfect. Provide rules for how to handle blurry images, rare objects, or situations where multiple interpretations are possible. * **"When in Doubt" Rules:** Give your team a clear default action to take when they are unsure, such as flagging the asset for review. Use the following template as a reference (or download this [PDF file](https://storage.googleapis.com/labelbox-sample-datasets/Docs/Labelbox_labeling_instructions_template_v4.pdf)).