<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: DEDIPYA SADE</title>
    <description>The latest articles on DEV Community by DEDIPYA SADE (@dedipya_sade_ded044996b1a).</description>
    <link>https://dev.to/dedipya_sade_ded044996b1a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3526604%2Ffd4b4254-c9ae-4d35-aeb7-5f1c9ed6a423.png</url>
      <title>DEV Community: DEDIPYA SADE</title>
      <link>https://dev.to/dedipya_sade_ded044996b1a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dedipya_sade_ded044996b1a"/>
    <language>en</language>
    <item>
      <title>Adaptive Terrain Intelligence and Navigation</title>
      <dc:creator>DEDIPYA SADE</dc:creator>
      <pubDate>Thu, 25 Sep 2025 08:26:34 +0000</pubDate>
      <link>https://dev.to/dedipya_sade_ded044996b1a/adaptive-terrain-intelligence-and-navigation-76j</link>
      <guid>https://dev.to/dedipya_sade_ded044996b1a/adaptive-terrain-intelligence-and-navigation-76j</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flejcs9w4233ghtcp2ad1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flejcs9w4233ghtcp2ad1.jpg" alt=" " width="800" height="1777"&gt;&lt;/a&gt;&lt;br&gt;
python3 -m venv venv&lt;br&gt;
source venv/bin/activate&lt;br&gt;
pip install opencv-python-headless numpy flask matplotlib scipy pillow&lt;/p&gt;

&lt;h1&gt;
  
  
  !/usr/bin/env python3
&lt;/h1&gt;

&lt;p&gt;"""&lt;br&gt;
robot.py&lt;br&gt;
Single-file GPS-denied robot demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKF (wheel odom + IMU, occasional VO updates)&lt;/li&gt;
&lt;li&gt;Visual Odometry (ORB)&lt;/li&gt;
&lt;li&gt;Occupancy Grid mapping (log-odds)&lt;/li&gt;
&lt;li&gt;A* planner on the occupancy grid&lt;/li&gt;
&lt;li&gt;Pure Pursuit controller + safety stop&lt;/li&gt;
&lt;li&gt;Simple Flask dashboard showing occupancy, pose, sensors
Sensor read functions are stubbed/simulated — replace with real drivers on real hardware.
"""&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;import time&lt;br&gt;
import threading&lt;br&gt;
import math&lt;br&gt;
import io&lt;br&gt;
import base64&lt;br&gt;
from typing import List, Tuple&lt;/p&gt;

&lt;p&gt;import numpy as np&lt;br&gt;
import cv2&lt;br&gt;
from flask import Flask, Response, render_template_string, jsonify&lt;br&gt;
import matplotlib&lt;br&gt;
matplotlib.use('Agg')&lt;br&gt;
import matplotlib.pyplot as plt&lt;br&gt;
from PIL import Image&lt;br&gt;
import heapq&lt;/p&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Sensor stubs / wrappers
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class SensorHealth:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self):&lt;br&gt;
        t = time.time()&lt;br&gt;
        self.last_imu = t&lt;br&gt;
        self.last_odom = t&lt;br&gt;
        self.last_cam = t&lt;br&gt;
        self.last_lidar = t&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def update(self, imu=False, odom=False, cam=False, lidar=False):&lt;br&gt;
    now = time.time()&lt;br&gt;
    if imu: self.last_imu = now&lt;br&gt;
    if odom: self.last_odom = now&lt;br&gt;
    if cam: self.last_cam = now&lt;br&gt;
    if lidar: self.last_lidar = now

&lt;p&gt;def ok(self, timeout=1.0):&lt;br&gt;
    now = time.time()&lt;br&gt;
    return {&lt;br&gt;
        'imu': (now - self.last_imu) &amp;lt; timeout,&lt;br&gt;
        'odom': (now - self.last_odom) &amp;lt; timeout,&lt;br&gt;
        'cam': (now - self.last_cam) &amp;lt; timeout,&lt;br&gt;
        'lidar': (now - self.last_lidar) &amp;lt; timeout&lt;br&gt;
    }&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Replace these with real sensor calls on hardware&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;def read_imu():&lt;br&gt;
    """&lt;br&gt;
    Return (wz, ax) = (angular velocity z in rad/s, linear accel x in m/s^2)&lt;br&gt;
    Replace with real IMU reading (e.g., MPU driver).&lt;br&gt;
    """&lt;br&gt;
    wz = np.random.normal(0.0, 0.02)&lt;br&gt;
    ax = np.random.normal(0.0, 0.1)&lt;br&gt;
    return float(wz), float(ax)&lt;/p&gt;

&lt;p&gt;def read_wheel_odometry():&lt;br&gt;
    """&lt;br&gt;
    Return (delta_distance_m, delta_heading_rad) since last call.&lt;br&gt;
    Replace with encoder integration.&lt;br&gt;
    """&lt;br&gt;
    # Simulated small forward motion per loop (for demo)&lt;br&gt;
    d = 0.02 + np.random.normal(0.0, 0.004)&lt;br&gt;
    dtheta = np.random.normal(0.0, 0.004)&lt;br&gt;
    return float(d), float(dtheta)&lt;/p&gt;

&lt;p&gt;def read_lidar_scan(num_beams=180, max_range=5.0):&lt;br&gt;
    """&lt;br&gt;
    Return (ranges, angles) arrays.&lt;br&gt;
    Replace with real lidar driver output.&lt;br&gt;
    This stub simulates a front obstacle.&lt;br&gt;
    """&lt;br&gt;
    angles = np.linspace(-np.pi/2, np.pi/2, num_beams)&lt;br&gt;
    ranges = max_range * np.ones_like(angles)&lt;br&gt;
    center_idx = num_beams // 2&lt;br&gt;
    ranges[center_idx-3:center_idx+4] = 1.5 + np.random.normal(0.0, 0.03, 7)&lt;br&gt;
    ranges += np.random.normal(0.0, 0.02, size=angles.shape)&lt;br&gt;
    ranges = np.clip(ranges, 0.02, max_range)&lt;br&gt;
    return ranges.astype(float), angles.astype(float)&lt;/p&gt;

&lt;p&gt;class CameraWrapper:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, source=0, width=320, height=240):&lt;br&gt;
        self.cap = cv2.VideoCapture(source)&lt;br&gt;
        # attempt to set size (may fail on some cameras)&lt;br&gt;
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)&lt;br&gt;
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)&lt;br&gt;
        self.last_frame = None&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def read(self):&lt;br&gt;
    ret, frame = self.cap.read()&lt;br&gt;
    if not ret or frame is None:&lt;br&gt;
        # return blank frame to avoid breaking VO&lt;br&gt;
        frame = np.zeros((240,320,3), dtype=np.uint8)&lt;br&gt;
    self.last_frame = frame&lt;br&gt;
    return frame

&lt;p&gt;def release(self):&lt;br&gt;
    self.cap.release()&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  EKF (3-state)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class EKF:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self):&lt;br&gt;
        self.x = np.zeros((3,1))  # x, y, theta&lt;br&gt;
        self.P = np.diag([0.1, 0.1, 0.05])&lt;br&gt;
        # small process noise&lt;br&gt;
        self.Q = np.diag([0.02, 0.02, 0.01])&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def predict(self, delta_dist, delta_theta):&lt;br&gt;
    x, y, th = self.x.flatten()&lt;br&gt;
    th_new = th + delta_theta&lt;br&gt;
    x_new = x + delta_dist * np.cos(th_new)&lt;br&gt;
    y_new = y + delta_dist * np.sin(th_new)&lt;br&gt;
    self.x = np.array([[x_new],[y_new],[th_new]])&lt;br&gt;
    # simple covariance increase&lt;br&gt;
    self.P = self.P + self.Q

