Switch to unified view

a b/ants/contrib/sampling/affine3d.py
1
"""
2
Affine transforms
3
4
See http://www.cs.cornell.edu/courses/cs4620/2010fa/lectures/03transforms3d.pdf
5
"""
6
7
__all__ = [
8
    "Zoom3D",
9
    "RandomZoom3D",
10
    "Rotate3D",
11
    "RandomRotate3D",
12
    "Shear3D",
13
    "RandomShear3D",
14
    "Translate3D",
15
    "RandomTranslate3D",
16
    "Affine3D",
17
]
18
19
import random
20
import math
21
import numpy as np
22
23
from ...core import ants_transform as tio
24
25
class Affine3D(object):
26
    """
27
    Create a specified ANTs Affine Transform
28
    """
29
30
    def __init__(self, transformation, reference=None, lazy=False):
31
        """
32
        Initialize a Affine object
33
34
        Arguments
35
        ---------
36
        transformation : array
37
            affine transformation array (3x4)
38
39
        reference : ANTsImage (optional but recommended)
40
            image providing the reference space for the transform.
41
            this will also set the transform fixed parameters.
42
43
        lazy : boolean (default = False)
44
            if True, calling the `transform` method only returns
45
            the randomly generated transform and does not actually
46
            transform the image
47
        """
48
        if (not isinstance(transformation, np.ndarray) or transformation.shape != (3,4)):
49
            raise ValueError(
50
                "transformation argument must be 3x4 Numpy array!"
51
            )
52
53
        self.transformation = transformation
54
        self.lazy = lazy
55
        self.reference = reference
56
57
        self.tx = tio.ANTsTransform(
58
            precision="float", dimension=3, transform_type="AffineTransform"
59
        )
60
        if self.reference is not None:
61
            self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
62
63
    def transform(self, X=None, y=None):
64
        """
65
        Transform an image using an Affine transform with the given
66
        translation parameters.  Return the transform if X=None.
67
68
        Arguments
69
        ---------
70
        X : ANTsImage
71
            Image to transform
72
73
        y : ANTsImage (optional)
74
            Another image to transform
75
76
        Returns
77
        -------
78
        ANTsImage if y is None, else a tuple of ANTsImage types
79
80
        Examples
81
        --------
82
        >>> import ants
83
        >>> img = ants.image_read(ants.get_data('ch2'))
84
        >>> tx = ants.contrib.Affine3D(transformation=np.array([[1, 0, 0, dx], [0, 1, 0, dy],[0, 0, 1, dz]])
85
        >>> img2_x = tx.transform(img)# image translated by (dx, dy, dz)
86
        """
87
        # unpack
88
89
        transformation_matrix = self.transformation
90
91
92
        self.tx.set_parameters(transformation_matrix)
93
        if self.lazy or X is None:
94
            return self.tx
95
        else:
96
            if y is None:
97
                return self.tx.apply_to_image(X, reference=self.reference)
98
            else:
99
                return (
100
                    self.tx.apply_to_image(X, reference=self.reference),
101
                    self.tx.apply_to_image(y, reference=self.reference),
102
                )
103
104
105
class Translate3D(object):
106
    """
107
    Create an ANTs Affine Transform with a specified translation.
108
    """
109
110
    def __init__(self, translation, reference=None, lazy=False):
111
        """
112
        Initialize a Translate3D object
113
114
        Arguments
115
        ---------
116
        translation : list or tuple
117
            translation values for each axis, in degrees.
118
            Negative values can be used for translation in the
119
            other direction
120
121
        reference : ANTsImage (optional but recommended)
122
            image providing the reference space for the transform.
123
            this will also set the transform fixed parameters.
124
125
        lazy : boolean (default = False)
126
            if True, calling the `transform` method only returns
127
            the randomly generated transform and does not actually
128
            transform the image
129
        """
130
        if (not isinstance(translation, (list, tuple))) or (len(translation) != 3):
