In Aug2020 I encounted a multi-extension FITS file that needed to be numerically manipulated. These operations were trivial, but reading this 3-d image data into memory using a fortran program has proven problematic This leads me to look, yet again, at using astropy and numpy.
Just as I use astroquery in astropy to get catalogs from PANSTARRS, I might use the fitsio library in astropy to read FITS images and tables. I have little idea how they work, but as long as they work, what do I care? What I do need is to brush up on some python skills so that I can perform basic numerical tasks on the pixels of an image. Thst is the reason for this doc.
Each section below handles a somewhat different aspect of using numpy or some other python concepts. Each section is built about a single code. The souce codes, for now, reside in /home/sco/NumPy. Note that I keep an updated copy of this work directory in my html code: $scohtm/scocodes/Work/NumPy.
Return to top of page.Here is a simple code to get going. The souce, for now, code is in /home/sco/NumPy. Basically I define some lists and show how to assemble them into a numpy data array (or ndarray). I add some extra things that remind me how to determine the type of a given entity, how to print out properties of the entity, etc.... I remind myself what a tuple is, and I try not to let myself be bothers by the OO-worlds love of just making up new words.
#!/usr/bin/env python # I use examples from # https://towardsdatascience.com/the-ultimate-beginners-guide-to-numpy-f5a2f99aef54 import numpy as np # Make 3 lists of values row0 = [ 1.1, 2.2, 1.3, 1.4, 1.5 ] row1 = [ 2.1, 2.2, 2.3, 2.4, 2.5 ] row2 = [ 3.1, 3.2, 3.3, 3.4, 3.5 ] # Establish the numpy array named A A = np.array([ row0, row1, row2 ]) print "\nHere is the numpy array A: \n", print A # Now store the data as a numpy array print "\nProperties of A:\n", print "Type of A = %s \n" % ( type(A) ), print "Type of A.shape = %s\n" % ( type(A.shape) ), #print A.shape #================================================================= # A.shape is a tuple (a tuple is a list of strings) # To dump all of these trings into one easily printed string # we use the join method to put all of strings of the tuple (A) # into a single string (that I name line1) line1 = ' '.join(str(x) for x in A.shape) print "A.shape as a single string (line1) = %s\n" % ( line1 ) #================================================================= print "\nHere is A[1,3]: \n", print A[1,3] print "\nHere is row 2 (index=1): \n", print A[1,:] print "\nHere is column 5 (index=4): \n", print A[:,4] # I can slice the A array print "\nNow I am slicing the A[1,:], or the first row: \n", slice0 = A[1,:] print slice0 print "The type of slice0 is: ", print type(slice0) print "Mean of slice0 = %8.3f \n" % ( slice0.mean() ), print "Min of slice0 = %8.3f \n" % ( slice0.min() ), print "Max of slice0 = %8.3f \n" % ( slice0.max() ), # I can slice the A array print "\nNow I am slicing A[1,0:3], or a part of the first row: \n", slice1 = A[1,0:4] print slice1 print "The type of slice1 is: ", print type(slice1) print "Mean of slice1 = %8.3f \n" % ( slice1.mean() ), print "Min of slice1 = %8.3f \n" % ( slice1.min() ), print "Max of slice1 = %8.3f \n" % ( slice1.max() ), % python 2darray_ex1.py Here is the numpy array A: [[1.1 2.2 1.3 1.4 1.5] [2.1 2.2 2.3 2.4 2.5] [3.1 3.2 3.3 3.4 3.5]] Properties of A: Type of A =This simple example seems to work! In this 2-D case, the ndarry is referenced in the order or roows and then columns. Referncings parts of the array (the image) is done with "slicing". I note that I can use a colon-delimited set of numbers to specify a range in the rows or columns. The second number, as in all python slicing, is alwasy one greater than the actual place we wnat to stop in the specification (not intuitive at all). Having just a ":" alone indicates we are specifiying the entive row or column. Return to top of page.Type of A.shape = A.shape as a single string (line1) = 3 5 Here is A[1,3]: 2.4 Here is row 2 (index=1): [2.1 2.2 2.3 2.4 2.5] Here is column 5 (index=4): [1.5 2.5 3.5] Now I am slicing the A[1,:], or the first row: [2.1 2.2 2.3 2.4 2.5] The type of slice0 is: Mean of slice0 = 2.300 Min of slice0 = 2.100 Max of slice0 = 2.500 Now I am slicing the A[1,0:4], or the first 4 elements of row 1: [2.1 2.2 2.3 2.4] The type of slice1 is: Mean of slice1 = 2.250 Min of slice1 = 2.100 Max of slice1 = 2.400
Here I use a really simple way to read numbers into a numpy array directly from a local file named "test.txt". This code is named "2darray_ex2.py". The reading text part is what is new, but I play around a little more with indexing.
#!/usr/bin/env python import numpy as np #======================================================= # I read the text lines directly into a numpy array b = np.loadtxt('test.txt', dtype=float) print "\nHere is my loaded numpy array (named b):\n", print b print "Type of b = %s \n" % ( type(b) ), print "b.dtype = ", b.dtype print "b.shape = ", b.shape #======================================================= print "Mean of b = %8.3f \n" % ( b.mean() ), print "Min of b = %8.3f \n" % ( b.min() ), print "Max of b = %8.3f \n" % ( b.max() ) # Just print one value rr = raw_input("Enter index of row for dispaly = ") ix=int(rr) rr = raw_input("Enter index of column for dispaly = ") iy=int(rr) print "b at this position = ", b[ix,iy] # A neat way to get the dimensions directly in integer form nrows, ncols = b.shape #print type(nrows), type(ncols) print "Shape of b = ", ( b.shape ) print "Dimensions of b (as integers) = %d %d\n" % (nrows,ncols) numpix=0 sumf=0.0 for rowindex in range(0,nrows): for colindex in range(0,ncols): print "rowindex,colindex = ", rowindex,colindex, " b=", b[rowindex,colindex] sumf = sumf + b[rowindex,colindex] numpix = numpix + 1 print "number of pixels, sum of values = %d %f \n" % (numpix,sumf) % python 2darray_ex2.py Here is my loaded numpy array (named b): [[1.1 1.2 1.3 1.4 1.5] [2.1 2.2 2.3 2.4 2.5] [3.1 3.2 3.3 3.4 3.5]] Type of b =This took up the good part of a Saturday! Return to top of page.b.dtype = float64 b.shape = (3, 5) Mean of b = 2.300 Min of b = 1.100 Max of b = 3.500 Enter index of row for display = 1 Enter index of column for display = 1 b at this position = 2.2 Shape of b = (3, 5) Dimensions of b (as integers) = 3 5 rowindex,colindex = 0 0 b= 1.1 rowindex,colindex = 0 1 b= 1.2 rowindex,colindex = 0 2 b= 1.3 rowindex,colindex = 0 3 b= 1.4 rowindex,colindex = 0 4 b= 1.5 rowindex,colindex = 1 0 b= 2.1 rowindex,colindex = 1 1 b= 2.2 rowindex,colindex = 1 2 b= 2.3 rowindex,colindex = 1 3 b= 2.4 rowindex,colindex = 1 4 b= 2.5 rowindex,colindex = 2 0 b= 3.1 rowindex,colindex = 2 1 b= 3.2 rowindex,colindex = 2 2 b= 3.3 rowindex,colindex = 2 3 b= 3.4 rowindex,colindex = 2 4 b= 3.5 number of pixels, sum of values = 15 34.500000
I wanted to use a FITS image as the source for my numpy array, and so I generated a simple FITS image. The code I do this with is named "2darray_ex3.py". I show the source code and a typical run below:
#!/usr/bin/env python import numpy as np from astropy.io import fits #======================================================= # I read the text lines directly into a numpy array hdu_list = fits.open('a1.fits') hdu_list.info() any = raw_input("Enter any key to continue ") # Now I put the image data into an array name "b" b = hdu_list[0].data print "\nHere is my loaded numpy array (named b):\n", print b print "\nb.shape = ", b.shape any = raw_input("Enter any key to continue ") # Here is how I get the value of a header card naxis1 = hdu_list[0].header['NAXIS1'] print "The type of naxis1 is = ", type(naxis1) print "naxis1 = %d \n" % (naxis1) any= raw_input("Enter any key to continue ") #======================================================= print "\nHere are some simple statistics for b: \n" print "Mean of b = %8.3f \n" % ( b.mean() ), print "Min of b = %8.3f \n" % ( b.min() ), print "Max of b = %8.3f \n" % ( b.max() ) # Just print one value rr = raw_input("\nEnter index of row for display = ") ix=int(rr) rr = raw_input("Enter index of column for display = ") iy=int(rr) print "\nb at this position = ", b[ix,iy] # A neat way to get the dimensions directly in integer form nrows, ncols = b.shape #print type(nrows), type(ncols) print "Shape of b = ", ( b.shape ) print "Dimensions of b (as integers) = %d %d\n" % (nrows,ncols) numpix=0 sumf=0.0 for rowindex in range(0,nrows): for colindex in range(0,ncols): print "rowindex,colindex = ", rowindex,colindex, " b=", b[rowindex,colindex] sumf = sumf + b[rowindex,colindex] numpix = numpix + 1 print "\nnumber of pixels, sum of values = %d %f \n" % (numpix,sumf) Here is an example where I run the code: % python 2darray_ex3.py Filename: a1.fits No. Name Ver Type Cards Dimensions Format 0 PRIMARY 1 PrimaryHDU 7 (6, 5) float32 Enter any key to continue Here is my loaded numpy array (named b): [[50. 80. 49.896 49.916 49.984 50.1 ] [50.04 80. 49.936 49.956 50.024 50.14 ] [50.08 80. 49.976 49.996 50.064 50.18 ] [50.12 80. 50.016 50.036 50.104 50.22 ] [50.16 49.5 50.056 50.076 50.144 50.26 ]] b.shape = (5, 6) Enter any key to continue The type of naxis1 is =Notice that the hdu_list.info() routine does list the image dimensions as 6x5, and that is becasue this is an astropy module. The numpy shape is given as rows and columns and hence the shape is given as "5 6". Note also that the indices are zero-indexed. Despite all this crap, I was able to specify the correct row and colunm index so that I got the custom pixel value f I=49.5 to be displayed.naxis1 = 6 Enter any key to continue Here are some simple statistics for b: Mean of b = 54.033 Min of b = 49.500 Max of b = 80.000 Enter index of row for display = 4 Enter index of column for display = 1 b at this position = 49.5 Shape of b = (5, 6) Dimensions of b (as integers) = 5 6 rowindex,colindex = 0 0 b= 50.0 rowindex,colindex = 0 1 b= 80.0 rowindex,colindex = 0 2 b= 49.896 rowindex,colindex = 0 3 b= 49.916 rowindex,colindex = 0 4 b= 49.984 rowindex,colindex = 0 5 b= 50.1 rowindex,colindex = 1 0 b= 50.04 rowindex,colindex = 1 1 b= 80.0 rowindex,colindex = 1 2 b= 49.936 rowindex,colindex = 1 3 b= 49.956 rowindex,colindex = 1 4 b= 50.024 rowindex,colindex = 1 5 b= 50.14 rowindex,colindex = 2 0 b= 50.08 rowindex,colindex = 2 1 b= 80.0 rowindex,colindex = 2 2 b= 49.976 rowindex,colindex = 2 3 b= 49.996 rowindex,colindex = 2 4 b= 50.064 rowindex,colindex = 2 5 b= 50.18 rowindex,colindex = 3 0 b= 50.12 rowindex,colindex = 3 1 b= 80.0 rowindex,colindex = 3 2 b= 50.016 rowindex,colindex = 3 3 b= 50.036 rowindex,colindex = 3 4 b= 50.104 rowindex,colindex = 3 5 b= 50.22 rowindex,colindex = 4 0 b= 50.16 rowindex,colindex = 4 1 b= 49.5 # THIS IS OUR UNIQUE CUSTOM PIXEL VALUE!!! rowindex,colindex = 4 2 b= 50.056 rowindex,colindex = 4 3 b= 50.076 rowindex,colindex = 4 4 b= 50.144 rowindex,colindex = 4 5 b= 50.26 number of pixels, sum of values = 30 1620.980000
Finally, notice that when I print the numpy array (b) near the top of my example output, the image is flipped in the Y dimension compared to the ds9 example below. This is becasue numpy (and python in general) assign X,Y=0,0 to the upper-left corner of the image. This is a publishing convention, probably one that is older than the astronony convention used in ds9.
![]() |
Here is a small test FITS image made for testing some early numpy codes. This
6x5 FITS image uses a 3 term polynomial to generate a backround with pixels values
around I=50.0, and a few pixels are marked with custon values (49.5 and 80.0)
using an optional input file named "pix.1". The command used to do this are shown here: % clip_build_fits.sh 6 5 poly.1 a1.fits -p pix.1I have marked the pixels in column 2 (x=2) that have a value of I=80, and the single pixel at the top of column 2 (x=2,y=5) that has a value of I=49.5. I have also marked the corners of the X,Y values that a display gui like ds9 will show as the coordintes for these pixels. I have chnaged the color of the numpy array print above to purple. Compare that array print to the orientation of the FITS image displayed by ds9 above. |