&lt;p&gt;def update_pose(self, z, R):&lt;br&gt;
    """&lt;br&gt;
    z: 3x1 measurement&lt;br&gt;
    R: 3x3 measurement covariance&lt;br&gt;
    """&lt;br&gt;
    H = np.eye(3)&lt;br&gt;
    y = z - H.dot(self.x)&lt;br&gt;
    # normalize angle residual&lt;br&gt;
    y[2] = (y[2] + np.pi) % (2*np.pi) - np.pi&lt;br&gt;
    S = H.dot(self.P).dot(H.T) + R&lt;br&gt;
    K = self.P.dot(H.T).dot(np.linalg.inv(S))&lt;br&gt;
    self.x = self.x + K.dot(y)&lt;br&gt;
    self.P = (np.eye(3) - K.dot(H)).dot(self.P)&lt;/p&gt;

&lt;p&gt;def get_state(self):&lt;br&gt;
    return self.x.flatten()&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Visual Odometry (ORB)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class VisualOdometry:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, max_features=1000, focal=300.0):&lt;br&gt;
        self.orb = cv2.ORB_create(max_features)&lt;br&gt;
        self.bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)&lt;br&gt;
        self.prev_kp = None&lt;br&gt;
        self.prev_des = None&lt;br&gt;
        self.prev_frame = None&lt;br&gt;
        self.f = focal&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_frame(self, frame_gray):&lt;br&gt;
    if self.prev_frame is None:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = self.orb.detectAndCompute(frame_gray, None)&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    kp, des = self.orb.detectAndCompute(frame_gray, None)&lt;br&gt;
    if des is None or self.prev_des is None:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = kp, des&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    matches = self.bf.match(self.prev_des, des)&lt;br&gt;
    matches = sorted(matches, key=lambda x: x.distance)[:150]&lt;br&gt;
    if len(matches) &amp;lt; 8:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = kp, des&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    pts_prev = np.float32([ self.prev_kp[m.queryIdx].pt for m in matches ])&lt;br&gt;
    pts_cur  = np.float32([ kp[m.trainIdx].pt for m in matches ])&lt;br&gt;
    E, mask = cv2.findEssentialMat(pts_cur, pts_prev, focal=self.f,&lt;br&gt;
                                   pp=(frame_gray.shape[1]/2, frame_gray.shape[0]/2),&lt;br&gt;
                                   method=cv2.RANSAC, prob=0.999, threshold=1.0)&lt;br&gt;
    if E is None:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = kp, des&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    _, R, t, mask = cv2.recoverPose(E, pts_cur, pts_prev, focal=self.f,&lt;br&gt;
                                    pp=(frame_gray.shape[1]/2, frame_gray.shape[0]/2))&lt;br&gt;
    # approximate 2D motion: forward ~ t_z, yaw ~ small yaw from R&lt;br&gt;
    if t is None:&lt;br&gt;
        dz = 0.0&lt;br&gt;
    else:&lt;br&gt;
        dz = float(t[2,0]) if t.shape==(3,1) else 0.0&lt;br&gt;
    yaw = math.atan2(R[1,0], R[0,0])&lt;br&gt;
    self.prev_frame = frame_gray&lt;br&gt;
    self.prev_kp, self.prev_des = kp, des&lt;br&gt;
    return dz, 0.0, yaw&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Occupancy Grid SLAM (log-odds)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class OccupancyGrid:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, width_m=10.0, height_m=10.0, resolution=0.05):&lt;br&gt;
        self.resolution = resolution&lt;br&gt;
        self.width = int(width_m / resolution)&lt;br&gt;
        self.height = int(height_m / resolution)&lt;br&gt;
        self.log_odds = np.zeros((self.height, self.width), dtype=np.float32)&lt;br&gt;
        self.l_free = -0.4&lt;br&gt;
        self.l_occ = 0.85&lt;br&gt;
        self.origin_x = -width_m / 2.0&lt;br&gt;
        self.origin_y = -height_m / 2.0&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def world_to_grid(self, x, y):&lt;br&gt;
    gx = int((x - self.origin_x) / self.resolution)&lt;br&gt;
    gy = int((y - self.origin_y) / self.resolution)&lt;br&gt;
    return gx, gy

&lt;p&gt;def in_bounds(self, gx, gy):&lt;br&gt;
    return 0 &amp;lt;= gx &amp;lt; self.width and 0 &amp;lt;= gy &amp;lt; self.height&lt;/p&gt;

&lt;p&gt;def bresenham(self, x0, y0, x1, y1):&lt;br&gt;
    points = []&lt;br&gt;
    dx = abs(x1 - x0)&lt;br&gt;
    sx = 1 if x0 &amp;lt; x1 else -1&lt;br&gt;
    dy = -abs(y1 - y0)&lt;br&gt;
    sy = 1 if y0 &amp;lt; y1 else -1&lt;br&gt;
    err = dx + dy&lt;br&gt;
    x, y = x0, y0&lt;br&gt;
    while True:&lt;br&gt;
        points.append((x,y))&lt;br&gt;
        if x == x1 and y == y1:&lt;br&gt;
            break&lt;br&gt;
        e2 = 2*err&lt;br&gt;
        if e2 &amp;gt;= dy:&lt;br&gt;
            err += dy&lt;br&gt;
            x += sx&lt;br&gt;
        if e2 &amp;lt;= dx:&lt;br&gt;
            err += dx&lt;br&gt;
            y += sy&lt;br&gt;
    return points&lt;/p&gt;

&lt;p&gt;def integrate_scan(self, pose: Tuple[float,float,float], ranges: np.ndarray, angles: np.ndarray, max_range=5.0):&lt;br&gt;
    x, y, th = pose&lt;br&gt;
    for r, a in zip(ranges, angles):&lt;br&gt;
        beam_angle = th + a&lt;br&gt;
        ex = x + r * math.cos(beam_angle)&lt;br&gt;
        ey = y + r * math.sin(beam_angle)&lt;br&gt;
        gx0, gy0 = self.world_to_grid(x, y)&lt;br&gt;
        gx1, gy1 = self.world_to_grid(ex, ey)&lt;br&gt;
        points = self.bresenham(gx0, gy0, gx1, gy1)&lt;br&gt;
        # free along beam (except last)&lt;br&gt;
        for (gx, gy) in points[:-1]:&lt;br&gt;
            if self.in_bounds(gx, gy):&lt;br&gt;
                self.log_odds[gy, gx] += self.l_free&lt;br&gt;
        # last cell&lt;br&gt;
        gx, gy = points[-1]&lt;br&gt;
        if self.in_bounds(gx, gy) and r &amp;lt; (max_range - 0.01):&lt;br&gt;
            self.log_odds[gy, gx] += self.l_occ&lt;br&gt;
    self.log_odds = np.clip(self.log_odds, -8.0, 8.0)&lt;/p&gt;

