or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotation-framework.mdcli.mdcoco-integration.mdimage-slicing.mdindex.mdmodel-integration.mdpostprocessing.mdprediction-functions.mdutilities.md

postprocessing.mddocs/

0

# Postprocessing

1

2

SAHI provides advanced postprocessing methods for combining overlapping predictions from sliced inference. These algorithms intelligently merge predictions to eliminate duplicates and improve detection accuracy across slice boundaries.

3

4

## Capabilities

5

6

### Postprocessing Base Class

7

8

Base class for all prediction postprocessing algorithms with configurable matching criteria.

9

10

```python { .api }

11

class PostprocessPredictions:

12

def __init__(

13

self,

14

match_threshold: float = 0.5,

15

match_metric: str = "IOS",

16

class_agnostic: bool = False,

17

):

18

"""

19

Initialize postprocessing configuration.

20

21

Parameters:

22

- match_threshold (float): Overlap threshold for matching predictions (0-1)

23

- match_metric (str): Overlap calculation method ("IOU" or "IOS")

24

- "IOU": Intersection over Union

25

- "IOS": Intersection over Smaller area

26

- class_agnostic (bool): Whether to ignore class when matching predictions

27

"""

28

29

def __call__(

30

self,

31

object_predictions: List[ObjectPrediction],

32

) -> List[ObjectPrediction]:

33

"""

34

Apply postprocessing to prediction list.

35

36

Parameters:

37

- object_predictions: List of ObjectPrediction instances

38

39

Returns:

40

List[ObjectPrediction]: Processed predictions with duplicates removed

41

"""

42

```

43

44

### Non-Maximum Suppression (NMS)

45

46

Classic NMS algorithm that removes predictions with high overlap, keeping only the highest confidence detection.

47

48

```python { .api }

49

class NMSPostprocess(PostprocessPredictions):

50

"""

51

Non-Maximum Suppression postprocessing.

52

Removes overlapping predictions, keeping only the highest confidence detection.

53

"""

54

55

def nms(

56

predictions: np.ndarray,

57

match_threshold: float = 0.5,

58

class_agnostic: bool = False,

59

) -> List[int]:

60

"""

61

Non-Maximum Suppression algorithm implementation.

62

63

Parameters:

64

- predictions (np.ndarray): Predictions array with bbox and scores

65

- match_threshold (float): IoU threshold for suppression

66

- class_agnostic (bool): Whether to apply NMS across all classes

67

68

Returns:

69

List[int]: Indices of predictions to keep

70

"""

71

72

def batched_nms(

73

predictions: np.ndarray,

74

match_threshold: float = 0.5,

75

class_agnostic: bool = False,

76

) -> List[int]:

77

"""

78

Batched Non-Maximum Suppression for efficient processing.

79

80

Parameters:

81

- predictions (np.ndarray): Predictions with bbox, scores, and classes

82

- match_threshold (float): IoU threshold for suppression

83

- class_agnostic (bool): Apply NMS across all classes

84

85

Returns:

86

List[int]: Indices of kept predictions

87

"""

88

```

89

90

### Non-Maximum Merging (NMM)

91

92

Advanced algorithm that merges overlapping predictions instead of simply removing them, preserving information from multiple detections.

93

94

```python { .api }

95

class NMMPostprocess(PostprocessPredictions):

96

"""

97

Non-Maximum Merging postprocessing.

98

Merges overlapping predictions instead of removing them, combining confidence scores

99

and bounding box coordinates to create more accurate final predictions.

100

"""

101

102

def nmm(

103

predictions: np.ndarray,

104

match_threshold: float = 0.5,

105

class_agnostic: bool = False,

106

) -> List[int]:

107

"""

108

Non-Maximum Merging algorithm implementation.

109

110

Parameters:

111

- predictions (np.ndarray): Predictions array with bbox and scores

112

- match_threshold (float): Overlap threshold for merging

113

- class_agnostic (bool): Whether to merge across all classes

114

115

Returns:

116

List[int]: Indices of final merged predictions

117

"""

118

119

def batched_nmm(

120

predictions: np.ndarray,

121

match_threshold: float = 0.5,

122

class_agnostic: bool = False,

123

) -> List[int]:

124

"""

125

Batched Non-Maximum Merging for efficient processing.

126

127

Parameters:

128

- predictions (np.ndarray): Predictions with bbox, scores, and classes

129

- match_threshold (float): Overlap threshold for merging

130

- class_agnostic (bool): Merge across all classes

131

132

Returns:

133

List[int]: Indices of merged predictions

134

"""

135

```