131
            raise ValueError(
132
                "translation argument must be list/tuple with three values!"
133
            )
134
135
        self.translation = translation
136
        self.lazy = lazy
137
        self.reference = reference
138
139
        self.tx = tio.ANTsTransform(
140
            precision="float", dimension=3, transform_type="AffineTransform"
141
        )
142
        if self.reference is not None:
143
            self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
144
145
    def transform(self, X=None, y=None):
146
        """
147
        Transform an image using an Affine transform with the given
148
        translation parameters.  Return the transform if X=None.
149
150
        Arguments
151
        ---------
152
        X : ANTsImage
153
            Image to transform
154
155
        y : ANTsImage (optional)
156
            Another image to transform
157
158
        Returns
159
        -------
160
        ANTsImage if y is None, else a tuple of ANTsImage types
161
162
        Examples
163
        --------
164
        >>> import ants
165
        >>> img = ants.image_read(ants.get_data('ch2'))
166
        >>> tx = ants.contrib.Translate3D(translation=(10,0,0))
167
        >>> img2_x = tx.transform(img)# x axis stays same
168
        >>> tx = ants.contrib.Translate3D(translation=(-10,0,0)) # other direction
169
        >>> img2_x = tx.transform(img)# x axis stays same
170
        >>> tx = ants.contrib.Translate3D(translation=(0,10,0))
171
        >>> img2_y = tx.transform(img) # y axis stays same
172
        >>> tx = ants.contrib.Translate3D(translation=(0,0,10))
173
        >>> img2_z = tx.transform(img) # z axis stays same
174
        >>> tx = ants.contrib.Translate3D(translation=(10,10,10))
175
        >>> img2 = tx.transform(img)
176
        """
177
        # unpack
178
        translation_x, translation_y, translation_z = self.translation
179
180
        translation_matrix = np.array(
181
            [
182
                [1, 0, 0, translation_x],
183
                [0, 1, 0, translation_y],
184
                [0, 0, 1, translation_z],
185
            ]
186
        )
187
        self.tx.set_parameters(translation_matrix)
188
        if self.lazy or X is None:
189
            return self.tx
190
        else:
191
            if y is None:
192
                return self.tx.apply_to_image(X, reference=self.reference)
193
            else:
194
                return (
195
                    self.tx.apply_to_image(X, reference=self.reference),
196
                    self.tx.apply_to_image(y, reference=self.reference),
197
                )
198
199
200
class RandomTranslate3D(object):
201
    """
202
    Apply a Translate3D transform to an image, but with the shear
203
    parameters randomly generated from a user-specified range.
204
    The range is determined by a mean (first parameter) and standard deviation
205
    (second parameter) via calls to random.gauss.
206
    """
207
208
    def __init__(self, translation_range, reference=None, lazy=False):
209
        """
210
        Initialize a RandomTranslate3D object
211
212
        Arguments
213
        ---------
214
        translation_range : list or tuple
215
            Lower and Upper bounds on rotation parameter, in degrees.
216
            e.g. translation_range = (-10,10) will result in a random
217
            draw of the rotation parameters between -10 and 10 degrees
218
219
        reference : ANTsImage (optional but recommended)
220
            image providing the reference space for the transform.
221
            this will also set the transform fixed parameters.
222
223
        lazy : boolean (default = False)
224
            if True, calling the `transform` method only returns
225
            the randomly generated transform and does not actually
226
            transform the image
227
        """
228
        if (not isinstance(translation_range, (list, tuple))) or (
229
            len(translation_range) != 2
230
        ):
231
            raise ValueError("shear_range argument must be list/tuple with two values!")
232
233
        self.translation_range = translation_range
234
        self.reference = reference
235
        self.lazy = lazy
236
237
    def transform(self, X=None, y=None):