&lt;p&gt;def get_prob_map(self):&lt;br&gt;
    odds = np.exp(self.log_odds)&lt;br&gt;
    prob = odds / (1.0 + odds)&lt;br&gt;
    return prob&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Planner (A*)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;def heuristic(a, b):&lt;br&gt;
    return math.hypot(b[0]-a[0], b[1]-a[1])&lt;/p&gt;

&lt;p&gt;def a_star(grid_prob: np.ndarray, start: Tuple[int,int], goal: Tuple[int,int], threshold=0.6) -&amp;gt; List[Tuple[int,int]]:&lt;br&gt;
    h, w = grid_prob.shape&lt;br&gt;
    def neighbors(node):&lt;br&gt;
        x, y = node&lt;br&gt;
        for dx, dy in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,1),(-1,1),(1,-1)]:&lt;br&gt;
            nx, ny = x+dx, y+dy&lt;br&gt;
            if 0 &amp;lt;= nx &amp;lt; w and 0 &amp;lt;= ny &amp;lt; h and grid_prob[ny, nx] &amp;lt; threshold:&lt;br&gt;
                yield (nx, ny)&lt;br&gt;
    close_set = set()&lt;br&gt;
    came_from = {}&lt;br&gt;
    gscore = {start:0.0}&lt;br&gt;
    fscore = {start:heuristic(start, goal)}&lt;br&gt;
    oheap = []&lt;br&gt;
    heapq.heappush(oheap, (fscore[start], start))&lt;br&gt;
    while oheap:&lt;br&gt;
        _, current = heapq.heappop(oheap)&lt;br&gt;
        if current == goal:&lt;br&gt;
            path = [current]&lt;br&gt;
            while current in came_from:&lt;br&gt;
                current = came_from[current]&lt;br&gt;
                path.append(current)&lt;br&gt;
            path.reverse()&lt;br&gt;
            return path&lt;br&gt;
        close_set.add(current)&lt;br&gt;
        for neighbor in neighbors(current):&lt;br&gt;
            tentative_g = gscore[current] + heuristic(current, neighbor)&lt;br&gt;
            if neighbor in close_set and tentative_g &amp;gt;= gscore.get(neighbor, 1e9):&lt;br&gt;
                continue&lt;br&gt;
            if tentative_g &amp;lt; gscore.get(neighbor, 1e9) or neighbor not in [i[1] for i in oheap]:&lt;br&gt;
                came_from[neighbor] = current&lt;br&gt;
                gscore[neighbor] = tentative_g&lt;br&gt;
                fscore[neighbor] = tentative_g + heuristic(neighbor, goal)&lt;br&gt;
                heapq.heappush(oheap, (fscore[neighbor], neighbor))&lt;br&gt;
    return []&lt;/p&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Controller (Pure Pursuit)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class PurePursuitController:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, lookahead_m=0.3, max_speed=0.2):&lt;br&gt;
        self.lookahead = lookahead_m&lt;br&gt;
        self.max_speed = max_speed&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def compute_control(self, pose: Tuple[float,float,float], path_world: List[Tuple[float,float]], obstacle_dist: float):&lt;br&gt;
    x, y, th = pose&lt;br&gt;
    if len(path_world) == 0:&lt;br&gt;
        return 0.0, 0.0&lt;br&gt;
    distances = [math.hypot(px-x, py-y) for (px,py) in path_world]&lt;br&gt;
    idx = next((i for i,d in enumerate(distances) if d &amp;gt;= self.lookahead), len(distances)-1)&lt;br&gt;
    goal = path_world[idx]&lt;br&gt;
    dx = goal[0]-x&lt;br&gt;
    dy = goal[1]-y&lt;br&gt;
    # rotate into robot frame&lt;br&gt;
    x_r = math.cos(-th)&lt;em&gt;dx - math.sin(-th)*dy&lt;br&gt;
    y_r = math.sin(-th)*dx + math.cos(-th)*dy&lt;br&gt;
    if abs(x_r) &amp;lt; 1e-6:&lt;br&gt;
        curvature = 0.0&lt;br&gt;
    else:&lt;br&gt;
        curvature = 2.0 * y_r / (self.lookahead&lt;/em&gt;*2)&lt;br&gt;
    v = self.max_speed&lt;br&gt;
    if obstacle_dist &amp;lt; 0.4:&lt;br&gt;
        v = 0.0&lt;br&gt;
    elif obstacle_dist &amp;lt; 1.0:&lt;br&gt;
        v = self.max_speed * max(0.05, (obstacle_dist - 0.3) / 0.7)&lt;br&gt;
    omega = v * curvature&lt;br&gt;
    return v, omega&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Dashboard (Flask)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;app = Flask(&lt;strong&gt;name&lt;/strong&gt;)&lt;br&gt;
_state = {&lt;br&gt;
    'pose': (0.0, 0.0, 0.0),&lt;br&gt;
    'grid_prob': None,&lt;br&gt;
    'path': [],&lt;br&gt;
    'sensors': {}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;@app.route('/')&lt;br&gt;
def index():&lt;br&gt;
    return render_template_string("""&lt;br&gt;
    &lt;br&gt;
    &lt;/p&gt;Robot Dashboard&lt;br&gt;
    &lt;br&gt;
      &lt;h2&gt;SIH Robot — GPS-denied Dashboard&lt;/h2&gt;
&lt;br&gt;
      &lt;img src="/map.png"&gt;&lt;br&gt;
      &lt;h3&gt;Pose&lt;/h3&gt;
&lt;br&gt;
      &lt;pre id="pose"&gt;&lt;/pre&gt;
&lt;br&gt;
      &lt;h3&gt;Sensors&lt;/h3&gt;
&lt;br&gt;
      &lt;pre id="sensors"&gt;&lt;/pre&gt;
&lt;br&gt;
      &amp;lt;br&amp;gt;
        async function update(){&amp;lt;br&amp;gt;
          let p = await fetch(&amp;amp;#39;/state&amp;amp;#39;).then(r=&amp;amp;gt;r.json());&amp;lt;br&amp;gt;
          document.getElementById(&amp;amp;#39;pose&amp;amp;#39;).innerText = JSON.stringify(p.pose, null, 2);&amp;lt;br&amp;gt;
          document.getElementById(&amp;amp;#39;sensors&amp;amp;#39;).innerText = JSON.stringify(p.sensors, null, 2);&amp;lt;br&amp;gt;
          setTimeout(update, 500);&amp;lt;br&amp;gt;
        }&amp;lt;br&amp;gt;
        update();&amp;lt;br&amp;gt;
      &lt;br&gt;
    &lt;br&gt;
    &lt;br&gt;
    """)

&lt;p&gt;@app.route('/state')&lt;br&gt;
def state():&lt;br&gt;
    return jsonify(_state)&lt;/p&gt;

&lt;p&gt;@app.route('/map.png')&lt;br&gt;
def map_png():&lt;br&gt;
    grid = _state.get('grid_prob')&lt;br&gt;
    pose = _state.get('pose', (0,0,0))&lt;br&gt;
    path = _state.get('path', [])&lt;br&gt;
    plt.figure(figsize=(6.4,4.8))&lt;br&gt;
    if grid is None:&lt;br&gt;
        plt.imshow(np.ones((480,640)), origin='lower', cmap='gray')&lt;br&gt;
    else:&lt;br&gt;
        # grid is probability map in grid coordinates (height, width)&lt;br&gt;
        # Show with origin lower&lt;br&gt;
        plt.imshow(1.0 - grid, origin='lower', cmap='gray')&lt;br&gt;
        if len(path)&amp;gt;0:&lt;br&gt;
            xs = [p[0] for p in path]&lt;br&gt;
            ys = [p[1] for p in path]&lt;br&gt;
            plt.plot(xs, ys, '-r', linewidth=1.5)&lt;br&gt;
        x,y,th = pose&lt;br&gt;
        plt.scatter([x],[y], c='b')&lt;br&gt;
        plt.title('Occupancy Grid (white=free, black=occ)')&lt;br&gt;
    buf = io.BytesIO()&lt;br&gt;
    plt.savefig(buf, format='png')&lt;br&gt;
    plt.close()&lt;br&gt;
    buf.seek(0)&lt;br&gt;
    return Response(buf.read(), mimetype='image/png')&lt;/p&gt;

&lt;p&gt;def dashboard_set_state(pose=None, grid_prob=None, path=None, sensors=None):&lt;br&gt;
    if pose is not None:&lt;br&gt;
        _state['pose'] = pose&lt;br&gt;
    if grid_prob is not None:&lt;br&gt;
        # dashboard expects world coords... we'll map path to world later&lt;br&gt;
        _state['grid_prob'] = grid_prob&lt;br&gt;
    if path is not None:&lt;br&gt;
        _state['path'] = path&lt;br&gt;
    if sensors is not None:&lt;br&gt;
        _state['sensors'] = sensors&lt;/p&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Main robot orchestration
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;def run_dashboard_server():&lt;br&gt;
    # run Flask app in thread&lt;br&gt;
    app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)&lt;/p&gt;

