Saturday, October 24, 2026

 

Using VLMs requires correct model hyperparameters

For example

(vllm-env) PS C:\users\ravib\venv\Qwen> ollama pull qwen2.5vl:7b

pulling manifest

pulling a99b7f834d75: 100% ▕█████████████████████████████████████████████████████████ ▏ 6.0 GB/6.0 GB   62 MB/s      0s

verifying sha256 digest

writing manifest

success

(vllm-env) PS C:\users\ravib\venv\Qwen> ollama run qwen2.5vl:7b

 

>>> How many red cars are in the image at "C:\Users\ravib\Downloads\frames\frames\frame11.jpg"

Added image 'C:\Users\ravib\Downloads\frames\frames\frame11.jpg'

There are two red cars visible in the image. One is parked on the left side of the street, and the other is parked

on the right side of the street.

 

>>> /bye

This is the correct answer.

Now with a python script:

import requests

import json

import base64

import os

 

url = "http://localhost:8848/v1/chat/completions"

 

# 1. Path to your local image

image_path = r"C:\Users\ravib\Downloads\frames\frames\frame11.jpg"

 

# 2. Open the image file and encode it to Base64

if os.path.exists(image_path):

    with open(image_path, "rb") as image_file:

        base64_image = base64.b64encode(image_file.read()).decode('utf-8')

else:

    print(f"Error: File not found at {image_path}")

    exit()

 

# 3. Construct the payload with the Base64 data URI format

payload = {

    "model": "qwen2.5vl:7b",

    "messages": [

        {

            "role": "user",

            "content": [

                {

                    "type": "text",

                    "text": "How many red cars are in the image?"

                },

                {

                    "type": "image_url",

                    "image_url": {

                        "url": f"data:image/jpeg;base64,{base64_image}"

                    }

                }

            ]

        }

    ]

}

 

headers = {

    "Content-Type": "application/json"

}

 

response = requests.post(url, headers=headers, data=json.dumps(payload))

 

print("Status:", response.status_code)

print("Response:")

print(response.text)

 

## Response

# Status: 200

# Response:

#{"id":"chatcmpl-429","object":"chat.completion","created":1788656816,"model":"qwen2.5vl:7b","system_fingerprint":"fp_ollama","choices":[{"index":0,"message":{"role":"assistant","content":"There is one red car on the street in the image."},"finish_reason":"stop"}],"usage":{"prompt_tokens":1226,"prompt_tokens_details":{"cached_tokens":22},"completion_tokens":13,"total_tokens":1239}}

 

This is the incorrect answer.

Now, the fix that works:

import base64

from PIL import Image

import io

import ollama

 

# 1. Path to your image file

image_path = r"C:\Users\ravib\Downloads\frames\frames\frame11.jpg"

 

# 2. Open image with Pillow to ensure it passes cleanly as standard bytes

with Image.open(image_path) as img:

    if img.mode in ("RGBA", "P"):

        img = img.convert("RGB")

   

    # Optional: Keep resolution native but ensure metadata handles boundaries well

    buffer = io.BytesIO()

    img.save(buffer, format="JPEG", quality=100)

    image_bytes = buffer.getvalue()

 

# 3. Direct programmatic call to the Ollama server on your port

# (Point the client explicitly to your active instance)

client = ollama.Client(host="http://localhost:8848")

 

response = client.chat(

    model="qwen2.5vl:7b",

    messages=[

        {

            "role": "user",

            "content": "Look closely at the entire scene, including any cars parked down the sides of the street. How many red cars are visible in this image?",

            "images": [image_bytes]  # Pass raw image bytes, avoiding Base64 URI scaling issues

        }

    ],

    options={

        "temperature": 0.0,          # Eliminates random sampling path bias

        "num_predict": 100,          # Gives the model breathing room to elaborate

        "num_ctx": 8192              # Expand context window to avoid aggressive clipping

    }

)

 

# Print the final result cleanly

print("Response:")

print(response['message']['content'])

Response:

In the image, there are two red cars visible. One is parked on the left side of the street near the tall building, and the other is parked further down the street on the right side.

Latest models of Qwen recommend:

·        Thinking Mode: temperature=1.0, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=0.0, repetition_penalty=1.0

·        Instruct (or non-thinking) mode: temperature=0.7, top_p=0.80, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0

 

Friday, October 23, 2026

 This script uses DROID’s selected keyframes, optimized poses, and depth maps and then adds a depth-based orthographic renderer. This avoids MicMac, ODM, and commercial licenses, but it introduces two requirements: a working CUDA-enabled DROID-SLAM installation and camera intrinsics. Another variation of a solution to the problem1 we started to solve. 

#!/usr/bin/env python3 
"""DROID-SLAM keyframes + dense depths -> approximate orthographic color mosaic. 
Not a PKS implementation, not a metric/survey true orthophoto. No ODM/MicMac. 
Use --probe first. A CUDA DROID-SLAM installation and calibration are required. 
""" 
import argparse 
from functools import lru_cache 
import hashlib 
import json 
import logging 
import math 
from pathlib import Path 
import shutil 
import subprocess 
import sys 
import time 
 
import numpy as np 
LOG=logging.getLogger('droid_orthomosaic') 
VIDEO_PATH='mainindexedvideo.mp4' 
SCHEMA=1 
 
 
def save_json(path,obj): 
    path=Path(path);tmp=path.with_name(path.name+'.tmp') 
    tmp.write_text(json.dumps(obj,indent=2),encoding='utf-8');tmp.replace(path) 
 
 
def load_json(path):return json.loads(Path(path).read_text(encoding='utf-8')) 
 
 
def digest(path): 
    h=hashlib.sha256() 
    with open(path,'rb') as f: 
        for b in iter(lambda:f.read(1024*1024),b''):h.update(b) 
    return h.hexdigest() 
 
 
def positive(x,name,zero=False): 
    if not math.isfinite(x) or (x<0 if zero else x<=0):raise ValueError(name+' must be finite and '+('nonnegative' if zero else 'positive')) 
 
 