238
        """
239
        Transform an image using an Affine transform with
240
        translation parameters randomly generated from the user-specified
241
        range.  Return the transform if X=None.
242
243
        Arguments
244
        ---------
245
        X : ANTsImage
246
            Image to transform
247
248
        y : ANTsImage (optional)
249
            Another image to transform
250
251
        Returns
252
        -------
253
        ANTsImage if y is None, else a tuple of ANTsImage types
254
255
        Examples
256
        --------
257
        >>> import ants
258
        >>> img = ants.image_read(ants.get_data('ch2'))
259
        >>> tx = ants.contrib.RandomShear3D(translation_range=(-10,10))
260
        >>> img2 = tx.transform(img)
261
        """
262
        # random draw in translation range
263
        translation_x = random.gauss(
264
            self.translation_range[0], self.translation_range[1]
265
        )
266
        translation_y = random.gauss(
267
            self.translation_range[0], self.translation_range[1]
268
        )
269
        translation_z = random.gauss(
270
            self.translation_range[0], self.translation_range[1]
271
        )
272
        self.params = (translation_x, translation_y, translation_z)
273
274
        tx = Translate3D(
275
            (translation_x, translation_y, translation_z),
276
            reference=self.reference,
277
            lazy=self.lazy,
278
        )
279
280
        return tx.transform(X, y)
281
282
283
class Shear3D(object):
284
    """
285
    Create an ANTs Affine Transform with a specified shear.
286
    """
287
288
    def __init__(self, shear, reference=None, lazy=False):
289
        """
290
        Initialize a Shear3D object
291
292
        Arguments
293
        ---------
294
        shear : list or tuple
295
            shear values for each axis, in degrees.
296
            Negative values can be used for shear in the
297
            other direction
298
299
        reference : ANTsImage (optional but recommended)
300
            image providing the reference space for the transform.
301
            this will also set the transform fixed parameters.
302
303
        lazy : boolean (default = False)
304
            if True, calling the `transform` method only returns
305
            the randomly generated transform and does not actually
306
            transform the image
307
        """
308
        if (not isinstance(shear, (list, tuple))) or (len(shear) != 3):
309
            raise ValueError("shear argument must be list/tuple with three values!")
310
311
        self.shear = shear
312
        self.lazy = lazy
313
        self.reference = reference
314
315
        self.tx = tio.ANTsTransform(
316
            precision="float", dimension=3, transform_type="AffineTransform"
317
        )
318
        if self.reference is not None:
319
            self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
320
321
    def transform(self, X=None, y=None):
322
        """
323
        Transform an image using an Affine transform with the given
324
        shear parameters.  Return the transform if X=None.
325
326
        Arguments
327
        ---------
328
        X : ANTsImage
329
            Image to transform
330
331
        y : ANTsImage (optional)
332
            Another image to transform
333
334
        Returns
335
        -------
336
        ANTsImage if y is None, else a tuple of ANTsImage types
337
338
        Examples
339
        --------
340
        >>> import ants
341
        >>> img = ants.image_read(ants.get_data('ch2'))
342
        >>> tx = ants.contrib.Shear3D(shear=(10,0,0))
343
        >>> img2_x = tx.transform(img)# x axis stays same
344
        >>> tx = ants.contrib.Shear3D(shear=(-10,0,0)) # other direction
345
        >>> img2_x = tx.transform(img)# x axis stays same
346
        >>> tx = ants.contrib.Shear3D(shear=(0,10,0))
347
        >>> img2_y = tx.transform(img) # y axis stays same
348
        >>> tx = ants.contrib.Shear3D(shear=(0,0,10))
349
        >>> img2_z = tx.transform(img) # z axis stays same
350
        >>> tx = ants.contrib.Shear3D(shear=(10,10,10))
351
        >>> img2 = tx.transform(img)
352
        """
353
        # convert to radians and unpack
354
        shear = [math.pi / 180 * s for s in self.shear]
