Checklist
0 / 21
Capstone Project · Brief 2026 v2

An interactive tool for diagnosing UAV survey plans.

A drone photographs a heritage structure from 185 planned viewpoints. Whether that plan is any good cannot be judged from a list of coordinates. You are building the instrument that lets an operator see the weak parts of a plan — and test whether one extra camera fixes them — before anything flies.

Scene
Kiri Vehera
Viewpoints
185
Surface targets
60 000
Mastery share
20 %
01

The brief in one page

Read the glossary once before anything else. Every technical term below — pose, picking, sector, weight, visibility — has a plain-language definition, reproduced in section 09.

What the tool is for

UAVs capture the photographs used to build 3D models of buildings, including sites of cultural significance. Before the drone flies, a planning algorithm produces a set of camera viewpoints — a position plus where it is pointing — intended to cover the whole surface from multiple angles.

The operator needs to know whether every part of the surface is observed often enough, from enough different directions, at a sensible distance and viewing angle. Your application answers that question visually, and lets the user intervene.

Provided · not marked

  • The viewscope_core library — every formula, constant and geometric computation
  • Pose construction and projection
  • Offscreen photographic and depth rendering
  • Visibility test with occlusion
  • Sector assignment, weights, scores
  • Safe-placement test; validating loaders
  • The Kiri Vehera data package

Yours · all of it marked

  • Architecture and the whole user interface
  • Picking, selection, drawing the plan
  • Surface colouring, heatmaps, sector and weight visualisations
  • First-person view, photo export, manual camera placement
  • Deciding when to call the library and caching what it returns
  • Incremental update, threading, process orchestration
  • Packaging, tests, documentation
The one sentence to remember

The library computes. Your application decides when to compute, remembers what was computed, and shows it.

No scaffold application is supplied

Standing up the GUI, 3D and packaging toolchain is itself part of the project. VTK is advised for the interactive viewport — it is fast and avoids performance problems later.

02

The data you are given

Five files, one world frame. +Z up, right-handed, metres, degrees. Nothing is ever recentred or rescaled, so geometry from any two files can be drawn together directly.

FileWhat it holdsUsed for
scene.objColoured triangle mesh — vertices carry real scanned colourDisplay and photography
targets.ply60 000 surface points, each with an outward normalCoverage is judged here — never at mesh vertices
plan.json185 viewpoints: position, yaw, pitch, plus the camera modelThe plan under inspection
flyable.npzCollision-free flight volume as a voxel gridWhere a UAV may safely be
visibility.npzWhich targets each shipped viewpoint can see, occlusion includedLookup for shipped cameras
scores.npzPer-observer weight, f_rho, f_theta, sector (8 and 4), best-per-sector, diversity, target scoreEverything the panels display

Surface targets are the measurement grid

They are 3D points spread evenly over the scanned surface, each carrying a normal — the direction sticking straight out of the surface. A camera can only usefully observe a target from the side its normal points towards. For every target, the package lists the viewpoints that can see it, with occlusion by the building already accounted for.

Each observing camera arrives with values you display, not compute

Sector

One of a fixed number of equal directional wedges measured around the target normal — like compass directions around the point. Eight is the default, four is the coarse alternative. Cameras in different sectors see the target from genuinely different directions, which is exactly what photogrammetry needs.

Weight

A number between 0 and 1 saying how useful that camera is for that target. It is supplied together with its two factors — one for distance, one for viewing angle — so your interface can show which of the two is holding a camera back.

Collision-free flight volume

A voxel grid marking where a UAV may safely be: outside the building, clear of it by the safe distance, above the minimum altitude, and reachable from outside airspace. Every planned viewpoint already lies inside it.

The division of labour is always the same

Shipped viewpoints are looked up. Edited viewpoints are recomputed through the library. A camera the user adds or moves has no precomputed values — your application must obtain them at interaction time by calling the library.

03

The library you call

viewscope_core is a dependency, not a starting point. You never reimplement, modify or tune any of its mathematics. If you believe something in it is wrong, report it — do not fork it. Install with pip install -e as described in the Resources README.

CallReturnsWhen you use it
load_scene / load_targetsMesh; positions + normalsStartup, with validation
load_planPlan: camera, ids, positions, yaws, pitchesStartup
load_scoresScores: weights, factors, sectors, best, diversity, scoreStartup — then look up
camera_pose(pos, yaw, pitch)4 × 4 pose matrixEvery time you need a pose. Never build it yourself
frustum_corners(...) / camera_glyph(...)Frustum geometry for drawingDrawing viewpoints as oriented pyramids
SceneRenderer.render_color / render_depthRGB image · depth mapFirst-person view, batch export, occlusion
camera_visibility(...)Boolean mask over all targetsEvery camera the user adds or moves
observer_geometry / weight_factorsρ, θ · weight, fρ, fθNew-camera scoring
sector_indices / best_per_sectorSector per observer · leaders + maximaNew-camera scoring
angular_diversity / target_scoreDiversity figure · target quality scoreSector plot, heatmap
check_placement(mesh, targets, pos)(ok, reason)Before accepting a manual placement

What a full recompute for one new camera looks like

Pattern — run this off the interface thread
# 1 · refuse unsafe placements before doing any work
ok, reason = vc.check_placement(mesh, targets.positions, position)
if not ok:
    return Rejected(reason)          # show `reason` to the user verbatim

