Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
56ed891
project_point_to_pixel endpoint
maplesyrup-0606 Feb 28, 2026
296cebc
minor tweak
maplesyrup-0606 Feb 28, 2026
8886d77
Finish task 1 UI
anthonyl8 Feb 28, 2026
b6de82f
Merge branch 'feature/project_point_to_pixel' of https://github.com/u…
Michael-R-Dickinson Mar 7, 2026
535dc71
removeed pixel input
maplesyrup-0606 Mar 7, 2026
06df111
fixed to de-project pixel to point.
maplesyrup-0606 Mar 7, 2026
ef9532f
add depth_data to odlc images type
Michael-R-Dickinson Mar 7, 2026
55429ee
Merge origin/feature/project_point_to_pixel, resolve conflicts
Michael-R-Dickinson Mar 7, 2026
4b9261f
scrap old depth calculation endpoint
Michael-R-Dickinson Mar 8, 2026
5085f6b
foundations for odlc image depth calcuations
Michael-R-Dickinson Mar 8, 2026
bc398ac
fixed to brown conrady, can remove instrincics from input now.
maplesyrup-0606 Mar 8, 2026
076b269
process depth into 3d distance
Michael-R-Dickinson Mar 8, 2026
1b6bdf5
Merge branch 'feature/project_point_to_pixel' of https://github.com/u…
ramjayakumar21 Mar 21, 2026
68b612d
Merge branch 'main' of https://github.com/ubcuas/gcom into task-1-merge
Michael-R-Dickinson Mar 27, 2026
fb2ccf9
Allow float confidence_level in OdlcImageSchema
Michael-R-Dickinson Mar 28, 2026
fc5fa21
Paginate ODLC image sidebar to show 10 images per page
Michael-R-Dickinson Mar 28, 2026
32665c4
smaller page size
Michael-R-Dickinson Mar 28, 2026
9bfe0dc
remove unnecessary log
Michael-R-Dickinson Mar 28, 2026
87f73fa
more test images
athalia123 Mar 28, 2026
b39e136
Merge remote-tracking branch 'origin/feature/project_point_to_pixel' …
Michael-R-Dickinson Mar 28, 2026
9579530
consistently use .venv instead of venv
Michael-R-Dickinson Mar 28, 2026
33e2668
format
Michael-R-Dickinson Mar 28, 2026
a185933
use correct deproject pixel endpoint
Michael-R-Dickinson Mar 28, 2026
5e40baf
March 29th working
ramjayakumar21 Mar 31, 2026
87a43dd
Merge branch 'task-1-merge' into task1-frontend-new
maplesyrup-0606 Apr 4, 2026
8aeedef
merged task-1-merge to task1-frontend-new
maplesyrup-0606 Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
projects/web-backend/src/db.sqlite3-journal
/.idea
*.rdb

*/.venv
36 changes: 18 additions & 18 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
args: ['--maxkb=500']
- id: check-merge-conflict
- id: debug-statements
- id: check-case-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-added-large-files
args: ["--maxkb=500"]
- id: check-merge-conflict
- id: debug-statements
- id: check-case-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: local
- id: ruff
args: [--fix]
- id: ruff-format
- repo: local
hooks:
- id: eslint
- id: eslint
name: eslint
entry: bash -c 'cd projects/web-frontend && npm run lint'
language: system
files: projects/web-frontend/.*\.[jt]sx?$
pass_filenames: false
- id: generate-web-backend-api-spec
- id: generate-web-backend-api-spec
name: Generate Web Backend API Spec
entry: bash -c 'cd projects/web-backend && venv/bin/python src/manage.py spectacular --file api_spec.yaml --validate'
entry: bash -c 'cd projects/web-backend && .venv/bin/python src/manage.py spectacular --file api_spec.yaml --validate'
language: system
files: ^projects/web-backend/
pass_filenames: false
- id: generate-mp-api-spec
- id: generate-mp-api-spec
name: Generate Mission Planner API Spec
entry: bash -c 'cd projects/mission-planner && venv/bin/python generate_spec.py'
entry: bash -c 'cd projects/mission-planner && .venv/bin/python generate_spec.py'
language: system
files: ^projects/mission-planner/
pass_filenames: false
37 changes: 37 additions & 0 deletions guide/2026 Startup Guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Software Preflight Setup

## DRONE