355
        shear_x, shear_y, shear_z = shear
356
357
        shear_matrix = np.array(
358
            [
359
                [1, shear_x, shear_x, 0],
360
                [shear_y, 1, shear_y, 0],
361
                [shear_z, shear_z, 1, 0],
362
            ]
363
        )
364
        self.tx.set_parameters(shear_matrix)
365
        if self.lazy or X is None:
366
            return self.tx
367
        else:
368
            if y is None:
369
                return self.tx.apply_to_image(X, reference=self.reference)
370
            else:
371
                return (
372
                    self.tx.apply_to_image(X, reference=self.reference),
373
                    self.tx.apply_to_image(y, reference=self.reference),
374
                )
375
376
377
class RandomShear3D(object):
378
    """
379
    Apply a Shear3D transform to an image, but with the shear
380
    parameters randomly generated from a user-specified range.
381
    The range is determined by a mean (first parameter) and standard deviation
382
    (second parameter) via calls to random.gauss.
383
    """
384
385
    def __init__(self, shear_range, reference=None, lazy=False):
386
        """
387
        Initialize a RandomShear3D object
388
389
        Arguments
390
        ---------
391
        shear_range : list or tuple
392
            Lower and Upper bounds on rotation parameter, in degrees.
393
            e.g. shear_range = (-10,10) will result in a random
394
            draw of the rotation parameters between -10 and 10 degrees
395
396
        reference : ANTsImage (optional but recommended)
397
            image providing the reference space for the transform.
398
            this will also set the transform fixed parameters.
399
400
        lazy : boolean (default = False)
401
            if True, calling the `transform` method only returns
402
            the randomly generated transform and does not actually
403
            transform the image
404
        """
405
        if (not isinstance(shear_range, (list, tuple))) or (len(shear_range) != 2):
406
            raise ValueError("shear_range argument must be list/tuple with two values!")
407
408
        self.shear_range = shear_range
409
        self.reference = reference
410
        self.lazy = lazy
411
412
    def transform(self, X=None, y=None):
413
        """
414
        Transform an image using an Affine transform with
415
        shear parameters randomly generated from the user-specified
416
        range.  Return the transform if X=None.
417
418
        Arguments
419
        ---------
420
        X : ANTsImage
421
            Image to transform
422
423
        y : ANTsImage (optional)
424
            Another image to transform
425
426
        Returns
427
        -------
428
        ANTsImage if y is None, else a tuple of ANTsImage types
429
430
        Examples
431
        --------
432
        >>> import ants
433
        >>> img = ants.image_read(ants.get_data('ch2'))
434
        >>> tx = ants.contrib.RandomShear3D(shear_range=(-10,10))
435
        >>> img2 = tx.transform(img)
436
        """
437
        # random draw in shear range
438
        shear_x = random.gauss(self.shear_range[0], self.shear_range[1])
439
        shear_y = random.gauss(self.shear_range[0], self.shear_range[1])
440
        shear_z = random.gauss(self.shear_range[0], self.shear_range[1])
441
        self.params = (shear_x, shear_y, shear_z)
442
443
        tx = Shear3D(
444
            (shear_x, shear_y, shear_z), reference=self.reference, lazy=self.lazy
445
        )
446
447
        return tx.transform(X, y)
448
449
450
class Rotate3D(object):
451
    """
452
    Create an ANTs Affine Transform with a specified level
453
    of rotation.
454
    """
455
456
    def __init__(self, rotation, reference=None, lazy=False):
457
        """
458
        Initialize a Rotate3D object
459
460
        Arguments
461
        ---------
462
        rotation : list or tuple
463
            rotation values for each axis, in degrees.
464
            Negative values can be used for rotation in the
465
            other direction
466
467
        reference : ANTsImage (optional but recommended)
468
            image providing the reference space for the transform.
469
            this will also set the transform fixed parameters.
470
471
        lazy : boolean (default = False)
472
            if True, calling the `transform` method only returns
473
            the randomly generated transform and does not actually
474
            transform the image
475
        """