&lt;p&gt;def main_loop(rate_hz=10):&lt;br&gt;
    dt = 1.0 / rate_hz&lt;br&gt;
    ekf = EKF()&lt;br&gt;
    vo = VisualOdometry()&lt;br&gt;
    grid = OccupancyGrid(width_m=8.0, height_m=8.0, resolution=0.05)&lt;br&gt;
    controller = PurePursuitController(lookahead_m=0.25, max_speed=0.18)&lt;br&gt;
    sensor_health = SensorHealth()&lt;br&gt;
    cam = CameraWrapper(0)&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example goal 2 meters ahead in world coords&lt;br&gt;
goal_world = (2.0, 0.0)

&lt;p&gt;def world_to_grid_idx(x, y):&lt;br&gt;
    gx = int((x - grid.origin_x) / grid.resolution)&lt;br&gt;
    gy = int((y - grid.origin_y) / grid.resolution)&lt;br&gt;
    return (gx, gy)&lt;/p&gt;

&lt;p&gt;def grid_idx_to_world(gx, gy):&lt;br&gt;
    x = grid.origin_x + gx * grid.resolution&lt;br&gt;
    y = grid.origin_y + gy * grid.resolution&lt;br&gt;
    return (x, y)&lt;/p&gt;

&lt;p&gt;path_world = []&lt;/p&gt;