- [ ] Insert LTE Board into miniPCIe module
- [ ] Insert SIM Card into LTE Board
- Hold it upside down to let gravity help you insert
- [ ] Attach antenna to LTE board
- > **TODO:** UPDATE HERE ABOUT CORRECT PORTS
- [ ] Attach Jetson to drone
- [ ] Attach camera to mount
- [ ] Connect power to Jetson
- [ ] Connect camera to Jetson

---

## GCOM / PC

- [ ] Connect radio transmitter to thinkpad computer
- Remember to put arrow to 1 (bottom pin row) on wire
- [ ] Create 2 Terminal tabs for GCOM and Mavproxy
- [ ] Start `mavproxy.py` with port forwarding for Mission Planner and GCOM
- > **TODO:** Add command here
- [ ] Start Mission Planner and connect to mavproxy
- [ ] Start GCOM with startup script for all 3 services
- **CHECK:** Ensure all three tmux windows have no errors
- [ ] Start WebRTC signalling server with script + ngrok
- Potentially update to new IP on server
- [ ] Check GCOM frontend to see if drone made connection

---

## JETSON

- [ ] SSH onto system
- [ ] Run `docker compose up -d` in `hawkeye-os` repo to start ROS container
- **CHECK:** Check logs to see if container is running properly
1 change: 0 additions & 1 deletion projects/mission-planner/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
__pycache__
*.pyc
venv
src.egg-info
src/fence_test.py
poetry.lock
Expand Down
2 changes: 1 addition & 1 deletion projects/web-backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
*.pyc
/.idea
files/
oldc_sessions/
odlc_sessions/

dump.rdb
**/.DS_Store
56 changes: 56 additions & 0 deletions projects/web-backend/src/vision/projection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# This is chosen by me (mercury), might need to change.
FLT_EPSILON = 1e-6

# Fixed camera intrinsics (Brown-Conrady / plumb_bob model)
# Values from: ros2 topic echo /camera/camera/color/camera_info (1280x720)
CAMERA_INTRINSICS = {
'fx': 643.2360229492188,
'fy': 642.1893920898438,
'ppx': 661.8545532226562,
'ppy': 365.9696044921875,
}

# Brown-Conrady distortion coefficients [k1, k2, p1, p2, k3]
# Values from: ros2 topic echo /camera/camera/color/camera_info (1280x720)
DISTORTION_COEFFS = [
-0.05651168152689934,
0.06660270690917969,
-0.00015544862253591418,
0.0008432056056335568,
-0.02149238809943199,
]


def _is_distortion_zero(coeffs: list[float]) -> bool:
return all(abs(c) < FLT_EPSILON for c in coeffs)


def deproject_pixel_to_point(
pixel: list[float], depth: float) -> list[float]:
"""Deproject a 2D pixel coordinate to a 3D point using Brown-Conrady distortion model.

Re-implemented from https://github.com/realsenseai/librealsense/blob/78cb605b11f5ba80176e7b8d70292f76ba625565/src/rs.cpp#L4273
"""
fx = CAMERA_INTRINSICS["fx"]
fy = CAMERA_INTRINSICS["fy"]
ppx = CAMERA_INTRINSICS["ppx"]
ppy = CAMERA_INTRINSICS["ppy"]
coeffs = DISTORTION_COEFFS

Comment on lines +38 to +39
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deproject_pixel_to_point accepts an intrinsics dict, but the distortion coefficients are always taken from the module-level DISTORTION_COEFFS constant (the caller-provided coeffs/model are ignored). This makes the API misleading and will produce incorrect results if intrinsics/distortion differ per device. Either honor caller-provided distortion coefficients (with validation) or remove/rename the parameter to reflect that distortion is fixed server-side.

Suggested change
coeffs = DISTORTION_COEFFS
# Prefer caller-provided distortion coefficients when available, otherwise
# fall back to the module-level defaults.
coeffs = intrinsics.get("coeffs") or intrinsics.get("distortion_coeffs")
if coeffs is None:
coeffs = DISTORTION_COEFFS
else:
if not isinstance(coeffs, (list, tuple)):
raise TypeError("intrinsics['coeffs'] must be a list or tuple of floats")
if len(coeffs) < 5:
raise ValueError(
"intrinsics['coeffs'] must contain at least 5 distortion coefficients"
)