476
        if (not isinstance(rotation, (list, tuple))) or (len(rotation) != 3):
477
            raise ValueError("rotation argument must be list/tuple with three values!")
478
479
        self.rotation = rotation
480
        self.lazy = lazy
481
        self.reference = reference
482
483
        self.tx = tio.ANTsTransform(
484
            precision="float", dimension=3, transform_type="AffineTransform"
485
        )
486
        if self.reference is not None:
487
            self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
488
489
    def transform(self, X=None, y=None):
490
        """
491
        Transform an image using an Affine transform with the given
492
        rotation parameters.  Return the transform if X=None.
493
494
        Arguments
495
        ---------
496
        X : ANTsImage
497
            Image to transform
498
499
        y : ANTsImage (optional)
500
            Another image to transform
501
502
        Returns
503
        -------
504
        ANTsImage if y is None, else a tuple of ANTsImage types
505
506
        Examples
507
        --------
508
        >>> import ants
509
        >>> img = ants.image_read(ants.get_data('ch2'))
510
        >>> tx = ants.contrib.Rotate3D(rotation=(10,-5,12))
511
        >>> img2 = tx.transform(img)
512
        """
513
        # unpack zoom range
514
        rotation_x, rotation_y, rotation_z = self.rotation
515
516
        # Rotation about X axis
517
        theta_x = math.pi / 180 * rotation_x
518
        rotate_matrix_x = np.array(
519
            [
520
                [1, 0, 0, 0],
521
                [0, math.cos(theta_x), -math.sin(theta_x), 0],
522
                [0, math.sin(theta_x), math.cos(theta_x), 0],
523
                [0, 0, 0, 1],
524
            ]
525
        )
526
527
        # Rotation about Y axis
528
        theta_y = math.pi / 180 * rotation_y
529
        rotate_matrix_y = np.array(
530
            [
531
                [math.cos(theta_y), 0, math.sin(theta_y), 0],
532
                [0, 1, 0, 0],
533
                [-math.sin(theta_y), 0, math.cos(theta_y), 0],
534
                [0, 0, 0, 1],
535
            ]
536
        )
537
538
        # Rotation about Z axis
539
        theta_z = math.pi / 180 * rotation_z
540
        rotate_matrix_z = np.array(
541
            [
542
                [math.cos(theta_z), -math.sin(theta_z), 0, 0],
543
                [math.sin(theta_z), math.cos(theta_z), 0, 0],
544
                [0, 0, 1, 0],
545
                [0, 0, 0, 1],
546
            ]
547
        )
548
        rotate_matrix = rotate_matrix_x.dot(rotate_matrix_y).dot(rotate_matrix_z)[:3, :]
549
550
        self.tx.set_parameters(rotate_matrix)
551
        if self.lazy or X is None:
552
            return self.tx
553
        else:
554
            if y is None:
555
                return self.tx.apply_to_image(X, reference=self.reference)
556
            else:
557
                return (
558
                    self.tx.apply_to_image(X, reference=self.reference),
559
                    self.tx.apply_to_image(y, reference=self.reference),
560
                )
561
562
563
class RandomRotate3D(object):
564
    """
565
    Apply a Rotate3D transform to an image, but with the zoom
566
    parameters randomly generated from a user-specified range.
567
    The range is determined by a mean (first parameter) and standard deviation
568
    (second parameter) via calls to random.gauss.
569
    """
570
571
    def __init__(self, rotation_range, reference=None, lazy=False):