def probe_video(path): 
    import cv2 
    cap=cv2.VideoCapture(str(path)) 
    try: 
        if not cap.isOpened():raise RuntimeError('Cannot open local video: '+str(path)) 
        ok,img=cap.read() 
        if not ok:raise RuntimeError('Cannot decode first video frame') 
        fps=float(cap.get(cv2.CAP_PROP_FPS));n=float(cap.get(cv2.CAP_PROP_FRAME_COUNT)) 
        return dict(width=img.shape[1],height=img.shape[0],reported_fps=fps,reported_frame_count=n, 
                    approximate_duration=n/fps if math.isfinite(fps) and fps>0 else None) 
    finally:cap.release() 
 
 
def extract_frames(video_path,output_dir,seconds_interval=.125,*,max_frame_size=1600,start=0,end=None): 
    """Chronological CFR sampling. Cache metadata is OUTSIDE image-only directory.""" 
    import cv2 
    positive(seconds_interval,'sampling interval');positive(start,'start',True) 
    if max_frame_size<64:raise ValueError('max_frame_size must be >=64') 
    if end is not None and (not math.isfinite(end) or end<=start):raise ValueError('end must be finite and > start') 
    video=Path(video_path).expanduser().resolve() 
    if not video.is_file():raise FileNotFoundError(str(video)) 
    folder=Path(output_dir).expanduser().resolve();folder.mkdir(parents=True,exist_ok=True) 
    manifest=folder.parent/(folder.name+'_manifest.json');st=video.stat() 
    spec=dict(schema=SCHEMA,video=str(video),size=st.st_size,mtime_ns=st.st_mtime_ns, 
              seconds_interval=seconds_interval,max_frame_size=max_frame_size,start=start,end=end) 
    if manifest.is_file(): 
        old=load_json(manifest);rows=old.get('frames',[]) 
        if old.get('spec')!=spec:raise RuntimeError('Extraction inputs/settings changed; choose new --work-dir/--frames-dir') 
        if rows and all((folder/r['name']).is_file() and digest(folder/r['name'])==r['sha256'] for r in rows): 
            return [str(folder/r['name']) for r in rows] 
        raise RuntimeError('Cached extracted frames missing/changed') 
    if any(folder.iterdir()):raise RuntimeError('Nonempty unmanaged frame directory; use --images-dir or choose a new directory') 
    cap=cv2.VideoCapture(str(video));rows=[] 
    try: 
        if not cap.isOpened():raise RuntimeError('Cannot open video') 
        fps=float(cap.get(cv2.CAP_PROP_FPS)) 
        if not math.isfinite(fps) or fps<=0:raise RuntimeError('Invalid reported FPS; inspect/transcode video; FPS is not guessed') 
        reported=float(cap.get(cv2.CAP_PROP_FRAME_COUNT));count=int(reported) if math.isfinite(reported) and reported>0 else 0 
        index=0;next_time=start 
        while cap.grab(): 
            t=index/fps 
            if end is not None and t>=end:break 
            if t+1e-9>=next_time: 
                ok,img=cap.retrieve() 
                if not ok or img is None:raise RuntimeError('Cannot decode frame '+str(index)) 
                next_time=start+(math.floor((t-start)/seconds_interval+1e-9)+1)*seconds_interval 
                h,w=img.shape[:2];s=min(1,max_frame_size/max(h,w)) 
                if s<1:img=cv2.resize(img,(max(1,round(w*s)),max(1,round(h*s))),interpolation=cv2.INTER_AREA) 
                name='frame_%010d.jpg'%index;path=folder/name 
                if not cv2.imwrite(str(path),img,[cv2.IMWRITE_JPEG_QUALITY,95]):raise RuntimeError('Cannot save '+str(path)) 
                rows.append(dict(name=name,source_frame=index,seconds=t,sha256=digest(path))) 
                if len(rows)%100==0:LOG.info('Extracted %d SLAM input frames at %.1fs',len(rows),t) 
            index+=1 
        if end is None and count and index<count-max(5,round(fps)):raise RuntimeError('Video decoding stopped unexpectedly early') 
        if len(rows)<12:raise RuntimeError('Need at least 12 sampled frames; choose a longer segment or higher --input-fps') 
        save_json(manifest,dict(spec=spec,original_fps=fps,frames=rows)) 
        return [str(folder/r['name']) for r in rows] 
    finally:cap.release() 
 
 
def list_images(path): 
    folder=Path(path).expanduser().resolve() 
    if not folder.is_dir():raise FileNotFoundError(str(folder)) 
    files=sorted((p for p in folder.iterdir() if p.suffix.lower() in ('.jpg','.jpeg','.png')),key=lambda p:p.name) 
    if len(files)<12:raise ValueError('Need at least 12 chronological input images') 
    return list(map(str,files)) 
 
 
def calibration_for_size(size,calib=None,calib_size=None,assume_hfov=None): 
    w,h=size 
    if calib is not None: 
        if assume_hfov is not None:raise ValueError('Choose --calib OR --assume-hfov') 
        if not calib_size:raise ValueError('--calib-size WIDTH HEIGHT is required with --calib') 
        v=np.asarray(np.loadtxt(calib),dtype=float).reshape(-1) 
        if len(v) not in (4,8,9,12) or not np.isfinite(v).all():raise ValueError('Calibration must contain fx fy cx cy and optional 4/5/8 OpenCV distortion coefficients') 
        cw,ch=calib_size 
        if cw<=0 or ch<=0 or abs((cw/ch)/(w/h)-1)>.005:raise ValueError('Calibration/source aspect ratios differ; use calibration for the actual video crop') 
        if v[0]<=0 or v[1]<=0 or not (0<=v[2]<cw and 0<=v[3]<ch):raise ValueError('Invalid focal length or principal point') 
        out=v.copy();out[[0,2]]*=w/cw;out[[1,3]]*=h/ch 
        return dict(values=out.tolist(),width=w,height=h,approximate=False,source_file=str(Path(calib).resolve()),calibration_size=[cw,ch]) 
    if assume_hfov is None:raise ValueError('Supply --calib camera.txt --calib-size W H, or explicitly opt into --assume-hfov DEGREES') 
    if not math.isfinite(assume_hfov) or not 20<assume_hfov<150:raise ValueError('Assumed horizontal FOV must be 20..150 degrees') 
    f=w/(2*math.tan(math.radians(assume_hfov)/2)) 
    LOG.warning('Using USER-ASSUMED intrinsics, NOT measured calibration; geometry can be wrong') 
    return dict(values=[f,f,(w-1)/2,(h-1)/2],width=w,height=h,approximate=True,assumed_hfov_degrees=assume_hfov) 
 
 
