0
# Morphological Operations
1
2
Comprehensive morphological image processing operations for shape analysis, noise reduction, object separation, and structural enhancement using mathematical morphology principles.
3
4
## Capabilities
5
6
### Basic Morphological Operations
7
8
Fundamental morphological operations using structuring elements for image processing.
9
10
```python { .api }
11
def BinaryErode(image: Image, kernel: Image = None, kernelType: int = sitk.sitkBall,
12
kernelRadius: list = [1, 1, 1], foregroundValue: float = 1.0,
13
backgroundValue: float = 0.0, boundaryToForeground: bool = False) -> Image:
14
"""
15
Binary erosion operation
16
17
Args:
18
image: Binary input image
19
kernel: Custom structuring element (optional)
20
kernelType: Type of structuring element (Ball, Box, Cross)
21
kernelRadius: Radius of structuring element per dimension
22
foregroundValue: Value representing foreground
23
backgroundValue: Value representing background
24
boundaryToForeground: Treat boundary as foreground
25
26
Returns:
27
Eroded binary image
28
"""
29
30
def BinaryDilate(image: Image, kernel: Image = None, kernelType: int = sitk.sitkBall,
31
kernelRadius: list = [1, 1, 1], foregroundValue: float = 1.0,
32
backgroundValue: float = 0.0, boundaryToForeground: bool = False) -> Image:
33
"""
34
Binary dilation operation
35
36
Args:
37
image: Binary input image
38
kernel: Custom structuring element (optional)
39
kernelType: Type of structuring element
40
kernelRadius: Radius of structuring element per dimension
41
foregroundValue: Value representing foreground
42
backgroundValue: Value representing background
43
boundaryToForeground: Treat boundary as foreground
44
45
Returns:
46
Dilated binary image
47
"""
48
49
def BinaryOpening(image: Image, kernelRadius: list = [1, 1, 1],
50
kernelType: int = sitk.sitkBall, foregroundValue: float = 1.0,
51
backgroundValue: float = 0.0) -> Image:
52
"""
53
Binary opening (erosion followed by dilation)
54
55
Args:
56
image: Binary input image
57
kernelRadius: Radius of structuring element
58
kernelType: Type of structuring element
59
foregroundValue: Value representing foreground
60
backgroundValue: Value representing background
61
62
Returns:
63
Opened binary image (removes small objects, smooths boundaries)
64
"""
65
66
def BinaryClosing(image: Image, kernelRadius: list = [1, 1, 1],
67
kernelType: int = sitk.sitkBall, foregroundValue: float = 1.0,
68
backgroundValue: float = 0.0) -> Image:
69
"""
70
Binary closing (dilation followed by erosion)
71
72
Args:
73
image: Binary input image
74
kernelRadius: Radius of structuring element
75
kernelType: Type of structuring element
76
foregroundValue: Value representing foreground
77
backgroundValue: Value representing background
78
79
Returns:
80
Closed binary image (fills holes, connects nearby objects)
81
"""
82
```
83
84
**Usage Examples:**
85
86
```python
87
import SimpleITK as sitk
88
89
# Load binary image
90
binary_image = sitk.ReadImage('binary_shapes.png')
91
92
# Basic morphological operations
93
eroded = sitk.BinaryErode(binary_image, kernelRadius=[2, 2], kernelType=sitk.sitkBall)
94
dilated = sitk.BinaryDilate(binary_image, kernelRadius=[2, 2], kernelType=sitk.sitkBall)
95
96
# Opening and closing
97
opened = sitk.BinaryOpening(binary_image, kernelRadius=[3, 3])
98
closed = sitk.BinaryClosing(binary_image, kernelRadius=[3, 3])
99
100
# Different structuring elements
101
ball_kernel = sitk.BinaryOpening(binary_image, kernelRadius=[2, 2],
102
kernelType=sitk.sitkBall)
103
box_kernel = sitk.BinaryOpening(binary_image, kernelRadius=[2, 2],
104
kernelType=sitk.sitkBox)
105
cross_kernel = sitk.BinaryOpening(binary_image, kernelRadius=[2, 2],
106
kernelType=sitk.sitkCross)
107
108
# Display results
109
sitk.Show(binary_image, "Original")
110
sitk.Show(opened, "Opened (removes noise)")
111
sitk.Show(closed, "Closed (fills gaps)")
112
sitk.Show(eroded, "Eroded")
113
sitk.Show(dilated, "Dilated")
114
```
115
116
### Grayscale Morphological Operations
117
118
Morphological operations extended to grayscale images for intensity-based processing.
119
120
```python { .api }
121
def GrayscaleErode(image: Image, kernel: Image = None, kernelType: int = sitk.sitkBall,
122
kernelRadius: list = [1, 1, 1]) -> Image:
123
"""
124
Grayscale erosion (local minimum)
125
126
Args:
127
image: Grayscale input image
128
kernel: Custom structuring element
129
kernelType: Type of structuring element
130
kernelRadius: Radius of structuring element
131
132
Returns:
133
Eroded grayscale image (darkens features)
134
"""
135
136
def GrayscaleDilate(image: Image, kernel: Image = None, kernelType: int = sitk.sitkBall,
137
kernelRadius: list = [1, 1, 1]) -> Image:
138
"""
139
Grayscale dilation (local maximum)
140
141
Args:
142
image: Grayscale input image
143
kernel: Custom structuring element
144
kernelType: Type of structuring element
145
kernelRadius: Radius of structuring element
146
147
Returns:
148
Dilated grayscale image (brightens features)
149
"""
150
151
def GrayscaleOpening(image: Image, kernelRadius: list = [1, 1, 1],
152
kernelType: int = sitk.sitkBall) -> Image:
153
"""
154
Grayscale opening (erosion then dilation)
155
156
Args:
157
image: Grayscale input image
158
kernelRadius: Radius of structuring element
159
kernelType: Type of structuring element
160
161
Returns:
162
Opened grayscale image (removes bright features smaller than kernel)
163
"""
164
165
def GrayscaleClosing(image: Image, kernelRadius: list = [1, 1, 1],
166
kernelType: int = sitk.sitkBall) -> Image:
167
"""
168
Grayscale closing (dilation then erosion)
169
170
Args:
171
image: Grayscale input image
172
kernelRadius: Radius of structuring element
173
kernelType: Type of structuring element
174
175
Returns:
176
Closed grayscale image (removes dark features smaller than kernel)
177
"""
178
179
def MorphologicalGradient(image: Image, kernelRadius: list = [1, 1, 1],
180
kernelType: int = sitk.sitkBall) -> Image:
181
"""
182
Morphological gradient (dilation - erosion)
183
184
Args:
185
image: Input image
186
kernelRadius: Radius of structuring element
187
kernelType: Type of structuring element
188
189
Returns:
190
Gradient image highlighting edges
191
"""
192
193
def WhiteTopHat(image: Image, kernelRadius: list = [1, 1, 1],
194
kernelType: int = sitk.sitkBall) -> Image:
195
"""
196
White top-hat transform (original - opening)
197
198
Args:
199
image: Input image
200
kernelRadius: Radius of structuring element
201
kernelType: Type of structuring element
202
203
Returns:
204
White top-hat image (bright features smaller than kernel)
205
"""
206
207
def BlackTopHat(image: Image, kernelRadius: list = [1, 1, 1],
208
kernelType: int = sitk.sitkBall) -> Image:
209
"""
210
Black top-hat transform (closing - original)
211
212
Args:
213
image: Input image
214
kernelRadius: Radius of structuring element
215
kernelType: Type of structuring element
216
217
Returns:
218
Black top-hat image (dark features smaller than kernel)
219
"""
220
```
221
222
**Usage Examples:**
223
224
```python
225
import SimpleITK as sitk
226
227
# Load grayscale image
228
image = sitk.ReadImage('texture.png', sitk.sitkFloat32)
229
230
# Basic grayscale morphology
231
eroded_gray = sitk.GrayscaleErode(image, kernelRadius=[3, 3])
232
dilated_gray = sitk.GrayscaleDilate(image, kernelRadius=[3, 3])
233
234
# Opening and closing for noise removal
235
opened_gray = sitk.GrayscaleOpening(image, kernelRadius=[2, 2])
236
closed_gray = sitk.GrayscaleClosing(image, kernelRadius=[2, 2])
237
238
# Morphological gradient for edge detection
239
gradient = sitk.MorphologicalGradient(image, kernelRadius=[1, 1])
240
241
# Top-hat transforms for feature extraction
242
white_tophat = sitk.WhiteTopHat(image, kernelRadius=[10, 10]) # Small bright features
243
black_tophat = sitk.BlackTopHat(image, kernelRadius=[10, 10]) # Small dark features
244
245
# Combine operations for enhanced processing
246
# Remove noise then enhance features
247
denoised = sitk.GrayscaleOpening(image, kernelRadius=[2, 2])
248
enhanced = sitk.Add(denoised, sitk.Multiply(white_tophat, 0.5))
249
250
# Display results
251
sitk.Show(image, "Original")
252
sitk.Show(gradient, "Morphological Gradient")
253
sitk.Show(white_tophat, "White Top-hat")
254
sitk.Show(enhanced, "Enhanced")
255
```
256
257
### Distance Maps and Skeletonization
258
259
Distance transforms and skeleton extraction for shape analysis.
260
261
```python { .api }
262
def SignedMaurerDistanceMap(image: Image, insideIsPositive: bool = False,
263
squaredDistance: bool = False, useImageSpacing: bool = False,
264
backgroundValue: float = 0.0) -> Image:
265
"""
266
Signed Maurer distance map
267
268
Args:
269
image: Binary input image
270
insideIsPositive: Whether inside distance is positive
271
squaredDistance: Return squared distances
272
useImageSpacing: Use actual image spacing
273
backgroundValue: Value representing background
274
275
Returns:
276
Signed distance map (negative inside, positive outside or vice versa)
277
"""
278
279
def DanielssonDistanceMap(image: Image, inputIsBinary: bool = False,
280
squaredDistance: bool = False, useImageSpacing: bool = False) -> Image:
281
"""
282
Danielsson distance map algorithm
283
284
Args:
285
image: Input image (binary or label)
286
inputIsBinary: Whether input is binary image
287
squaredDistance: Return squared distances
288
useImageSpacing: Use actual image spacing
289
290
Returns:
291
Distance map from object boundaries
292
"""
293
294
def BinaryThinning(image: Image) -> Image:
295
"""
296
Binary thinning (skeletonization)
297
298
Args:
299
image: Binary input image
300
301
Returns:
302
Thinned binary image (skeleton)
303
"""
304
305
def BinaryPruning(image: Image, iteration: int = 1) -> Image:
306
"""
307
Remove endpoints from binary skeleton
308
309
Args:
310
image: Binary skeleton image
311
iteration: Number of pruning iterations
312
313
Returns:
314
Pruned skeleton with endpoints removed
315
"""
316
```
317
318
**Usage Examples:**
319
320
```python
321
import SimpleITK as sitk
322
import numpy as np
323
324
# Load binary shape
325
shape = sitk.ReadImage('shape.png')
326
327
# Distance maps
328
distance_map = sitk.SignedMaurerDistanceMap(shape,
329
insideIsPositive=True,
330
useImageSpacing=True)
331
332
# Separate interior and exterior distances
333
interior_distance = sitk.Mask(distance_map, shape)
334
exterior_distance = sitk.Mask(distance_map, sitk.Not(shape))
335
336
# Skeletonization
337
skeleton = sitk.BinaryThinning(shape)
338
339
# Prune skeleton to remove small branches
340
pruned_skeleton = sitk.BinaryPruning(skeleton, iteration=3)
341
342
# Use distance map for morphological operations
343
# Create structuring element from distance
344
distance_array = sitk.GetArrayFromImage(distance_map)
345
max_distance = np.max(distance_array)
346
347
# Progressive morphological opening based on distance
348
opened_distance = sitk.BinaryOpening(shape,
349
kernelRadius=[int(max_distance//4)] * 2)
350
351
# Visualization
352
sitk.Show(shape, "Original Shape")
353
sitk.Show(distance_map, "Distance Map")
354
sitk.Show(skeleton, "Skeleton")
355
sitk.Show(pruned_skeleton, "Pruned Skeleton")
356
```
357
358
### Hit-or-Miss and Advanced Operations
359
360
Advanced morphological operations for pattern matching and complex shape analysis.
361
362
```python { .api }
363
def HitOrMiss(image: Image, kernel: Image, kernelType: int = sitk.sitkBall) -> Image:
364
"""
365
Hit-or-miss transform for pattern matching
366
367
Args:
368
image: Binary input image
369
kernel: Structuring element defining pattern
370
kernelType: Type of kernel if not providing custom kernel
371
372
Returns:
373
Binary image with pattern matches
374
"""
375
376
def BinaryMorphologicalOpening(image: Image, kernelRadius: list = [1, 1, 1],
377
kernelType: int = sitk.sitkBall,
378
foregroundValue: float = 1.0,
379
backgroundValue: float = 0.0) -> Image:
380
"""
381
Binary morphological opening with detailed control
382
383
Args:
384
image: Binary input image
385
kernelRadius: Radius of structuring element
386
kernelType: Type of structuring element
387
foregroundValue: Foreground pixel value
388
backgroundValue: Background pixel value
389
390
Returns:
391
Morphologically opened image
392
"""
393
394
def BinaryMorphologicalClosing(image: Image, kernelRadius: list = [1, 1, 1],
395
kernelType: int = sitk.sitkBall,
396
foregroundValue: float = 1.0,
397
backgroundValue: float = 0.0) -> Image:
398
"""
399
Binary morphological closing with detailed control
400
401
Args:
402
image: Binary input image
403
kernelRadius: Radius of structuring element
404
kernelType: Type of structuring element
405
foregroundValue: Foreground pixel value
406
backgroundValue: Background pixel value
407
408
Returns:
409
Morphologically closed image
410
"""
411
412
def VotingBinary(image: Image, radius: list = [1, 1, 1],
413
birthThreshold: int = 1, survivalThreshold: int = 1,
414
foregroundValue: float = 1.0, backgroundValue: float = 0.0) -> Image:
415
"""
416
Voting-based binary morphological operation
417
418
Args:
419
image: Binary input image
420
radius: Neighborhood radius for voting
421
birthThreshold: Votes needed to create foreground pixel
422
survivalThreshold: Votes needed to keep foreground pixel
423
foregroundValue: Foreground pixel value
424
backgroundValue: Background pixel value
425
426
Returns:
427
Filtered binary image based on voting
428
"""
429
```
430
431
**Usage Examples:**
432
433
```python
434
import SimpleITK as sitk
435
436
# Create custom structuring elements for hit-or-miss
437
def create_line_detector(length, angle_degrees):
438
"""Create structuring element for line detection"""
439
size = [length + 4, length + 4]
440
kernel = sitk.Image(size, sitk.sitkUInt8)
441
442
# Create line pattern (simplified example)
443
center = [size[0]//2, size[1]//2]
444
kernel[center[0], center[1]] = 1
445
# Add more points along line based on angle...
446
447
return kernel
448
449
# Load binary image with lines
450
binary_lines = sitk.ReadImage('lines.png')
451
452
# Detect horizontal lines
453
h_line_kernel = create_line_detector(length=7, angle_degrees=0)
454
horizontal_lines = sitk.HitOrMiss(binary_lines, h_line_kernel)
455
456
# Detect vertical lines
457
v_line_kernel = create_line_detector(length=7, angle_degrees=90)
458
vertical_lines = sitk.HitOrMiss(binary_lines, v_line_kernel)
459
460
# Voting-based noise removal
461
cleaned = sitk.VotingBinary(binary_lines,
462
radius=[2, 2],
463
birthThreshold=3,
464
survivalThreshold=2)
465
466
# Advanced morphological reconstruction
467
def morphological_reconstruction(marker, mask):
468
"""Morphological reconstruction by dilation"""
469
previous = marker
470
current = marker
471
472
while True:
473
# Dilate marker
474
current = sitk.BinaryDilate(previous, kernelRadius=[1, 1])
475
# Intersect with mask
476
current = sitk.And(current, mask)
477
478
# Check for convergence
479
if sitk.GetArrayFromImage(current).sum() == sitk.GetArrayFromImage(previous).sum():
480
break
481
previous = current
482
483
return current
484
485
# Example: Remove objects touching border
486
border_objects = sitk.ReadImage('border_objects.png')
487
h, w = border_objects.GetSize()
488
489
# Create marker from border pixels
490
marker = sitk.Image([w, h], sitk.sitkUInt8)
491
# Set border pixels as markers (simplified)
492
for i in range(w):
493
marker[i, 0] = border_objects[i, 0]
494
marker[i, h-1] = border_objects[i, h-1]
495
for j in range(h):
496
marker[0, j] = border_objects[0, j]
497
marker[w-1, j] = border_objects[w-1, j]
498
499
# Reconstruct border-connected objects
500
border_connected = morphological_reconstruction(marker, border_objects)
501
502
# Remove them from original
503
internal_objects = sitk.Subtract(border_objects, border_connected)
504
```
505
506
### Multi-Scale Morphological Operations
507
508
Morphological operations across multiple scales for comprehensive analysis.
509
510
```python { .api }
511
def MultiScaleMorphologicalOpening(image: Image, kernelType: int = sitk.sitkBall,
512
scales: list = [1, 2, 4]) -> list:
513
"""
514
Multi-scale morphological opening
515
516
Args:
517
image: Input binary/grayscale image
518
kernelType: Type of structuring element
519
scales: List of kernel radii for different scales
520
521
Returns:
522
List of opened images at different scales
523
"""
524
525
def MultiScaleMorphologicalClosing(image: Image, kernelType: int = sitk.sitkBall,
526
scales: list = [1, 2, 4]) -> list:
527
"""
528
Multi-scale morphological closing
529
530
Args:
531
image: Input binary/grayscale image
532
kernelType: Type of structuring element
533
scales: List of kernel radii for different scales
534
535
Returns:
536
List of closed images at different scales
537
"""
538
```
539
540
**Usage Examples:**
541
542
```python
543
import SimpleITK as sitk
544
545
def multiscale_morphological_analysis(image, scales=[1, 2, 4, 8]):
546
"""Comprehensive multi-scale morphological analysis"""
547
548
results = {
549
'openings': [],
550
'closings': [],
551
'white_tophats': [],
552
'black_tophats': [],
553
'gradients': []
554
}
555
556
for scale in scales:
557
radius = [scale, scale] if image.GetDimension() == 2 else [scale, scale, scale]
558
559
# Basic operations
560
opening = sitk.GrayscaleOpening(image, kernelRadius=radius)
561
closing = sitk.GrayscaleClosing(image, kernelRadius=radius)
562
563
# Feature extraction
564
white_tophat = sitk.WhiteTopHat(image, kernelRadius=radius)
565
black_tophat = sitk.BlackTopHat(image, kernelRadius=radius)
566
gradient = sitk.MorphologicalGradient(image, kernelRadius=radius)
567
568
results['openings'].append(opening)
569
results['closings'].append(closing)
570
results['white_tophats'].append(white_tophat)
571
results['black_tophats'].append(black_tophat)
572
results['gradients'].append(gradient)
573
574
return results
575
576
def granulometry_analysis(binary_image, max_scale=20):
577
"""Granulometry analysis for size distribution"""
578
579
granulometry = []
580
original_volume = sitk.GetArrayFromImage(binary_image).sum()
581
582
for scale in range(1, max_scale + 1):
583
opened = sitk.BinaryOpening(binary_image, kernelRadius=[scale, scale])
584
remaining_volume = sitk.GetArrayFromImage(opened).sum()
585
586
if original_volume > 0:
587
proportion = remaining_volume / original_volume
588
else:
589
proportion = 0
590
591
granulometry.append(proportion)
592
593
# Stop if all objects removed
594
if remaining_volume == 0:
595
break
596
597
return granulometry
598
599
# Apply multi-scale analysis
600
image = sitk.ReadImage('texture_image.png')
601
multiscale_results = multiscale_morphological_analysis(image)
602
603
# Combine results for enhanced processing
604
combined_features = multiscale_results['white_tophats'][0]
605
for i in range(1, len(multiscale_results['white_tophats'])):
606
combined_features = sitk.Add(combined_features,
607
multiscale_results['white_tophats'][i])
608
609
# Size analysis
610
binary_objects = sitk.ReadImage('particles.png')
611
size_distribution = granulometry_analysis(binary_objects, max_scale=15)
612
613
print("Size distribution:", size_distribution)
614
```
615
616
### Watershed and Region-Based Operations
617
618
Advanced morphological operations for object separation and region analysis.
619
620
```python { .api }
621
def HMinimaImageFilter(image: Image, height: float = 2.0,
622
fullyConnected: bool = False) -> Image:
623
"""
624
H-minima transform (suppress minima)
625
626
Args:
627
image: Grayscale input image
628
height: Minimum depth of minima to preserve
629
fullyConnected: Use full connectivity
630
631
Returns:
632
Image with shallow minima removed
633
"""
634
635
def HMaximaImageFilter(image: Image, height: float = 2.0,
636
fullyConnected: bool = False) -> Image:
637
"""
638
H-maxima transform (suppress maxima)
639
640
Args:
641
image: Grayscale input image
642
height: Minimum height of maxima to preserve
643
fullyConnected: Use full connectivity
644
645
Returns:
646
Image with low maxima removed
647
"""
648
649
def RegionalMinimaImageFilter(image: Image, fullyConnected: bool = False,
650
flatIsMinima: bool = True) -> Image:
651
"""
652
Find regional minima in image
653
654
Args:
655
image: Grayscale input image
656
fullyConnected: Use full connectivity
657
flatIsMinima: Consider flat regions as minima
658
659
Returns:
660
Binary image marking regional minima
661
"""
662
663
def RegionalMaximaImageFilter(image: Image, fullyConnected: bool = False,
664
flatIsMaxima: bool = True) -> Image:
665
"""
666
Find regional maxima in image
667
668
Args:
669
image: Grayscale input image
670
fullyConnected: Use full connectivity
671
flatIsMaxima: Consider flat regions as maxima
672
673
Returns:
674
Binary image marking regional maxima
675
"""
676
```
677
678
**Usage Examples:**
679
680
```python
681
import SimpleITK as sitk
682
683
def watershed_segmentation_workflow(image):
684
"""Complete watershed segmentation workflow"""
685
686
# Preprocessing
687
smoothed = sitk.Median(image, radius=[2, 2])
688
689
# Create gradient magnitude for watershed
690
gradient = sitk.GradientMagnitude(smoothed)
691
692
# Find markers using H-minima
693
h_minima_filter = sitk.HMinimaImageFilter()
694
h_minima_filter.SetHeight(10)
695
minima_markers = h_minima_filter.Execute(sitk.InvertIntensity(smoothed))
696
697
# Label the markers
698
markers = sitk.ConnectedComponent(minima_markers)
699
700
# Apply marker-controlled watershed
701
watershed_result = sitk.MorphologicalWatershedFromMarkers(
702
gradient, markers, markWatershedLine=True
703
)
704
705
# Post-processing: remove small regions
706
relabeled = sitk.RelabelComponent(watershed_result, minimumObjectSize=50)
707
708
return relabeled
709
710
def morphological_feature_extraction(image):
711
"""Extract morphological features for analysis"""
712
713
# Regional extrema
714
maxima = sitk.RegionalMaximaImageFilter()
715
maxima.SetFlatIsMaxima(False)
716
local_maxima = maxima.Execute(image)
717
718
minima = sitk.RegionalMinimaImageFilter()
719
minima.SetFlatIsMinima(False)
720
local_minima = minima.Execute(image)
721
722
# Multi-scale top-hat features
723
features = []
724
scales = [3, 7, 15, 31]
725
726
for scale in scales:
727
radius = [scale, scale]
728
729
# Bright features
730
white_th = sitk.WhiteTopHat(image, kernelRadius=radius)
731
features.append(('white_tophat_' + str(scale), white_th))
732
733
# Dark features
734
black_th = sitk.BlackTopHat(image, kernelRadius=radius)
735
features.append(('black_tophat_' + str(scale), black_th))
736
737
# Texture features
738
opening = sitk.GrayscaleOpening(image, kernelRadius=radius)
739
closing = sitk.GrayscaleClosing(image, kernelRadius=radius)
740
texture = sitk.Subtract(closing, opening)
741
features.append(('texture_' + str(scale), texture))
742
743
return {
744
'local_maxima': local_maxima,
745
'local_minima': local_minima,
746
'features': features
747
}
748
749
# Apply watershed segmentation
750
input_image = sitk.ReadImage('cells.tif')
751
segmented = watershed_segmentation_workflow(input_image)
752
753
# Extract morphological features
754
feature_results = morphological_feature_extraction(input_image)
755
756
# Display key results
757
sitk.Show(input_image, "Original")
758
sitk.Show(segmented, "Watershed Segmentation")
759
sitk.Show(feature_results['local_maxima'], "Local Maxima")
760
761
# Save feature images
762
for name, feature_image in feature_results['features']:
763
sitk.WriteImage(feature_image, f'feature_{name}.nii')
764
```
765
766
## Advanced Morphological Patterns
767
768
### Morphological Reconstruction
769
770
```python
771
import SimpleITK as sitk
772
773
def morphological_reconstruction_by_dilation(marker, mask, connectivity=4):
774
"""Morphological reconstruction by dilation"""
775
776
# Ensure marker is subset of mask
777
marker = sitk.And(marker, mask)
778
779
previous = marker
780
iteration = 0
781
max_iterations = 1000
782
783
while iteration < max_iterations:
784
# Geodesic dilation: dilate then intersect with mask
785
dilated = sitk.BinaryDilate(previous, kernelRadius=[1, 1])
786
current = sitk.And(dilated, mask)
787
788
# Check convergence
789
diff = sitk.Xor(current, previous)
790
if sitk.GetArrayFromImage(diff).sum() == 0:
791
break
792
793
previous = current
794
iteration += 1
795
796
return current
797
798
def morphological_reconstruction_by_erosion(marker, mask, connectivity=4):
799
"""Morphological reconstruction by erosion"""
800
801
# Ensure marker is superset of mask
802
marker = sitk.Or(marker, mask)
803
804
previous = marker
805
iteration = 0
806
max_iterations = 1000
807
808
while iteration < max_iterations:
809
# Geodesic erosion: erode then union with mask
810
eroded = sitk.BinaryErode(previous, kernelRadius=[1, 1])
811
current = sitk.Or(eroded, mask)
812
813
# Check convergence
814
diff = sitk.Xor(current, previous)
815
if sitk.GetArrayFromImage(diff).sum() == 0:
816
break
817
818
previous = current
819
iteration += 1
820
821
return current
822
823
def remove_border_objects(binary_image):
824
"""Remove objects touching image border"""
825
826
size = binary_image.GetSize()
827
828
# Create border marker
829
border_marker = sitk.Image(size, sitk.sitkUInt8)
830
831
# Set border pixels from original image
832
array = sitk.GetArrayFromImage(binary_image)
833
border_array = sitk.GetArrayFromImage(border_marker)
834
835
# Copy border pixels
836
border_array[0, :] = array[0, :] # Top
837
border_array[-1, :] = array[-1, :] # Bottom
838
border_array[:, 0] = array[:, 0] # Left
839
border_array[:, -1] = array[:, -1] # Right
840
841
border_marker = sitk.GetImageFromArray(border_array)
842
border_marker.CopyInformation(binary_image)
843
844
# Reconstruct border-connected components
845
border_objects = morphological_reconstruction_by_dilation(border_marker, binary_image)
846
847
# Remove from original
848
internal_objects = sitk.Subtract(binary_image, border_objects)
849
850
return internal_objects, border_objects
851
852
# Example usage
853
binary_img = sitk.ReadImage('binary_objects.png')
854
internal, border = remove_border_objects(binary_img)
855
```
856
857
### Morphological Filtering Chains
858
859
```python
860
import SimpleITK as sitk
861
862
class MorphologicalFilterChain:
863
"""Chain morphological operations with parameter optimization"""
864
865
def __init__(self, image):
866
self.image = image
867
self.operations = []
868
869
def add_operation(self, operation, **params):
870
"""Add morphological operation to chain"""
871
self.operations.append((operation, params))
872
return self
873
874
def execute(self):
875
"""Execute the complete filter chain"""
876
result = self.image
877
878
for operation, params in self.operations:
879
if operation == 'opening':
880
result = sitk.BinaryOpening(result, **params)
881
elif operation == 'closing':
882
result = sitk.BinaryClosing(result, **params)
883
elif operation == 'erosion':
884
result = sitk.BinaryErode(result, **params)
885
elif operation == 'dilation':
886
result = sitk.BinaryDilate(result, **params)
887
elif operation == 'thinning':
888
result = sitk.BinaryThinning(result)
889
elif operation == 'pruning':
890
result = sitk.BinaryPruning(result, **params)
891
# Add more operations as needed
892
893
return result
894
895
def optimize_parameters(self, ground_truth=None, metric='dice'):
896
"""Optimize filter parameters (simplified example)"""
897
if ground_truth is None:
898
return self
899
900
best_params = None
901
best_score = 0
902
903
# Simple grid search over kernel sizes
904
for kernel_size in [1, 2, 3, 5]:
905
# Update parameters
906
test_chain = MorphologicalFilterChain(self.image)
907
for op, params in self.operations:
908
new_params = params.copy()
909
if 'kernelRadius' in params:
910
new_params['kernelRadius'] = [kernel_size] * len(params['kernelRadius'])
911
test_chain.add_operation(op, **new_params)
912
913
# Execute and evaluate
914
result = test_chain.execute()
915
score = self._compute_dice(result, ground_truth)
916
917
if score > best_score:
918
best_score = score
919
best_params = kernel_size
920
921
# Update with best parameters
922
if best_params is not None:
923
for i, (op, params) in enumerate(self.operations):
924
if 'kernelRadius' in params:
925
self.operations[i] = (op, {
926
**params,
927
'kernelRadius': [best_params] * len(params['kernelRadius'])
928
})
929
930
return self
931
932
def _compute_dice(self, pred, truth):
933
"""Compute Dice coefficient"""
934
pred_array = sitk.GetArrayFromImage(pred)
935
truth_array = sitk.GetArrayFromImage(truth)
936
937
intersection = (pred_array * truth_array).sum()
938
union = pred_array.sum() + truth_array.sum()
939
940
if union == 0:
941
return 1.0
942
943
return 2.0 * intersection / union
944
945
# Example usage
946
def denoise_and_segment_workflow(noisy_binary):
947
"""Complete morphological processing workflow"""
948
949
# Create processing chain
950
chain = (MorphologicalFilterChain(noisy_binary)
951
.add_operation('opening', kernelRadius=[2, 2], kernelType=sitk.sitkBall)
952
.add_operation('closing', kernelRadius=[3, 3], kernelType=sitk.sitkBall)
953
.add_operation('opening', kernelRadius=[1, 1], kernelType=sitk.sitkCross))
954
955
# Execute chain
956
cleaned = chain.execute()
957
958
# Additional processing
959
skeleton = sitk.BinaryThinning(cleaned)
960
pruned = sitk.BinaryPruning(skeleton, iteration=2)
961
962
return {
963
'cleaned': cleaned,
964
'skeleton': skeleton,
965
'pruned_skeleton': pruned
966
}
967
968
# Apply workflow
969
noisy_image = sitk.ReadImage('noisy_binary.png')
970
results = denoise_and_segment_workflow(noisy_image)
971
```
972
973
## Type Definitions
974
975
```python { .api }
976
# Core Types
977
Image = sitk.Image
978
"""SimpleITK Image object"""
979
980
KernelType = int
981
"""Structuring element type (sitkBall, sitkBox, sitkCross, etc.)"""
982
983
# Kernel Types Constants
984
BALL_KERNEL = sitk.sitkBall
985
"""Spherical/circular structuring element"""
986
987
BOX_KERNEL = sitk.sitkBox
988
"""Rectangular/cubic structuring element"""
989
990
CROSS_KERNEL = sitk.sitkCross
991
"""Cross-shaped structuring element"""
992
993
# Parameter Types
994
KernelRadius = list[int]
995
"""Radius of structuring element per dimension"""
996
997
ForegroundValue = float
998
"""Value representing foreground pixels"""
999
1000
BackgroundValue = float
1001
"""Value representing background pixels"""
1002
1003
IterationCount = int
1004
"""Number of iterations for iterative operations"""
1005
1006
# Distance Transform Types
1007
DistanceMap = Image
1008
"""Distance transform result image"""
1009
1010
SignedDistanceMap = Image
1011
"""Signed distance transform (negative inside, positive outside)"""
1012
1013
# Morphological Operation Results
1014
MorphologicalResult = Image
1015
"""Result of morphological operation"""
1016
1017
SkeletonImage = Image
1018
"""Binary skeleton/medial axis image"""
1019
1020
# Advanced Operation Types
1021
ReconstructionMarker = Image
1022
"""Marker image for morphological reconstruction"""
1023
1024
ReconstructionMask = Image
1025
"""Mask image for morphological reconstruction"""
1026
1027
# Multi-scale Types
1028
ScaleList = list[int]
1029
"""List of scales/kernel sizes for multi-scale processing"""
1030
1031
FeatureImageSet = list[tuple[str, Image]]
1032
"""Named collection of morphological feature images"""
1033
1034
GranulometryProfile = list[float]
1035
"""Size distribution profile from granulometry analysis"""
1036
1037
# Voting Parameters
1038
VotingRadius = list[int]
1039
"""Neighborhood radius for voting operations"""
1040
1041
ThresholdValue = int
1042
"""Vote threshold for binary decisions"""
1043
```