&lt;p&gt;try:&lt;br&gt;
    while True:&lt;br&gt;
        start = time.time()&lt;br&gt;
        # --- sensor reads ---&lt;br&gt;
        wz, ax = read_imu()&lt;br&gt;
        sensor_health.update(imu=True)&lt;br&gt;
        d, dtheta = read_wheel_odometry()&lt;br&gt;
        sensor_health.update(odom=True)&lt;br&gt;
        ranges, angles = read_lidar_scan()&lt;br&gt;
        sensor_health.update(lidar=True)&lt;br&gt;
        frame = cam.read()&lt;br&gt;
        sensor_health.update(cam=True)&lt;br&gt;
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # --- visual odometry ---
    dx_vo, dy_vo, yaw_vo = vo.process_frame(gray)

    # --- EKF predict (use wheel odom and small IMU blend) ---
    ekf.predict(d, dtheta + wz*dt*0.5)

    # --- occasionally update EKF with VO pseudo-measurement ---
    xek, yek, thek = ekf.get_state()
    dx_world = dx_vo * math.cos(thek) - dy_vo * math.sin(thek)
    dy_world = dx_vo * math.sin(thek) + dy_vo * math.cos(thek)
    z_pose = np.array([[xek + dx_world],[yek + dy_world],[thek + yaw_vo]])
    R_vo = np.diag([0.1, 0.1, 0.05])
    ekf.update_pose(z_pose, R_vo)

    # --- mapping (LIDAR into grid) ---
    pose = ekf.get_state()
    grid.integrate_scan(pose, ranges, angles, max_range=5.0)
    grid_prob = grid.get_prob_map()

    # --- planning (A*) on grid ---
    goal_g = world_to_grid_idx(goal_world[0], goal_world[1])
    gx, gy = world_to_grid_idx(pose[0], pose[1])
    start_g = (gx, gy)
    planned = []
    if grid.in_bounds(goal_g[0], goal_g[1]) and grid.in_bounds(start_g[0], start_g[1]):
        planned = a_star(grid_prob, start_g, goal_g, threshold=0.58)
    path_world = [ grid_idx_to_world(px, py) for (px,py) in planned ]

    # --- safety: estimate nearest obstacle ahead ---
    center_sector = ranges[len(ranges)//2 - 3 : len(ranges)//2 + 4]
    obstacle_dist = float(np.median(center_sector))

    # --- control ---
    v_cmd, omega_cmd = controller.compute_control(pose, path_world, obstacle_dist)

    # --- fail-safes ---
    health = sensor_health.ok(timeout=1.5)
    if not (health['lidar'] and health['odom']):
        v_cmd, omega_cmd = 0.0, 0.0
    if max(np.diag(ekf.P)) &amp;amp;gt; 4.0:
        v_cmd, omega_cmd = 0.0, 0.0

    # --- actuator output (replace with real actuator code) ---
    # send_actuator_commands(v_cmd, omega_cmd)
    print(f"[{time.strftime('%H:%M:%S')}] pose={tuple(np.round(pose,3))} v={v_cmd:.2f} w={omega_cmd:.2f} obs={obstacle_dist:.2f}")

    # --- update dashboard state ---
    dashboard_set_state(pose=tuple(pose),
                        grid_prob=grid_prob,
                        path=path_world,
                        sensors={
                            'obstacle_dist': obstacle_dist,
                            'vo_dx': float(dx_vo),
                            'vo_yaw': float(yaw_vo),
                            'ekf_P_diag': [float(x) for x in ekf.P.diagonal()],
                            'health': health
                        })

    elapsed = time.time() - start
    sleep = max(0.0, dt - elapsed)
    time.sleep(sleep)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;except KeyboardInterrupt:&lt;br&gt;
    cam.release()&lt;br&gt;
    print("Shutting down main loop")&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Entrypoint
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == '&lt;strong&gt;main&lt;/strong&gt;':&lt;br&gt;
    # start dashboard thread&lt;br&gt;
    t = threading.Thread(target=run_dashboard_server, daemon=True)&lt;br&gt;
    t.start()&lt;br&gt;
    print("Dashboard running at &lt;a href="http://0.0.0.0:5000%22" rel="noopener noreferrer"&gt;http://0.0.0.0:5000"&lt;/a&gt;)&lt;br&gt;
    main_loop(rate_hz=10)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SIH code</title>
      <dc:creator>DEDIPYA SADE</dc:creator>
      <pubDate>Wed, 24 Sep 2025 11:01:01 +0000</pubDate>
      <link>https://dev.to/dedipya_sade_ded044996b1a/sih-code-18pc</link>
      <guid>https://dev.to/dedipya_sade_ded044996b1a/sih-code-18pc</guid>
      <description>&lt;p&gt;python3 -m venv venv&lt;br&gt;
source venv/bin/activate&lt;br&gt;
pip install opencv-python-headless numpy flask matplotlib scipy pillow&lt;/p&gt;

&lt;h1&gt;
  
  
  !/usr/bin/env python3
&lt;/h1&gt;

&lt;p&gt;"""&lt;br&gt;
robot.py&lt;br&gt;
Single-file GPS-denied robot demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKF (wheel odom + IMU, occasional VO updates)&lt;/li&gt;
&lt;li&gt;Visual Odometry (ORB)&lt;/li&gt;
&lt;li&gt;Occupancy Grid mapping (log-odds)&lt;/li&gt;
&lt;li&gt;A* planner on the occupancy grid&lt;/li&gt;
&lt;li&gt;Pure Pursuit controller + safety stop&lt;/li&gt;
&lt;li&gt;Simple Flask dashboard showing occupancy, pose, sensors
Sensor read functions are stubbed/simulated — replace with real drivers on real hardware.
"""&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;import time&lt;br&gt;
import threading&lt;br&gt;
import math&lt;br&gt;
import io&lt;br&gt;
import base64&lt;br&gt;
from typing import List, Tuple&lt;/p&gt;

&lt;p&gt;import numpy as np&lt;br&gt;
import cv2&lt;br&gt;
from flask import Flask, Response, render_template_string, jsonify&lt;br&gt;
import matplotlib&lt;br&gt;
matplotlib.use('Agg')&lt;br&gt;
import matplotlib.pyplot as plt&lt;br&gt;
from PIL import Image&lt;br&gt;
import heapq&lt;/p&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Sensor stubs / wrappers
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class SensorHealth:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self):&lt;br&gt;
        t = time.time()&lt;br&gt;
        self.last_imu = t&lt;br&gt;
        self.last_odom = t&lt;br&gt;
        self.last_cam = t&lt;br&gt;
        self.last_lidar = t&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def update(self, imu=False, odom=False, cam=False, lidar=False):&lt;br&gt;
    now = time.time()&lt;br&gt;
    if imu: self.last_imu = now&lt;br&gt;
    if odom: self.last_odom = now&lt;br&gt;
    if cam: self.last_cam = now&lt;br&gt;
    if lidar: self.last_lidar = now

&lt;p&gt;def ok(self, timeout=1.0):&lt;br&gt;
    now = time.time()&lt;br&gt;
    return {&lt;br&gt;
        'imu': (now - self.last_imu) &amp;lt; timeout,&lt;br&gt;
        'odom': (now - self.last_odom) &amp;lt; timeout,&lt;br&gt;
        'cam': (now - self.last_cam) &amp;lt; timeout,&lt;br&gt;
        'lidar': (now - self.last_lidar) &amp;lt; timeout&lt;br&gt;
    }&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Replace these with real sensor calls on hardware&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;def read_imu():&lt;br&gt;
    """&lt;br&gt;
    Return (wz, ax) = (angular velocity z in rad/s, linear accel x in m/s^2)&lt;br&gt;
    Replace with real IMU reading (e.g., MPU driver).&lt;br&gt;
    """&lt;br&gt;
    wz = np.random.normal(0.0, 0.02)&lt;br&gt;
    ax = np.random.normal(0.0, 0.1)&lt;br&gt;
    return float(wz), float(ax)&lt;/p&gt;

&lt;p&gt;def read_wheel_odometry():&lt;br&gt;
    """&lt;br&gt;
    Return (delta_distance_m, delta_heading_rad) since last call.&lt;br&gt;
    Replace with encoder integration.&lt;br&gt;
    """&lt;br&gt;
    # Simulated small forward motion per loop (for demo)&lt;br&gt;
    d = 0.02 + np.random.normal(0.0, 0.004)&lt;br&gt;
    dtheta = np.random.normal(0.0, 0.004)&lt;br&gt;
    return float(d), float(dtheta)&lt;/p&gt;

&lt;p&gt;def read_lidar_scan(num_beams=180, max_range=5.0):&lt;br&gt;
    """&lt;br&gt;
    Return (ranges, angles) arrays.&lt;br&gt;
    Replace with real lidar driver output.&lt;br&gt;
    This stub simulates a front obstacle.&lt;br&gt;
    """&lt;br&gt;
    angles = np.linspace(-np.pi/2, np.pi/2, num_beams)&lt;br&gt;
    ranges = max_range * np.ones_like(angles)&lt;br&gt;
    center_idx = num_beams // 2&lt;br&gt;
    ranges[center_idx-3:center_idx+4] = 1.5 + np.random.normal(0.0, 0.03, 7)&lt;br&gt;
    ranges += np.random.normal(0.0, 0.02, size=angles.shape)&lt;br&gt;
    ranges = np.clip(ranges, 0.02, max_range)&lt;br&gt;
    return ranges.astype(float), angles.astype(float)&lt;/p&gt;

&lt;p&gt;class CameraWrapper:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, source=0, width=320, height=240):&lt;br&gt;
        self.cap = cv2.VideoCapture(source)&lt;br&gt;
        # attempt to set size (may fail on some cameras)&lt;br&gt;
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)&lt;br&gt;
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)&lt;br&gt;
        self.last_frame = None&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def read(self):&lt;br&gt;
    ret, frame = self.cap.read()&lt;br&gt;
    if not ret or frame is None:&lt;br&gt;
        # return blank frame to avoid breaking VO&lt;br&gt;
        frame = np.zeros((240,320,3), dtype=np.uint8)&lt;br&gt;
    self.last_frame = frame&lt;br&gt;
    return frame

&lt;p&gt;def release(self):&lt;br&gt;
    self.cap.release()&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  EKF (3-state)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class EKF:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self):&lt;br&gt;
        self.x = np.zeros((3,1))  # x, y, theta&lt;br&gt;
        self.P = np.diag([0.1, 0.1, 0.05])&lt;br&gt;
        # small process noise&lt;br&gt;
        self.Q = np.diag([0.02, 0.02, 0.01])&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def predict(self, delta_dist, delta_theta):&lt;br&gt;
    x, y, th = self.x.flatten()&lt;br&gt;
    th_new = th + delta_theta&lt;br&gt;
    x_new = x + delta_dist * np.cos(th_new)&lt;br&gt;
    y_new = y + delta_dist * np.sin(th_new)&lt;br&gt;
    self.x = np.array([[x_new],[y_new],[th_new]])&lt;br&gt;
    # simple covariance increase&lt;br&gt;
    self.P = self.P + self.Q