def demo_shape(h,w): 
    factor=math.sqrt(384*512/(h*w));h1=int(h*factor);w1=int(w*factor) 
    return h1,w1,h1-h1%8,w1-w1%8 
 
 
def run_logged(command,log_path,cwd=None): 
    LOG.info('Running: %s',subprocess.list2cmdline(command)) 
    with open(log_path,'a',encoding='utf-8') as log: 
        log.write('\nCOMMAND: '+json.dumps(command)+'\n');log.flush() 
        p=subprocess.Popen(command,cwd=str(cwd) if cwd else None,stdout=subprocess.PIPE,stderr=subprocess.STDOUT, 
                           text=True,encoding='utf-8',errors='replace',bufsize=1) 
        try: 
            for line in p.stdout:print(line,end='',flush=True);log.write(line);log.flush() 
            code=p.wait() 
        except BaseException: 
            p.terminate() 
            try:p.wait(timeout=15) 
            except subprocess.TimeoutExpired:p.kill();p.wait() 
            raise 
        if code:raise RuntimeError('Subprocess failed (%s); see %s'%(code,log_path)) 
 
 
def check_droid(repo,weights,droid_python,work): 
    repo=Path(repo).expanduser().resolve();weights=Path(weights).expanduser().resolve() 
    if not (repo/'demo.py').is_file() or not weights.is_file(): 
        raise ValueError('Provide an existing DROID-SLAM checkout and pretrained droid.pth') 
    work=Path(work).resolve();work.mkdir(parents=True,exist_ok=True) 
    check='import torch,lietorch,droid_backends; assert torch.cuda.is_available(), "CUDA is required"; print(torch.cuda.get_device_name(0)); print("GPU total bytes:",torch.cuda.get_device_properties(0).total_memory)' 
    run_logged([str(droid_python),'-c',check],work/'preflight.log',repo) 
 
 