136

137

### Greedy Non-Maximum Merging

138

139

Greedy variant of NMM that processes predictions in confidence order for improved performance on sliced inference results.

140

141

```python { .api }

142

class GreedyNMMPostprocess(PostprocessPredictions):

143

"""

144

Greedy Non-Maximum Merging postprocessing.

145

Processes predictions in descending confidence order, greedily merging

146

overlapping detections. Optimized for sliced inference scenarios.

147

"""

148

149

def greedy_nmm(

150

predictions: np.ndarray,

151

match_threshold: float = 0.5,

152

class_agnostic: bool = False,

153

) -> List[int]:

154

"""

155

Greedy Non-Maximum Merging algorithm implementation.

156

157

Parameters:

158

- predictions (np.ndarray): Predictions array with bbox and scores

159

- match_threshold (float): Overlap threshold for merging

160

- class_agnostic (bool): Whether to merge across classes

161

162

Returns:

163

List[int]: Indices of merged predictions

164

"""

165

166

def batched_greedy_nmm(

167

predictions: np.ndarray,

168

match_threshold: float = 0.5,

169

class_agnostic: bool = False,

170

) -> List[int]:

171

"""

172

Batched Greedy Non-Maximum Merging for efficient processing.

173

174

Parameters:

175

- predictions (np.ndarray): Predictions with bbox, scores, and classes

176

- match_threshold (float): Overlap threshold for merging

177

- class_agnostic (bool): Merge predictions across all classes

178

179

Returns:

180

List[int]: Indices of kept predictions after merging

181

"""

182

```

183

184

### Linear Soft NMS

185

186

Soft NMS variant that gradually reduces confidence scores of overlapping predictions instead of hard removal.

187

188

```python { .api }

189

class LSNMSPostprocess(PostprocessPredictions):

190

"""

191

Linear Soft NMS postprocessing.

192

Applies soft suppression by linearly reducing confidence scores of overlapping

193

predictions instead of hard removal, preserving more detections.

194

"""

195

```

196

197

### Postprocessing Algorithm Mapping

198

199

```python { .api }

200

POSTPROCESS_NAME_TO_CLASS = {

201

"GREEDYNMM": GreedyNMMPostprocess,

202

"NMM": NMMPostprocess,

203

"NMS": NMSPostprocess,

204

"LSNMS": LSNMSPostprocess,

205

}

206

```

207

208

## Usage Examples

209

210

### Basic NMS Postprocessing

211

212

```python

213

from sahi.postprocess.combine import NMSPostprocess

214

from sahi import get_sliced_prediction

215

216

# Create NMS postprocessor

217

nms_postprocess = NMSPostprocess(

218

match_threshold=0.5,

219

match_metric="IOU",

220

class_agnostic=False

221

)

222

223

# Apply to sliced prediction

224

result = get_sliced_prediction(

225

image="large_image.jpg",

226

detection_model=model,

227

slice_height=640,

228

slice_width=640,

229

postprocess=nms_postprocess

230

)

231

232

print(f"Found {len(result.object_prediction_list)} objects after NMS")

233

```

234

235

### Greedy NMM for Sliced Inference

236

237

```python

238

from sahi.postprocess.combine import GreedyNMMPostprocess

239

240

# Recommended for sliced inference

241

greedy_nmm = GreedyNMMPostprocess(

242

match_threshold=0.5,

243

match_metric="IOS", # Intersection over Smaller area

244

class_agnostic=False

245

)

246

247

result = get_sliced_prediction(

248

image="satellite_image.tif",

249

detection_model=model,

250

slice_height=1024,

251

slice_width=1024,

252

postprocess=greedy_nmm

253

)

254

```

255

256

### Comparing Postprocessing Methods

257

258