&lt;p&gt;def update_pose(self, z, R):&lt;br&gt;
    """&lt;br&gt;
    z: 3x1 measurement&lt;br&gt;
    R: 3x3 measurement covariance&lt;br&gt;
    """&lt;br&gt;
    H = np.eye(3)&lt;br&gt;
    y = z - H.dot(self.x)&lt;br&gt;
    # normalize angle residual&lt;br&gt;
    y[2] = (y[2] + np.pi) % (2*np.pi) - np.pi&lt;br&gt;
    S = H.dot(self.P).dot(H.T) + R&lt;br&gt;
    K = self.P.dot(H.T).dot(np.linalg.inv(S))&lt;br&gt;
    self.x = self.x + K.dot(y)&lt;br&gt;
    self.P = (np.eye(3) - K.dot(H)).dot(self.P)&lt;/p&gt;

&lt;p&gt;def get_state(self):&lt;br&gt;
    return self.x.flatten()&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Visual Odometry (ORB)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class VisualOdometry:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, max_features=1000, focal=300.0):&lt;br&gt;
        self.orb = cv2.ORB_create(max_features)&lt;br&gt;
        self.bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)&lt;br&gt;
        self.prev_kp = None&lt;br&gt;
        self.prev_des = None&lt;br&gt;
        self.prev_frame = None&lt;br&gt;
        self.f = focal&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def process_frame(self, frame_gray):&lt;br&gt;
    if self.prev_frame is None:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = self.orb.detectAndCompute(frame_gray, None)&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    kp, des = self.orb.detectAndCompute(frame_gray, None)&lt;br&gt;
    if des is None or self.prev_des is None:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = kp, des&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    matches = self.bf.match(self.prev_des, des)&lt;br&gt;
    matches = sorted(matches, key=lambda x: x.distance)[:150]&lt;br&gt;
    if len(matches) &amp;lt; 8:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = kp, des&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    pts_prev = np.float32([ self.prev_kp[m.queryIdx].pt for m in matches ])&lt;br&gt;
    pts_cur  = np.float32([ kp[m.trainIdx].pt for m in matches ])&lt;br&gt;
    E, mask = cv2.findEssentialMat(pts_cur, pts_prev, focal=self.f,&lt;br&gt;
                                   pp=(frame_gray.shape[1]/2, frame_gray.shape[0]/2),&lt;br&gt;
                                   method=cv2.RANSAC, prob=0.999, threshold=1.0)&lt;br&gt;
    if E is None:&lt;br&gt;
        self.prev_frame = frame_gray&lt;br&gt;
        self.prev_kp, self.prev_des = kp, des&lt;br&gt;
        return 0.0, 0.0, 0.0&lt;br&gt;
    _, R, t, mask = cv2.recoverPose(E, pts_cur, pts_prev, focal=self.f,&lt;br&gt;
                                    pp=(frame_gray.shape[1]/2, frame_gray.shape[0]/2))&lt;br&gt;
    # approximate 2D motion: forward ~ t_z, yaw ~ small yaw from R&lt;br&gt;
    if t is None:&lt;br&gt;
        dz = 0.0&lt;br&gt;
    else:&lt;br&gt;
        dz = float(t[2,0]) if t.shape==(3,1) else 0.0&lt;br&gt;
    yaw = math.atan2(R[1,0], R[0,0])&lt;br&gt;
    self.prev_frame = frame_gray&lt;br&gt;
    self.prev_kp, self.prev_des = kp, des&lt;br&gt;
    return dz, 0.0, yaw&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Occupancy Grid SLAM (log-odds)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class OccupancyGrid:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, width_m=10.0, height_m=10.0, resolution=0.05):&lt;br&gt;
        self.resolution = resolution&lt;br&gt;
        self.width = int(width_m / resolution)&lt;br&gt;
        self.height = int(height_m / resolution)&lt;br&gt;
        self.log_odds = np.zeros((self.height, self.width), dtype=np.float32)&lt;br&gt;
        self.l_free = -0.4&lt;br&gt;
        self.l_occ = 0.85&lt;br&gt;
        self.origin_x = -width_m / 2.0&lt;br&gt;
        self.origin_y = -height_m / 2.0&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def world_to_grid(self, x, y):&lt;br&gt;
    gx = int((x - self.origin_x) / self.resolution)&lt;br&gt;
    gy = int((y - self.origin_y) / self.resolution)&lt;br&gt;
    return gx, gy

&lt;p&gt;def in_bounds(self, gx, gy):&lt;br&gt;
    return 0 &amp;lt;= gx &amp;lt; self.width and 0 &amp;lt;= gy &amp;lt; self.height&lt;/p&gt;

&lt;p&gt;def bresenham(self, x0, y0, x1, y1):&lt;br&gt;
    points = []&lt;br&gt;
    dx = abs(x1 - x0)&lt;br&gt;
    sx = 1 if x0 &amp;lt; x1 else -1&lt;br&gt;
    dy = -abs(y1 - y0)&lt;br&gt;
    sy = 1 if y0 &amp;lt; y1 else -1&lt;br&gt;
    err = dx + dy&lt;br&gt;
    x, y = x0, y0&lt;br&gt;
    while True:&lt;br&gt;
        points.append((x,y))&lt;br&gt;
        if x == x1 and y == y1:&lt;br&gt;
            break&lt;br&gt;
        e2 = 2*err&lt;br&gt;
        if e2 &amp;gt;= dy:&lt;br&gt;
            err += dy&lt;br&gt;
            x += sx&lt;br&gt;
        if e2 &amp;lt;= dx:&lt;br&gt;
            err += dx&lt;br&gt;
            y += sy&lt;br&gt;
    return points&lt;/p&gt;

&lt;p&gt;def integrate_scan(self, pose: Tuple[float,float,float], ranges: np.ndarray, angles: np.ndarray, max_range=5.0):&lt;br&gt;
    x, y, th = pose&lt;br&gt;
    for r, a in zip(ranges, angles):&lt;br&gt;
        beam_angle = th + a&lt;br&gt;
        ex = x + r * math.cos(beam_angle)&lt;br&gt;
        ey = y + r * math.sin(beam_angle)&lt;br&gt;
        gx0, gy0 = self.world_to_grid(x, y)&lt;br&gt;
        gx1, gy1 = self.world_to_grid(ex, ey)&lt;br&gt;
        points = self.bresenham(gx0, gy0, gx1, gy1)&lt;br&gt;
        # free along beam (except last)&lt;br&gt;
        for (gx, gy) in points[:-1]:&lt;br&gt;
            if self.in_bounds(gx, gy):&lt;br&gt;
                self.log_odds[gy, gx] += self.l_free&lt;br&gt;
        # last cell&lt;br&gt;
        gx, gy = points[-1]&lt;br&gt;
        if self.in_bounds(gx, gy) and r &amp;lt; (max_range - 0.01):&lt;br&gt;
            self.log_odds[gy, gx] += self.l_occ&lt;br&gt;
    self.log_odds = np.clip(self.log_odds, -8.0, 8.0)&lt;/p&gt;

&lt;p&gt;def get_prob_map(self):&lt;br&gt;
    odds = np.exp(self.log_odds)&lt;br&gt;
    prob = odds / (1.0 + odds)&lt;br&gt;
    return prob&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Planner (A*)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;def heuristic(a, b):&lt;br&gt;
    return math.hypot(b[0]-a[0], b[1]-a[1])&lt;/p&gt;