CONVERT_CODE=r""" 
import sys,os,json,numpy as np,torch 
from scipy.spatial.transform import Rotation 
src,out=sys.argv[1:] 
# This is ONLY the locally generated official-demo reconstruction, never an untrusted .pt. 
try:data=torch.load(src,map_location='cpu',weights_only=True) 
except TypeError:data=torch.load(src,map_location='cpu') 
required={'tstamps','images','disps','poses','intrinsics'} 
if not required.issubset(data):raise RuntimeError('Unsupported DROID reconstruction schema') 
def a(k):return data[k].detach().cpu().numpy() 
poses=a('poses').astype(np.float64);K=a('intrinsics').astype(np.float64)*8.0 
D=a('disps').astype(np.float32);t=a('tstamps').astype(np.float64) 
N=len(t) 
if N<3 or poses.shape!=(N,7) or K.shape!=(N,4) or D.ndim!=3 or D.shape[0]!=N:raise RuntimeError('Invalid exported shapes') 
if not np.isfinite(poses).all() or not np.isfinite(K).all() or not np.isfinite(t).all():raise RuntimeError('Nonfinite poses/calibration/timestamps') 
if not np.allclose(t,np.round(t),atol=1e-4):raise RuntimeError('Expected integer demo image indexes, not physical timestamps') 
# Stored pose is world-to-camera [tx,ty,tz,qx,qy,qz,qw]. Invert to camera-to-world. 
Rcw=Rotation.from_quat(poses[:,3:]).as_matrix();Rwc=Rcw.transpose(0,2,1) 
C=-np.einsum('nij,nj->ni',Rwc,poses[:,:3]) 
T=np.tile(np.eye(4),(N,1,1));T[:,:3,:3]=Rwc;T[:,:3,3]=C 
if np.count_nonzero(np.isfinite(D)&(D>0))<100:raise RuntimeError('No usable upsampled inverse depths') 
os.makedirs(out,exist_ok=True) 
for name,array in [('camera_to_world',T),('intrinsics_full',K),('inverse_depth',D),('input_indexes',np.round(t).astype(np.int64))]: 
    np.save(os.path.join(out,name+'.npy'),array,allow_pickle=False) 
print(json.dumps({'keyframes':N,'depth_shape':list(D.shape),'pose':'camera_to_world','intrinsics':'saved intrinsics multiplied by 8'})) 
""" 
 
 
def reconstruct(frame_paths,work_dir,*,repo,weights,droid_python=sys.executable,calibration, 
                buffer=512,filter_thresh=2.4,keyframe_thresh=4.0): 
    from PIL import Image 
    repo=Path(repo).expanduser().resolve();weights=Path(weights).expanduser().resolve();work=Path(work_dir).resolve() 
    if not (repo/'demo.py').is_file() or not weights.is_file():raise ValueError('Provide real --droid-repo checkout and --weights droid.pth') 
    demo=(repo/'demo.py').read_text(encoding='utf-8') 
    for flag in ('--reconstruction_path','--disable_vis','--buffer'): 
        if flag not in demo:raise RuntimeError('Unsupported upstream demo.py interface: missing '+flag) 
    if buffer<16:raise ValueError('--buffer must be >=16') 
    positive(filter_thresh,'filter_thresh');positive(keyframe_thresh,'keyframe_thresh') 
    paths=[Path(p).expanduser().resolve() for p in frame_paths] 
    if len(paths)<12:raise ValueError('At least 12 chronological frames required') 
    for p in paths: 
        with Image.open(p) as im: 
            if im.size!=(calibration['width'],calibration['height']):raise ValueError('Mixed image sizes or calibration/image-size mismatch') 
    sources=[] 
    for relative in ('demo.py','droid_slam/depth_video.py','droid_slam/droid.py','droid_slam/motion_filter.py'): 
        p=repo/relative 
        if not p.is_file():raise RuntimeError('Unsupported repo layout: '+relative) 
        sources.append(dict(path=relative,sha256=digest(p))) 
    git=subprocess.run(['git','-C',str(repo),'rev-parse','HEAD'],capture_output=True,text=True) if shutil.which('git') else None 
    spec=dict(schema=SCHEMA,inputs=[dict(path=str(p),sha256=digest(p)) for p in paths],calibration=calibration, 
              weights_sha256=digest(weights),repo=str(repo),source_files=sources,git_commit=git.stdout.strip() if git and git.returncode==0 else None, 
              buffer=buffer,filter_thresh=filter_thresh,keyframe_thresh=keyframe_thresh,python=str(droid_python)) 
    work.mkdir(parents=True,exist_ok=True);manifest=work/'droid_manifest.json';done=work/'droid_success.json';maps=work/'reconstruction' 
    if manifest.is_file(): 
        if load_json(manifest)!=spec:raise RuntimeError('SLAM inputs/settings changed; use a NEW --work-dir') 
    else: 
        if (work/'slam_input').exists() or maps.exists():raise RuntimeError('Unmanaged reconstruction/staged images; choose a new work directory') 
        save_json(manifest,spec) 
    arrays=('camera_to_world.npy','intrinsics_full.npy','inverse_depth.npy','input_indexes.npy') 
    if done.is_file() and all((maps/n).is_file() for n in arrays): 
        hashes=load_json(done).get('hashes',{}) 
        if all(digest(maps/n)==hashes.get(n) for n in arrays):return maps 
        raise RuntimeError('Cached SLAM arrays changed; use a new work directory') 
    # GPU/dependency errors surface here before launching the expensive demo. 
    check_droid(repo,weights,droid_python,work) 
    staged=work/'slam_input';staged.mkdir(exist_ok=True) 
    expected=[] 
    for i,p in enumerate(paths): 
        target=staged/('frame_%010d'%i+p.suffix.lower());expected.append(target.name) 
        if not target.is_file() or digest(target)!=spec['inputs'][i]['sha256']:shutil.copyfile(p,target) 
    if {p.name for p in staged.iterdir()}!=set(expected):raise RuntimeError('Unexpected files in SLAM input directory') 
    calib_path=work/'calibration_staged.txt';np.savetxt(calib_path,np.asarray(calibration['values'])[None],fmt='%.12g') 
    pt=work/'reconstruction.pt';pt.unlink(missing_ok=True) 
    command=[str(droid_python),str(repo/'demo.py'),'--imagedir',str(staged),'--calib',str(calib_path), 
             '--stride','1','--weights',str(weights),'--buffer',str(buffer),'--filter_thresh',str(filter_thresh), 
             '--keyframe_thresh',str(keyframe_thresh),'--disable_vis','--reconstruction_path',str(pt)] 
    t0=time.perf_counter();run_logged(command,work/'droid.log',repo) 
    if not pt.is_file():raise RuntimeError('DROID did not produce reconstruction.pt') 
    maps.mkdir(exist_ok=True) 
    run_logged([str(droid_python),'-c',CONVERT_CODE,str(pt),str(maps)],work/'conversion.log',repo) 
    data=MapData(maps,calibration,paths) 
    if len(data.ids)>=buffer:LOG.warning('Keyframe buffer is full; inspect tracking and rerun in a new work directory with a suitable buffer') 
    keyframes=[dict(keyframe=i,input_index=int(k),source_file=str(paths[k])) for i,k in enumerate(data.ids)] 
    save_json(work/'keyframes.json',keyframes) 
    save_json(done,dict(hashes={n:digest(maps/n) for n in arrays},slam_and_conversion_seconds=time.perf_counter()-t0, 
                        retained_keyframes=len(data.ids),note='No claim that every input frame tracked successfully')) 
    return maps 
 
 
class MapData: 
    def __init__(self,maps,calibration,paths): 
        maps=Path(maps);self.T=np.load(maps/'camera_to_world.npy',mmap_mode='r');self.K=np.load(maps/'intrinsics_full.npy',mmap_mode='r') 
        self.disp=np.load(maps/'inverse_depth.npy',mmap_mode='r');self.ids=np.load(maps/'input_indexes.npy',mmap_mode='r') 
        self.paths=list(map(Path,paths));self.calibration=calibration 
        n=len(self.ids) 
        if n<3 or self.T.shape!=(n,4,4) or self.K.shape!=(n,4) or self.disp.ndim!=3 or self.disp.shape[0]!=n: 
            raise ValueError('Unsupported reconstruction array shapes') 
        if len(set(map(int,self.ids)))!=n or np.any(np.diff(self.ids)<=0) or np.any(self.ids<0) or np.any(self.ids>=len(paths)): 
            raise ValueError('Invalid keyframe/input index mapping') 
        if not np.isfinite(self.T).all() or not np.isfinite(self.K).all() or np.any(self.K[:,:2]<=0):raise ValueError('Invalid pose/intrinsics arrays') 
        h,w=calibration['height'],calibration['width'];h1,w1,hd,wd=demo_shape(h,w) 
        if self.disp.shape[1:]!=(hd,wd):raise RuntimeError('DROID demo resizing has changed; this adapter will not guess the geometry') 
        c=np.asarray(calibration['values'][:4]);expected=c*np.array([w1/w,h1/h,w1/w,h1/h]) 
        if not np.allclose(self.K,expected[None],rtol=2e-3,atol=.1):raise RuntimeError('Saved-intrinsics convention differs from inspected upstream (/8); refusing incorrect backprojection') 
        self.h1,self.w1=h1,w1;self.depth_h,self.depth_w=hd,wd 
    @lru_cache(maxsize=8) 
    def depth(self,i): 
        d=np.asarray(self.disp[i]);valid=np.isfinite(d)&(d>1e-8) 
        z=np.zeros_like(d,dtype=np.float32);z[valid]=1/d[valid] 
        if valid.any(): 
            med=float(np.median(z[valid]));valid&=(z>=med*.2)&(z<=med*5) 
        z[~valid]=0 
        return z 
 
 
