Adapters
chemistry
biology
finance
legal
art
climate
agent
Merge
BACCHUS45 commited on
Commit
546a3df
·
verified ·
1 Parent(s): 2c6638a

Upload integral_ml_and_dashboard.md

Browse files
Files changed (1) hide show
  1. integral_ml_and_dashboard.md +440 -0
integral_ml_and_dashboard.md ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Integral — ML Verification Notebook + React GraphQL Integration + Map UI
2
+
3
+ This document contains three deliverables you requested: **(A) ML verification notebook** (pothole detection & verification), **(B) React dashboard GraphQL queries & subscription wiring**, and **(C) Map UI (Leaflet)** integration. Each section includes runnable code, dependency lists, and quickstart instructions.
4
+
5
+ ---
6
+
7
+ ## A — ML Verification Notebook (Jupyter, Python)
8
+
9
+ **Purpose:** Train a pothole detection model (segmentation + bbox) and produce a confidence score per detection for the verification-service. Exports inference results to the `detection-service` endpoint or writes directly to the Postgres `objects` table.
10
+
11
+ **Notes:** Use labeled images from vehicle dashcams, crowd-sourced uploads, and satellite tiles. This notebook uses PyTorch + torchvision and a simple U-Net-style segmentation + simple classifier for confidence.
12
+
13
+ ### Dependencies
14
+
15
+ ```bash
16
+ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # or CPU wheel
17
+ pip install jupyterlab opencv-python scikit-learn geopandas rasterio shapely matplotlib tqdm requests
18
+ pip install albumentations
19
+ ```
20
+
21
+ ### Notebook (save as `ml_verification.ipynb` — here shown as linear Python cells)
22
+
23
+ ```python
24
+ # cell 1: imports
25
+ import os
26
+ import json
27
+ from pathlib import Path
28
+ import random
29
+ import numpy as np
30
+ import cv2
31
+ import torch
32
+ import torch.nn as nn
33
+ from torch.utils.data import Dataset, DataLoader
34
+ import torchvision.transforms as T
35
+ import albumentations as A
36
+ from sklearn.model_selection import train_test_split
37
+ from tqdm import tqdm
38
+ import requests
39
+
40
+ # cell 2: config
41
+ DATA_DIR = Path('data')
42
+ IMAGES_DIR = DATA_DIR/'images'
43
+ MASKS_DIR = DATA_DIR/'masks' # segmentation masks where potholes marked
44
+ BATCH_SIZE = 8
45
+ NUM_EPOCHS = 20
46
+ DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
47
+ MODEL_DIR = Path('model')
48
+ MODEL_DIR.mkdir(parents=True, exist_ok=True)
49
+
50
+ # cell 3: dataset
51
+ class PotholeDataset(Dataset):
52
+ def __init__(self, image_paths, mask_paths, transforms=None):
53
+ self.images = image_paths
54
+ self.masks = mask_paths
55
+ self.transforms = transforms
56
+
57
+ def __len__(self):
58
+ return len(self.images)
59
+
60
+ def __getitem__(self, idx):
61
+ img = cv2.imread(str(self.images[idx]), cv2.IMREAD_COLOR)
62
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
63
+ mask = cv2.imread(str(self.masks[idx]), cv2.IMREAD_GRAYSCALE)
64
+ # normalize mask to 0/1
65
+ mask = (mask > 127).astype('float32')
66
+ if self.transforms:
67
+ augmented = self.transforms(image=img, mask=mask)
68
+ img = augmented['image']
69
+ mask = augmented['mask']
70
+ img = img.astype('float32') / 255.0
71
+ img = np.transpose(img, (2,0,1))
72
+ img_t = torch.tensor(img, dtype=torch.float32)
73
+ mask_t = torch.tensor(mask, dtype=torch.float32).unsqueeze(0)
74
+ return img_t, mask_t
75
+
76
+ # cell 4: simple U-Net model
77
+ class DoubleConv(nn.Module):
78
+ def __init__(self, in_c, out_c):
79
+ super().__init__()
80
+ self.net = nn.Sequential(
81
+ nn.Conv2d(in_c, out_c, 3, padding=1),
82
+ nn.ReLU(inplace=True),
83
+ nn.Conv2d(out_c, out_c, 3, padding=1),
84
+ nn.ReLU(inplace=True)
85
+ )
86
+ def forward(self,x): return self.net(x)
87
+
88
+ class UNet(nn.Module):
89
+ def __init__(self, n_channels=3, n_classes=1):
90
+ super().__init__()
91
+ self.enc1 = DoubleConv(n_channels, 64)
92
+ self.pool = nn.MaxPool2d(2)
93
+ self.enc2 = DoubleConv(64,128)
94
+ self.enc3 = DoubleConv(128,256)
95
+ self.enc4 = DoubleConv(256,512)
96
+ self.up3 = nn.ConvTranspose2d(512,256,2,stride=2)
97
+ self.dec3 = DoubleConv(512,256)
98
+ self.up2 = nn.ConvTranspose2d(256,128,2,stride=2)
99
+ self.dec2 = DoubleConv(256,128)
100
+ self.up1 = nn.ConvTranspose2d(128,64,2,stride=2)
101
+ self.dec1 = DoubleConv(128,64)
102
+ self.final = nn.Conv2d(64, n_classes, 1)
103
+ def forward(self,x):
104
+ e1 = self.enc1(x)
105
+ e2 = self.enc2(self.pool(e1))
106
+ e3 = self.enc3(self.pool(e2))
107
+ e4 = self.enc4(self.pool(e3))
108
+ d3 = self.dec3(torch.cat([self.up3(e4), e3], dim=1))
109
+ d2 = self.dec2(torch.cat([self.up2(d3), e2], dim=1))
110
+ d1 = self.dec1(torch.cat([self.up1(d2), e1], dim=1))
111
+ out = self.final(d1)
112
+ return torch.sigmoid(out)
113
+
114
+ # cell 5: prepare data lists (you must provide matching images & masks filenames)
115
+ image_files = sorted(list((IMAGES_DIR).glob('*.jpg')))
116
+ mask_files = sorted(list((MASKS_DIR).glob('*.png')))
117
+ train_imgs, val_imgs, train_masks, val_masks = train_test_split(image_files, mask_files, test_size=0.2, random_state=42)
118
+
119
+ transform = A.Compose([
120
+ A.Resize(256,256),
121
+ A.HorizontalFlip(p=0.5),
122
+ A.RandomBrightnessContrast(p=0.3),
123
+ ])
124
+
125
+ train_ds = PotholeDataset(train_imgs, train_masks, transforms=transform)
126
+ val_ds = PotholeDataset(val_imgs, val_masks, transforms=A.Compose([A.Resize(256,256)]))
127
+ train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)
128
+ val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False)
129
+
130
+ # cell 6: training loop
131
+ model = UNet().to(DEVICE)
132
+ optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
133
+ criterion = nn.BCELoss()
134
+
135
+ for epoch in range(NUM_EPOCHS):
136
+ model.train()
137
+ running_loss = 0.0
138
+ for imgs, masks in tqdm(train_loader, desc=f'train {epoch}'):
139
+ imgs = imgs.to(DEVICE); masks = masks.to(DEVICE)
140
+ preds = model(imgs)
141
+ loss = criterion(preds, masks)
142
+ optimizer.zero_grad(); loss.backward(); optimizer.step()
143
+ running_loss += loss.item()
144
+ print(f'Epoch {epoch} loss {running_loss/len(train_loader):.4f}')
145
+ # validation
146
+ model.eval()
147
+ val_loss = 0.0
148
+ with torch.no_grad():
149
+ for imgs, masks in val_loader:
150
+ imgs = imgs.to(DEVICE); masks = masks.to(DEVICE)
151
+ preds = model(imgs)
152
+ val_loss += criterion(preds, masks).item()
153
+ print(f'Val loss {val_loss/len(val_loader):.4f}')
154
+ torch.save(model.state_dict(), MODEL_DIR/f'unet_epoch_{epoch}.pt')
155
+
156
+ # cell 7: inference helper -> produce detections
157
+ import scipy.ndimage as ndi
158
+
159
+ def infer_and_extract(img_path, model, threshold=0.3, min_area=50):
160
+ img = cv2.imread(img_path)
161
+ h0,w0 = img.shape[:2]
162
+ img_r = cv2.resize(img, (256,256))
163
+ x = np.transpose(img_r.astype('float32')/255.0, (2,0,1))
164
+ x = torch.tensor(x).unsqueeze(0).to(DEVICE)
165
+ with torch.no_grad():
166
+ pred = model(x)[0,0].cpu().numpy()
167
+ # resize mask back
168
+ mask = cv2.resize((pred>threshold).astype('uint8'), (w0,h0))
169
+ labeled, n = ndi.label(mask)
170
+ detections = []
171
+ for region in range(1,n+1):
172
+ ys, xs = np.where(labeled==region)
173
+ if len(xs) < min_area: continue
174
+ x_min, x_max = xs.min(), xs.max()
175
+ y_min, y_max = ys.min(), ys.max()
176
+ area = len(xs)
177
+ conf = pred[ys, xs].mean() # approximate confidence
178
+ detections.append({
179
+ 'bbox': [int(x_min), int(y_min), int(x_max), int(y_max)],
180
+ 'area': int(area),
181
+ 'confidence': float(conf)
182
+ })
183
+ return detections, mask
184
+
185
+ # cell 8: export detections -> call detection-service
186
+ DETECTION_API = os.getenv('DETECTION_API','http://localhost:3001/detect')
187
+
188
+ def export_detection(location, image_url, detections, provenance):
189
+ # choose highest confidence detection
190
+ if not detections: return None
191
+ top = max(detections, key=lambda d: d['confidence'])
192
+ payload = {
193
+ 'namespace':'satellite',
194
+ 'type':'pothole-detection',
195
+ 'timestamp':None,
196
+ 'location': location,
197
+ 'severity': int(min(5, max(1, int(top['area']/500)))), # heuristic
198
+ 'images':[image_url],
199
+ 'provenance': provenance
200
+ }
201
+ try:
202
+ r = requests.post(DETECTION_API, json=payload, timeout=5)
203
+ return r.json()
204
+ except Exception as e:
205
+ print('export failed', e)
206
+ return None
207
+
208
+ # usage example
209
+ if __name__ == '__main__':
210
+ model.load_state_dict(torch.load(MODEL_DIR/'unet_epoch_19.pt', map_location=DEVICE))
211
+ test_img = 'data/ground/test1.jpg'
212
+ dets, mask = infer_and_extract(test_img, model)
213
+ print(dets)
214
+ # export with a dummy location/provenance
215
+ print(export_detection({'lat':-29.12,'lon':26.22}, 'https://example.com/test1.jpg', dets, {'source':'ml-run','license':'CC0'}))
216
+ ```
217
+
218
+ ---
219
+
220
+ ## B — React Dashboard: GraphQL queries & subscriptions (Apollo Client)
221
+
222
+ **Goal:** Wire the dashboard to the Apollo Server created earlier. Provide query examples, subscription usage, and UI integration (hooks + state).
223
+
224
+ ### Dependencies
225
+
226
+ ```bash
227
+ npm install @apollo/client graphql subscriptions-transport-ws graphql-ws
228
+ npm install leaflet react-leaflet
229
+ ```
230
+
231
+ > Note: `graphql-ws` is recommended for modern websockets; for Apollo v3 use `subscriptions-transport-ws` or adapt to your server.
232
+
233
+ ### Apollo client setup (src/apollo.js)
234
+
235
+ ```js
236
+ import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client';
237
+ import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
238
+ import { createClient } from 'graphql-ws';
239
+ import { getMainDefinition } from '@apollo/client/utilities';
240
+
241
+ const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
242
+ const wsLink = new GraphQLWsLink(createClient({ url: 'ws://localhost:4000/graphql' }));
243
+
244
+ const splitLink = split(
245
+ ({ query }) => {
246
+ const def = getMainDefinition(query);
247
+ return def.kind === 'OperationDefinition' && def.operation === 'subscription';
248
+ },
249
+ wsLink,
250
+ httpLink
251
+ );
252
+
253
+ export const client = new ApolloClient({
254
+ link: splitLink,
255
+ cache: new InMemoryCache(),
256
+ });
257
+ ```
258
+
259
+ ### Queries & Subscriptions (src/graphql/queries.js)
260
+
261
+ ```js
262
+ import { gql } from '@apollo/client';
263
+
264
+ export const LIST_FAULTS = gql`
265
+ query ListInfraFaults($limit:Int,$offset:Int){
266
+ listInfraFaults(limit:$limit,offset:$offset){
267
+ id namespace type timestamp severity confirmed images provenance
268
+ }
269
+ }
270
+ `;
271
+
272
+ export const FAULT_CREATED = gql`
273
+ subscription { faultCreated { id namespace type timestamp location severity confirmed images provenance } }
274
+ `;
275
+
276
+ export const FAULT_CONFIRMED = gql`
277
+ subscription { faultConfirmed { id confirmed } }
278
+ `;
279
+
280
+ export const PAYOUT_UPDATED = gql`
281
+ subscription { payoutUpdated { id faultId amountMinorUnits currency payeeId status txRef } }
282
+ `;
283
+ ```
284
+
285
+ ### React hook usage (src/hooks/useFaults.js)
286
+
287
+ ```js
288
+ import { useQuery, useSubscription } from '@apollo/client';
289
+ import { LIST_FAULTS, FAULT_CREATED, FAULT_CONFIRMED } from '../graphql/queries';
290
+
291
+ export function useFaults() {
292
+ const { data, loading, error, fetchMore } = useQuery(LIST_FAULTS, { variables: { limit: 50, offset: 0 } });
293
+
294
+ useSubscription(FAULT_CREATED, {
295
+ onSubscriptionData: ({ client, subscriptionData }) => {
296
+ const newFault = subscriptionData.data.faultCreated;
297
+ // optional: update cache or refetch
298
+ client.cache.modify({
299
+ fields: {
300
+ listInfraFaults(existing = []) {
301
+ const newRef = client.cache.writeFragment({
302
+ data: newFault,
303
+ fragment: gql`fragment NewFault on InfrastructureFault { id namespace type timestamp severity confirmed images provenance }`
304
+ });
305
+ return [newRef, ...existing];
306
+ }
307
+ }
308
+ });
309
+ }
310
+ });
311
+
312
+ useSubscription(FAULT_CONFIRMED, {
313
+ onSubscriptionData: ({ client, subscriptionData }) => {
314
+ const changed = subscriptionData.data.faultConfirmed;
315
+ // update cache entry
316
+ client.cache.modify({ id: client.cache.identify({ __typename: 'InfrastructureFault', id: changed.id }), fields: { confirmed() { return changed.confirmed; } } });
317
+ }
318
+ });
319
+
320
+ return { data, loading, error, fetchMore };
321
+ }
322
+ ```
323
+
324
+ ### Wiring into dashboard component (snippet)
325
+
326
+ ```js
327
+ import React from 'react';
328
+ import { useFaults } from './hooks/useFaults';
329
+
330
+ export default function InfraPanel(){
331
+ const { data, loading } = useFaults();
332
+ if(loading) return <div>Loading...</div>;
333
+ return (
334
+ <div>
335
+ {data.listInfraFaults.map(f => (
336
+ <div key={f.id} className="card">
337
+ <h4>{f.type} — severity {f.severity}</h4>
338
+ <p>Confirmed: {String(f.confirmed)}</p>
339
+ </div>
340
+ ))}
341
+ </div>
342
+ );
343
+ }
344
+ ```
345
+
346
+ ---
347
+
348
+ ## C — Map UI (Leaflet + React-Leaflet) with Satellite overlays
349
+
350
+ **Goal:** Display pothole markers, heatmap, and satellite overlays (Sentinel tile layers or Mapbox). Clicking a marker opens verification panel and create/settle actions.
351
+
352
+ ### Dependencies
353
+
354
+ ```bash
355
+ npm install leaflet react-leaflet leaflet.heat
356
+ ```
357
+
358
+ Also add CSS in your app root for leaflet:
359
+
360
+ ```css
361
+ /* index.css */
362
+ .leaflet-container { height: 100%; width: 100%; }
363
+ ```
364
+
365
+ ### Map component (src/components/MapView.jsx)
366
+
367
+ ```jsx
368
+ import React, { useEffect, useState } from 'react';
369
+ import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet';
370
+ import 'leaflet/dist/leaflet.css';
371
+ import L from 'leaflet';
372
+
373
+ // fix default icon issues in many bundlers
374
+ delete L.Icon.Default.prototype._getIconUrl;
375
+ L.Icon.Default.mergeOptions({
376
+ iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
377
+ iconUrl: require('leaflet/dist/images/marker-icon.png'),
378
+ shadowUrl: require('leaflet/dist/images/marker-shadow.png')
379
+ });
380
+
381
+ import { useFaults } from '../hooks/useFaults';
382
+
383
+ export default function MapView(){
384
+ const { data } = useFaults();
385
+ const [center] = useState([-29.12,26.22]);
386
+
387
+ return (
388
+ <MapContainer center={center} zoom={13} style={{height:'600px'}}>
389
+ <TileLayer
390
+ attribution='&copy; OpenStreetMap contributors'
391
+ url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
392
+ />
393
+
394
+ {/* Optional Satellite overlay with Mapbox (requires token): */}
395
+ {/* <TileLayer url={`https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/{z}/{x}/{y}?access_token=${process.env.MAPBOX_TOKEN}`} /> */}
396
+
397
+ {data && data.listInfraFaults.map(f => (
398
+ <Marker key={f.id} position={[f.location.lat,f.location.lon]}>
399
+ <Popup>
400
+ <div>
401
+ <strong>{f.type}</strong>
402
+ <p>Severity: {f.severity}</p>
403
+ <p>Confirmed: {String(f.confirmed)}</p>
404
+ {f.images && f.images.length>0 && <img src={f.images[0]} alt="pothole" style={{width:'200px'}}/>}
405
+ <div>
406
+ <button onClick={()=>{/* call confirm mutation */}}>Confirm</button>
407
+ </div>
408
+ </div>
409
+ </Popup>
410
+ </Marker>
411
+ ))}
412
+
413
+ </MapContainer>
414
+ );
415
+ }
416
+ ```
417
+
418
+ ### Heatmap (optional)
419
+
420
+ Use `leaflet.heat` plugin. Convert faults into weighted points by severity.
421
+
422
+ ---
423
+
424
+ ## Deployment & Running
425
+
426
+ 1. Start Postgres + Redis + Apollo server (see earlier `integral-apollo-server` canvas doc). Run migrations.
427
+ 2. Start the Apollo client React app (`npm start`) with `client` configured to `http://localhost:4000/graphql` and WS `ws://localhost:4000/graphql`.
428
+ 3. Prepare training data and run `ml_verification.ipynb` to train a model and generate detection exports to the detection-service endpoint.
429
+ 4. Use the dashboard to observe faults appearing in real-time and interact with map UI to verify and settle payouts.
430
+
431
+ ---
432
+
433
+ ## Next suggestions
434
+
435
+ - I can **export the ML notebook as an actual `.ipynb` file** and attach it for download.
436
+ - I can **generate the full React project files** (components, hooks, package.json) in the canvas and zip them.
437
+ - I can **produce a small sample dataset** (synthetic images + masks) so you can run training quickly.
438
+
439
+ Which of those would you like me to produce now?
440
+