572
        """
573
        Initialize a RandomRotate3D object
574
575
        Arguments
576
        ---------
577
        rotation_range : list or tuple
578
            Lower and Upper bounds on rotation parameter, in degrees.
579
            e.g. rotation_range = (-10,10) will result in a random
580
            draw of the rotation parameters between -10 and 10 degrees
581
582
        reference : ANTsImage (optional but recommended)
583
            image providing the reference space for the transform.
584
            this will also set the transform fixed parameters.
585
586
        lazy : boolean (default = False)
587
            if True, calling the `transform` method only returns
588
            the randomly generated transform and does not actually
589
            transform the image
590
        """
591
        if (not isinstance(rotation_range, (list, tuple))) or (
592
            len(rotation_range) != 2
593
        ):
594
            raise ValueError(
595
                "rotation_range argument must be list/tuple with two values!"
596
            )
597
598
        self.rotation_range = rotation_range
599
        self.reference = reference
600
        self.lazy = lazy
601
602
    def transform(self, X=None, y=None):
603
        """
604
        Transform an image using an Affine transform with
605
        rotation parameters randomly generated from the user-specified
606
        range.  Return the transform if X=None.
607
608
        Arguments
609
        ---------
610
        X : ANTsImage
611
            Image to transform
612
613
        y : ANTsImage (optional)
614
            Another image to transform
615
616
        Returns
617
        -------
618
        ANTsImage if y is None, else a tuple of ANTsImage types
619
620
        Examples
621
        --------
622
        >>> import ants
623
        >>> img = ants.image_read(ants.get_data('ch2'))
624
        >>> tx = ants.contrib.RandomRotate3D(rotation_range=(-10,10))
625
        >>> img2 = tx.transform(img)
626
        """
627
        # random draw in rotation range
628
        rotation_x = random.gauss(self.rotation_range[0], self.rotation_range[1])
629
        rotation_y = random.gauss(self.rotation_range[0], self.rotation_range[1])
630
        rotation_z = random.gauss(self.rotation_range[0], self.rotation_range[1])
631
        self.params = (rotation_x, rotation_y, rotation_z)
632
633
        tx = Rotate3D(
634
            (rotation_x, rotation_y, rotation_z),
635
            reference=self.reference,
636
            lazy=self.lazy,
637
        )
638
639
        return tx.transform(X, y)
640
641
642
class Zoom3D(object):
643
    """
644
    Create an ANTs Affine Transform with a specified level
645
    of zoom. Any value greater than 1 implies a "zoom-out" and anything
646
    less than 1 implies a "zoom-in".
647
    """
648
649
    def __init__(self, zoom, reference=None, lazy=False):
650
        """
651
        Initialize a Zoom3D object
652
653
        Arguments
654
        ---------
655
        zoom_range : list or tuple
656
            Lower and Upper bounds on zoom parameter.
657
            e.g. zoom_range = (0.7,0.9) will result in a random
658
            draw of the zoom parameters between 0.7 and 0.9
659
660
        reference : ANTsImage (optional but recommended)
661
            image providing the reference space for the transform.
662
            this will also set the transform fixed parameters.
663
664
        lazy : boolean (default = False)
665
            if True, calling the `transform` method only returns
666
            the randomly generated transform and does not actually
667
            transform the image
668
        """
669
        if (not isinstance(zoom, (list, tuple))) or (len(zoom) != 3):
670
            raise ValueError(
671
                "zoom_range argument must be list/tuple with three values!"
672
            )
673
674
        self.zoom = zoom
675
        self.lazy = lazy
676
        self.reference = reference
677
678
        self.tx = tio.ANTsTransform(
679
            precision="float", dimension=3, transform_type="AffineTransform"
680
        )
681
        if self.reference is not None:
682
            self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
683
684
    def transform(self, X=None, y=None):
685
        """
686
        Transform an image using an Affine transform with the given
687
        zoom parameters.  Return the transform if X=None.
688
689
        Arguments
690
        ---------
691
        X : ANTsImage
692
            Image to transform
693
694
        y : ANTsImage (optional)
695
            Another image to transform
696
697
        Returns
698
        -------
699
        ANTsImage if y is None, else a tuple of ANTsImage types
700
701
        Examples
702
        --------
703
        >>> import ants
704
        >>> img = ants.image_read(ants.get_data('ch2'))
705
        >>> tx = ants.contrib.Zoom3D(zoom=(0.8,0.8,0.8))
706
        >>> img2 = tx.transform(img)
707
        """