def camera_frame(T): 
    views=np.asarray(T[:,:3,2],dtype=np.float64).copy();views/=np.linalg.norm(views,axis=1)[:,None] 
    mean=views.mean(axis=0);coherence=float(np.linalg.norm(mean)) 
    if coherence<.7:raise RuntimeError('Reconstructed camera directions are not consistently nadir; inspect SLAM') 
    up=-mean/coherence;x=np.asarray(T[0,:3,0]).copy();x-=up*np.dot(x,up) 
    if np.linalg.norm(x)<1e-8:raise RuntimeError('Degenerate local horizontal axis') 
    x/=np.linalg.norm(x);y=np.cross(up,x);y/=np.linalg.norm(y) 
    return np.stack([x,y,up]),np.median(T[:,:3,3],axis=0),coherence 
 
 
def backproject(u,v,z,K,T): 
    fx,fy,cx,cy=K 
    pc=np.column_stack(((u-cx)*z/fx,(v-cy)*z/fy,z)) 
    return pc@T[:3,:3].T+T[:3,3] 
 
 
def consistency(data,i,points,neighbors=2,tolerance=.06,min_support=1): 
    support=np.zeros(len(points),dtype=np.uint8) 
    for j in range(max(0,i-neighbors),min(len(data.ids),i+neighbors+1)): 
        if j==i:continue 
        pc=(points-data.T[j,:3,3])@data.T[j,:3,:3] 
        fx,fy,cx,cy=data.K[j];z=pc[:,2] 
        valid=np.isfinite(pc).all(axis=1)&(z>1e-8) 
        u=np.zeros(len(z));v=np.zeros(len(z));u[valid]=fx*pc[valid,0]/z[valid]+cx;v[valid]=fy*pc[valid,1]/z[valid]+cy 
        valid&=np.isfinite(u)&np.isfinite(v)&(u>=0)&(v>=0)&(u<data.depth_w-.5)&(v<data.depth_h-.5) 
        ix=np.zeros(len(z),dtype=np.int64);iy=np.zeros(len(z),dtype=np.int64) 
        ix[valid]=np.rint(u[valid]).astype(np.int64);iy[valid]=np.rint(v[valid]).astype(np.int64) 
        k=np.flatnonzero(valid) 
        other=data.depth(j)[iy[k],ix[k]] 
        good=(other>0)&(np.abs(other-z[k])<=tolerance*np.maximum(other,z[k])) 
        support[k[good]]+=1 
    return support>=min_support 
 
 
def sample_geometry(data,i,stride=4,border=.03,neighbors=2,tolerance=.06,min_support=1): 
    h,w=data.depth_h,data.depth_w 
    yy,xx=np.mgrid[0:h:stride,0:w:stride];u=xx.ravel();v=yy.ravel();z=data.depth(i)[v,u] 
    ok=(z>0)&(u>=border*w)&(u<(1-border)*w)&(v>=border*h)&(v<(1-border)*h) 
    points=backproject(u[ok],v[ok],z[ok],data.K[i],data.T[i]) 
    return points[consistency(data,i,points,neighbors,tolerance,min_support)] 
 
 
def pixel_plan(lo,hi,long_edge,max_megapixels): 
    if not 256<=long_edge<=20000:raise ValueError('--long-edge must be 256..20000') 
    positive(max_megapixels,'max_megapixels') 
    span=np.asarray(hi)-lo 
    if not np.isfinite(span).all() or np.any(span<=0):raise ValueError('Degenerate XY bounds') 
    # Pixel CENTERS include both extent endpoints. 
    ppu=(long_edge-1)/max(span);w=int(math.ceil(span[0]*ppu))+1;h=int(math.ceil(span[1]*ppu))+1 
    if w*h>max_megapixels*1e6:raise ValueError('Raster exceeds --max-megapixels; lower --long-edge or explicitly increase budget') 
    return dict(width=w,height=h,pixels_per_model_unit=float(ppu),xy_min=list(map(float,lo)),xy_max=list(map(float,hi))) 
 
 
def zbuffer_update(zbuf,scorebuf,rgbbuf,rows,cols,z,score,colors,height_tolerance): 
    """Deterministic per-pixel top-surface selection, then view score near that surface.""" 
    if not len(z):return 
    width=zbuf.shape[1];cell=rows.astype(np.int64)*width+cols 
    order=np.lexsort((-score,-z,cell));ordered=cell[order] 
    first=np.r_[True,ordered[1:]!=ordered[:-1]];take=order[first] 
    r,c=rows[take],cols[take];nz=z[take];ns=score[take] 
    old=zbuf[r,c];old_score=scorebuf[r,c] 
    replace=(nz>old+height_tolerance)|((np.abs(nz-old)<=height_tolerance)&(ns>old_score)) 
    k=take[replace];r,c=rows[k],cols[k] 
    zbuf[r,c]=z[k];scorebuf[r,c]=score[k];rgbbuf[r,c]=colors[k] 
 
 