&lt;p&gt;def a_star(grid_prob: np.ndarray, start: Tuple[int,int], goal: Tuple[int,int], threshold=0.6) -&amp;gt; List[Tuple[int,int]]:&lt;br&gt;
    h, w = grid_prob.shape&lt;br&gt;
    def neighbors(node):&lt;br&gt;
        x, y = node&lt;br&gt;
        for dx, dy in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,1),(-1,1),(1,-1)]:&lt;br&gt;
            nx, ny = x+dx, y+dy&lt;br&gt;
            if 0 &amp;lt;= nx &amp;lt; w and 0 &amp;lt;= ny &amp;lt; h and grid_prob[ny, nx] &amp;lt; threshold:&lt;br&gt;
                yield (nx, ny)&lt;br&gt;
    close_set = set()&lt;br&gt;
    came_from = {}&lt;br&gt;
    gscore = {start:0.0}&lt;br&gt;
    fscore = {start:heuristic(start, goal)}&lt;br&gt;
    oheap = []&lt;br&gt;
    heapq.heappush(oheap, (fscore[start], start))&lt;br&gt;
    while oheap:&lt;br&gt;
        _, current = heapq.heappop(oheap)&lt;br&gt;
        if current == goal:&lt;br&gt;
            path = [current]&lt;br&gt;
            while current in came_from:&lt;br&gt;
                current = came_from[current]&lt;br&gt;
                path.append(current)&lt;br&gt;
            path.reverse()&lt;br&gt;
            return path&lt;br&gt;
        close_set.add(current)&lt;br&gt;
        for neighbor in neighbors(current):&lt;br&gt;
            tentative_g = gscore[current] + heuristic(current, neighbor)&lt;br&gt;
            if neighbor in close_set and tentative_g &amp;gt;= gscore.get(neighbor, 1e9):&lt;br&gt;
                continue&lt;br&gt;
            if tentative_g &amp;lt; gscore.get(neighbor, 1e9) or neighbor not in [i[1] for i in oheap]:&lt;br&gt;
                came_from[neighbor] = current&lt;br&gt;
                gscore[neighbor] = tentative_g&lt;br&gt;
                fscore[neighbor] = tentative_g + heuristic(neighbor, goal)&lt;br&gt;
                heapq.heappush(oheap, (fscore[neighbor], neighbor))&lt;br&gt;
    return []&lt;/p&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Controller (Pure Pursuit)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;class PurePursuitController:&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, lookahead_m=0.3, max_speed=0.2):&lt;br&gt;
        self.lookahead = lookahead_m&lt;br&gt;
        self.max_speed = max_speed&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def compute_control(self, pose: Tuple[float,float,float], path_world: List[Tuple[float,float]], obstacle_dist: float):&lt;br&gt;
    x, y, th = pose&lt;br&gt;
    if len(path_world) == 0:&lt;br&gt;
        return 0.0, 0.0&lt;br&gt;
    distances = [math.hypot(px-x, py-y) for (px,py) in path_world]&lt;br&gt;
    idx = next((i for i,d in enumerate(distances) if d &amp;gt;= self.lookahead), len(distances)-1)&lt;br&gt;
    goal = path_world[idx]&lt;br&gt;
    dx = goal[0]-x&lt;br&gt;
    dy = goal[1]-y&lt;br&gt;
    # rotate into robot frame&lt;br&gt;
    x_r = math.cos(-th)&lt;em&gt;dx - math.sin(-th)*dy&lt;br&gt;
    y_r = math.sin(-th)*dx + math.cos(-th)*dy&lt;br&gt;
    if abs(x_r) &amp;lt; 1e-6:&lt;br&gt;
        curvature = 0.0&lt;br&gt;
    else:&lt;br&gt;
        curvature = 2.0 * y_r / (self.lookahead&lt;/em&gt;*2)&lt;br&gt;
    v = self.max_speed&lt;br&gt;
    if obstacle_dist &amp;lt; 0.4:&lt;br&gt;
        v = 0.0&lt;br&gt;
    elif obstacle_dist &amp;lt; 1.0:&lt;br&gt;
        v = self.max_speed * max(0.05, (obstacle_dist - 0.3) / 0.7)&lt;br&gt;
    omega = v * curvature&lt;br&gt;
    return v, omega&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Dashboard (Flask)
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;app = Flask(&lt;strong&gt;name&lt;/strong&gt;)&lt;br&gt;
_state = {&lt;br&gt;
    'pose': (0.0, 0.0, 0.0),&lt;br&gt;
    'grid_prob': None,&lt;br&gt;
    'path': [],&lt;br&gt;
    'sensors': {}&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;@app.route('/')&lt;br&gt;
def index():&lt;br&gt;
    return render_template_string("""&lt;br&gt;
    &lt;br&gt;
    &lt;/p&gt;Robot Dashboard&lt;br&gt;
    &lt;br&gt;
      &lt;h2&gt;SIH Robot — GPS-denied Dashboard&lt;/h2&gt;
&lt;br&gt;
      &lt;img src="/map.png"&gt;&lt;br&gt;
      &lt;h3&gt;Pose&lt;/h3&gt;
&lt;br&gt;
      &lt;pre id="pose"&gt;&lt;/pre&gt;
&lt;br&gt;
      &lt;h3&gt;Sensors&lt;/h3&gt;
&lt;br&gt;
      &lt;pre id="sensors"&gt;&lt;/pre&gt;
&lt;br&gt;
      &amp;lt;br&amp;gt;
        async function update(){&amp;lt;br&amp;gt;
          let p = await fetch(&amp;amp;#39;/state&amp;amp;#39;).then(r=&amp;amp;gt;r.json());&amp;lt;br&amp;gt;
          document.getElementById(&amp;amp;#39;pose&amp;amp;#39;).innerText = JSON.stringify(p.pose, null, 2);&amp;lt;br&amp;gt;
          document.getElementById(&amp;amp;#39;sensors&amp;amp;#39;).innerText = JSON.stringify(p.sensors, null, 2);&amp;lt;br&amp;gt;
          setTimeout(update, 500);&amp;lt;br&amp;gt;
        }&amp;lt;br&amp;gt;
        update();&amp;lt;br&amp;gt;
      &lt;br&gt;
    &lt;br&gt;
    &lt;br&gt;
    """)

&lt;p&gt;@app.route('/state')&lt;br&gt;
def state():&lt;br&gt;
    return jsonify(_state)&lt;/p&gt;

&lt;p&gt;@app.route('/map.png')&lt;br&gt;
def map_png():&lt;br&gt;
    grid = _state.get('grid_prob')&lt;br&gt;
    pose = _state.get('pose', (0,0,0))&lt;br&gt;
    path = _state.get('path', [])&lt;br&gt;
    plt.figure(figsize=(6.4,4.8))&lt;br&gt;
    if grid is None:&lt;br&gt;
        plt.imshow(np.ones((480,640)), origin='lower', cmap='gray')&lt;br&gt;
    else:&lt;br&gt;
        # grid is probability map in grid coordinates (height, width)&lt;br&gt;
        # Show with origin lower&lt;br&gt;
        plt.imshow(1.0 - grid, origin='lower', cmap='gray')&lt;br&gt;
        if len(path)&amp;gt;0:&lt;br&gt;
            xs = [p[0] for p in path]&lt;br&gt;
            ys = [p[1] for p in path]&lt;br&gt;
            plt.plot(xs, ys, '-r', linewidth=1.5)&lt;br&gt;
        x,y,th = pose&lt;br&gt;
        plt.scatter([x],[y], c='b')&lt;br&gt;
        plt.title('Occupancy Grid (white=free, black=occ)')&lt;br&gt;
    buf = io.BytesIO()&lt;br&gt;
    plt.savefig(buf, format='png')&lt;br&gt;
    plt.close()&lt;br&gt;
    buf.seek(0)&lt;br&gt;
    return Response(buf.read(), mimetype='image/png')&lt;/p&gt;

&lt;p&gt;def dashboard_set_state(pose=None, grid_prob=None, path=None, sensors=None):&lt;br&gt;
    if pose is not None:&lt;br&gt;
        _state['pose'] = pose&lt;br&gt;
    if grid_prob is not None:&lt;br&gt;
        # dashboard expects world coords... we'll map path to world later&lt;br&gt;
        _state['grid_prob'] = grid_prob&lt;br&gt;
    if path is not None:&lt;br&gt;
        _state['path'] = path&lt;br&gt;
    if sensors is not None:&lt;br&gt;
        _state['sensors'] = sensors&lt;/p&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Main robot orchestration
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;def run_dashboard_server():&lt;br&gt;
    # run Flask app in thread&lt;br&gt;
    app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)&lt;/p&gt;