Copilot uses AI. Check for mistakes.
x = (pixel[0] - ppx) / fx
y = (pixel[1] - ppy) / fy

xo = x
yo = y

if not _is_distortion_zero(coeffs):
for _ in range(10):
r2 = x * x + y * y
icdist = 1.0 / (
1 + ((coeffs[4] * r2 + coeffs[1]) * r2 + coeffs[0]) * r2)
delta_x = 2 * coeffs[2] * x * y + coeffs[3] * (r2 + 2 * x * x)
delta_y = 2 * coeffs[3] * x * y + coeffs[2] * (r2 + 2 * y * y)
x = (xo - delta_x) * icdist
y = (yo - delta_y) * icdist

return [depth * x, depth * y, depth]
10 changes: 8 additions & 2 deletions projects/web-backend/src/vision/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter

from .views import GroundObjectViewset, ImageViewset, save_oldc_session
from .views import (
GroundObjectViewset,
ImageViewset,
deproject_pixel,
save_odlc_session,
)

router = DefaultRouter()
router.register(r"image", ImageViewset, basename="image")
router.register(r"groundobject", GroundObjectViewset, basename="groundobject")

urlpatterns = [
path("", include(router.urls)),
path("oldc-session/save/", save_oldc_session, name="save_oldc_session"),
path("odlc-session/save/", save_odlc_session, name="save_odlc_session"),
path("deproject-pixel/", deproject_pixel, name="deproject_pixel"),
]
43 changes: 39 additions & 4 deletions projects/web-backend/src/vision/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,48 @@
from rest_framework import viewsets

from .models import GroundObject, Image
from .projection import deproject_pixel_to_point
from .serializers import GroundObjectSerializer, ImageSerializer

OLDC_SESSIONS_DIR = Path(__file__).resolve().parent.parent.parent / "oldc_sessions"
ODLC_SESSIONS_DIR = Path(__file__).resolve().parent.parent.parent / "odlc_sessions"


@csrf_exempt
@require_http_methods(["POST"])
def save_oldc_session(request):
def deproject_pixel(request):
try:
data = json.loads(request.body)
pixel = data.get("pixel")
depth = data.get("depth")

if not isinstance(pixel, list) or len(pixel) != 2:
return JsonResponse(
{"error": "Invalid input: pixel must be a list of 2 floats"}, status=400
)

if not isinstance(depth, (int, float)):
return JsonResponse(
{"error": "Invalid input: depth must be a float"}, status=400
)

point = deproject_pixel_to_point(
pixel=pixel, depth=float(depth)
)

return JsonResponse({"point": point})
Comment on lines 17 to +39
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New endpoints (deproject-pixel/, odlc-session/save/) were added, but vision/tests.py appears to only cover the Image/GroundObject viewsets. Since this app has an existing test suite, please add API tests for these new endpoints (success + validation error cases) to prevent regressions.

Copilot uses AI. Check for mistakes.
except (ValueError, NotImplementedError) as e:
return JsonResponse({"error": str(e)}, status=400)
except (KeyError, TypeError) as e:
return JsonResponse({"error": "Invalid input", "details": str(e)}, status=400)
except Exception as e:
return JsonResponse(
{"error": "Internal server error", "details": str(e)}, status=500
)


@csrf_exempt
@require_http_methods(["POST"])
def save_odlc_session(request):
try:
data = json.loads(request.body)
session_id = data.get("sessionId")
Expand All @@ -24,14 +58,15 @@ def save_oldc_session(request):
if not session_id or not isinstance(images, list):
return JsonResponse({"error": "Invalid input"}, status=400)

OLDC_SESSIONS_DIR.mkdir(exist_ok=True)
session_file = OLDC_SESSIONS_DIR / f"{session_id}.json"
ODLC_SESSIONS_DIR.mkdir(exist_ok=True)
session_file = ODLC_SESSIONS_DIR / f"{session_id}.json"
session_file.write_text(json.dumps({"sessionId": session_id, "images": images}))

return HttpResponse(status=200)
except (KeyError, ValueError, TypeError) as e:
return JsonResponse({"error": "Invalid input", "details": str(e)}, status=400)
except Exception as e:
print(f"Error saving ODLC session: {e}")
return JsonResponse(
{"error": "Internal server error", "details": str(e)}, status=500
)
Expand Down
Loading