def render_color_points(data,i,row_chunk=96,render_stride=1,border=.03): 
    import cv2 
    image=cv2.imread(str(data.paths[int(data.ids[i])])) 
    if image is None:raise RuntimeError('Cannot read keyframe source image') 
    cal=np.asarray(data.calibration['values']);K0=np.array([[cal[0],0,cal[2]],[0,cal[1],cal[3]],[0,0,1.]]) 
    if len(cal)>4:image=cv2.undistort(image,K0,cal[4:]) 
    h,w=image.shape[:2] 
    for y0 in range(0,h,row_chunk): 
        ys=np.arange(y0,min(h,y0+row_chunk),render_stride);xs=np.arange(0,w,render_stride) 
        yy,xx=np.meshgrid(ys,xs,indexing='ij') 
        u=xx.ravel();v=yy.ravel();ud=u*data.w1/w;vd=v*data.h1/h 
        inside=(ud>=border*data.depth_w)&(ud<(1-border)*data.depth_w)&(vd>=border*data.depth_h)&(vd<(1-border)*data.depth_h) 
        u,v,ud,vd=u[inside],v[inside],ud[inside],vd[inside] 
        # Bilinear interpolation of the estimated depth increases sampling density, NOT geometric information. 
        z=cv2.remap(data.depth(i),ud.astype(np.float32)[None],vd.astype(np.float32)[None],cv2.INTER_LINEAR, 
                    borderMode=cv2.BORDER_CONSTANT,borderValue=0).ravel() 
        near=data.depth(i)[np.clip(np.rint(vd).astype(int),0,data.depth_h-1),np.clip(np.rint(ud).astype(int),0,data.depth_w-1)] 
        ok=(z>0)&(near>0)&(np.abs(z-near)<=.1*np.maximum(z,near)) 
        if not ok.any():continue 
        u,v,ud,vd,z=u[ok],v[ok],ud[ok],vd[ok],z[ok] 
        points=backproject(ud,vd,z,data.K[i],data.T[i]) 
        fx,fy,cx,cy=data.K[i];score=1/(1+((ud-cx)/fx)**2+((vd-cy)/fy)**2) 
        # Crop source boundary and prefer image centers; color is from source, not low-res saved demo images. 
        score*=np.minimum.reduce([ud/data.depth_w,vd/data.depth_h,1-ud/data.depth_w,1-vd/data.depth_h]) 
        yield points,score.astype(np.float32),image[v,u,::-1] 
 
 
def export_raster(output_tif,output_image,rgb,zbuf,plan,axes,origin,fill_pixels=0,preview_max_size=None): 
    import rasterio 
    from rasterio.windows import Window 
    from rasterio.transform import from_origin 
    from PIL import Image 
    valid=np.isfinite(zbuf) 
    if not valid.any():raise RuntimeError('No valid orthographic pixels after visibility filtering') 
    if fill_pixels: 
        from scipy.ndimage import distance_transform_edt 
        distance,indices=distance_transform_edt(~valid,return_indices=True) 
        fill=(~valid)&(distance<=fill_pixels) 
        rgb[fill]=rgb[indices[0][fill],indices[1][fill]] 
        valid[fill]=True 
        del distance,indices 
    h,w=valid.shape;ppu=plan['pixels_per_model_unit'];lo=plan['xy_min'];hi=plan['xy_max'] 
    # Local arbitrary-unit affine, NO CRS; pixel centers match the sampled local positions. 
    transform=from_origin(lo[0]-.5/ppu,hi[1]+.5/ppu,1/ppu,1/ppu) 
    temporary=output_tif.with_name(output_tif.stem+'.writing.tif') 
    with rasterio.open(temporary,'w',driver='GTiff',width=w,height=h,count=4,dtype='uint8', 
                       transform=transform,compress='deflate',tiled=True,blockxsize=256,blockysize=256,BIGTIFF='IF_SAFER') as dst: 
        from rasterio.enums import ColorInterp 
        dst.colorinterp=(ColorInterp.red,ColorInterp.green,ColorInterp.blue,ColorInterp.alpha) 
        for row in range(0,h,256): 
            for col in range(0,w,256): 
                hh,ww=min(256,h-row),min(256,w-col);s=np.s_[row:row+hh,col:col+ww] 
                tile=np.asarray(rgb[s]).copy();tile[~valid[s]]=255 
                rgba=np.concatenate([tile,np.where(valid[s],255,0).astype(np.uint8)[...,None]],axis=2) 
                dst.write(np.moveaxis(rgba,2,0),window=Window(col,row,ww,hh)) 
        dst.update_tags(description='DROID-SLAM approximate orthographic color mosaic; NOT survey-controlled', 
                        model_units='arbitrary',orientation='camera-derived, not geographic north', 
                        local_axes=json.dumps(axes.tolist()),local_origin=json.dumps(origin.tolist())) 
    with rasterio.open(temporary) as src: 
        if (src.width,src.height)!=(w,h):raise RuntimeError('Exported raster dimensions do not match render') 
    temporary.replace(output_tif) 
    viewing=np.asarray(rgb).copy();viewing[~valid]=255 
    image=Image.fromarray(viewing) 
    if preview_max_size: 
        positive(preview_max_size,'preview_max_size');image.thumbnail((preview_max_size,preview_max_size),Image.Resampling.LANCZOS) 
    tmp=output_image.with_name(output_image.stem+'.writing'+output_image.suffix) 
    image.save(tmp,**({'quality':95,'subsampling':0} if output_image.suffix.lower() in ('.jpg','.jpeg') else {}));tmp.replace(output_image) 
    return dict(width=w,height=h,viewing_width=image.width,viewing_height=image.height,valid_fraction=float(valid.mean())) 
 
 