```python

259

from sahi.postprocess.combine import (

260

NMSPostprocess,

261

GreedyNMMPostprocess,

262

NMMPostprocess

263

)

264

265

# Test different postprocessing approaches

266

postprocessors = {

267

"NMS": NMSPostprocess(match_threshold=0.5),

268

"NMM": NMMPostprocess(match_threshold=0.5),

269

"GreedyNMM": GreedyNMMPostprocess(match_threshold=0.5)

270

}

271

272

results = {}

273

for name, postprocessor in postprocessors.items():

274

result = get_sliced_prediction(

275

image="test_image.jpg",

276

detection_model=model,

277

postprocess=postprocessor

278

)

279

results[name] = len(result.object_prediction_list)

280

print(f"{name}: {results[name]} detections")

281

```

282

283

### Custom Postprocessing Configuration

284

285

```python

286

from sahi.postprocess.combine import GreedyNMMPostprocess

287

288

# Fine-tuned for specific use case

289

custom_postprocess = GreedyNMMPostprocess(

290

match_threshold=0.3, # Lower threshold for aggressive merging

291

match_metric="IOS", # Use Intersection over Smaller area

292

class_agnostic=True # Merge across different classes

293

)

294

295

# Apply with sliced prediction

296

result = get_sliced_prediction(

297

image="crowded_scene.jpg",

298

detection_model=model,

299

slice_height=512,

300

slice_width=512,

301

overlap_height_ratio=0.3, # Higher overlap

302

overlap_width_ratio=0.3,

303

postprocess=custom_postprocess,

304

verbose=2

305

)

306

```

307

308

### Using String-based Postprocessing

309

310

```python

311

from sahi.predict import get_sliced_prediction

312

313

# Use string identifiers for postprocessing

314

result = get_sliced_prediction(

315

image="image.jpg",

316

detection_model=model,

317

postprocess_type="GREEDYNMM", # Algorithm name

318

postprocess_match_metric="IOS", # Overlap metric

319

postprocess_match_threshold=0.5, # Threshold

320

postprocess_class_agnostic=False # Class-aware processing

321

)

322

```

323

324

### Direct Algorithm Usage

325

326

```python

327

from sahi.postprocess.combine import greedy_nmm, nms

328

import numpy as np

329

330

# Prepare predictions array [x1, y1, x2, y2, score, class_id]

331

predictions = np.array([

332

[10, 10, 50, 50, 0.9, 0], # High confidence person

333

[15, 15, 55, 55, 0.7, 0], # Overlapping person detection

334

[100, 100, 150, 150, 0.8, 1] # Car detection

335

])

336

337

# Apply Greedy NMM directly

338

kept_indices = greedy_nmm(

339

predictions=predictions,

340

match_threshold=0.5,

341

class_agnostic=False

342

)

343

344

final_predictions = predictions[kept_indices]

345

print(f"Kept {len(final_predictions)} predictions after merging")

346

347

# Compare with standard NMS

348

nms_indices = nms(

349

predictions=predictions,

350

match_threshold=0.5,

351

class_agnostic=False

352

)

353

354

print(f"NMS would keep {len(nms_indices)} predictions")

355

```

356

357

### Class-Agnostic vs Class-Aware Processing

358

359

```python

360

# Class-aware: merge only predictions of same class

361

class_aware = GreedyNMMPostprocess(

362

match_threshold=0.5,

363

class_agnostic=False # Only merge same-class predictions

364

)

365

366

# Class-agnostic: merge any overlapping predictions

367

class_agnostic = GreedyNMMPostprocess(

368

match_threshold=0.5,

369

class_agnostic=True # Merge overlapping predictions regardless of class

370

)

371

372

# Compare results

373

aware_result = get_sliced_prediction(

374

image="multi_class_scene.jpg",

375

detection_model=model,

376

postprocess=class_aware

377

)

378

379

agnostic_result = get_sliced_prediction(

380

image="multi_class_scene.jpg",

381

detection_model=model,

382

postprocess=class_agnostic

383

)

384

385

print(f"Class-aware: {len(aware_result.object_prediction_list)} detections")

386

print(f"Class-agnostic: {len(agnostic_result.object_prediction_list)} detections")

387

```