Week 5: Release Edition

The eagerly awaited final installment of LancAstro.py blog is here! Or is it the final installment? This week, LancAstro gets an overhaul and some extra modules with some bells and whistles. With the completion of the LancAstro Example Library last week, I had the chance to move back onto my LancAstro.py package, my baby! With the newly christened Plot2D up and running, the next logical step seemed to be to go 3D!

At first glance through the MatPlotLib documentation, it looked like hacking Plot2D into Plot3D would be a doddle, so much so that I could just merge them into one all powerful LancPlotter that could do all the dimensions! It seemed like all one had to do was add an extra argument in plt.errorbar() for z and therefore I presumed z errors. Seems the logical course of action right? WRONG! Turns out that the syntax for 3D is needlessly complicated and frustrating!

To plot something in 3D, you’ve got to use a separate 3D axes object which has its own entirely separate methods and syntax to the standard pyplot methods I’ve been using in Plot2D. This therefore completely bricks my code!

# Sets up the figure size from parameters before plotting commences  
fig = plt.figure(figsize=figsize)

# Setting this axis to '3d' produces a Axes3D object    
ax = fig.add_subplot(111, projection='3d')

So I had to go through and rewrite the majority of my code to fit this ridiculous syntax.

# Have to use scatter() rather than errorbar() for 3D
PLOT = ax.scatter(xs=np.array(x[i]),ys=np.array(y[i]),zs=np.array(z[i]),
                  c=c,cmap=CMAP[i],s=SIZES[i],marker=POINTSTYLES[i])
  
# Though I did add a colourbar to the z-axis data by using cmap and parsing
# PLOT into this:
cb = plt.colorbar(PLOT)

It took many failed attempts till it ran and even when it did it often looked terrible. This was down to yet more syntax that one would think was generic to MatPlotLib but wasn’t. For instance, you’d think that all these rcParams I’ve been using for x and y would equally work for z by just following the same logic of the syntax. Wrong again. It’s a completely different style! WHY?

# ============================================================================
#                           PARAMETERS
# ============================================================================
# FIGURE AND AXIS SIZES ======================================================
mpl.rcParams['xtick.labelsize'] = 10        # Size of x-tick labels
mpl.rcParams['ytick.labelsize'] = 10        # Size of y-tick labels
# mpl.rcParams['ztick.labelsize'] = 10 <== This is not legit 😦

It very much seems that even though this 3D plotting is cool and quite powerful, it does seem from what I’ve read around that it is a bit of an after thought tacked onto MatPlotLib’s library and therefore has some very strange and quirky syntax and lacking some rather obvious features.

For example, the labels of the axis are defaulted to be placed level but this often looks awful because as the the axis are all at angles, they run into them.

Fig 1: As you can see the labels often run into the axis

You’d think there was a simple keyword argument (**kwargs as its known in computer science) to set the labels to be parallel to the axis they’re associated with? Nope, apparently it’s a persistent problem people have tried to work around, often involving long, complicated bits of code.

As you can probably tell by this point, it was week 5, the syntax was bonkers and involving ripping up half my code to make it work and the lab was boiling hot in a heat wave so my sanity was fairly questionable at this point! But, my hard-work and fever-ish mentality would not be in vain, for the finished results were pretty cool!

Fig 2: Visualisation of SC4K data processed by Sergio Santos and Josh Butterworth using Plot3D. The colourbar represents the redshift of the galaxies

I also worked on developing MultiPlot, a module to create a grid of subplots, inspired by the grid created for the SC4K Sobral 2018 et.al paper which I reworked the code for last week. Unlike the 3D plotting thankfully, this could reuse the Plot2D code.

Essentially, each subplot is just a plot like any other, the difference being that with each subplot, a subplot object is created.

# Plots all the subplots
for i in range(len(x)):
    # Creates a subplot object that defines how many rows and columns the grid has
    # i+1 defines what number in the grid this plot is 
    # Therefore this ties all subplots together
    plt.subplot(n_cols,n_rows,i+1)
    
    # Calls plot method to plot each variable from the dataset    
    HANDLES = laplt.plot(x[i],y[i],x_error=x_error[i],y_error=y_error[i],
                         DATALABELS=DATALABELS[i],COLOURS=COLOURS[i],
                         FILL_COLOURS=FILL_COLOURS[i],SHADE=SHADE[i],
                         SIZES=SIZES[i],POINTSTYLES=POINTSTYLES[i],
                         EDGEWID=EDGEWID[i],EDGECOLOURS=EDGECOLOURS[i],
                         LWID=LWID[i],ERBWID=ERBWID[i])

You tell it how many columns and rows this grid of subplots will have, then whenever you make a subplot, you tell it which number this one is and it is then added to the grid!

It was therefore easy to make a grid of plots using the same SC4K data processed by Sergio and Josh that I’ve been using to test all my code. The issue with this however is all the little ancillary bits to make a graph like the axis labels and ticks etc. If left to its own devices, all these subplot’s labels and ticks overrun into a horrid mess. The way round this is having to supply a 2D array which defines the shape of the grid which can then be used to set it up.