def render_orthomosaic(data,work,output,*,long_edge=6000,max_megapixels=64,neighbors=2, 
                       tolerance=.06,min_support=1,render_stride=1,border=.03,fill_pixels=0,preview_max_size=None): 
    work=Path(work).resolve();output=Path(output).expanduser().resolve() 
    if output.suffix.lower() not in ('.jpg','.jpeg','.png'):raise ValueError('--output must be JPG or PNG') 
    if not 1<=neighbors<=10 or not 1<=min_support<=2*neighbors:raise ValueError('Invalid neighbor/support settings') 
    if not 0<tolerance<.5 or not 0<=border<.2:raise ValueError('Invalid depth tolerance/border') 
    if not 1<=render_stride<=8 or not 0<=fill_pixels<=5:raise ValueError('Invalid render stride/hole fill') 
    axes,origin,coherence=camera_frame(data.T);lo=np.full(2,np.inf);hi=np.full(2,-np.inf);supported=0;nonempty=0 
    for i in range(len(data.ids)): 
        p=sample_geometry(data,i,4,border,neighbors,tolerance,min_support) 
        if len(p): 
            local=(p-origin)@axes.T;lo=np.minimum(lo,local[:,:2].min(axis=0));hi=np.maximum(hi,local[:,:2].max(axis=0));supported+=len(p);nonempty+=1 
    if supported<100 or nonempty<3:raise RuntimeError('Insufficient cross-view-consistent geometry; do not lower thresholds blindly; inspect calibration/tracking') 
    # Small padding: bounds came from sampled geometry, not necessarily exact surface perimeter. 
    pad=(hi-lo)*.01;lo-=pad;hi+=pad 
    plan=pixel_plan(lo,hi,long_edge,max_megapixels);h,w=plan['height'],plan['width'];ppu=plan['pixels_per_model_unit'] 
    LOG.info('Orthographic target %dx%d; %d retained keyframes, %d with sampled supported geometry',w,h,len(data.ids),nonempty) 
    raster_dir=work/'orthomosaic';raster_dir.mkdir(parents=True,exist_ok=True);output.parent.mkdir(parents=True,exist_ok=True) 
    # Disk-backed arrays prevent the entire z/color/score raster needing resident RAM simultaneously. 
    zbuf=np.memmap(raster_dir/'zbuffer.bin',dtype='float32',mode='w+',shape=(h,w));zbuf[:]=-np.inf 
    scorebuf=np.memmap(raster_dir/'score.bin',dtype='float32',mode='w+',shape=(h,w));scorebuf[:]=-np.inf 
    rgb=np.memmap(raster_dir/'rgb.bin',dtype='uint8',mode='w+',shape=(h,w,3));rgb[:]=255 
    scene_scale=float(np.median([np.median(data.depth(i)[data.depth(i)>0]) for i in range(len(data.ids)) if np.any(data.depth(i)>0)])) 
    t0=time.perf_counter();accepted=0 
    for i in range(len(data.ids)): 
        for points,score,color in render_color_points(data,i,render_stride=render_stride,border=border): 
            ok=consistency(data,i,points,neighbors,tolerance,min_support) 
            if not ok.any():continue 
            local=(points[ok]-origin)@axes.T;score=score[ok];color=color[ok] 
            col=np.rint((local[:,0]-lo[0])*ppu).astype(np.int64);row=np.rint((hi[1]-local[:,1])*ppu).astype(np.int64) 
            inside=(row>=0)&(col>=0)&(row<h)&(col<w) 
            if not inside.any():continue 
            zbuffer_update(zbuf,scorebuf,rgb,row[inside],col[inside],local[inside,2],score[inside],color[inside],scene_scale*.003) 
            accepted+=int(inside.sum()) 
        LOG.info('Rendered keyframe %d/%d',i+1,len(data.ids)) 
    zbuf.flush();scorebuf.flush();rgb.flush();tif=raster_dir/'orthomosaic.tif' 
    result=export_raster(tif,output,rgb,zbuf,plan,axes,origin,fill_pixels,preview_max_size) 
    save_json(work/'orthomosaic_report.json',dict(schema=SCHEMA,engine='DROID-SLAM + custom depth point-splat orthographic renderer', 
         retained_keyframes=len(data.ids),supported_sample_points=supported,keyframes_with_sampled_support=nonempty, 
         accepted_color_samples=accepted,plan=plan,result=result,view_coherence=coherence, 
         visibility=dict(neighbors=neighbors,min_other_view_support=min_support,relative_depth_tolerance=tolerance), 
         color_render_stride=render_stride,border_fraction=border,hole_fill_pixels=fill_pixels, 
         approximate_calibration=data.calibration.get('approximate',False),render_seconds=time.perf_counter()-t0, 
         local_axes=axes.tolist(),local_origin=origin.tolist(), 
         limitations='Approximate monocular depth-based orthographic mosaic; no mesh/surface optimization, radiometric equalization, ' 
                     'global seam optimization, independent loop detector, metric scale, georeferencing or accuracy guarantee. ' 
                     'Interpolated depth does not create detail; point splatting may leave holes and ghosts.')) 
    # Close mmap handles before removing scratch files (important on Windows). 
    del zbuf,scorebuf,rgb 
    for name in ('zbuffer.bin','score.bin','rgb.bin'):(raster_dir/name).unlink(missing_ok=True) 
    return tif 
 
 
def read_preview(tif,max_size=None): 
    import rasterio 
    from rasterio.enums import Resampling 
    with rasterio.open(tif) as src: 
        scale=min(1,max_size/max(src.width,src.height)) if max_size else 1 
        h,w=max(1,round(src.height*scale)),max(1,round(src.width*scale)) 
        rgb=src.read([1,2,3],out_shape=(3,h,w),resampling=Resampling.bilinear) 
        alpha=src.read(4,out_shape=(h,w),resampling=Resampling.nearest);rgb[:,alpha==0]=255 
        return np.ascontiguousarray(np.moveaxis(rgb,0,-1)[:,:,::-1]) 
 
 
def stitch_frames(frame_paths,*,work_dir='droid_work',repo,weights,calibration,long_edge=6000, 
                  droid_python=sys.executable,output='orthomosaic.jpg',**render_options): 
    """Legacy BGR-array contract with explicit new SLAM/calibration dependencies.""" 
    maps=reconstruct(frame_paths,work_dir,repo=repo,weights=weights,droid_python=droid_python,calibration=calibration) 
    data=MapData(maps,calibration,frame_paths) 
    return read_preview(render_orthomosaic(data,work_dir,output,long_edge=long_edge,**render_options)) 
 
 
