25
25
from . import _galsim
26
26
from ._galsim import Interpolant
27
27
from ._galsim import Nearest , Linear , Cubic , Quintic , Lanczos , SincInterpolant , Delta
28
+ import numpy as np
28
29
29
30
class InterpolatedImage (GSObject ):
30
31
"""A class describing non-parametric profiles specified using an Image, which can be
@@ -258,8 +259,6 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization=
258
259
depr ('dx' , 1.1 , 'scale' )
259
260
scale = dx
260
261
261
- import numpy
262
-
263
262
# first try to read the image as a file. If it's not either a string or a valid
264
263
# pyfits hdu or hdulist, then an exception will be raised, which we ignore and move on.
265
264
try :
@@ -270,8 +269,8 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization=
270
269
# make sure image is really an image and has a float type
271
270
if not isinstance (image , galsim .Image ):
272
271
raise ValueError ("Supplied image is not an Image instance" )
273
- if image .dtype != numpy .float32 and image .dtype != numpy .float64 :
274
- raise ValueError ("Supplied image is not an image of floats or doubles !" )
272
+ if image .dtype != np .float32 and image .dtype != np .float64 :
273
+ raise ValueError ("Supplied image does not have dtype of float32 or float64 !" )
275
274
276
275
# it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor
277
276
if not image .bounds .isDefined ():
@@ -320,12 +319,11 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization=
320
319
321
320
# Check that given pad_image is valid:
322
321
if pad_image :
323
- import numpy
324
322
if isinstance (pad_image , str ):
325
323
pad_image = galsim .fits .read (pad_image )
326
324
if not isinstance (pad_image , galsim .Image ):
327
325
raise ValueError ("Supplied pad_image is not an Image!" )
328
- if pad_image .dtype != numpy .float32 and pad_image .dtype != numpy .float64 :
326
+ if pad_image .dtype != np .float32 and pad_image .dtype != np .float64 :
329
327
raise ValueError ("Supplied pad_image is not one of the allowed types!" )
330
328
331
329
# Check that the given noise_pad is valid:
@@ -489,8 +487,6 @@ def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization=
489
487
def buildNoisePadImage (self , noise_pad_size , noise_pad , rng ):
490
488
"""A helper function that builds the `pad_image` from the given `noise_pad` specification.
491
489
"""
492
- import numpy as np
493
-
494
490
# Make it with the same dtype as the image
495
491
pad_image = galsim .Image (noise_pad_size , noise_pad_size , dtype = self .image .dtype )
496
492
@@ -550,6 +546,163 @@ def __setstate__(self, d):
550
546
_force_stepk = self ._stepk , _force_maxk = self ._maxk )
551
547
552
548
549
+ class InterpolatedKImage (GSObject ):
550
+ """A class describing non-parametric profiles specified by samples -- i.e., images -- of their
551
+ complex Fourier transform.
552
+
553
+ The InterpolatedKImage class is useful if you have a non-parametric description of the Fourier
554
+ transform of an object as a pair of Images -- one for the real part and one for the imaginary
555
+ part -- of the complex-valued profile that you wish to manipulate / transform using GSObject
556
+ methods such as shear(), magnify(), shift(), etc. Note that neither real-space convolution nor
557
+ photon-shooting of InterpolatedKImages is currently implemented. Please submit an issue at
558
+ http://github.com/GalSim-developers/GalSim/issues if you require either of these use cases.
559
+
560
+ The images required for creating an InterpolatedKImage are precisely those returned by the
561
+ GSObject `.drawKImage()` method. The `a` and `b` objects in the following command will produce
562
+ essentially equivalent images when drawn with the `.drawImage()` method:
563
+
564
+ >>> a = returns_a_GSObject()
565
+ >>> b = galsim.InterpolatedKImage(*a.drawKImage())
566
+
567
+ The real- and imaginary-part Images must have the same data type, same bounds, and same scale.
568
+ The only wcs permitted is a simple PixelScale. Furthermore, the complex-valued Fourier profile
569
+ formed by `real_kimage` and `imag_kimage` must be Hermitian, since it represents a real-valued
570
+ real-space profile. (To see an example of valid input to `InterpolatedKImage`, you can look at
571
+ the output of `drawKImage`).
572
+
573
+ The user may optionally specify an interpolant, `k_interpolant`, for Fourier-space
574
+ manipulations (e.g., shearing, resampling). If none is specified, then by default, a Quintic
575
+ interpolant is used. The Quintic interpolant has been found to be a good compromise between
576
+ speed and accuracy for real-and Fourier-space interpolation of objects specified by samples of
577
+ their real-space profiles (e.g., in InterpolatedImage), though no extensive testing has been
578
+ performed for objects specified by samples of their Fourier-space profiles (e.g., this
579
+ class).
580
+
581
+ Initialization
582
+ --------------
583
+
584
+ >>> interpolated_kimage = galsim.InterpolatedKImage(real_kimage, imag_kimage,
585
+ k_interpolant=None, stepk=0., gsparams=None)
586
+
587
+ Initializes `interpolated_kimage` as an InterpolatedKImage instance.
588
+
589
+ @param real_kimage The Image corresponding to the real part of the Fourier-space samples.
590
+ @param imag_kimage The Image corresponding to the imaginary part of the Fourier-space
591
+ samples.
592
+ @param k_interpolant Either an Interpolant instance or a string indicating which k-space
593
+ interpolant should be used. Options are 'nearest', 'sinc', 'linear',
594
+ 'cubic', 'quintic', or 'lanczosN' where N should be the integer order
595
+ to use. [default: galsim.Quintic()]
596
+ @param stepk By default, the stepk value (the sampling frequency in Fourier-space)
597
+ of the underlying SBProfile is set by the `scale` attribute of the
598
+ supplied images. This keyword allows the user to specify a coarser
599
+ sampling in Fourier-space, which may increase efficiency at the expense
600
+ of decreasing the separation between neighboring copies of the
601
+ DFT-rendered real-space profile. (See the GSParams docstring for the
602
+ parameter `folding_threshold` for more information).
603
+ [default: real_kimage.scale]
604
+ @param gsparams An optional GSParams argument. See the docstring for GSParams for
605
+ details. [default: None]
606
+
607
+ Methods
608
+ -------
609
+
610
+ There are no additional methods for InterpolatedKImage beyond the usual GSObject methods.
611
+ """
612
+ _req_params = { 'real_kimage' : str ,
613
+ 'imag_kimage' : str }
614
+ _opt_params = {
615
+ 'k_interpolant' : str ,
616
+ 'stepk' : float
617
+ }
618
+ _single_params = []
619
+ _takes_rng = False
620
+ _takes_logger = False
621
+
622
+ def __init__ (self , real_kimage , imag_kimage , k_interpolant = None , stepk = None ,
623
+ gsparams = None ):
624
+
625
+ # make sure real_kimage, imag_kimage are really `Image`s, are floats, and are congruent.
626
+ if not isinstance (real_kimage , galsim .Image ) or not isinstance (imag_kimage , galsim .Image ):
627
+ raise ValueError ("Supplied kimage is not an Image instance" )
628
+ if ((real_kimage .dtype != np .float32 and real_kimage .dtype != np .float64 )
629
+ or (imag_kimage .dtype != np .float32 and imag_kimage .dtype != np .float64 )):
630
+ raise ValueError ("Supplied image does not have dtype of float32 or float64!" )
631
+ if real_kimage .bounds != imag_kimage .bounds :
632
+ raise ValueError ("Real and Imag kimages must have same bounds." )
633
+ if real_kimage .scale != imag_kimage .scale :
634
+ raise ValueError ("Real and Imag kimages must have same scale." )
635
+
636
+ # Make sure any `wcs`s are `PixelScale`s.
637
+ if ((real_kimage .wcs is not None
638
+ and not real_kimage .wcs .isPixelScale ())
639
+ or (imag_kimage .wcs is not None
640
+ and not imag_kimage .wcs .isPixelScale ())):
641
+ raise ValueError ("Real and Imag kimage wcs's must be PixelScale's or None." )
642
+
643
+ # Check for Hermitian symmetry properties of real_kimage and imag_kimage
644
+ shape = real_kimage .array .shape
645
+ # If image is even-sized, ignore first row/column since in this case not every pixel has
646
+ # a symmetric partner to which to compare.
647
+ bd = galsim .BoundsI (real_kimage .xmin + (1 if shape [1 ]% 2 == 0 else 0 ),
648
+ real_kimage .xmax ,
649
+ real_kimage .ymin + (1 if shape [0 ]% 2 == 0 else 0 ),
650
+ real_kimage .ymax )
651
+ if not (np .allclose (real_kimage [bd ].array ,
652
+ real_kimage [bd ].array [::- 1 ,::- 1 ])
653
+ or np .allclose (imag_kimage [bd ].array ,
654
+ - imag_kimage [bd ].array [::- 1 ,::- 1 ])):
655
+ raise ValueError ("Real and Imag kimages must form a Hermitian complex matrix." )
656
+
657
+ if stepk is None :
658
+ stepk = real_kimage .scale
659
+ else :
660
+ if stepk < real_kimage .scale :
661
+ import warnings
662
+ warnings .warn (
663
+ "Provided stepk is smaller than kimage.scale; overriding with kimage.scale." )
664
+ stepk = real_kimage .scale
665
+
666
+ self ._real_kimage = real_kimage
667
+ self ._imag_kimage = imag_kimage
668
+ self ._stepk = stepk
669
+ self ._gsparams = gsparams
670
+
671
+ # set up k_interpolant if none was provided by user, or check that the user-provided one
672
+ # is of a valid type
673
+ if k_interpolant is None :
674
+ self .k_interpolant = galsim .Quintic (tol = 1e-4 )
675
+ else :
676
+ self .k_interpolant = galsim .utilities .convert_interpolant (k_interpolant )
677
+
678
+ GSObject .__init__ (self , galsim ._galsim .SBInterpolatedKImage (
679
+ self ._real_kimage .image , self ._imag_kimage .image ,
680
+ self ._real_kimage .scale , self ._stepk , self .k_interpolant , gsparams ))
681
+
682
+ def __repr__ (self ):
683
+ return ('galsim.InterpolatedKImage(%r, %r, %r, stepk=%r, gsparams=%r)' )% (
684
+ self ._real_kimage , self ._imag_kimage , self .k_interpolant ,
685
+ self ._stepk , self ._gsparams )
686
+
687
+ def __str__ (self ): return 'galsim.InterpolatedKImage(real_kimage=%s)' % (
688
+ self ._real_kimage )
689
+
690
+ def __getstate__ (self ):
691
+ # The SBInterpolatedKImage and the SBProfile both are picklable, but they are pretty
692
+ # inefficient, due to the large images being written as strings. Better to pickle
693
+ # the intermediate products and then call init again on the other side. There's still
694
+ # an image to be pickled, but at least it will be through the normal pickling rules,
695
+ # rather than the repr.
696
+ d = self .__dict__ .copy ()
697
+ del d ['SBProfile' ]
698
+ return d
699
+
700
+ def __setstate__ (self , d ):
701
+ self .__dict__ = d
702
+ self .SBProfile = galsim ._galsim .SBInterpolatedKImage (
703
+ self ._real_kimage .image , self ._imag_kimage .image ,
704
+ self ._real_kimage .scale , self ._stepk , self .k_interpolant , self ._gsparams )
705
+
553
706
_galsim .SBInterpolatedImage .__getinitargs__ = lambda self : (
554
707
self .getImage (), self .getXInterp (), self .getKInterp (), 1.0 ,
555
708
self .stepK (), self .maxK (), self .getGSParams ())
@@ -558,6 +711,20 @@ def __setstate__(self, d):
558
711
_galsim .SBInterpolatedImage .__repr__ = lambda self : \
559
712
'galsim._galsim.SBInterpolatedImage(%r, %r, %r, %r, %r, %r, %r)' % self .__getinitargs__ ()
560
713
714
+ def _SBIKI_getinitargs (self ):
715
+ if self ._cenIsSet ():
716
+ return (self ._getKData (), self .dK (), self .stepK (), self .maxK (), self .getKInterp (),
717
+ self .centroid ().x , self .centroid ().y , True , self .getGSParams ())
718
+ else :
719
+ return (self ._getKData (), self .dK (), self .stepK (), self .maxK (), self .getKInterp (),
720
+ 0.0 , 0.0 , False , self .getGSParams ())
721
+ _galsim .SBInterpolatedKImage .__getinitargs__ = _SBIKI_getinitargs
722
+ _galsim .SBInterpolatedKImage .__getstate__ = lambda self : None
723
+ _galsim .SBInterpolatedKImage .__setstate__ = lambda self , state : 1
724
+ _galsim .SBInterpolatedKImage .__repr__ = lambda self : (
725
+ 'galsim._galsim.SBInterpolatedKImage(%r, %r, %r, %r, %r, %r, %r, %r, %r, )'
726
+ % self .__getinitargs__ ())
727
+
561
728
_galsim .Interpolant .__getinitargs__ = lambda self : (self .makeStr (), self .getTol ())
562
729
_galsim .Delta .__getinitargs__ = lambda self : (self .getTol (), )
563
730
_galsim .Nearest .__getinitargs__ = lambda self : (self .getTol (), )
0 commit comments