if i+1 in left_edge:
    plt.ylabel(r'%s'%y_label, {'color'    : '%s'%y_colour,  'fontsize'   : y_size })
    plt.yticks(y_ticks,y_tick_label)

if i+1 in bottom_edge:
    plt.xlabel(r'%s'%x_label, {'color'    : "%s"%x_colour,  'fontsize'   : x_size })
    plt.xticks(x_ticks,x_tick_label)

if i+1 not in left_edge:
    plt.yticks(y_ticks,[])

if i+1 not in bottom_edge:
    plt.xticks(x_ticks,[])

This can then also be used to work out the edges of the grid so that each subplot can be checked to see if its on an edge and thereby turn its labels and ticks on/off accordingly.

Fig 3: Grid of SC4K data processed by Sergio Santos and Josh Butterworth at 12 different wavelength bands

With this working and my time left in Lancaster before heading home for the ‘Summer’ quickly running out, I thought that I’d have to leave with one last eye-catching hurrah to cover up that my internship had run a *little* behind schedule (if you remember back to the optomistic gantt chart from week 1&2). NB: summer mainly meant a 2nd internship at home (because 1 isn’t enough right?) and panic writing my lit review.

So I came up with a way to make gifs of 3D visualisations of data cubes. Basically some code that punched out the same data cube at different orientations. This turned out to be surprisingly trivial. I added a new method to Plot3D which was essentially a modified copy of create_figure() that was excecuted from a script that fed it the data and gave it the angle(s) to stay fixed at while the other rotational axis angle would be incremented on every iteration.

# Plotting done before this but not saved to file
# Then this creates a new save of the plot but at an incremented angle
# This gives the effect of panning about the data without having to replot 
# the data everytime
for i in np.arange(0,no_frames,1):
    cpfig = fig

    # Sets elevation and azimuthal angle of plot
    if azim == None: 
        ax.view_init(elev=elev,azim=(float(i)/float(no_frames)
                     *360.0))
    if elev == None: 
        ax.view_init(elev=(float(i)/float(no_frames)*360.0),azim=azim)
    if elev == None and azim == None:
        ax.view_init(elev=(float(i)/float(no_frames)*360.0),
                     azim=(float(i)/float(no_frames)*360.0))

    cpfig.savefig("%s%d%s"%(folder,i,figure_name),bbox_inches='tight')

    if print_on == True:
        pb.printProgressBar(i+1,no_frames+2,prefix='FRAME NO.%d SAVED'%i,
                            suffix='COMPLETE', length=40)

    plt.close()

After some experiment with this, the best results seemed to come from letting both azimuthal and elevation angles to be incremented together, giving a good panning about the data cube.

And the results, drum roll please, are shown in this neat gif below!

Fig 4: gif of the same SC4K data cube from before with frames at intervals of azimuthal and elevation angles

I have to say, I was pretty pleased with myself!

So with Friday drawing to a close, my farewells given to my friends in the lab and my laptop packed away for the journey home it would seem that that was that for the LancAstro 2019 internship.

This internship has been a fantastic opputunity to expand my skills and experience in Python and coding in general. It’s taught me how to plot using Python, a skill I’m sure will come in handy countless times in the future. I’ve experienced the highs and lows of trying to make a package of scripts/ modules; the challenge of trying to make them generic and not turn them into a mess of code that only I can understand. And I’ve had fantastic fun sitting in a lab full of intelligent, funny and charming people; us all sharing in the trials and tribulations that work like this brings and the exholtation of success. The satisfaction of that graph finally looking right, to the frustration of a circle being plotted as a 3-sided, open square with curved sides (shout-out to Charlie Alexander there who joined us in the lab working for Dr Licia Ray 😉 ), and of course a continous stream of tea, coffee, biscuits and baked goods that kept us soldiering on. So thank you to all the interns in the astro lab this summer who made working their a real joy! You can check out the blogs of what they’ve done this summer which I had the opputunity to see being worked on, here:

I’d like to give a special thank you to Dr David Sobral who without his incredible hard-work and dedication we wouldn’t have these opputunities like the XGAL internships that provide more than just work in the summer, but also a fantastically designed programme which includes:

  • Weekly XGAL meetings to share our projects with himself and his PhD students and see their work
  • Lunches/ tea and coffee sessions every week with the rest of the astrophysics academics which gives us a chance to hear about their research and hear what academic life is like
  • A weekly paper review where we all present recent papers, giving us a chance to peer into the latest science.

But wait, the work continues! I know there are many improvements to be made to the code, bugs to squash, modules to add and an all important manual for users to be able to try and deceipher this web of modules I’ve knotted to make. I’ll also be able to apply some of the new skills I’ve learnt so far on my 2nd internship of the summer like using GitHub and PyCharm! So the blog will continue for another bonus summer holiday installment; LancAstro: Plotting Beneath the Decks on the High Seas

– Harry

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s