708
        # unpack zoom range
709
        zoom_x, zoom_y, zoom_z = self.zoom
710
711
        self.params = (zoom_x, zoom_y, zoom_z)
712
        zoom_matrix = np.array(
713
            [[zoom_x, 0, 0, 0], [0, zoom_y, 0, 0], [0, 0, zoom_z, 0]]
714
        )
715
        self.tx.set_parameters(zoom_matrix)
716
        if self.lazy or X is None:
717
            return self.tx
718
        else:
719
            if y is None:
720
                return self.tx.apply_to_image(X, reference=self.reference)
721
            else:
722
                return (
723
                    self.tx.apply_to_image(X, reference=self.reference),
724
                    self.tx.apply_to_image(y, reference=self.reference),
725
                )
726
727
728
class RandomZoom3D(object):
729
    """
730
    Apply a Zoom3D transform to an image, but with the zoom
731
    parameters randomly generated from a user-specified range.
732
    The range is determined by a mean (first parameter) and standard deviation
733
    (second parameter) via calls to random.gauss.
734
    """
735
736
    def __init__(self, zoom_range, reference=None, lazy=False):
737
        """
738
        Initialize a RandomZoom3D object
739
740
        Arguments
741
        ---------
742
        zoom_range : list or tuple
743
            Lower and Upper bounds on zoom parameter.
744
            e.g. zoom_range = (0.7,0.9) will result in a random
745
            draw of the zoom parameters between 0.7 and 0.9
746
747
        reference : ANTsImage (optional but recommended)
748
            image providing the reference space for the transform
749
            this will also set the transform fixed parameters.
750
751
        lazy : boolean (default = False)
752
            if True, calling the `transform` method only returns
753
            the randomly generated transform and does not actually
754
            transform the image
755
        """
756
        if (not isinstance(zoom_range, (list, tuple))) or (len(zoom_range) != 2):
757
            raise ValueError("zoom_range argument must be list/tuple with two values!")
758
759
        self.zoom_range = zoom_range
760
        self.reference = reference
761
        self.lazy = lazy
762
763
    def transform(self, X=None, y=None):
764
        """
765
        Transform an image using an Affine transform with
766
        zoom parameters randomly generated from the user-specified
767
        range.  Return the transform if X=None.
768
769
        Arguments
770
        ---------
771
        X : ANTsImage
772
            Image to transform
773
774
        y : ANTsImage (optional)
775
            Another image to transform
776
777
        Returns
778
        -------
779
        ANTsImage if y is None, else a tuple of ANTsImage types
780
781
        Examples
782
        --------
783
        >>> import ants
784
        >>> img = ants.image_read(ants.get_data('ch2'))
785
        >>> tx = ants.contrib.RandomZoom3D(zoom_range=(0.8,0.9))
786
        >>> img2 = tx.transform(img)
787
        """
788
        # random draw in zoom range
789
        zoom_x = np.exp(
790
            random.gauss(np.log(self.zoom_range[0]), np.log(self.zoom_range[1]))
791
        )
792
        zoom_y = np.exp(
793
            random.gauss(np.log(self.zoom_range[0]), np.log(self.zoom_range[1]))
794
        )
795
        zoom_z = np.exp(
796
            random.gauss(np.log(self.zoom_range[0]), np.log(self.zoom_range[1]))
797
        )
798
        self.params = (zoom_x, zoom_y, zoom_z)
799
800
        tx = Zoom3D((zoom_x, zoom_y, zoom_z), reference=self.reference, lazy=self.lazy)
801
802
        return tx.transform(X, y)