{"id":31799682,"url":"https://github.com/chaffybird56/mappinglocalization-av","last_synced_at":"2026-05-17T15:35:57.564Z","repository":{"id":315321735,"uuid":"1059022245","full_name":"chaffybird56/MappingLocalization-AV","owner":"chaffybird56","description":"implements a 2-D occupancy grid mapper for an autonomous vehicle using LiDAR scans and wheel/IMU odometry.","archived":false,"fork":false,"pushed_at":"2025-09-30T19:51:26.000Z","size":185,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-10T22:25:55.713Z","etag":null,"topics":["autonomous-vehicles","jetson-nano","lidar","nvidia","ros","slam","slam-algorithms"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chaffybird56.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-17T22:07:47.000Z","updated_at":"2025-09-30T19:51:29.000Z","dependencies_parsed_at":"2025-09-18T00:26:30.396Z","dependency_job_id":"9027a670-216a-44cc-abe7-361269f917d6","html_url":"https://github.com/chaffybird56/MappingLocalization-AV","commit_stats":null,"previous_names":["chaffybird56/autonomous-vehicles-occupancy-grid-mapper","chaffybird56/mappinglocalization-av"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chaffybird56/MappingLocalization-AV","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaffybird56%2FMappingLocalization-AV","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaffybird56%2FMappingLocalization-AV/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaffybird56%2FMappingLocalization-AV/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaffybird56%2FMappingLocalization-AV/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chaffybird56","download_url":"https://codeload.github.com/chaffybird56/MappingLocalization-AV/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaffybird56%2FMappingLocalization-AV/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33143591,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T09:28:26.183Z","status":"ssl_error","status_checked_at":"2026-05-17T09:27:52.702Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autonomous-vehicles","jetson-nano","lidar","nvidia","ros","slam","slam-algorithms"],"created_at":"2025-10-10T22:24:36.076Z","updated_at":"2026-05-17T15:35:57.507Z","avatar_url":"https://github.com/chaffybird56.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Autonomous Vehicles — Occupancy Grid Mapper\n\n\u003e A compact 2‑D occupancy‑grid mapper for a teaching autonomous vehicle (MacAEV). LiDAR scans are fused with wheel/IMU odometry to estimate obstacle occupancy in real time and publish a `nav_msgs/OccupancyGrid` map.\n\n---\n\n## 🚗 Overview\n\u003cp align=\"center\"\u003e\u003cimg width=\"400\" height=\"465\" alt=\"SCR-20250930-nzgt\" src=\"https://github.com/user-attachments/assets/f15f8c3e-723a-4d45-92e4-70df3422b88f\" /\u003e\u003c/p\u003e\n\nThis project implements the classical **occupancy‑grid mapping** pipeline on a small autonomous platform. The mapper consumes:\n\n* **Localization:** wheel odometry for $x,y$ translation and **IMU yaw** for heading ($\\theta$)\n* **Perception:** 2‑D **LiDAR** scan returns (ranges and angles)\n\nand produces a planar grid in the fixed **odometry** frame (`odom`) whose cells encode the probability of being **occupied**, **free**, or **unknown**.\n\n**Big picture.** The vehicle drives, the LiDAR sends out many straight‑line beams each cycle, and the mapper converts what those beams “saw” into colored squares on a grid (free along the beam, occupied where it hits). Accumulating evidence over time sharpens the map.\n\nRepository layout:\n\n* `OccupancyGridMapping_.py` — ROS node (Python) that subscribes to Odometry and LaserScan, updates log‑odds per cell, and publishes a `nav_msgs/OccupancyGrid`.\n* `expermient_cpp_.txt` — wheel‑odometry (C++) logic used in a prior lab; included for context.\n* `Activity4_.bag` — example rosbag recorded during mapping (for playback/inspection).\n\n---\n\n## 🔧 Platform \u0026 Frames (ROS tf)\n\n**Quick ROS primer (context).**\n\n* **ROS** provides a pub/sub system for robot data. Programs are **nodes**; they exchange typed **messages** on named **topics** (e.g., `/scan` for LiDAR, `/odom` for wheel+IMU pose).\n* A **TF tree** tracks coordinate frames (e.g., `odom → base_link → laser`). TF lets any node transform points between frames with correct timing.\n* A **map** in this project is a `nav_msgs/OccupancyGrid` (a 2‑D array with metadata).\n\n**Frames used here.**\n\n* **`odom`** — fixed world frame for the session (the grid is published here).\n* **`base_link`** — body frame attached to the vehicle’s chassis.\n* **`laser`** — LiDAR frame (static transform from `base_link`).\n\n**Pose state.** The vehicle pose in `odom` is $X=[x\\ y\\ \\theta]^T$. Wheel odometry supplies $x,y$; IMU provides $\\theta$.\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/user-attachments/assets/557e761b-5474-4ca3-ae2d-1319fa63c426\" width=\"600\" alt=\"Fig 1 — Vehicle pose \u0026 frames (odom/base_link/laser)\" /\u003e\u003c/p\u003e\n\u003csub\u003e\u003cb\u003eFig 1.\u003c/b\u003e Coordinate frames and pose definition. The map is attached to \u003ccode\u003eodom\u003c/code\u003e; LiDAR originates in \u003ccode\u003elaser\u003c/code\u003e.\u003c/sub\u003e\n\n---\n\n## 🧭 Localization (Wheel + IMU yaw)\n\n**What “odometry” means here.** Odometry estimates pose by integrating measured motion: wheel travel gives a forward speed; steering sets the turn rate; IMU yaw provides absolute heading. Heading from the IMU avoids accumulating gyro drift and stabilizes geometry for mapping.\n\n**Kinematic bicycle model (continuous time):**\n\n$$\n\\dot x = v_s\\cos\\theta,\\qquad\n\\dot y = v_s\\sin\\theta,\\qquad\n\\dot\\theta = \\frac{v_s}{\\ell}\\tan\\delta,\n$$\n\nwith speed $v_s$, steering angle $\\delta$, and wheelbase $\\ell$.\n\n**Discrete updates (Euler) with IMU yaw for heading:**\n\n$$\n\\begin{aligned}\n x_{k+1}\u0026=x_k + \\Delta t_k\\,v_{s,k}\\cos\\theta_k,\\\\\n y_{k+1}\u0026=y_k + \\Delta t_k\\,v_{s,k}\\sin\\theta_k,\\\\\n \\theta_k\u0026=\\text{yaw}^{\\mathrm{IMU}}_{k}-\\theta_0\\, .\n\\end{aligned}\n$$\n\nThe C++ excerpt in `expermient_cpp_.txt` publishes both a TF (odom to base_link) and `nav_msgs/Odometry` with this fusion.\n\n---\n\n## 🗺️ Occupancy Grid Mapping — Intuition first\n\nAn **occupancy grid** divides the plane into small cells (meters per cell). Each cell stores a belief $p(m_{ij})$ that something solid occupies that square.\n\n**How LiDAR evidence maps to cells.** For any cell center, the algorithm picks the **closest LiDAR beam** to that direction and reasons:\n\n* along the beam **before** the hit → **free**;\n* the **cell containing the hit** → **occupied**;\n* just beyond the hit → **unknown**.\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/user-attachments/assets/fb3e2a15-66c8-4108-8ccc-a82362c7f249\" width=\"600\" alt=\"Fig 2 — Grid in odom, LiDAR rays in laser, and projection to cells\" /\u003e\u003c/p\u003e\n\u003csub\u003e\u003cb\u003eFig 2.\u003c/b\u003e Cells are updated by projecting LiDAR rays from \u003ccode\u003elaser\u003c/code\u003e into the grid in \u003ccode\u003eodom\u003c/code\u003e.\u003c/sub\u003e\n\n---\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"https://github.com/user-attachments/assets/616cf8ff-7911-4044-98bc-3daf7c56bf57\" width=\"600\" alt=\"Fig 3 — Inverse sensor model: along-ray cells free, hit cell occupied\" /\u003e\u003c/p\u003e\n\u003csub\u003e\u003cb\u003eFig 3.\u003c/b\u003e Inverse sensor model: free along the ray (before range), occupied at the terminal cell, unknown past the hit.\u003c/sub\u003e\n\n---\n\n## 🧮 From probabilities to log‑odds (and back)\n\nDirectly adding probabilities is awkward. Each cell instead holds **log‑odds**\n\n$$\n\\ell=\\log\\frac{p}{1-p}\\, .\n$$\n\nThe recursive update per cell is\n\n$$\n\\ell_t\\big(m_{ij}\\big) = \\ell_{t-1}\\big(m_{ij}\\big) + \\underbrace{\\ell\\big(m_{ij}\\mid z_t,x_t\\big)}_{\\text{inverse sensor contribution}} - \\ell_0\\big(m_{ij}\\big)\\, .\n$$\n\nWith an uninformative prior ($p_0 = 0.5 \\Rightarrow \\ell_0 = 0$), this becomes adding a negative constant for **free** cells and a positive constant for **occupied** cells, with clamping to $[\\ell_{\\min},\\ell_{\\max}]$. Probability recovery uses\n\n$$\n p = \\frac{1}{1+e^{-\\ell}}\\, .\n$$\n\n---\n\n## 🧩 Mapper pipeline (what the node actually does)\n\n1. **Subscribe** to `nav_msgs/Odometry` (pose in `odom`) and `sensor_msgs/LaserScan` (ranges $r_n$, angles $\\alpha_n$).\n2. **Cache pose** $x_k$ on odom callback; **process scan** on laser callback.\n3. For each **cell center** $c_{ij}$: transform to the LiDAR frame, select nearest beam, compare distance $d=\\lVert c_{\\text{laser}}\\rVert$ to measured range $r$, and add the appropriate log‑odds increment (free / occupied / unknown). Clamp values.\n4. **Publish** `nav_msgs/OccupancyGrid`: set `info.resolution`, `info.width/height`, `info.origin` (pose of cell (0,0) in `odom`), and row‑major `data` where **100** = occupied, **0** = free, **−1** = unknown.\n\n### Minimal pseudocode (annotated)\n\n```python\n# OccupancyGridMapping_.py (sketch)\ndef laser_cb(scan):\n    pose = latest_odom_pose  # (x, y, theta) in 'odom'\n    T_odom_laser = tf_odom_to_laser(pose)\n    for (i, j) in all_grid_cells:\n        c_odom  = origin_xy + np.array([i, j]) * res\n        c_laser = inv(T_odom_laser) @ to_homog(c_odom)\n        phi     = atan2(c_laser.y, c_laser.x)\n        n       = nearest_index(phi, scan.angle_min, scan.angle_increment)\n        r       = scan.ranges[n]\n        d       = hypot(c_laser.x, c_laser.y)\n        if hit_inside_cell(r, d, res):\n            L[i, j] = clamp(L[i, j] + logit(p_occ))\n        elif r \u003e d + margin:\n            L[i, j] = clamp(L[i, j] + logit(p_free))\n        # else unknown\n    publish_grid(L)\n```\n\n---\n\n## 🧪 Expected behavior (qualitative)\n\nObserved during lab playback and consistent with the documents:\n\n* **Free‑space fans** accumulate as the platform moves, carving corridors of free cells.\n* **Obstacle rims** appear at beam terminations (walls, carts, cardboard).\n* **Unknown regions** persist where no beam ever passed (behind obstacles, unobserved corners).\n* **Heading stabilization** from IMU yaw reduces rotational drift; small translation drift may misalign loop closures (expected without scan‑matching).\n* **Resolution trade‑off:** $0.05\\!\\text{ m}$–$0.10\\!\\text{ m}$ per cell balances crispness vs. compute.\n\n---\n\n## 🧰 Practical parameters (typical)\n\n* `resolution`: **0.05–0.10 m/cell**\n* `p_occ`: **0.65–0.75**  → $\\ell_{\\text{occ}} = \\log\\!\\frac{p_{\\text{occ}}}{1-p_{\\text{occ}}}$\n* `p_free`: **0.30–0.45** → $\\ell_{\\text{free}} = \\log\\!\\frac{p_{\\text{free}}}{1-p_{\\text{free}}}$ (negative)\n* `l_min`, `l_max`: **\\[−4.0, +4.0]**\n* `margin`: \\~ **0.5 × resolution**\n* `frame_id`: `'odom'` for the published grid; ensure TF tree is consistent\n\n---\n\n## 🧾 Message \u0026 data conventions\n\n* Grid indexing is **row‑major**: `data[row*width + col]`.\n* **Origin** (`info.origin`) stores the pose of cell (0,0) in `odom`. Sliding windows require updating this origin.\n* **Unknown** cells (−1) remain until evidence arrives; no prior is injected.\n\n---\n\n## 🗣️ Glossary (quick context)\n\n* **LiDAR beam** — a ray with known angle and measured range.\n* **Inverse sensor model** — rule mapping a beam’s geometry to cell beliefs.\n* **Log‑odds** — $\\ell=\\log\\tfrac{p}{1-p}$; turns probabilistic fusion into addition.\n* **Bresenham / ray‑tracing** — integer grid traversal visiting all crossed cells.\n* **TF** — ROS transform system between frames (e.g., `odom→base_link→laser`).\n\n---\n\n## 📚 How this ties together\n\n1. **Pose** from wheel+IMU determines the LiDAR origin in `odom`.\n2. **Each scan** is a bundle of rays; intersecting them with the grid yields free and occupied evidence.\n3. **Log‑odds** accumulate evidence over time, resisting noise and enabling correction.\n4. The published **OccupancyGrid** feeds planners, localizers, and RViz visualization.\n\n---\n\n## License\n\nReleased under the **MIT License** — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchaffybird56%2Fmappinglocalization-av","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchaffybird56%2Fmappinglocalization-av","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchaffybird56%2Fmappinglocalization-av/lists"}