&lt;p&gt;def main_loop(rate_hz=10):&lt;br&gt;
    dt = 1.0 / rate_hz&lt;br&gt;
    ekf = EKF()&lt;br&gt;
    vo = VisualOdometry()&lt;br&gt;
    grid = OccupancyGrid(width_m=8.0, height_m=8.0, resolution=0.05)&lt;br&gt;
    controller = PurePursuitController(lookahead_m=0.25, max_speed=0.18)&lt;br&gt;
    sensor_health = SensorHealth()&lt;br&gt;
    cam = CameraWrapper(0)&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example goal 2 meters ahead in world coords&lt;br&gt;
goal_world = (2.0, 0.0)

&lt;p&gt;def world_to_grid_idx(x, y):&lt;br&gt;
    gx = int((x - grid.origin_x) / grid.resolution)&lt;br&gt;
    gy = int((y - grid.origin_y) / grid.resolution)&lt;br&gt;
    return (gx, gy)&lt;/p&gt;

&lt;p&gt;def grid_idx_to_world(gx, gy):&lt;br&gt;
    x = grid.origin_x + gx * grid.resolution&lt;br&gt;
    y = grid.origin_y + gy * grid.resolution&lt;br&gt;
    return (x, y)&lt;/p&gt;

&lt;p&gt;path_world = []&lt;/p&gt;

&lt;p&gt;try:&lt;br&gt;
    while True:&lt;br&gt;
        start = time.time()&lt;br&gt;
        # --- sensor reads ---&lt;br&gt;
        wz, ax = read_imu()&lt;br&gt;
        sensor_health.update(imu=True)&lt;br&gt;
        d, dtheta = read_wheel_odometry()&lt;br&gt;
        sensor_health.update(odom=True)&lt;br&gt;
        ranges, angles = read_lidar_scan()&lt;br&gt;
        sensor_health.update(lidar=True)&lt;br&gt;
        frame = cam.read()&lt;br&gt;
        sensor_health.update(cam=True)&lt;br&gt;
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # --- visual odometry ---
    dx_vo, dy_vo, yaw_vo = vo.process_frame(gray)

    # --- EKF predict (use wheel odom and small IMU blend) ---
    ekf.predict(d, dtheta + wz*dt*0.5)

    # --- occasionally update EKF with VO pseudo-measurement ---
    xek, yek, thek = ekf.get_state()
    dx_world = dx_vo * math.cos(thek) - dy_vo * math.sin(thek)
    dy_world = dx_vo * math.sin(thek) + dy_vo * math.cos(thek)
    z_pose = np.array([[xek + dx_world],[yek + dy_world],[thek + yaw_vo]])
    R_vo = np.diag([0.1, 0.1, 0.05])
    ekf.update_pose(z_pose, R_vo)

    # --- mapping (LIDAR into grid) ---
    pose = ekf.get_state()
    grid.integrate_scan(pose, ranges, angles, max_range=5.0)
    grid_prob = grid.get_prob_map()

    # --- planning (A*) on grid ---
    goal_g = world_to_grid_idx(goal_world[0], goal_world[1])
    gx, gy = world_to_grid_idx(pose[0], pose[1])
    start_g = (gx, gy)
    planned = []
    if grid.in_bounds(goal_g[0], goal_g[1]) and grid.in_bounds(start_g[0], start_g[1]):
        planned = a_star(grid_prob, start_g, goal_g, threshold=0.58)
    path_world = [ grid_idx_to_world(px, py) for (px,py) in planned ]

    # --- safety: estimate nearest obstacle ahead ---
    center_sector = ranges[len(ranges)//2 - 3 : len(ranges)//2 + 4]
    obstacle_dist = float(np.median(center_sector))

    # --- control ---
    v_cmd, omega_cmd = controller.compute_control(pose, path_world, obstacle_dist)

    # --- fail-safes ---
    health = sensor_health.ok(timeout=1.5)
    if not (health['lidar'] and health['odom']):
        v_cmd, omega_cmd = 0.0, 0.0
    if max(np.diag(ekf.P)) &amp;amp;gt; 4.0:
        v_cmd, omega_cmd = 0.0, 0.0

    # --- actuator output (replace with real actuator code) ---
    # send_actuator_commands(v_cmd, omega_cmd)
    print(f"[{time.strftime('%H:%M:%S')}] pose={tuple(np.round(pose,3))} v={v_cmd:.2f} w={omega_cmd:.2f} obs={obstacle_dist:.2f}")

    # --- update dashboard state ---
    dashboard_set_state(pose=tuple(pose),
                        grid_prob=grid_prob,
                        path=path_world,
                        sensors={
                            'obstacle_dist': obstacle_dist,
                            'vo_dx': float(dx_vo),
                            'vo_yaw': float(yaw_vo),
                            'ekf_P_diag': [float(x) for x in ekf.P.diagonal()],
                            'health': health
                        })

    elapsed = time.time() - start
    sleep = max(0.0, dt - elapsed)
    time.sleep(sleep)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;except KeyboardInterrupt:&lt;br&gt;
    cam.release()&lt;br&gt;
    print("Shutting down main loop")&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  



&lt;/h1&gt;


&lt;h1&gt;
  
  
  Entrypoint
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ---------------------------
&lt;/h1&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == '&lt;strong&gt;main&lt;/strong&gt;':&lt;br&gt;
    # start dashboard thread&lt;br&gt;
    t = threading.Thread(target=run_dashboard_server, daemon=True)&lt;br&gt;
    t.start()&lt;br&gt;
    print("Dashboard running at &lt;a href="http://0.0.0.0:5000%22" rel="noopener noreferrer"&gt;http://0.0.0.0:5000"&lt;/a&gt;)&lt;br&gt;
    main_loop(rate_hz=10)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62m1gmd93cg5jkoe1n0e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62m1gmd93cg5jkoe1n0e.jpg" alt=" " width="800" height="1777"&gt;&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
