-
Notifications
You must be signed in to change notification settings - Fork 0
Annotation synchronization #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
b-Tomas
wants to merge
20
commits into
main
Choose a base branch
from
b-Tomas/waypoint-sync
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
c3215ee
Initial implementation
b-Tomas b6b28c2
Simplify by removing bidirectional sync and cleanup interfaces to sep…
b-Tomas c46422e
Rename waypoint -> annotation
b-Tomas bd639d6
Remove diabled state
b-Tomas b0a975a
Fix docs and an import. Docs still need review
b-Tomas 7c92a15
Some documentation
b-Tomas 094ecdf
Refactor for config API module reusabitliy, rewrite some docs and ref…
b-Tomas c70e733
Fix some CLRF vs LF line ending issues
b-Tomas b7c39d7
Remove debug prints and fix a typo
b-Tomas cb67bbf
Fix race condition in sync manager initialization
b-Tomas 8830b67
Fix annotation filter in manager sync
b-Tomas 0627bd7
Fix kind checking in config api sync method
b-Tomas a32abdf
Add missing validtor to AnnotationSyncConfig
b-Tomas ba7f088
Remove extra meaningless test
b-Tomas e4172ba
Update pyproject.toml
b-Tomas de4b715
Fix potential race condition starting manages
b-Tomas e699e05
Fix the filter function for annotatoin sync
b-Tomas e0e6de5
Formatting
b-Tomas c43a839
Update docs
b-Tomas 364994a
Git renormalize
b-Tomas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| --- | ||
| title: "Annotation Synchronization" | ||
| description: "API specification for synchronizing annotations between external systems and InOrbit" | ||
| --- | ||
|
|
||
| > **Note:** Annotation synchronization is currently an experimental feature with partial support in the InOrbit platform. | ||
|
|
||
| The annotation synchronization feature enables connectors to synchronize waypoint annotations between external systems and InOrbit's Config API. | ||
|
|
||
| For Config API models and client, see [Config API](config-api). | ||
|
|
||
| ## Terminology | ||
|
|
||
| - **Annotation**: An InOrbit `SpatialAnnotation` object. Currently, only waypoint annotations are supported. | ||
| - **Position**: A waypoint/location in the external system. | ||
| - **External system**: The software the connector interacts with (fleet manager, robot software). | ||
|
|
||
| ## AnnotationSyncConfig | ||
|
|
||
| Configuration for annotation synchronization: | ||
|
|
||
| ```python | ||
| from inorbit_connector.annotation_sync import AnnotationSyncConfig, AnnotationSyncMode | ||
|
|
||
| config = AnnotationSyncConfig( | ||
| enabled=True, | ||
| mode=AnnotationSyncMode.EXTERNAL_TO_INORBIT, | ||
| sync_interval_seconds=300, | ||
| location_id="location-id" | ||
| ) | ||
| ``` | ||
|
|
||
| ## Interfaces | ||
|
|
||
| ### ExternalAnnotationProvider[TExternalPosition] | ||
|
|
||
| Protocol for external position providers: | ||
|
|
||
| ```python | ||
| @runtime_checkable | ||
| class ExternalAnnotationProvider(Protocol[TExternalPosition]): | ||
| async def list_positions(self, frame_id: str) -> list[TExternalPosition]: ... | ||
| async def create_position(self, position: TExternalPosition) -> None: ... | ||
| async def update_position(self, position_id: str, position: TExternalPosition) -> None: ... | ||
| async def delete_position(self, position_id: str) -> None: ... | ||
| ``` | ||
|
|
||
| ### AnnotationConverter[TExternalPosition] | ||
|
|
||
| Protocol for converting between positions and annotations: | ||
|
|
||
| ```python | ||
| from inorbit_connector.inorbit import SpatialAnnotationData | ||
|
|
||
| @runtime_checkable | ||
| class AnnotationConverter(Protocol[TExternalPosition]): | ||
| def position_to_annotation( | ||
| self, position: TExternalPosition, frame_id: str | ||
| ) -> SpatialAnnotationData: ... | ||
| def annotation_to_position( | ||
| self, annotation_data: SpatialAnnotationData | ||
| ) -> TExternalPosition: ... | ||
| def get_position_id(self, position: TExternalPosition) -> str: ... | ||
| ``` | ||
|
|
||
| **Note**: External positions must be Pydantic `BaseModel` subclasses. | ||
|
|
||
| ## AnnotationSyncManager | ||
|
|
||
| Sync manager for synchronizing positions between external systems and InOrbit. | ||
|
|
||
| ### Constructor | ||
|
|
||
| ```python | ||
| from inorbit_connector.annotation_sync import AnnotationSyncManager | ||
| from inorbit_connector.inorbit import InOrbitConfigAPI | ||
|
|
||
| manager = AnnotationSyncManager( | ||
| config=AnnotationSyncConfig(...), | ||
| inorbit_config_client=InOrbitConfigAPI(...), | ||
| position_provider=MyPositionProvider(), | ||
| annotation_converter=MyAnnotationConverter(), | ||
| account_id="account-id", | ||
| frame_id="map", | ||
| signature_value="connector-type" | ||
| ) | ||
| ``` | ||
|
|
||
| The connector creates manager instances automatically when new `frame_id`s are detected. | ||
|
|
||
| ### Methods | ||
|
|
||
| - `start()` / `stop()`: Start/stop periodic synchronization | ||
| - `sync_once()`: Execute single sync based on configured mode | ||
| - `sync_external_to_inorbit()`: Sync from external system to InOrbit | ||
| - `sync_inorbit_to_external()`: Sync from InOrbit to external system | ||
|
|
||
| ## Implementation Example | ||
|
|
||
| ```python | ||
| from pydantic import BaseModel | ||
| from inorbit_connector.annotation_sync import ( | ||
| AnnotationSyncConfig, | ||
| AnnotationSyncManager, | ||
| ExternalAnnotationProvider, | ||
| AnnotationConverter, | ||
| ) | ||
| from inorbit_connector.inorbit import ( | ||
| SpatialAnnotationData, | ||
| WaypointAnnotationSpec, | ||
| WaypointData, | ||
| ) | ||
|
|
||
| class MyPosition(BaseModel): | ||
| id: str | ||
| name: str | ||
| x: float | ||
| y: float | ||
| theta: float | ||
|
|
||
| class MyProvider: | ||
| async def list_positions(self, frame_id: str) -> list[MyPosition]: ... | ||
| # ... other methods | ||
|
|
||
| class MyConverter: | ||
| def position_to_annotation( | ||
| self, position: MyPosition, frame_id: str | ||
| ) -> SpatialAnnotationData: | ||
| return SpatialAnnotationData( | ||
| id=position.id, | ||
| spec=WaypointAnnotationSpec( | ||
| frameId=frame_id, | ||
| label=position.name, | ||
| data=WaypointData(x=position.x, y=position.y, theta=position.theta) | ||
| ) | ||
| ) | ||
| # ... other methods | ||
|
|
||
| class MyConnector(FleetConnector): | ||
| def __init__(self, config): | ||
| super().__init__(config) | ||
| self.register_annotation_sync(MyProvider(), MyConverter()) | ||
| ``` | ||
|
|
||
| See the [Annotation Sync Usage Guide](../usage/annotation-sync) for more details. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| --- | ||
| title: "Config API" | ||
| description: "InOrbit Config API client and models" | ||
| --- | ||
|
|
||
| The Config API provides access to InOrbit's configuration management system for managing configuration objects like spatial annotations. | ||
|
|
||
| For more information about the Config API, see the [InOrbit Developer Documentation](https://developer.inorbit.ai/docs#configuration-management). | ||
|
|
||
| ## Models | ||
|
|
||
| ### ConfigObject | ||
|
|
||
| Base model for all Config API objects: | ||
|
|
||
| ```python | ||
| from inorbit_connector.inorbit import ConfigObject, ConfigObjectMetadata | ||
|
|
||
| class ConfigObject(BaseModel, Generic[TSpec]): | ||
| apiVersion: Literal["v0.1"] = "v0.1" | ||
| kind: str | ||
| metadata: ConfigObjectMetadata | ||
| spec: TSpec | ||
| ``` | ||
|
|
||
| ### SpatialAnnotation | ||
|
|
||
| Model for spatial annotations (waypoints, zones, etc.): | ||
|
|
||
| ```python | ||
| from inorbit_connector.inorbit import SpatialAnnotation, WaypointAnnotationSpec | ||
|
|
||
| annotation = SpatialAnnotation( | ||
| metadata=ConfigObjectMetadata( | ||
| id="waypoint-001", | ||
| scope="tag/company-id/location-id" | ||
| ), | ||
| spec=WaypointAnnotationSpec( | ||
| frameId="map", | ||
| label="Dock Station", | ||
| data=WaypointData(x=1.0, y=2.0, theta=0.0), | ||
| properties={} | ||
| ) | ||
| ) | ||
| ``` | ||
|
|
||
| ## InOrbitConfigAPI | ||
|
|
||
| Client for interacting with the Config API. | ||
|
|
||
| ### Constructor | ||
|
|
||
| ```python | ||
| from inorbit_connector.inorbit import InOrbitConfigAPI | ||
|
|
||
| client = InOrbitConfigAPI( | ||
| base_url="https://api.inorbit.ai", | ||
| api_key="your-api-key", | ||
| timeout=30 | ||
| ) | ||
| ``` | ||
|
|
||
| ### Methods | ||
|
|
||
| #### `list_objects()` | ||
|
|
||
| ```python | ||
| async def list_objects( | ||
| kind: str, | ||
| scope: str, | ||
| format: str = "full" | ||
| ) -> list[ConfigObject] | ||
| ``` | ||
|
|
||
| #### `apply_object()` | ||
|
|
||
| ```python | ||
| async def apply_object(obj: ConfigObject) -> ConfigObject | ||
| ``` | ||
|
|
||
| #### `delete_object()` | ||
|
|
||
| ```python | ||
| async def delete_object(obj: ConfigObject) -> None | ||
| ``` | ||
|
|
||
| #### `synchronize_objects()` | ||
|
|
||
| ```python | ||
| async def synchronize_objects( | ||
| scope: str, | ||
| objects: list[ConfigObject], | ||
| filter_fn: Optional[Callable[[ConfigObject], bool]] = None | ||
| ) -> dict | ||
| ``` | ||
|
|
||
| Synchronizes objects with InOrbit by creating, updating, and deleting as needed. Deletion happens automatically for objects that no longer exist locally (filtered by `filter_fn` if provided). | ||
|
|
||
| Returns sync statistics: `created`, `updated`, `up_to_date`, `deleted`. | ||
|
|
||
| ## Example | ||
|
|
||
| ```python | ||
| from inorbit_connector.inorbit import ( | ||
| InOrbitConfigAPI, | ||
| SpatialAnnotation, | ||
| ConfigObjectMetadata, | ||
| WaypointAnnotationSpec, | ||
| WaypointData, | ||
| ) | ||
|
|
||
| client = InOrbitConfigAPI(base_url=url, api_key=key) | ||
|
|
||
| # List annotations | ||
| annotations = await client.list_objects( | ||
| kind="SpatialAnnotation", | ||
| scope="tag/company/location" | ||
| ) | ||
|
|
||
| # Create annotation | ||
| annotation = SpatialAnnotation( | ||
| metadata=ConfigObjectMetadata(id="wp1", scope="tag/company/location"), | ||
| spec=WaypointAnnotationSpec( | ||
| frameId="map", | ||
| label="Waypoint 1", | ||
| data=WaypointData(x=1.0, y=2.0, theta=0.0) | ||
| ) | ||
| ) | ||
| await client.apply_object(annotation) | ||
|
|
||
| # Delete annotation | ||
| await client.delete_object(annotation) | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After taking a look at the whole implementation I really don't like these names. Both Annotation and Position are really annotations that come from different sources. I also don't like "external", it depends on the user's side or pov.
I suggest using:
Also, let's avoid using "location" since it already has a precise meaning in InOrbit.
I believe these names can bring a lot of clarity.