def main(argv=None): 
    p=argparse.ArgumentParser(description=__doc__) 
    p.add_argument('video',nargs='?',default=VIDEO_PATH) 
    p.add_argument('--probe',action='store_true');p.add_argument('--images-dir') 
    p.add_argument('--check-droid',action='store_true',help='Test repo/weights/CUDA extensions without video extraction') 
    p.add_argument('--work-dir',default='droid_work');p.add_argument('--frames-dir') 
    p.add_argument('--droid-repo');p.add_argument('--weights');p.add_argument('--droid-python',default=sys.executable) 
    p.add_argument('--calib');p.add_argument('--calib-size',type=int,nargs=2,metavar=('WIDTH','HEIGHT')) 
    p.add_argument('--assume-hfov',type=float,help='Explicit approximate-intrinsics experiment, NOT measured camera calibration') 
    p.add_argument('--input-fps',type=float,default=8);p.add_argument('--interval',type=float,help='Overrides input-fps with seconds between samples') 
    p.add_argument('--max-frame-size',type=int,default=1600);p.add_argument('--start',type=float,default=0);p.add_argument('--end',type=float) 
    p.add_argument('--buffer',type=int,default=512);p.add_argument('--filter-thresh',type=float,default=2.4);p.add_argument('--keyframe-thresh',type=float,default=4) 
    p.add_argument('--render-only',action='store_true',help='Use saved maps/calibration; no CUDA/repo/weights needed') 
    p.add_argument('--long-edge',type=int,default=6000);p.add_argument('--max-megapixels',type=float,default=64) 
    p.add_argument('--neighbors',type=int,default=2);p.add_argument('--depth-tolerance',type=float,default=.06);p.add_argument('--min-support',type=int,default=1) 
    p.add_argument('--render-stride',type=int,default=1);p.add_argument('--border',type=float,default=.03) 
    p.add_argument('--hole-fill-pixels',type=int,default=0);p.add_argument('--preview-max-size',type=int) 
    p.add_argument('--output',default='orthomosaic.jpg') 
    a=p.parse_args(argv);logging.basicConfig(level=logging.INFO,format='%(levelname)s: %(message)s') 
    try: 
        if a.probe:print(json.dumps(probe_video(a.video),indent=2));return 0 
        if a.check_droid: 
            if not a.droid_repo or not a.weights:raise ValueError('--check-droid requires --droid-repo and --weights') 
            check_droid(a.droid_repo,a.weights,a.droid_python,a.work_dir);return 0 
        import rasterio # Missing export dependencies must fail BEFORE expensive reconstruction. 
        from PIL import Image 
        import cv2 
        if not 256<=a.long_edge<=20000:raise ValueError('--long-edge must be 256..20000') 
        positive(a.max_megapixels,'max_megapixels') 
        if a.preview_max_size is not None:positive(a.preview_max_size,'preview_max_size') 
        work=Path(a.work_dir).expanduser().resolve();work.mkdir(parents=True,exist_ok=True) 
        if a.render_only: 
            if a.calib or a.calib_size or a.assume_hfov is not None or a.images_dir or a.video!=VIDEO_PATH: 
                raise ValueError('--render-only uses saved sources/calibration; do not pass a new source or calibration') 
            spec=load_json(work/'droid_manifest.json');calibration=spec['calibration'];paths=[r['path'] for r in spec['inputs']] 
            if not all(Path(r['path']).is_file() and digest(r['path'])==r['sha256'] for r in spec['inputs']):raise RuntimeError('Source images changed/missing') 
            done=load_json(work/'droid_success.json') 
            if not all(digest(work/'reconstruction'/n)==h for n,h in done['hashes'].items()):raise RuntimeError('Cached reconstruction arrays changed') 
            maps=work/'reconstruction' 
        else: 
            if not a.droid_repo or not a.weights:raise ValueError('Supply --droid-repo and --weights, or use --render-only for an existing map') 
            if not a.calib and a.assume_hfov is None:raise ValueError('Measured intrinsics (--calib + --calib-size), or explicit --assume-hfov, are required') 
            if a.calib and (not a.calib_size or not Path(a.calib).is_file()):raise ValueError('Provide an existing --calib file AND --calib-size WIDTH HEIGHT') 
            if not (work/'droid_success.json').is_file():check_droid(a.droid_repo,a.weights,a.droid_python,work) 
            if a.images_dir: 
                if a.video!=VIDEO_PATH:raise ValueError('Choose positional video OR --images-dir') 
                paths=list_images(a.images_dir) 
            else: 
                positive(a.input_fps,'input_fps');interval=a.interval if a.interval is not None else 1/a.input_fps 
                paths=extract_frames(a.video,a.frames_dir or work/'frames',interval,max_frame_size=a.max_frame_size,start=a.start,end=a.end) 
            with Image.open(paths[0]) as im:size=im.size 
            calibration=calibration_for_size(size,a.calib,a.calib_size,a.assume_hfov) 
            maps=reconstruct(paths,work,repo=a.droid_repo,weights=a.weights,droid_python=a.droid_python, 
                      calibration=calibration,buffer=a.buffer,filter_thresh=a.filter_thresh,keyframe_thresh=a.keyframe_thresh) 
        data=MapData(maps,calibration,paths) 
        tif=render_orthomosaic(data,work,a.output,long_edge=a.long_edge,max_megapixels=a.max_megapixels, 
               neighbors=a.neighbors,tolerance=a.depth_tolerance,min_support=a.min_support,render_stride=a.render_stride, 
               border=a.border,fill_pixels=a.hole_fill_pixels,preview_max_size=a.preview_max_size) 
        LOG.info('Saved full TIFF: %s',tif);LOG.info('Saved viewing image: %s',Path(a.output).resolve()) 
        LOG.warning('Approximate depth-based orthographic mosaic, not a calibrated true orthophoto or a survey-controlled map.') 
        return 0 
    except KeyboardInterrupt:LOG.error('Interrupted; completed SLAM maps can be reused with --render-only');return 130 
    except Exception as exc:LOG.error('%s',exc);return 1 
 
 
if __name__=='__main__':raise SystemExit(main()) 


python .\generate_orthomosaic.py mainindexedvideo.mp4 --probe 

{ 

  "width": 1280, 

  "height": 720, 

  "reported_fps": 29.978690988842313, 

  "reported_frame_count": 9216.0, 

  "approximate_duration": 307.41835937499997 

}