# 2 · pose, then visibility against a freshly rendered depth map
pose  = vc.camera_pose(position, yaw_deg, pitch_deg)
depth = renderer.render_depth(pose)          # separate process — pyrender needs its own GL
vis   = vc.camera_visibility(depth, targets.positions, targets.normals,
                             position, pose, plan.camera)

# 3 · score this camera against every target it can see
seen = np.flatnonzero(vis)
for t in seen:
    rho, theta = vc.observer_geometry(targets.positions[t], targets.normals[t],
                                       position[None, :])
    w, f_rho, f_theta = vc.weight_factors(rho, theta)
    s = vc.sector_indices(targets.positions[t], targets.normals[t],
                            position[None, :], sector_count)

# 4 · cache the result, then update every dependent view incrementally
cache.put(camera_id, vis, weights, sectors)
bus.emit("camera_changed", camera_id)
Prove your wiring

Recompute the visibility of one shipped viewpoint through the library and match the packaged visibility.npz row for it exactly. Keep the input geometry and camera model precisely as shipped. This is the test that proves you are calling the library correctly — make it an automated test, not a one-off.

04

The scoring model

All constants live inside the library. You never type a numeric model parameter yourself — the values below are shown so you can read the panels, not so you can enter them.

ConstantValueMeaning
MU_RHO_M10.0 mPreferred stand-off distance — the reference ring on the sector plot
SIGMA_RHO_M5.0 mHow quickly usefulness falls off with distance
MU_THETA_DEG45°Preferred incidence angle to the target normal
SIGMA_THETA_DEG15°Angular tolerance around that preference
SAFE_DISTANCE_M5.0 mClosest a camera may be placed to the surface
DEFAULT_SECTOR_COUNT8Default directional wedges
ALTERNATE_SECTOR_COUNT4Coarse alternative — must be switchable in the UI
ZNEAR_M0.1 mNear plane; targets closer than this are not visible

How a target ends up with a score

Step 1 — weight per observing camera

Two Gaussian factors, multiplied. f_rho peaks when the camera is 10 m away; f_theta peaks at 45° off the normal. Both are supplied separately so a panel can say “this camera is fine on angle but far too close.”

Step 2 — sector assignment

Each observer's direction is projected into the tangent plane at the target and binned by azimuth into one of 8 (or 4) equal wedges. Exactly one wedge per observer.

Step 3 — best per sector

Within each wedge, the highest-weight camera wins. That is the camera your 3D view marks, and the bar the sector chart draws.

Step 4 — target score

The mean over all sectors of the best weight in that sector, with empty sectors counting zero. This is what the heatmap colours. A target seen brilliantly from one direction only still scores badly — which is the entire point.

Angular diversity

A separate figure: how evenly observers are spread across the wedges, capped at the ideal share per wedge. Shown on the sector plot alongside the score.

05

Core requirements

Sixteen items, 80 % of the marks. The approach below is the suggested one — you may modify it, but you must be able to explain your approach clearly and credit any external sources used. Tick items off as you complete them; your progress is saved in this browser.

06

Mastery work · 20 %

Work beyond the core. These are engineering extensions, not new mathematics — the marks awarded depend on the complexity of what you add. The items are independent of one another, and the animation and photography items may follow the raw viewpoint order.

07

Code structure

Four rules the brief states outright. They are cheap to satisfy early and expensive to retrofit.

Modularise as a pipeline

Loading and validation → scene construction → interaction and selection → scoring lookup and aggregation → presentation. Each stage should be replaceable without disturbing its neighbours.

Suggested package layout
viewscope_app/
├─ io/          # loaders, validation, fail-closed errors
├─ scene/       # VTK actors: mesh, targets, frustums, axes, ground
├─ interact/    # picking, selection state, the selection bus
├─ analysis/    # scoring lookup, aggregation, cache, incremental update
├─ services/    # RenderService — one caller-agnostic rendering path
├─ ui/          # dockable panels, parameter panel, metrics
└─ workers/     # thread pool + renderer subprocess, clean shutdown

Keep components replaceable

The rendering back end, the scoring source and the persistence format each sit behind an interface. Most importantly: the first-person view and the batch exporter must be two callers of one rendering service — not two rendering implementations.

Use appropriate data structures

NumPy arrays for numerical data, dictionaries for metadata. A per-target Python object for 60 000 targets will not stay interactive. Consider a plugin architecture for extensibility.

Treat the library as a dependency

Do not modify it. Report anything you believe is wrong instead of forking it.

The performance contract, restated

Navigation must not drop frames. Every click gives visible feedback within 0.2 s. Anything over 1 s shows progress and offers cancellation while the rest of the interface stays usable. That needs threads — and separate processes where the library renderer is involved — with clean shutdown. Caching, incremental update and deferred recomputation are yours to design.

08

What you hand in

The application is not the whole submission.

DeliverableMust contain
Project reportAll implementation steps, design rationale, challenges encountered, and the solutions you developed — documented comprehensively
GitLab READMEAppropriate usage instructions — how to install, run and operate the tool
ApplicationStays interactive on the full Kiri Vehera dataset: 60 000 targets, 185 viewpoints
TestsIncluding the shipped-viewpoint visibility match (requirement 8)
PackagingPart of the marked work — no scaffold application is supplied
AttributionCredit for any external sources used
09

Glossary

Every term the brief uses, in plain language. Read it once before the brief.

10

Knowledge check

Eighteen questions on the things that are easy to get wrong and expensive to discover late. Answer once per question; the explanation appears immediately.

0 / 18 Nothing answered yet.