[{"url":".","title":"Intro","description":"Intro to using these notebooks.","tags":["intro"],"text":" A Pluto.jl notebook v0.20.24 frontmatter title \"Intro\" date \"2026 04 17\" tags \"intro\" description \"Intro to using these notebooks.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils using AstroImages, Colors, PlutoUI md\"\"\" SETI Unistellar ASP Labs Originally developed for the Unistellar College Astronomy Network UCAN https www.seti.org education ucan . Below are a few pointers for working with these labs. \"\"\" md\"\"\" Quickstart To get started, please follow the instructions in the following two steps below 1. Install Julia https julialang.org install 1. Install Pluto.jl https plutojl.org install Additional resources Learn Julia Julia website https julialang.org learning Learn Pluto Pluto.jl manual https plutojl.org en docs Noteworthy Differences from other Languages Julia manual https docs.julialang.org en v1 manual noteworthy differences Handy cheatsheets JuliaDocs https cheatsheet.juliadocs.org , MATLAB Python Julia https cheatsheets.quantecon.org Featured Pluto.jl notebooks https featured.plutojl.org \"\"\" md\"\"\" Running a notebook Each notebook to the left is a self contained exploration in a different astronomy topic. Here are some useful details for how to interact with them. \"\"\" md\"\"\" note \"Getting around\" Some parts of these Pluto notebooks https plutojl.org are partially interactive online, but for full interactive control, it is recommended to download and run this notebook locally. For instructions on how to do this, click the `Edit or run this notebook` button in the top right corner of the page. Note Each notebook will download all of the analysis packages and data needed for us, so the first time it runs may take a little while ~ a few minutes depending on your internet connection and platform . Clicking on the `Status` tab in the bottom right will bring up a progress window that we can use to monitor this process, and it also includes an option at the bottom marked `Notify when done` that can be selected to give us a notification pop up in our browser when everything is finished. note \" \" In the local version of this notebook, an \"eye\" icon will appear at the top left of each cell on hover to reveal the underlying code behind it and a `Live Docs` button will also be available in the bottom right of the page to pull up documentation for any function that is currently selected. In both local and online versions of this notebook, user defined functions and variables are also underlined, and ctrl clicking on them will jump to where they are defined. note \"Coffee? ☕\" The first time a notebook runs might take a while ~ a couple minutes on older devices because it will download and set up everything for us. This is a good chance to take a stretch or grab a nice beverage 🫖. \"\"\" let msg adding colors md\"\"\" Adding colors in Julia 🎨 This makes magenta ```julia using Colors RGB 1, 0, 0 RGB 0, 0, 1 ``` RGB 1, 0, 0 RGB 0, 0, 1 \"\"\" md\"\"\" tip \"Diving deeper\" Periodically throughout the notebook we will include collapsible sections like the one below to provide additional information about items outside the scope of this lab that may be of interest e.g., plotting, working with javascript, creating widgets . details \"Details\", msg adding colors \"\"\" end md\"\"\" warning \"Advanced bring your own editor\" These notebooks are fully hackable, so exploring the source code https github.com Unistellar science SETI Education and making your own modifications is encouraged Unlike Jupyter notebooks, Pluto notebooks are just plain Julia files. Any changes you make in the notebook are automatically saved to the source file. This works in the opposite direction too any changes you make to the source file, say in your favorite editor, will automatically be reflected in the notebook in your browser To enable this feature, just add this keyword to the function that was used to start Pluto ```julia repl julia using Pluto julia Pluto.run auto reload from file true This will be on by default in an upcoming release ``` The location of the file for this notebook is displayed in the bar at the very top of this page, and can also be modified there if you want to change where this notebook lives. \"\"\" PlutoUI.TableOfContents "},{"url":"search/","title":"Search results","description":"","tags":[],"text":"window.init_search();SearchResults\nLoading..."},{"url":"sidebar data/","title":"sidebar data","description":"","tags":[],"text":"sections \"image processing\", \"photometry\", \"astrometry\", \"time series\", \"spectroscopy\" Dict \"main\" uppercase section collections section .pages for section in sections , "},{"url":"astrometry/parallax_lab/","title":"Parallax Lab","description":"Measure the distance to a nearby asteroid with the parallax method.","tags":["astrometry","parallax","asteroids"],"text":" A Pluto.jl notebook v0.20.28 frontmatter image \"https www.seti.org media mrlmn0lr image 2 parallax.png\" title \"Parallax Lab\" date \"2025 08 01\" tags \"astrometry\", \"parallax\", \"asteroids\" description \"Measure the distance to a nearby asteroid with the parallax method.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end using DynamicQuantities using EphemerisSources, DataFramesMeta begin Notebook using PlutoUI Viz using AstroImages, PlutoPlotly AstroImages.set cmap cividis Analysis using CoordinateTransformations, DataDeps, ImageTransformations Use DataDeps.jl for dataset management Auto download data to current directory by default ENV \"DATADEPS ALWAYS ACCEPT\" \"true\" ENV \"DATADEPS LOAD PATH\" DIR DataDep \"data\", \"\"\" UCAN Data Files Website https www.seti.org education ucan unistellar education materials \"\"\", \"https www.dropbox.com scl fo k57vx9xnqyc1honkn2avc APka1AAqVQ2sWnnZvZQw1CU?rlkey 1l5thuixednvx7adc8dyy5ayu&st d5db4neq&dl 1\" , \"4af6928e26ebb41dd79a6ba15a7727ed9d319b526aa927a5757380d69948a531\" , post fetch method unpack, | register end md\"\"\" 👥 Parallax Lab In this lab we will estimate the distance to a near Earth object NEO based on its measured parallax. For more on taking these types of science observations, see our Unistellar Planetary Defense page https science.unistellar.com planetary defense . Having some familiarity in high level programming languages like Julia or Python will be useful, but not necessary, for following along with the topics covered. At the end of this notebook, you will hopefully have the tools to build your own analysis pipelines for processing astronomical data, as well as understand the principles behind other astronomical software at a broader level. \"\"\" md\"\"\" Introduction A nice way to visualize parallax is to look at your thumb or finger at arm's length and blink your eyes back and forth. It should appear to jump back and forth relative to its background. Thanks to the change in perspective afforded by viewing through each of our eyes, we are able to see our finger from a different angle literally , as in the schematic below note \" \" https upload.wikimedia.org wikipedia commons 2 2e Parallax Example.png Source JustinWick https upload.wikimedia.org wikipedia commons 2 2e Parallax Example.png Using a bit of trigonometry, we can then work out the distance to our finger based on how much it appears to shift. We will explore this method more later in the lab. \"\"\" md\"\"\" It turns out that the farther away an object is, the smaller its shift will appear to be, until it is so small that our eyes are just not strong enough to discern the shift anymore. This is also why the background appears to be static, and this still holds even if we replace our eyes with the largest telescopes on Earth. One way around this is to place our eyes farther apart i.e., increase the baseline to increase the apparent shift of our closer foreground object, and this is exactly what NASA did fairly recently with the New Horizons https science.nasa.gov mission new horizons mission, which sent a satellite with a few telescopes onboard out to the furthest reaches of our solar system and beyond. On April 22 23, 2020, at a distance of over 4 billion miles from Earth, New Horizons turned on one of its telescopes and took a look at our closest star, Proxima Centauri https imagine.gsfc.nasa.gov features cosmic nearest star info.html . What it captured clearly showed a jump relative to its background when compared to the same image taken back on Earth at the same time note \" \" https upload.wikimedia.org wikipedia commons e e2 New Horizons Proxima Centauri Parallax Animation.gif Source NASA New Horizons Mission https www.nasa.gov solar system nasas new horizons conducts the first interstellar parallax experiment For the first time, the parallax method had been used to measure the distance to another star. We do not have 4 billion miles to work with here on Earth, so instead we will use simultaneous observations from two separate ground based telescopes to measure the distance to a near Earth asteroid. Keeping track of these distances is a crucial step for detecting, and potentially diverting https science.nasa.gov mission dart , objects that may be on a collision course with Earth. \"\"\" md\"\"\" Data In this lab, we will use observations of the near Earth asteroid 153591 2001 SN263 https en.wikipedia.org wiki 153591 2001 SN263 taken from the following two eVscopes `OBSERVATORIES` \"\"\" const DATA DIR datadep\"data\" OBSERVATORIES load joinpath DATA DIR, \"nctq52 2022 02 25T19 58 34.991 Science Defense 103.fits\" \"eVscope West\", load joinpath DATA DIR, \"p4eaaw 2022 02 25T19 58 34.183 Science Defense 125.fits\" \"eVscope East\", md\"\"\" and store them into `img1` and `img2` for convenience \"\"\" begin img1, img2 obs.first for obs in OBSERVATORIES Place images on a common color scale const ZMIN, ZMAX let lims Zscale contrast 0.4 . img1, img2 minimum first, lims , maximum last, lims end AstroImages.set clims ZMIN, ZMAX end bind observatory Select OBSERVATORIES... observatory md\"\"\" tip \" \" Associated header file information for the above image. \"\"\" header observatory md\"\"\" Below is a side by side comparison with the size of each image in pixels displayed to better show the discrepancies. Try zooming or panning around to see how the images compare. \"\"\" img1.header md\"\"\" Due to various factors like differences in telescope size, sensors, and pointings, the images are out of sync with each other. To synchronize, we can perform an image alignment procedure. Once this is completed, the background stars should appear essentially motionless when compared with each other, making the parallax shift from the closer in asteroid more apparent. \"\"\" md\"\"\" Image alignment We start by assuming that one image, say `img 2`, can be rotated, translated, and or scaled to fit onto `img 1`. This type of process is known as an affine transformation https en.wikipedia.org wiki Affine transformation , and it is a common tool for aligning and stacking images. We will use the `kabsch` https juliageometry.github.io CoordinateTransformations.jl dev api CoordinateTransformations.kabsch function from CoordinateTransformations.jl https github.com JuliaGeometry CoordinateTransformations.jl to compute this transformation ``\\\\boldsymbol \\\\phi `` for us, given a set of starting source points e.g., point ``\\\\boldsymbol p `` in `img 2` that we would like to map to ending destination points e.g., point ``\\\\boldsymbol q `` in `img 1` as in the schematic below note \" \" https juliaimages.org ImageTransformations.jl stable assets warp resize.png Source JuliaImages https juliaimages.org ImageTransformations.jl stable index image warping Using the comparison plot in the previous section, identify the `` X, Y `` pixel coordinates for at least three stars in the source image and corresponding stars in the destination image, respectively. Record these values in the `point map` variable below, where ``\\boldsymbol p p X, p Y \\Rightarrow \\boldsymbol q q X, q Y ``. \"\"\" img 2 points img 1 points point map 1891, 1341 1219, 845 , 1779, 1177 1077, 709 , 1525, 1039 799, 625 , tfm kabsch last. point map first. point map scale true md\"\"\" For those curious about the linear algebra, the respective linear transformation matrix and translation vector are shown below. They will update in real time in the local version of this notebook each time `point map` is modified. \"\"\" linear matrix tfm.linear translation vector tfm.translation md\"\"\" We now apply this transformation and stack our images together using the `warp` https juliaimages.org ImageTransformations.jl stable reference ImageTransformations.warp function from ImageTransformations.jl https juliaimages.org ImageTransformations.jl stable . This is analogous to Python's `warp` https scikit image.org docs stable api skimage.transform.html skimage.transform.warp function in scikit image https scikit image.org . \"\"\" img2w shareheader img2, warp img2, tfm, axes img1 details md\"What is `shareheader` and `axes`?\", md\"\"\" We are using AstroImages.jl https juliaastro.org dev modules AstroImages to view and process our images. This allows for fits files to be displayed directly in the notebook, and for interactions with the larger JuliaImages https juliaimages.org latest and DimensionalData.jl https rafaqz.github.io DimensionalData.jl stable ecosystems. `AstroImages.shareheader` syncs the header stored in our original image with our `JuliaImages.ImageTransformations.warp`ed image, and `axes` makes sure that the coordinates of our transformed image are shown relative to our `destination` image reference frame of the underlying `DimensionalData`. For more information on this, see this section of the AstroImages.jl documentation https juliaastro.org dev modules AstroImages manual dimensions and world coordinates . \"\"\" md\"\"\" With our images now aligned, we can flip back and forth to view the remaining parallax shift from our asteroid. \"\"\" md\"\"\" hint The asteroid is very small compared to the other point sources in the image. Look closely for an object near the center of both frames that appears to shift. \"\"\" md\"\"\" With the asteroid's parallax shift now identified, we turn next to quantifying this motion and estimating a distance based on the parallax method. \"\"\" md\"\"\" Parallax technique Revisiting our schematic in the Introduction, let's modify it a bit by annotating the baseline distance `` b `` between observers, the distance `` d `` to what is being observed, and the apparent parallax shift `` \\theta `` along the sky observed between them note \" \" https www.dropbox.com scl fi d3hk9vqo3cl73da737z42 parallax diagram.svg?rlkey t7p1vv2sjfk765r46k1100pej&e 2&st v7bdh66b&raw 1 Source Modified from Wikimedia Commons https commons.wikimedia.org wiki File Parallax Example.png From this model, we can work out the distance `` d `` as the following ```math \\begin align \\tan\\frac \\theta 2 & \\frac b 2 d \\approx \\frac \\theta 2 \\text small angle approx. \\ , \\\\ d\\text AU &\\approx \\frac b\\text km \\theta\\text arcsec \\times \\frac 1\\text AU 1.496\\times10^ 8 \\text km \\times \\frac 206,265'' 1\\text rad \\\\ & \\boxed 0.00138 \\times \\frac b\\text km \\theta\\text arcsec \\ , \\end align ``` where ``b`` is measured in kilometers, and ``\\theta`` is measured in arseconds for convenience. We turn next to measuring the parallax shift on sky ``\\theta`` . tip \"Fun fact\" Due to symmetry, θ 2 is known as the \"parallax angle\". When an object appears to shift through a parallax angle of 1 arcsecond for a given half baseline of 1 AU, then by definition the object is 1 parsec https en.wikipedia.org wiki Parsec away. \"\"\" md\"\"\" Parallax shift To measure this shift, we can first estimate how many pixels the asteroid appears to move between our two stacked images, and then use the pixel scale of our reference destination image to convert to an angle. Thanks to our image stacking routine, the correpsonding objects in each image should be in roughly the same spot as we zoom in now. Use the plots below to fill out the coordinates for the asteroid's location in each image \"\"\" bindname asteroid px PlutoUI.combine do Child md\"\"\" |image|X|Y | | | | |left destination | Child \"dest x\", NumberField 1 size img1, 1 default 1017 | Child \"dest y\", NumberField 1 size img1, 2 default 747 |right source | Child \"src x\", NumberField 1 size img2, 1 default 1023 | Child \"src y\", NumberField 1 size img2, 2 default 747 \"\"\" end plate scale 1.326 eVscope 2 pixel scale '' px θ let ΔX asteroid px.dest x asteroid px.src x ΔY asteroid px.dest y asteroid px.src y hypot ΔX, ΔY plate scale end md\"\"\" Multiplying the Pythagorean distance between these two points by the known plate scale https en.wikipedia.org wiki Plate scale of our reference image plate scale '' pixel then gives a parallax shift of approximately ``\\theta `` round θ digits 2 ''. \"\"\" md\"\"\" Parallax distance We now have everything we need to estimate the distance d to our near Earth asteroid. \"\"\" Baseline kilometers b 621 md\"\"\" These eVscopes were located at a baseline of b b kilometers apart from each other at the time of observation on February 25th, 2022 at 19 58 UTC. Flipping back and forth, we can see that the field is roughly the same, but unlike the New Horizons example, all of the objects in the frame appear to move, making identifying the apparent parallax shift of the asteroid harder to pick out. \"\"\" Distance to asteroid AU d 0.00138 b θ md\"\"\" We can do a quick dimensions check with a units package like DynamicQuantities.jl https ai.damtp.cam.ac.uk dynamicquantities stable to verify our results. Note that this will differ slightly from our estimated result because we are internally using a more accurate value for the AU when we make our conversion. \"\"\" d units b u\"km\" θ u\"arcsec\" | us\"Constants.au\" md\"\"\" JPL Horizons To query the distance to other asteroids at a given date Edit field 2 in the Horizons System to search for the asteroid by name. Edit field 4 to specify the desired date range to query. For this target, we specified data within three minutes of the `DATE OBS` field reported in the header of our image files. Click `Generate Ephemeris`. Read off the `delta` field in the data table generated you may need to scroll down a bit . This will be the distance to the asteroid measured in AU. See here https ssd.jpl.nasa.gov horizons manual.html obsquan for definitions of the other table column names. The associated Small Body Database Lookup https ssd.jpl.nasa.gov tools sbdb lookup.html tool on this site also provides a 3D simulation of the asteroid's approximate orbit over time below and other quick facts. note \" \" fig1 https www.dropbox.com scl fi dcky7peoqa3j7hr86zwju orbit sim.png?rlkey 7kpiw4423rxvy6i0zwgkqqg4e&st ejfrdjzg&raw 1 \"\"\" md\"\"\" tip For a more automatic approach, we can also use the ephemeris lookup tool in `EphemerisSources.jl` https juliaastro.org EphemerisSources.jl docs stable \"\"\" Query 1 minute before and after the parallax observation df ephem ephemeris \"153591\", \"2022 02 25T19 57\", \"2022 02 25T19 59\", \"1 minute\" wrt \"earth\", units \"AU D\" | DataFrame Row 2 has our desired timestamp d0 hypot df ephem 2, x, y, z ... accuracy 100.0 d d0 d0 Percent diff md\"\"\" Based on our measurements, we estimate that the asteroid was about round d digits 3 AU away from the Earth at the time of observation. This is within abs round Int, accuracy % of the true distance reported by JPL https ssd.jpl.nasa.gov horizons app.html Try exerimenting with different pixel centers to see how this affects our calculated distance. \"\"\" md\"\"\" Wrapping up In this lab, we used simultaneous asteroid observations taken over a large enough baseline to measure the parallax effect. We then used this measurement to estimate the distance to the asteroid to within abs round Int, accuracy %. Not bad for essentially approximating the asteroid's apparent motion by eye What were some additional sources of error that could have contributed to this inaccuracy? What are some ways that we could improve these measurements? After considering, scroll over the section below to view a few potential improvements and extensions to our previous work. hint \" \" Baseline \\ Since ``\\theta`` goes like ``\\frac b d ``, taking measurements over a larger baseline would yield a larger parallax shift that we could then measure to a more accurate degree. Equatorial coordinates \\ We could use a tool like astrometry.net https nova.astrometry.net to plate solve https en.wikipedia.org wiki Astrometric solving our images. This would allow us to directly calculate the ``\\Delta\\text RA `` and ``\\Delta\\text DEC `` of the asteroid to a high precision instead of relying on pixel coordinates. Image transformation parameters \\ We relied on an affine transformation to stack our images. While flexible, the increased number of parameters relative to simpler transformation schemes can introduce additional error. Perhaps only rotatation and translation would be enough, although this might require using telescopes with comparable plate scales and apertures like an eVscope . Center identification \\ Regardless of the stacking scheme and coordinate system chosen, our parallax measurement is only as good as our ability to identify the approximate center of the asteroid in each image. Fitting a point spread function PSF https en.wikipedia.org wiki Point spread function would be useful for accurately identifying the center coordinates of our asteroid instead of eyeballing it as we did in this lab. \"\"\" md\"\"\" As of February 1st, 2025, nearly 40,000 near Earth asteroids https cneos.jpl.nasa.gov stats totals.html have been discovered. That number is expected to rise not only from the last bits of data released in November 2024 by the now concluded WISE NEOWISE https wise2.ipac.caltech.edu docs release neowise mission, but also from the Near Earth Object Surveyor https science.nasa.gov mission neo surveyor telescope that is scheduled for launch in 2028. Keeping track of these asteroids and their measured distances through parallax measurements and other means like elliptical path fitting https www.nasa.gov solar system asteroids asteroid fast facts are important components for defending our planet NASA https science.nasa.gov planetary defense , ESA https www.esa.int Space Safety Planetary Defence . \"\"\" md\"\"\" Notebook setup 🔧 \"\"\" TableOfContents Heuristic for keeping plotted images from blowing up const MAXPIXELS 10^6 function trace hm img colorbar x 0 imgv copy img Restriction prescription from AstroImages.jl Images.jl so plotting doesn't blow up for large images while length eachindex imgv MAXPIXELS imgv restrict imgv end imgv permutedims imgv return heatmap x dims imgv, X .val, y dims imgv, Y .val, z imgv.data zmin ZMIN, zmax ZMAX, colorscale Cividis, colorbar attr x colorbar x, thickness 10, title \"Counts\" , end function plot pair observatories img1, img2 obs.first for obs in observatories loc1, loc2 obs.second for obs in observatories Set up some subplots fig make subplots rows 1, cols 2, shared xaxes true, shared yaxes true, column titles loc1, loc2 , Make the subplot titles a smidgen bit smaller update annotations fig, font size 14 Manually place the colorbars so they don't clash add trace fig, trace hm img1 colorbar x 0.45 , col 1 add trace fig, trace hm img2 colorbar x 1 , col 2 Keep the images true to size update xaxes fig, matches \"x\", scaleanchor y, title \"X pixels \" update yaxes fig, matches \"y\", scaleanchor x Add a shared y label relayout fig, Layout yaxis title \"Y pixels \" , font size 10, template \"plotly white\", margin attr t 20 , uirevision 1 Display return fig end plot pair OBSERVATORIES plot pair img1 \"eVscope West\", img2w \"eVscope East stacked \" "},{"url":"image_processing/i_images_as_data/","title":"I - Images as Data","description":"Learn how to install the software needed to interpret astronomical images.","tags":["image processing","images","rgb","FITS"],"text":" A Pluto.jl notebook v0.20.28 frontmatter title \"I Images as Data\" date \"2025 09 19\" tags \"image processing\", \"images\", \"rgb\", \"FITS\" description \"Learn how to install the software needed to interpret astronomical images.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end begin Notebook widgets using PlutoUI Analysis tools using AstroImages, Colors Colormap default settings AstroImages.set cmap nothing end md\"\"\" 📷 Images as Data Resource \"https imgs.xkcd.com comics painbow award.png\" Image credit xkcd https xkcd.com 2537 . Alt text \"This year, our team took home the dark blue ribbon, better than the midnight blue we got last year but still short of the winning navy blue.\" What's in an image? Turns out just a nice, orderly set of numbers. We will explore how astronomers use scientific programming to interpret these numbers. \"\"\" md\"\"\" Software tools 💻 Today, there are a wide range of tools to select from when doing astronomical research. We will use Julia https julialang.org , a modern programming language geared towards science and engineering applications https juliahub.com industries case studies . A growing list of astronomy and astrophysics applications can be found on the JuliaAstro case studies page https juliaastro.org home case studies . To promote best practices in modern science software developement e.g., reproducibility, maintainability, and literacy , we will be using the Pluto.jl https plutojl.org notebook environment also written in Julia to share and work with real code used by professional astronomers. tip For folks approaching programming for the first time What is a notebook? https en.wikipedia.org wiki Notebook interface For folks coming from a Python background Julia is to Python as Pluto.jl is to Jupyter. \"\"\" md\"\"\" Quickstart To get started, please follow the instructions in the following two steps below 1. Install Julia https julialang.org install 1. Install Pluto.jl https plutojl.org install Additional resources Learn Julia Julia website https julialang.org learning Learn Pluto Pluto.jl manual https plutojl.org en docs Noteworthy Differences from other Languages Julia manual https docs.julialang.org en v1 manual noteworthy differences Handy cheatsheets JuliaDocs https cheatsheet.juliadocs.org , MATLAB Python Julia https cheatsheets.quantecon.org Featured Pluto.jl notebooks https featured.plutojl.org \"\"\" md\"\"\" Image formats 📚 Astronomical images start their lives as a box of numbers. This box can be represented in a variety of different formats, the most popular currently being the Flexible Image Transport System https en.wikipedia.org wiki FITS FITS format. We will explore FITS files shortly, but let's start with another common format that you might use every day, Portable Network Graphics https en.wikipedia.org wiki PNG PNG , to get an idea of how image data is represented and how these different formats relate to each other. \"\"\" md\"\"\" PNG We can use an image of anything, really. Below, we download a PNG of the famous \"Cosmic Cliffs\" https webbtelescope.org contents media images 2022 031 01G77PKB8NKR7S8Z6HBXMYATGJ image taken from JWST and store it in a variable named `img` \"\"\" md\"\"\" Image credit NASA, ESA, CSA, STScI | bind reset Button \"Reset\" \"\"\" md\"\"\" We now have an image that we can analyze. For starters, let's display some key characteristics about `img` \"\"\" bind resample Button \"Resample\" md\"\"\" Below our selected pixel, we map these R, G, B values to their corresponding sub pixel, where 0 represents black or no brightness , and 1 represents the peak brightness for the given color channel. The resulting color is then the additive combination https en.wikipedia.org wiki RGB color model Additive colors of these individual subpixels. \"\"\" md\"\"\" tip \" \" See our Unistellar image gallery https www.unistellar.com gallery or browse the ` images pretty` channel on our Unistellar citizen science workspace https help.unistellar.com hc en us articles 4405646109842 How do I join the Unistellar Citizen Science Community for more Astronomers typically work with black and white https hubblesite.org contents articles the meaning of light and color or grayscale https en.wikipedia.org wiki Grayscale images, so we will next see how we can convert our image to this form using the information we have above. Later, we will see why this is a beneficial form to have our image in when we explore the FITS file format. \"\"\" md\"\"\" Grayscale The converversion process from ``RGB`` to grayscale for a given pixel is achieved by taking a weighted average of its channel values according to an international standard https en.wikipedia.org wiki Luma %28video%29 Rec. 601 luma versus Rec. 709 luma coefficients established to emulate how the human eye perceives relative brightnesses https en.wikipedia.org wiki Grayscale Converting color to grayscale ```math 0.299 R 0.587 G 0.114 B \\quad. ``` This is already implemented for us https juliaimages.org latest examples color channels rgb grayscale in the `ColorTypes` package with the `Gray` function, which we apply below to each pixel of our image to produce the following grayscale version \"\"\" details \"Details\", md\"\"\" note \" \" Julia has a delightful way of applying a function element wise to its inputs, known as dot syntax https docs.julialang.org en v1 manual functions man vectorized . \"\"\" md\"\"\" Taking a look at the properties of our new image, we see that now instead of being a matrix composed of `RGB N0f8 ` types, it is composed of `Gray N0f8 `s \"\"\" md\"\"\" note We elide the package names for brevity. In other words, instead of three numbers representing each pixel, we now have a single number for each, which we can view directly \"\"\" md\"\"\" This \"box of numbers\" format is how image data is represented in FITS files. tip \"Exercise\" Try repeating the above analysis with you own PNG image by loading it below Note that the filetype must be a PNG. \"\"\" begin reset bind img local FilePicker MIME \"image png\" end img if isnothing img local load download \"https stsci opo.org STScI 01GA6KNV1S3TP2JBPCDT8G826T.png\" else let path tempname img local \"name\" write path, img local \"data\" load path end end nrows, ncols size img px type eltype img md\"\"\" We see here that our image is nrows rows by ncols columns wide, and each cell or pixel of this image is represented by a px type type. Even though this part is Julia specific, the underlying information is general enough to apply to most image processing libraries. Let's break down what each piece means `ColorTypes` https github.com JuliaGraphics ColorTypes.jl The name of the package where a type called `RGB` is defined. `RGB` https github.com JuliaGraphics ColorTypes.jl rgb plus bgr xrgb rgbx and rgb24 the abstractrgb group A type that stores the red, green, and blue intensity values of a pixel. These can be thought of as sub pixels https en.wikipedia.org wiki Pixel Subpixels . `FixedPointNumbers` https github.com JuliaMath FixedPointNumbers.jl The name of the package where a type called `N0f8` is defined. `N0f8` https github.com JuliaMath FixedPointNumbers.jl type hierarchy and interpretation A type that represents a number in memory. This essentially defines the specific number type used for each red, green, and blue value in each pixel. More on `N0f8` and other number formats https juliaimages.org latest tutorials quickstart The 0 to 1 intensity scale . To summarize, our image is just a matrix of pixels, where each pixel value is represented by a triple of RGB values stored in a memory efficient format. Let's explore next how these numbers connect to how we perceive color. \"\"\" begin N sampled px 5 resample sample px rand img, N sampled px end md\"\"\" Pixel colors Below, we sample N sampled px random pixels from `img`. Based on how colorful and varied the image is, these pixels can have a range of different colors between them. Pull the slider to look at each of these pixels one by one and or click the `Resample` button to select N sampled px new pixels at random. For convenience, we also display the individual R, G, B values next to our slider. \"\"\" bind px img Slider sample px show value true begin r, g, b px img .| red, green, blue md\"\"\" Pixel | R | G | B | | | px img | RGB r, 0, 0 | RGB 0, g, 0 | RGB 0, 0, b \"\"\" end Modify the indices below img 500 700, 500 700 img gray Gray. img eltype img gray img data gray. img gray md\"\"\" FITS FITS https en.wikipedia.org wiki FITS images are already in grayscale and can come packaged with additional metadata known as headers and data tables that inform us about the observing conditions e.g., longitude, latitude, gain, exposure time that our data were taken in. Together these are known as Headers Data Units or HDUs https heasarc.gsfc.nasa.gov docs heasarc fits overview.html , and they can help us reduce systematics from the instrument and environment. Additionally, individual science images can be stacked together to increase the overall signal to noise ratio SNR of our observations. note \"But why grayscale?\" FITS images give us a direct correspondence between the location of the pixel that a particular photon of light falls on in our array, and how strong that signal will be. Images taken at specific wavelengths can then be stacked together to create full color composite images https hubblesite.org contents articles the meaning of light and color . The downside for our particular usecase is that these images taken by our eVscope sensor have not been debayered https en.wikipedia.org wiki Bayer filter , which complicates this correspondance. We will explore some of the imaging artifacts that are introduced by this, and potential techniques that we can use to mitigate them. \"\"\" md\"\"\" 📖 Further reading Here are some additional resources that may be of interest for taking a deeper dive into color theory and data representation of images. note \"\" Visualization From Energy to Image https science.nasa.gov ems 04 energytoimage NASA How Are Webb’s Full Color Images Made? https webbtelescope.org contents articles how are webbs full color images made JWST Images as Data and Arrays https computationalthinking.mit.edu Fall24 images abstractions images Julia MIT \"\"\" md\"\"\" 🔧 Notebook setup \"\"\" TableOfContents depth 4 md\"\"\" Packages \"\"\" "},{"url":"image_processing/ii_array_operations/","title":"II - Array Operations","description":"Learn how to interpret and process images as an array of numbers.","tags":["image processing","images","arrays"],"text":" A Pluto.jl notebook v0.20.28 frontmatter title \"II Array Operations\" date \"2025 09 19\" tags \"image processing\", \"images\", \"arrays\" description \"Learn how to interpret and process images as an array of numbers.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils begin Notebook widgets using PlutoUI Analysis tools using AstroImages, ColorTypes Colormap default settings AstroImages.set cmap nothing end md\"\"\" Array Operations \"\"\" md\"\"\" Array representations 🔢 Now that we have this mathematical representation of our image, shown below, let's explore a few key operations that we can perform on it 1. Indexing 2. Slicing 3. Reducing \"\"\" md\"\"\" Indexing We actually saw this earlier already when looking at individual pixels in our image. Indexing is just another way of saying selecting a subset of our image. For example, \"\"\" md\"\"\" selects the element in the first row and first column of our image. We can also use generic keywords like `begin` and `end` to select the first or last element of our matrix, respectively. \"\"\" md\"\"\" tip \"Question\" What does indexing with only a single number, e.g., `img data 10 ` return? Why is this? \"\"\" md\"\"\" 👉 Your notes here \"\"\" md\"\"\" hint See here https docs.julialang.org en v1 manual arrays Linear indexing and here https docs.julialang.org en v1 manual performance tips man performance column major in the manual. \"\"\" md\"\"\" Slicing Selecting multiple elements that are next to each other contiguous is known as slicing. For example, \"\"\" md\"\"\" selects every column in the first row of `img data` while, \"\"\" details md\"What does `| ` do?\", md\"\"\" note \"\" Known as the pipe operator https docs.julialang.org en v1 manual functions Function composition and piping , this is a convenient way to pass the output of one function as input to another. For example, ```julia sqrt sum 1, 4, 5, 6 4.0 ``` is equivalent to ```julia 1, 4, 5, 6 | sum | sqrt 4.0 ``` Note how this seamlessly composes with the dot operator in our image example above. \"\"\" md\"\"\" tip \"Question\" Try going back to your original color image `img`. Using slices, try to produce the following image \"\"\" md\"\"\" For reference, here is the original color image \"\"\" img load ∘ download \"https stsci opo.org STScI 01GA6KNV1S3TP2JBPCDT8G826T.png\" img data img .| Gray .| gray img data 1, 1 img data row img data 1, img data corner img data 1 500, 1 500 md\"\"\" returns the first size img data corner, 1 rows and first size img data corner, 2 columns, i.e., the top left corner \"\"\" img corner gray img data corner .| Gray img 1 60, md\"\"\" 👉 Your notes here \"\"\" md\"\"\" 👇 Your code here \"\"\" md\"\"\" Reducing Often, we would like to know summary statistics about a given region in our image. Applying functions that boil down our box of numbers to a smaller representative set is known as reduction. For example, to get the total pixel value in each column of our above slice, shown below, \"\"\" img data 1 60, md\"\"\" we could do the following \"\"\" sum img data 1 60, dims 1 md\"\"\" tip \"Question\" What does the `dims` keyword do? Try it out on a simpler matrix like ```julia arr 1 2 3 4 ``` Note You can pull up the documentation for any function by selecting the `Live Docs` button in the bottom right corner of this notebook. \"\"\" md\"\"\" 👉 Your notes here \"\"\" md\"\"\" Color maps and color scales 🌈 So far, we have been using general purpose tools for displaying images. We now transition to a more specialized tool named AstroImages.jl https juliaastro.org AstroImages stable , which extends the functionality we have already used to work with astronomical images. details \"Aside\", md\" This method of extending functionality in separate packages is a core part of the Julia language. It allows for ecosystems of tools to form naturally in different fields of study, which then compose seamlessly with each other thanks to the multiple dispatch https docs.julialang.org en v1 manual methods Methods paradigm that underpins the language. For more on this, see this talk https www.youtube.com watch?v kc9HwsxE1OY by one of the Julia creators.\" To start, let's wrap our underlying image data in `img data corner` in an `AstroImage` type \"\"\" img astro gray AstroImage img data corner details \"Why do things looks flipped?\", md\"\"\" You may notice that `img astro` looks transposed and flipped relative to `img data`. This is to comply with the FITS convention https juliaastro.org AstroImages stable manual conventions of placing the origin of an image in the bottom left corner, instead of the top left. \"\"\" md\"\"\" We now have all of the usual benefits of working with image data that we have seen already, along with additional features specific to FITS files, like headers and WCS information if available. We will explore these features more later in the workshop. For now, we will use the `imview` https juliaastro.org AstroImages stable api AstroImages.imview function that comes with AstroImages.jl to explore different ways to visualize our given data. We called ```julia AstroImages.set cmap nothing ``` at the bottom of this notebook to set the default grayscale colormap the relationship between the given pixel value and associated color that our AstroImage.jl images are displayed in. We can override this mapping by passing the `cmap` keyword to `imview` \"\"\" imview img astro gray cmap cividis md\"\"\" This now our image using the Cividis colormap, which can be a good choice for people with color vision deficiencies to help make accurate interpretations of scientific data. To help bring out features of interest, we can also control the functional mapping between pixel value and color via the `stretch` keyword \"\"\" imview img astro gray cmap cividis, stretch powstretch md\"\"\" tip \"Question\" What is `powstretch`? What other functions can be passed? hint AstroImages.jl follows the colorscale specifications defined in DS9 https ds9.si.edu doc ref how.html . \"\"\" md\"\"\" 👉 Your notes here \"\"\" md\"\"\" Now that we have a handle on working with AstroImages.jl data, let's turn next to combining some real FITS data to make a full color image. \"\"\" md\"\"\" Image stacking 🥞 Let's start by pulling in some data. We'll use an example from the AstroImages.jl documentation https juliaastro.org AstroImages stable manual converting to rgb , which uses images of the colliding Antennae galaxies https www.nasa.gov image article antennae galaxies provided by NASA ESA. \"\"\" md\"\"\" RGB Below are images taken in the visible portion of the spectrum in red, green, and blue light, respectively. Data product information here https esahubble.org projects fits liberator antennaedata . \"\"\" antred AstroImage download \"https esahubble.org static projects fits liberator datasets antennae red.fits\" , begin 14 end antgreen AstroImage download \"https esahubble.org static projects fits liberator datasets antennae green.fits\" antblue AstroImage download \"https esahubble.org static projects fits liberator datasets antennae blue.fits\" , begin 14 end img channels antred, antgreen, antblue md\"\"\" note We array slice some of the images to help line things up before stacking. We'll discuss more methodical ways for aligning astronomical images later in the workshop. \"\"\" md\"\"\" We next call the `composecolors` function from AstroImages.jl to combine these three different color channels \"\"\" img rgb composecolors img channels stretch asinhstretch, asinhstretch, asinhstretch, , multiplier 1, 1.7, 1 , md\"\"\" tip \"Question\" Explore the documentation for `composecolors`. What keywords can be passed to it? Try adding your own modified version of the above image using these keywords. \"\"\" md\"\"\" 👉 Your notes here \"\"\" md\"\"\" 👇 Your code here \"\"\" md\"\"\" H alpha We aren't limited to just the light that we can see though. Now that we know about colormaps, we can apply arbitrary color assingments to the underlying data to help us gain insight. For example, let's overlay another image of the Antennae galaxy taken with a Hydrogen filter to highlight active star forming regions that glow brightly in the H alpha portion of the spectrum. We'll assign this activity to the color maroon \"\"\" anthalph AstroImage download \"https esahubble.org static projects fits liberator datasets antennae hydrogen.fits\" , begin 14 end img rgbh composecolors antred, antgreen, antblue, anthalph , \"red\", \"green\", \"blue\", \"maroon1\" , stretch asinhstretch, asinhstretch, asinhstretch, identity, , multiplier 1, 1.7, 1, 0.8 , md\"\"\" And with that, we now have a scientific image that we can use for future analysis. \"\"\" TableOfContents "},{"url":"photometry/i_aperture_photometry/","title":"I - Aperture Photometry","description":"Learn how to perform simple aperture photometry on an astronomical image.","tags":["photometry","arrays","FITS"],"text":" A Pluto.jl notebook v0.20.28 frontmatter title \"I Aperture Photometry\" date \"2025 09 19\" tags \"photometry\", \"arrays\", \"FITS\" description \"Learn how to perform simple aperture photometry on an astronomical image.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end begin Notebook widgets using PlutoUI, CommonMark Analysis tools using AstroImages, ColorTypes, Photometry, DataFramesMeta, CSV, PlutoPlotly, FlexiJoins, SkyCoords, VirtualObservatory, Unitful, UnitfulAstro Colormap default settings AstroImages.set cmap nothing end md\"\"\" 🎯 Aperture photometry Now that we have a handle on working with FITS files image processing i images as data FITS and treating images as arrays of numbers image processing ii array operations Array representations 🔢 , let's turn next to one of the fundamental steps of producing a science product from one of our Unistellar science campaigns, aperture photometry https lco.global spacebook telescopes what is photometry . Photometry is the the measurement of the amount of light that falls on our sensor. The aperture is the shape of the imaginary boundary that we are measuring the light within. A typical example would be placing an aperture around an imaged star or background part of the sky, and then counting up the total flux within that region. Brighter stars have more flux, and dimmer stars have less flux. For our purposes, each number in our \"box of numbers\" model will be a proxy for our flux measurement at that particular pixel. Let's explore this in the sample science image below \"\"\" bind reset Button \"Reset\" md\"\"\" What are some things that you notice? Try doing the same analysis on one of your own FITS images on your computer by clicking the `Browse...` button below \"\"\" begin reset bind img local FilePicker MIME \"image fits\" end md\"\"\" 👉 Your notes here \"\"\" md\"\"\" Estimating net flux 💡 To compute the net flux i.e., counts reported in our table above, we need to determine the flux coming from just the target within our circular aperture. To accomplish this, we subtract off the contributions from the background to the total observed flux. There are many ways to do this, but one common method is to estimate the average number of counts per pixel coming from the background within our annulus, and then use this to subtract the average background flux from the total counts measured from within our target aperture. Using the reported numbers above, we have ```math \\begin align \\text Net flux & \\textcolor darkorange \\text Target flux \\textcolor darkcyan \\text Background flux \\\\\\\\ & \\textcolor darkorange \\text Target flux \\textcolor darkcyan \\text Average background flux \\times \\text Target aperture area \\\\\\\\ & \\textcolor darkorange \\text Target flux \\color darkcyan \\frac \\text Background aperture flux \\text Background aperture area \\times \\text Target aperture area \\\\\\\\ & \\textcolor darkorange \\text Target flux \\textcolor darkcyan \\frac \\text Background aperture flux \\pi\\left R \\text outer, bg ^2 R \\text inner, bg ^2\\right \\times \\pi R \\text target ^2 \\\\\\\\ & \\textcolor darkorange \\text Target flux \\textcolor darkcyan \\text Background aperture flux \\times \\frac R \\text target ^2 R \\text outer, bg ^2 R \\text inner, bg ^2 \\end align ``` where ``\\color darkcyan R \\text target `` is the radius of our target aperture, and ``\\color darkcyan R \\text outer, bg `` and ``\\color darkcyan R \\text inner, bg `` are the outer and inner radius of our background annulus aperture, respectively. Try plugging the numbers in your tables above into this formula to confirm that they are consistent with the reported net flux measured. We now have one of the major fundamental tools in our roadmap to producing science products from our Unistellar science campaigns Photometry. \"\"\" md\"\"\" Code 💻 For those interested, the relevant programming commands are shown below \"\"\" md\"\"\" 🔧 Notebook setup \"\"\" PlutoUI.TableOfContents md\"\"\" Data \"\"\" md\"\"\" Plotting functions \"\"\" function tiny img imgv copy img while length eachindex imgv 10^6 imgv AstroImages.restrict imgv end return imgv end Julia photometry aperture object plotly shape object function circ ap, r ap.r line color lightgreen return circle ap.x r, x min ap.x r, x max ap.y r, y min ap.y r y max line color, end Plotly heatmap trace of img function htrace img zlims, title \"ADU\", restrict true, Account for plotly orientation convention img permutedims img dims is used here to convert back from an offset array to a simple array that JS can ingest zmin, zmax zlims return heatmap x dims img, X .val, y dims img, Y .val, z Matrix Float32 img.data , zmin, zmax, colorbar attr title , colorscale \"Cividis\", end Combines plotly trace and layout into a plot object function plot img img zlims Percent 99.5 img , restrict true imgv tiny ∘ AstroImage img imgv img hm htrace imgv zlims, restrict l Layout width 500, height 500, title \"Photometry example\", xaxis attr title \"X\", constrain \"domain\" , yaxis attr title \"Y\", scaleanchor \"x\", constrain \"domain\" , uirevision 1, return plot hm, l end Default from FilePicker function load img data Nothing img load ∘ download \"https stsci opo.org STScI 01GA6KNV1S3TP2JBPCDT8G826T.png\" img data img .| Gray .| gray img data corner img data 1 500, 1 500 return AstroImage img data corner end function load img data path tempname data \"name\" write path, data \"data\" return load path end img sci load img img local begin img size size img sci X max, Y max img size X mid, Y mid img size .÷ 2 bind coords PlutoUI.combine do Child md\"\"\" tip \"\" Try changing the values below to choose where our circular aperture and circular annulus aperture should be placed. The resulting aperture sums displayed below will update automatically. | | X pixels | Y pixels | radius inner pixels | radius outer pixels | | | | | | target | Child \"x\", NumberField 1 X max default X mid | Child \"y\", NumberField 1 Y max default Y mid | Child \"r\", NumberField 1 1000 default X mid ÷ 4 | | background | Child \"x bg\", NumberField 1 X max default X mid | Child \"y bg\", NumberField 1 Y max default Y mid | Child \"r in\", NumberField 1 1000 default 1.4 X mid ÷ 4 | Child \"r out\", NumberField 1 1000 default 1.7 X mid ÷ 4 \"\"\" end end Define the aperture based on `coords` specified in the above table ap CircularAperture coords.x, coords.y, coords.r Area of circular aperture area sum ap Could also do π ap.r^2 ap bg CircularAnnulus coords.x bg, coords.y bg, coords.r in, coords.r out, let p plot img img sci shapes circ ap line color darkorange , circ ap bg, ap bg.r in line color cyan , circ ap bg, ap bg.r out line color cyan , relayout p shapes p end Area of elliptical aperture area bg sum ap bg Could also do π ap bg.r out^2 ap bg.r in^2 Compute photometry in each aperture phot, phot bg photometry ap, ap bg , img sci Total flux measured in target aperture flux total phot.aperture sum Total flux measured in background annulus flux total bg phot bg.aperture sum Average flux per pixel in background annulus flux avg bg flux total bg area bg Background flux contribution in target aperture flux bg flux avg bg area Net flux target flux background flux flux net flux total flux bg \"\"\" note \"Photometry\" | Target flux counts | Background aperture flux counts | Net flux counts | | | | round Int, phot.aperture sum | round Int, phot bg.aperture sum | round Int, flux net \"\"\" | Markdown.parse md\"\"\" Packages \"\"\" "},{"url":"photometry/ii_cmd_lab/","title":"II - Color Magnitude Diagram Lab","description":"Learn how to create simple CMDs from star cluster observations.","tags":["photometry","arrays","FITS"],"text":" A Pluto.jl notebook v0.20.28 frontmatter title \"II Color Magnitude Diagram Lab\" date \"2026 04 17\" tags \"photometry\", \"arrays\", \"FITS\" description \"Learn how to create simple CMDs from star cluster observations.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end begin Notebook widgets using PlutoUI, CommonMark Analysis tools using AstroImages, ColorTypes, Photometry, DataFramesMeta, CSV, PlutoPlotly, FlexiJoins, SkyCoords, VirtualObservatory, Unitful, UnitfulAstro Colormap default settings AstroImages.set cmap nothing end md\"\"\" 🔵🟡🔴 Color Magnitude Diagram Lab In this lab, we will explore how astronomers take photometric measurements to estimate different properties of star e.g., age, distance, and temperature via so called color magnitude diagrams https apod.nasa.gov apod ap010223.html CMD s. Here is an example of one below https apod.nasa.gov apod image 0102 m55cmd mochejska.jpg For a given star, measuring the difference in observed flux measured between different filters e.g., B and V, x axis give an estimate of the star's temperature, while the flux measurement in a single filter gives an estimate of the star's luminosity y axis . Plotting these together reveals an underlying relationship that ties all stars together https science.nasa.gov asset hubble constructing the hertzsprung russell diagram for globular star cluster . Resource \"https assets.science.nasa.gov content dam science missions hubble releases 2010 10 STScI 01EVSKXA7JN0AR357P4TZ25FA5.mp4\" We will recreate these measurements for the famous M67 star cluster. \"\"\" md\"\"\" 1. Observe M67 Observe M67 either in Science Mode or Enhanced vision mode and load the corresponding FITS file into this notebook. This target should be available in the catalog within the app. Below is a sample EV observation taken by an eVscope 2 \"\"\" bind reset M67 Button \"Reset\" begin reset M67 md\"\"\" note \"Load your own data\" Select \"Browse\" below you would like to visualize your own data before moving on with the rest of this notebook bind img M67 local FilePicker MIME \"image fits\" \"\"\" end md\"\"\" 2. Upload FITS file to Astrometry.net Next, we upload our FITS file to Astrometry.net https nova.astrometry.net upload . This is a handy service for performing plate solving converting from pixel space to RA and Dec and photometry routines on our image. After clicking on the link above, you should see an upload page like this https github.com Unistellar science SETI Education blob main data photometry M67 astrometry net upload page.png?raw true About 30 seconds 1 minute after uploading our data following the instruction above, we should see a success page like the following https github.com Unistellar science SETI Education blob main data photometry M67 astrometry net success page.png?raw true Clicking \"Go to results page\" should then show the following https github.com Unistellar science SETI Education blob main data photometry M67 astrometry net results page.png?raw true There are a variety of different calibration products that we can download. For our purposes, we will just need the `image radec.fits` file shown in the box above. We click on this link to save the file into the same directory as this notebook. Once complete, we load this new file, which should look similar to the table below \"\"\" md\"\"\" This FITS file is a table of photometric measurements made by Astrometry.net, including the RA `ra` , Dec `dec` , and aperture flux `flux` of each source identified in our image. We will next combine this information with data from the Gaia mission https www.esa.int Science Exploration Space Science Gaia to produce our CMD. \"\"\" bind reset M67 phot local Button \"Reset\" begin reset M67 phot local md\"\"\" note \"Load your own data\" Select \"Browse\" below you would like to visualize your own data before moving on with the rest of this notebook bind img M67 phot local FilePicker MIME \"image fits\" \"\"\" end md\"\"\" 3. Load Gaia data Since its launch in 2013, this flagship satellite from the ESA observed nearly two billion celestial objects over its 11 year mission duration. This has lead to a vast catalog of stellar flux measurements that we can then use to supply the x axis of our CMD the \"red\" and \"blue\" measurements that provide stellar color temperature information . We start by loading in up to 2500 Gaia observation in a 30 arcminute radius our rough field of view around M67 \"\"\" md\"\"\" note To query Gaia data for your own target, change the name supplied in the quotes above. If no results are returned, try double checking the name on Simbad https simbad.u strasbg.fr simbad sim id?Ident M67&NbIdent 1&Radius 2&Radius.unit arcmin&submit submit id . \"\"\" coords simbad name let coords execute TAPService simbad , \"\"\" SELECT basic.ra, basic.dec FROM ident JOIN basic ON ident.oidref basic.oid WHERE id ' name ' \"\"\" return first. coords.ra, coords.dec end query gaia name let ra, dec coords simbad name df execute TAPService gaia , \"\"\" SELECT TOP 2500 gs.source id, gs.ra, gs.dec, gs.phot g mean mag, gs.bp rp, gs.parallax, gs.pmra, gs.pmdec, gs.ruwe, DISTANCE POINT gs.ra, gs.dec , POINT ra , dec 60 AS sep arcmin FROM gaiadr3.gaia source AS gs WHERE CONTAINS POINT gs.ra, gs.dec , CIRCLE ra , dec , 0.5 1 \"\"\" | DataFrame rtransform df coords ICRSCoords deg2rad ra , deg2rad dec end df gaia let df CSV.read download \"https raw.githubusercontent.com Unistellar science SETI Education refs heads main src asp workshop assets M67 gaia dr3.csv\" , DataFrame rtransform df coords ICRSCoords deg2rad ra , deg2rad dec end df gaia query gaia \"M 67\" md\"\"\" 4. Cross match Next, we match all entries in the table above to our table from Astrometry.net, where a match is considered to be any object within 2 arcseconds of each other. For various reasons, the coordinates estimated in our image will not always satifsy this constraint, so the total number of cross matched sources will be fewer \"\"\" md\"\"\" 5. Filter membership \"\"\" cm\"\"\" Finally, we only select for sources with similar parallax and proper motion to each other. Plotting our measured eVscope fluxes against the difference in the associated flux between Gaia's \"blue\" and \"red\" filters G sub BP sub and G sub RP sub , respectively then forms the CMD for our cluster. Use the sliders below to explore how different cut offs for the parallax and proper motion affect the plot \"\"\" bind reset cmd Button \"Reset\" begin reset cmd bind cmd cuts PlutoUI.combine do Child cm\"\"\" | Parallax br mas | RA proper motion br mas yr | Dec proper motion br mas yr | | | | | | Child \"plx\", RangeSlider 5 10 | Child \"pmra\", RangeSlider 36 24 | Child \"pmdec\", RangeSlider 60 12 | \"\"\" end end md\"\"\" Scroll over the reference image below to see how close your guesses were to the reported values for this cluster. \"\"\" md\"\"\" Try this with your own data following the steps above tip \"Coming soon\" Extracting color information directly from your images instead of relying on Gaia archives. \"\"\" md\"\"\" 🔧 Notebook setup \"\"\" PlutoUI.TableOfContents md\"\"\" Data \"\"\" Default from FilePicker load img data Nothing load ∘ download \"https github.com Unistellar science SETI Education raw refs heads main data photometry M67 20260303T060902 911 StackInput.fits\" function load img data path tempname data \"name\" write path, data \"data\" return load path end img M67 load img img M67 local Default from FilePicker function load phot data Nothing df load download \"https github.com Unistellar science SETI Education raw refs heads main data photometry M67 image radec stacked.fits\" , 2 | DataFrame return transform df coords ICRSCoords. deg2rad. ra , deg2rad. dec end function load phot data path tempname data \"name\" write path, data \"data\" df load path, 2 | DataFrame return transform df coords ICRSCoords. deg2rad. ra , deg2rad. dec end df phot load phot img M67 phot local df phot df matched let df innerjoin df phot, df gaia , by distance coords, SkyCoords.separation, ≤ 2u\"arcsecond\" dropmissing df, parallax, pmra, pmdec, ruwe end df matched let ids, sep SkyCoords.match df gaia.coords, df phot.coords df hcat df phot, df gaia ids, makeunique true df.arcsec diff coords . rad2deg sep 3600.0 rsubset df arcsec diff coords 2.0 dropmissing df, parallax, pmra, pmdec, ruwe df end df cluster chain df matched begin rsubset begin first cmd cuts.plx ≤ parallax parallax ≤ last cmd cuts.plx first cmd cuts.pmra ≤ pmra pmra ≤ last cmd cuts.pmra first cmd cuts.pmdec ≤ pmdec pmdec ≤ last cmd cuts.pmdec ruwe ≤ 1.4 Hard coded quality indicator end end plot scatter x df cluster.bp rp, y log10. df cluster.flux , mode markers , Layout xaxis title \"G sub BP sub G sub RP sub Gaia DR3 \", xaxis range 0, 2 , xaxis autorange false, yaxis title \"log F Unistellar \", yaxis range 2.5, 5 , yaxis autorange false, title \"M67 Color Magnitude Diagram N nrow df cluster \", uirevision 1, , df ans chain df matched begin rsubset begin 0.5 ≤ parallax parallax ≤ 1.7 12 ≤ pmra pmra ≤ 10 4 ≤ pmdec pmdec ≤ 2 ruwe ≤ 1.4 Hard coded quality indicator end end cm\"\"\" hint \"Comparison to Gaia\" plot scatter x df ans. var\"bp rp\", y df ans.phot g mean mag, mode markers, , Layout xaxis title \"G sub BP sub G sub RP sub \", xaxis range 0, 2 , xaxis autorange false, yaxis title \"G\", yaxis range 16, 8 , yaxis autorange reverse, title \"M67 Color Magnitude Diagram Gaia DR3\", uirevision 1, , Parallax ≈ 1.1 mas | RA proper motion ≈ 11.0 mas yr | Dec Proper motion ≈ 3.0 mas yr \"\"\" md\"\"\" Plotting functions \"\"\" function tiny img imgv copy img while length eachindex imgv 10^6 imgv AstroImages.restrict imgv end return imgv end Julia photometry aperture object plotly shape object function circ ap, r ap.r line color lightgreen return circle ap.x r, x min ap.x r, x max ap.y r, y min ap.y r y max line color, end Plotly heatmap trace of img function htrace img zlims, title \"ADU\", restrict true, Account for plotly orientation convention img permutedims img dims is used here to convert back from an offset array to a simple array that JS can ingest zmin, zmax zlims return heatmap x dims img, X .val, y dims img, Y .val, z Matrix Float32 img.data , zmin, zmax, colorbar attr title , colorscale \"Cividis\", end Combines plotly trace and layout into a plot object function plot img img zlims Percent 99.5 img , restrict true imgv tiny ∘ AstroImage img imgv img hm htrace imgv zlims, restrict l Layout width, height, title timestamp img , xaxis attr title \"X\", constrain \"domain\" , yaxis attr title \"Y\", scaleanchor \"x\", constrain \"domain\" , uirevision 1, return plot hm, l end plot img img M67 let p plot img img M67 aps CircularAperture. df cluster.x, df cluster.y, 20 shapes circ ap for ap in aps relayout p shapes, title \"Sources selected\" p end md\"\"\" Packages \"\"\" "},{"url":"spectroscopy/spectroscopy_lab/","title":"Spectroscopy Lab","description":"Collect and analyze the spectra of bright stars.","tags":["spectroscopy","wavelength calibration"],"text":" A Pluto.jl notebook v0.20.28 frontmatter image \"https www.seti.org media jtinir2n unistellar spectroscopy lab 2024 1.png\" title \"Spectroscopy Lab\" date \"2025 08 01\" tags \"spectroscopy\", \"wavelength calibration\" description \"Collect and analyze the spectra of bright stars.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end begin Web using JSON, PlutoUI using Downloads download Visualization using Images, AstroImages, PlutoPlotly end md\"\"\" 🌈 Spectroscopy Lab This notebook provides a short methods introduction to the field of spectroscopy https en.wikipedia.org wiki Astronomical spectroscopy in astronomy. As a companion to the RSpec Unistellar Manual https www.rspec astro.com download Unistellar%20Spectra.pdf , we will walk through some of the techniques behind the core concepts used there to produce and analyze astronomical spectra. Over the course of the notebook, we will introduce and use interactive tools and hands on live code examples to investigate the following concepts Image processing Array matrix operations Wavelength calibration Having some familiarity in high level programming languages like Julia or Python will be useful, but not necessary, for following along with the topics covered. At the end of this notebook, you will hopefully have the tools to build your own analysis pipelines for processing astronomical data, as well as understand the principles behind other astronomical software at a broader level. note \"Coffee? ☕\" The first time this notebook runs might take a while ~ a couple minutes on older devices because it will download and set up everything for us. This is a good chance to take a stretch or grab a nice beverage 🫖. \"\"\" md\"\"\" With this requisite information out of the way, let's get started \"\"\" msg adding colors md\"\"\" Adding colors in Julia 🎨 This makes magenta ```julia using AstroImages RGB RGB 1, 0, 0 RGB 0, 0, 1 ``` AstroImages.RGB 1, 0, 0 AstroImages.RGB 0, 0, 1 \"\"\" md\" \" details \"Using this notebook 🌱\", md\"\"\" note \"First time running\" Some parts of this Pluto notebook https plutojl.org are partially interactive online, but for full interactive control, it is recommended to download and run this notebook locally. For instructions on how to do this, click the `Edit or run this notebook` button in the top right corner of the page. Note This notebook will download all of the analysis packages and data needed for us, so the first time it runs may take a little while ~ a few minutes depending on your internet connection and platform . Clicking on the `Status` tab in the bottom right will bring up a progress window that we can use to monitor this process, and it also includes an option at the bottom marked `Notify when done` that can be selected to give us a notification pop up in our browser when everything is finished. tip \"Advanced bring your own editor\" This is a fully hackable notebook, so exploring the source code https github.com Unistellar science SETI Education blob main labs occulations occultations lab.jl and making your own modifications is encouraged Unlike Jupyter notebooks, Pluto notebook are just plain Julia files. Any changes you make in the notebook are automatically saved to the source file. This works in the opposite direction too any changes you make to the source file, say in your favorite editor, will automatically be reflected in the notebook in your browser To enable this feature, just add this keyword to the function that was used to start Pluto ```julia repl julia using Pluto julia Pluto.run auto reload from file true This will be on by default in an upcoming release ``` The location of the file for this notebook is displayed in the bar at the very top of this page, and can also be modified there if you want to change where this notebook lives. warning \"Diving deeper\" Periodically throughout the notebook we will include collapsible sections like the one below to provide additional information about items outside the scope of this lab that may be of interest e.g., plotting, working with javascript, creating widgets . details \"Details\", msg adding colors warning \" \" In the local version of this notebook, an \"eye\" icon will appear at the top left of each cell on hover to reveal the underlying code behind it and a `Live Docs` button will also be available in the bottom right of the page to pull up documentation for any function that is currently selected. In both local and online versions of this notebook, user defined functions and variables are also underlined, and ctrl clicking on them will jump to where they are defined. \"\"\" md\"\"\" Image processing Astronomical spectra start their lives as a picture. These images can come in a variety of different formats, the most popular being the Flexible Image Transport System https en.wikipedia.org wiki FITS FITS format. We will explore this later in the notebook, but let's start with another common format that you probably use everyday, Portable Network Graphics https en.wikipedia.org wiki PNG PNG , to get an idea of how image data is represented. \"\"\" md\"\"\" Color images \"\"\" md\"\"\" We can use an image of anything, really, so why not start with dogs? Clicking the button below will pull a new dog image from the internet. \"\"\" md\"\"\" warning \"Heads up\" Sometimes a url returned by the API https en.wikipedia.org wiki API leads to a 404 page. Just hit the button again to download a new dog if this happens. \"\"\" bind run again Button \"Random \" md\"\"\" Images courtesy of https dog.ceo dog api \"\"\" md\"\"\" We now have an image that we can analyze. For starters, let's display some key characteristics about this image \"\"\" md\"\"\" Even though this part is Julia specific, the underlying information is general enough to apply to most image processing libraries. Let's break down what each piece means `ColorTypes` https github.com JuliaGraphics ColorTypes.jl The name of the package where a type called `RGB` is defined. `RGB` https github.com JuliaGraphics ColorTypes.jl rgb plus bgr xrgb rgbx and rgb24 the abstractrgb group A type that stores the red, green, and blue intensity values of a pixel. These can be thought of as sub pixels https en.wikipedia.org wiki Pixel Subpixels `FixedPointNumbers` https github.com JuliaMath FixedPointNumbers.jl The name of the package where a type called `N0f8` is defined. `N0f8` https github.com JuliaMath FixedPointNumbers.jl type hierarchy and interpretation A type that represents a number in memory. This essentially defines the specific number type used for each red, green, and blue value in each pixel. More on `N0f8` and other number formats https juliaimages.org latest tutorials quickstart The 0 to 1 intensity scale . \"\"\" md\"\"\" To summarize, our image is just a matrix of pixels, where each pixel value is represented by a triple of RGB values stored in a memory efficient format. Let's explore next how these numbers connect to how we perceive color. \"\"\" bind resample Button \"Resample\" md\"\"\" Below our selected pixel, we map these R, G, B values to their corresponding sub pixel, where 0 represents black or no brightness , and 1 represents the peak brightness for the given color channel. The resulting color is then the additive combination https en.wikipedia.org wiki RGB color model Additive colors of these individual subpixels. \"\"\" md\"\"\" We are now one step closer to building a spectrum of our image. Astronomers typically work with black and white https hubblesite.org contents articles the meaning of light and color or grayscale https en.wikipedia.org wiki Grayscale images, so we will next see how we can convert our image to this form using the information we have above. Later, we will see why this is a beneficial form to have our image in when we explore the FITS file format. \"\"\" md\"\"\" Grayscale images \"\"\" md\"\"\" The converversion process from ``RGB`` to Grayscale for a given pixel is achieved by taking a weighted average of its channel values according to an international standard https en.wikipedia.org wiki Luma %28video%29 Rec. 601 luma versus Rec. 709 luma coefficients established to emulate how the human eye perceives relative brightnesses https en.wikipedia.org wiki Grayscale Converting color to grayscale ```math 0.299 R 0.587 G 0.114 B \\quad. ``` This is already implemented for us https juliaimages.org latest examples color channels rgb grayscale in the `ColorTypes` package, which we apply below to each pixel of our image to produce the following grayscale version \"\"\" md\"\"\" Taking a look at the properties of our new image, we see that now instead of being a matrix composed of `RGB N0f8 ` types, it is composed of `Gray N0f8 `s. \"\"\" md\"\"\" note We omit the package names for brevity. \"\"\" md\"\"\" In other words, instead of three numbers representing each pixel, we now have a single number for each, which we can view directly \"\"\" md\"\"\" We are now ready to build our spectrum by working directly with this matrix. \"\"\" md\"\"\" tip For more on image analysis and other modern math science computation tools, see this fantastic resource from Computational Thinking https computationalthinking.mit.edu Fall23 images abstractions images . \"\"\" md\"\"\" Array matrix operations \"\"\" md\"\"\" Now that we are able to access the underlying structure of our data, let's explore next how we can extract subsets that we might be interested in. One common approach, known as array slicing https en.wikipedia.org wiki Array slicing , accomplishes this by selecting the subset of pixels that fall within a specified rectangle. Try using the sliders below to specify a region of interest where we would like to build a spectrum from. Note that this will only work in the locally downladed version of this notebook. \"\"\" md\"\"\" Calling the `gray` function again, we have the following array of pixel values to work with \"\"\" md\"\"\" To build a spectrum of this selection across a given direction, we next perform a summation in the perpendicular direction. Synonymous terms for this are \"dimension\" and \"axis\". For example, if we wanted a spectrum in the \"horizontal direction\", we would sum up all the pixels in a given column. We call this final sum for a given column the intensity. Many libraries have this operation built in, typically with a `dims` or `axis` keyword to specify the direction to sum in, as shown below \"\"\" md\"\"\" This returns a vector that should be as long as the number of columns in our original image. Plotting these value as a function of the column location then gives us our 1D spectrum \"\"\" md\"\"\" tip Try moving the original region over different parts of the image to see if any particular features can be picked out in the final spectrum. \"\"\" md\"\"\" Now that we are experts at constructing the spectra of dogs, let's turn next to constructing the spectra of astronomical objects. \"\"\" md\"\"\" eVscope Live View image \"\"\" md\"\"\" Following the procedures outlined in the RSpec Unistellar Manual https www.rspec astro.com download Unistellar%20Spectra.pdf , here is a brief Live View image of Castor https en.wikipedia.org wiki Castor star that I captured from my backyard. \"\"\" md\"\"\" The zeroth order light appears as the bright white spot passing straight through the grating, and the first order spectrum of light can be seen being dispersed horizontally, with redder light to the right. Similarly to the dog images that we have been working with, this is just a regular PNG file which we can analyze in exactly the same way as earlier to produce our 1D spectrum. For convenience, we have modified the region of interest selection process so that it can be directly selected by clicking and dragging over the plot below. Note that the colormap used just artificially applies different colors for viewing purposes, but does not change the underlying data. \"\"\" md\"\"\" Below are the general steps used to produce the final spectrum ```julia Convert to grayscale arr ev live ev live .| Gray | channelview Get x and y limits from dragged region in plot xrange ev live, yrange ev live get lims arr ev live, limits ev live Use these bounds to select the region of interest from our grayscale image window ev live view arr ev live yrange ev live, xrange ev live Sum across rows for each column prof 1D ev live sum window ev live dims 1 | vec ``` \"\"\" md\"\"\" These steps to produce a 1D spectrum are common enough to wrap into a general function so that they can be re used for other targets. \"\"\" md\"\"\" To close out this section on performing array operations on image data, we will look at one of the most common image formats used in astronomy, FITS files. \"\"\" md\"\"\" FITS \"\"\" md\"\"\" Below is a science image of HD123657 https simbad.u strasbg.fr simbad sim basic?Ident HD123657&submit SIMBAD search taken courtesy of Unistellar Citizen Scientist, \\ Stephen Haythornthwaite . For more on taking science images, see here https www.unistellar.com citizen science exoplanets tutorial . One of the benefits of taking images in science mode is that it allows our users to download their raw data https help.unistellar.com hc en us articles 10989728346780 UniData Access How to Download Your RAW Data in FITS format. To open it, we use the `AstroImages.jl` https github.com JuliaAstro AstroImages.jl package which behaves similarly to `ds9` https sites.google.com cfa.harvard.edu saoimageds9 and `astropy` https docs.astropy.org en stable io fits . \"\"\" md\"\"\" tip For more on using science mode observations to analyze eVscope spectra, see this advanced section of the RSpec Unistellar Manual https www.rspec astro.com download Unistellar%20Spectra.pdf \"Using Method 3 Science menu’s Exoplanet transit mode with external stacking\". \"\"\" md\"\"\" Unlike Live View images, FITS https en.wikipedia.org wiki FITS images are already in grayscale and can come packaged with additional metadata known as headers and data tables that inform us about the observing conditions e.g., longitude, latitude, gain, exposure time that our data were taken in. Together these are known as Headers Data Units or HDUs https heasarc.gsfc.nasa.gov docs heasarc fits overview.html , and they can help us reduce systematics from the instrument and environment. Additionally, individual science images can be stacked together to increase the overall signal to noise ratio SNR of our observations. \"\"\" md\"\"\" note \"But why grayscale?\" FITS images give us a direct correspondence between the location of the pixel that a particular photon of light falls on in our array, and how strong that signal will be. Images taken at specific wavelengths can then be stacked together to create full color composite images https hubblesite.org contents articles the meaning of light and color . The downside for our particular usecase is that these images taken by our eVscope sensor have not been debayered https en.wikipedia.org wiki Bayer filter , which complicates this correspondance. We will explore some of the imaging artifacts that are introduced by this, and potential techniques that we can use to mitigate them. \"\"\" md\"\"\" note \"Why do the rows and columns look flipped?\" Note that the rows and columns appear flipped relative to what is shown in our plot. This is because, like Julia and Fortran, FITS files store their array data in column major https docs.julialang.org en v1 manual performance tips man performance column major format in memory. To match the convention that we have adopted for displaying images origin in top left corner, x increasing downwards, y increasing rightwards , we use the `permutedims` https docs.julialang.org en v1 base arrays Base.permutedims function to swap the row and column order. warning \"A note on debayering\" We see some immediate qualitative similarities and differences from our dog spectrum. The dips in our 1D spectrum line up with the dimmer regions in the image, just like the \"dog\" features noted earlier. Zooming in on the image though, we see a cross hatching pattern emerge. This is an artifact of the Bayer filter used by our sensor, and it manifests as a \"sawtooth\" pattern in our 1D specrtrum. As discussed in the \"Debayering\" section of the RSpec Unistellar Manual https www.rspec astro.com download Unistellar%20Spectra.pdf , this imaging artifact can be reduced by either binning our data beforehand or applying a debayering algorithm as part of a stacking routine when combining our FITS images. For our purposes, the spectrum we have is good enough quality for the low resolution spectroscopy analysis we are doing. For example, we already can see broad molecular band features that are characteristic of this M type https en.wikipedia.org wiki Stellar classification Class M star. tip Stay tuned for future labs on comparing different stellar types from eVscope spectral data \"\"\" md\"\"\" Below are similar steps that we used to produce the 1D spectrum of our eV Live View image from earlier. The main difference is that we are now working directly with the numerical image array instead of needing to convert from an RGB or grayscale image first. For more on working with FITS files, see here https juliaastro.org AstroImages stable manual loading images . \"\"\" md\"\"\" So far we have just been working with everything in pixel space. To begin analyzing potential absorption emission features https en.wikipedia.org wiki Spectral line , we will next see how to convert to wavelength space. \"\"\" md\"\"\" Wavelength calibration \"\"\" md\"\"\" In this final process, we will use information about spectral features from a known reference to determine the general relationship between the pixel coordinate `` \\mathrm px `` on our sensor, and the wavelength of light falling upon it `` \\lambda ``. One common approach is to assume a linear relationship between these two spaces. In other words ```math \\lambda \\lambda 0 d \\times \\mathrm px \\mathrm px 0 \\ , ``` where `` d `` is the dispersion in wavelength per pixel and `` \\mathrm px 0 `` is the pixel coordinate corresponding to where we would like the wavelength `` \\lambda 0 `` to be zero this allows us to have a relation of the form ``λ \\dots`` . Using the location of the zeroth order light is typically a good choice for this. To determine ``d`` we select one other feature in our reference spectrum with known wavelength. For example, let's use our image of Castor from earlier since it well, technically the three brightest stars in this sextuple system that all resolve into a single point is an A type star. These types of stars are wonderful calibration sources because they tend to have prominent Balmer series https en.wikipedia.org wiki Balmer series lines from hydrogen absorption in their atmopsheres. Identifying the pixel, wavelength pair `` \\mathrm px \\mathrm line , \\lambda \\mathrm line ``, we have ```math \\newcommand \\wavline \\lambda \\mathrm line \\newcommand \\pxline \\mathrm px \\mathrm line \\newcommand \\pxzero \\mathrm px 0 \\begin align d & \\frac \\wavline 0.0 \\pxline \\pxzero \\frac \\wavline \\pxline \\pxzero \\\\ \\lambda & \\boxed d \\times \\mathrm px \\pxzero \\ , \\end align ``` \"\"\" md\"\"\" Try to identify the zero point and H β line and record their column pixel coordinates in the fields below. The H β will typically be the deepest absorption feature in the A type spectrum. To see how we did, select the `Show lines` option to overlay the rest of the Balmer series lines. They should coincide with the other absorption features present in our spectrum. \"\"\" md\"\"\" bind show lines CheckBox Show lines \"\"\" md\"\"\" Notebook setup 🔧 \"\"\" TableOfContents function img info img nrows, ncols size img eltype img eltype img debug \"Image info\" nrows ncols eltype img return nrows, ncols, eltype img end msg x details \"Details\", x md\"\"\" note \"Web aside\" The website we are pulling images from provides an API https en.wikipedia.org wiki API to interact with its data. We use the stdlib `Downloads.jl` https github.com JuliaLang Downloads.jl to call this API, `JSON.jl` https github.com JuliaIO JSON.jl to parse the data that we downloaded, and `Images.jl` https github.com JuliaImages Images.jl to load it into Julia. This is essentially the same as doing the following on a local PNG file ```julia using Images img load LOCAL PATH TO MY FILE ``` In this case, the path is just the url of the hosted image online provided by the API. \"\"\" | msg md\"\"\" note By default, the value of a variable is displayed above the cell, and debugging logging information below. Adding a semicolon to the end of the line will suppress the former being displayed in the notebook if we like. \"\"\" | msg md\"\"\" note Julia has a delightful way of applying a function element wise to its inputs, known as dot syntax https docs.julialang.org en v1 manual functions man vectorized . \"\"\" | msg md\"\"\" note We used the optional ` view` https docs.julialang.org en v1 base arrays Base. view macro here to access the data directly instead of making a copy. For more on views vs. copies see here https docs.julialang.org en v1 base arrays Views SubArrays and other view types , and for more on macros see here https docs.julialang.org en v1 manual metaprogramming man macros . \"\"\" | msg md\"\"\" note \"Why vec?\" Array operations in Julia preserve dimensionality to make things more consistent and composable https stackoverflow.com a 42353230 16402912 . For example, ```julia sum 1 2 3 4 dims 1 ``` returns another matrix ```julia 1×2 Matrix Int64 4 6 ``` instead of silently changing the shape out from under us to a 1D vector ```julia 4, 6 ``` The flipside is that the plotting library we are using https plotly.com expects a simple vector, so we call `vec` https docs.julialang.org en v1 base arrays Base.vec on the original sum to make this transformation for us before passing it to Plotly. note \"What does | do?\" Known as the pipe operator https docs.julialang.org en v1 manual functions Function composition and piping , this is a convenient way to pass the output of one function as input to another. For example, ```julia sqrt sum 1, 4, 5, 6 4.0 ``` is equivalent to ```julia 1, 4, 5, 6 | sum | sqrt 4.0 ``` \"\"\" | msg md\"\"\" note \"Plotly commands\" ```julia p make subplots rows 2, shared xaxes true, vertical spacing 0.02, x title \"pixel column\", add trace p, scatter x col range dog, y prof 1D dog vals row 1 add trace p, heatmap x col range dog, y reverse row range dog , z window dog vals, colorscale Greys, showscale false, row 2 update p layout Layout yaxis attr title \"intensity\" , yaxis2 attr scaleanchor x, title \"pixel row\" ``` \"\"\" | msg md\"\"\" note \"What is channelview?\" This is a more general version of `gray` that also works for color images and returns a view instead of a copy. Either function can be used. For more information, see here https juliaimages.org v0.20 conversions views Color separations views for converting between numbers and colors 1 . \"\"\" | msg function get lims arr, limits Guard against bind run non interactively e.g., html export ismissing limits && return 1 size arr, 2 , 1 size arr, 1 ymax, xmax size arr xlims limits \"xaxis\" .| x round Int, x xlo, xhi xlims xlo max 1, xlo xhi min xhi, xmax ylims limits \"yaxis\" .| x round Int, x yhi, ylo ylims Assuming heatmap y axis reversed ylo max 1, ylo yhi min yhi, ymax debug vals xlo xhi ylo yhi return xlo xhi, ylo yhi end \"\"\" compute spec1D arr, region lims Given a rectangular region specified by `region lims` inside a 2D image array `arr`, return its 1D spectrum computed along the horizontal axis. Also return the horizontal range of the region for convenience when plotting the 1D spectrum with its corresponding image array. \"\"\" function compute spec1D arr, region lims xrange, yrange get lims arr, region lims region view arr yrange, xrange return vec sum region dims 1 , xrange end stake String ∘ take begin run again img dog let url \"https dog.ceo api breeds image random\" Dogs like stake payload download url, IOBuffer | stake url JSON.parse payload \"message\" load download url end end nrows dog, ncols dog, eltype dog img info img dog md\"\"\" We see here that our image is nrows dog rows by ncols dog columns wide, and each cell or pixel of this image is represented by \"\"\" debug eltype dog begin N sampled pixels 5 resample sample px dog rand img dog, N sampled pixels end md\"\"\" We have N sampled pixels pixels above sampled from our image. Based on how colorful and varied the image is, these pixels can have a range of different colors between them. Pull the slider to look at each of these pixels one by one and or click the `Resample` button to select N sampled pixels new pixels at random. For convenience, we also display the individual R, G, B values next to our slider. \"\"\" bind px dog Slider sample px dog show value true let r, g, b px dog .| red, green, blue md\"\"\" Selected pixel px dog ``\\Longrightarrow`` R RGB r, 0, 0 , G RGB 0, g, 0 , B RGB 0, 0, b \"\"\" end gray dog Gray. img dog img info gray dog gray. gray dog md\"\"\" `rows ` bind row range dog RangeSlider 1 size gray dog, 1 `columns` bind col range dog RangeSlider 1 size gray dog, 2 \"\"\" let tmp copy gray dog tmp row range dog, col range dog . RGB 0, 0, 0 tmp end window dog view gray dog row range dog, col range dog nrows window dog, ncols window dog, img info window dog md\"\"\" Based on our selections, the black rectangular region of interest extends from row first row range dog to last row range dog , and from column first col range dog to last col range dog of our original image, resulting in a slice that is nrows window dog rows by ncols window dog columns. We selected this range by using the following array syntax ```julia array slice original array row range, column range ``` \"\"\" window dog vals gray. window dog prof 1D dog vals sum window dog vals dims 1 | vec \"\"\" warning \"Heads up\" Be aware of potential arithmetic overflow https juliaimages.org latest tutorials arrays colors A note on arithmetic overflow when performing operations on your data. In this case, the function `sum` already takes care of this for us by first converting our pixel values to a larger data type eltype prof 1D dog vals . \"\"\" | Markdown.parse let p make subplots rows 2, shared xaxes true, vertical spacing 0.02, x title \"pixel column\", add trace p, scatter x col range dog, y prof 1D dog vals row 1 add trace p, heatmap x col range dog, y reverse row range dog , z window dog vals, colorscale Greys, showscale false, row 2 update p layout Layout yaxis attr title \"intensity\" , yaxis2 attr scaleanchor x, title \"pixel row\" end md\"\"\" note \"What's ∘?\" This is an operator that allows us to compose functions https docs.julialang.org en v1 manual functions Function composition and piping together. \"\"\" | msg const DPATH \"https github.com icweaver UCAN raw main spectroscopy data\" ev live load download joinpath DPATH, \"castor.png\" img info ev live Convert to grayscale arr ev live ev live .| Gray | channelview bind limits ev live let p plot heatmap z arr ev live, showscale false , Layout xaxis attr title \"column\" , yaxis attr title \"row\", autorange \"reversed\", scaleanchor x, , margin attr t 0, r 10 , add plotly listener p, \"plotly relayout\", \" e let layout PLOT.layout PLOT.value xaxis layout.xaxis.range, yaxis layout.yaxis.range PLOT.dispatchEvent new CustomEvent 'input' \" end prof 1D ev live, xrange ev live compute spec1D arr ev live, limits ev live p spec1D ev live plot xrange ev live, prof 1D ev live, Layout xaxis attr title \"column\" , yaxis attr title \"intensity\" , margin attr t 10, r 10 , p spec1D ev live md\"\"\" bind px 0 NumberField xrange ev live Zero point px bind px line NumberField xrange ev live H β Å \"\"\" λ d, px d px px 0 Load FITS file img fits load download joinpath DPATH, \"HD123657.fits\" img info img fits Get array data and swap rows cols to match plotting convention arr fits img fits.data | permutedims bind limits ev fits let p plot heatmap z arr fits, showscale false , Layout xaxis attr title \"column\" , yaxis attr title \"row\", autorange \"reversed\" , add plotly listener p, \"plotly relayout\", \" e let layout PLOT.layout PLOT.value xaxis layout.xaxis.range, yaxis layout.yaxis.range PLOT.dispatchEvent new CustomEvent 'input' \" end prof 1D fits, xrange ev fits compute spec1D arr fits, limits ev fits plot xrange ev fits, prof 1D fits, Layout xaxis attr title \"column\" , yaxis attr title \"intensity\" , const ref wavs Dict h alpha 6562.8, h beta 4861.4, h gamma 4340.5, h delta 4102.7, h epsilon 3970.1, λ line ref wavs h beta d λ line px line px 0 λ ev live λ. d, xrange ev live if 8.4 ≤ d ≤ 8.79 md\"\"\" tip \"Success 🎉\" Congratulations, you have successfully calibrated your 1D spectrum We hope that this brief introduction to analyzing spectra has provided you with some general tools for tackling your own datasets, and inspiration to explore further interesting topics in this field. Below are a few potential items that may be of interest for extending the techniques developed here. note \"Extension ideas\" Pixel binning Image stacking Background subtraction Non linear wavelength calibration \"\"\" else md\"\"\" warning \"Not quite\" Try double checking which line is the H β feature. A reference calibration sheet like this one https www.aavso.org sites default files Calibration Cheat Sheet.png may be helpful. \"\"\" end let p plot λ ev live, prof 1D ev live, Layout xaxis attr title \"wavelength Å \" , yaxis attr title \"intensity\" , title \"Dispersion round d digits 2 Å pixel\", Overlay reference lines show lines && for name, wav in ref wavs add vline p, wav line color darkgrey, line width 1 end p end "},{"url":"timeseries/eb_lab/","title":"Eclipsing Binaries Lab","description":"Measure light curves from your data. Example eclipsing binary use case included.","tags":["time series","eclipsing binaries"],"text":" A Pluto.jl notebook v0.20.28 frontmatter title \"Eclipsing Binaries Lab\" date \"2025 10 03\" tags \"time series\", \"eclipsing binaries\" description \"Measure light curves from your data. Example eclipsing binary use case included.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end begin Notebook UI using PlutoUI, CommonMark Data wrangling using CCDReduction, DataDeps, DataFramesMeta Web using HTTP, JSONTables, TableScraper Visualization and analysis using AstroImages, PlutoPlotly, AstroAngles, Photometry using AstroImages restrict using Astroalign using Dates, Unitful, Statistics using ImageFiltering AstroImages.set cmap cividis Use DataDeps.jl for dataset management Auto download data to current directory by default ENV \"DATADEPS ALWAYS ACCEPT\" \"true\" ENV \"DATADEPS LOAD PATH\" DIR DataDep \"sample data\", \"\"\" UCAN Data Files Website https www.seti.org education ucan unistellar education materials \"\"\", \"https www.dropbox.com scl fo om02nzsex9ql00gcnp0r4 AA11 SrUS2GUwvuMUQm85x8?rlkey np5upxstx4z6lhcch6tje1b0j&st st2wzmyp&dl 1\" , \"f5692a2382a035b892600e962ac950d70176c7137ae5e2d716816fc9c43aab7c\" , post fetch method unpack, | register end md\"\"\" Eclipsing Binaries Lab Using the photometric analysis tools developed in the previous notebook, we will now turn to the technique of generalizing this for images taken at multiple times to build a time series science product, aka a light curve. We will use sample eVscope data of an eclipsing binary system as a real life test case, and show how we can find additional targets to study from the American Association of Variable Star Observers AAVSO https www.aavso.org . \"\"\" md\"\"\" Background 📖 It turns out that the alien world described in the 3 Body Problem https www.netflix.com tudum articles 3 body problem teaser release date is not too far off from what we see in reality. Star systems can be made up of just one star like in our system, three as in the tv show and book series from which the 3 Body Problem draws its inspiration https en.wikipedia.org wiki Alpha Centauri , or even as many as six different stars as in this recently discovered system https science.nasa.gov universe exoplanets discovery alert first six star system where all six stars undergo eclipses While these would make for some quite interesting sunsets, a system's stability decreases as more bodies are added. This is partly why the most common star systems we see are singular star systems, followed closely behind by binary systems, which have two stars and account for nearly two thirds of all star systems in the Milky Way https pweb.cfa.harvard.edu news most milky way stars are single . A sub class of this binary star case, known as eclipsing binaries, has proved to be an invaluable tool for helping us learn more about orbital mechanics and stellar evolution https www.aavso.org introduction why are eclipsing binary stars important . In these types of systems, not only do these two stars orbit about their common center of mass, but they do so along our line of sight. In other words, eclipsing binaries are star systems where each star passes in front of the other from our vantage point. As they do so, the combined light that we receive from both objects will vary in time. \"\"\" md\"\"\" Resource \"https upload.wikimedia.org wikipedia commons transcoded 7 7e Artist%E2%80%99s impression of eclipsing binary.ogv Artist%E2%80%99s impression of eclipsing binary.ogv.720p.vp9.webm\" ESO L. Calçada In this visualization, we see how the observed brightness of an eclipsing binary system changes based on how much of each star is visible at a given point in time from our perspective. When they are both unobstructed the measured brightness is maximum, and when one is partially covered by the other, the combined brightness decreases periodically over time. In this lab, we will capture this dance going on in real time in a fairly popular constellation. \"\"\" md\"\"\" Introduction 🤝 W Ursae Majoris W UMa https www.aavso.org vsots wuma is an eclipsing binary system located in the Ursa Major https en.wikipedia.org wiki Ursa Major constellation, and can be seen being chased across the sky by the Big Dipper throughout the night Resource \"https github.com Unistellar science SETI Education blob main src ucan eclipsing binary assets constellation WUMa.png?raw true\" W UMa is marked by the larger, red dot to the right of the Big Dipper \"\"\" md\"\"\" Discovered in the early 1900s, this system is composed of two main sequence F type stars orbiting so closely together that they are expected to be contact binaries https en.wikipedia.org wiki Contact binary , meaning they share a common gaseous envelope. Their proximity to each other also gives this system an astonishingly short orbital period of just over 8 hours. Because of how neatly this fits into an Earth day, eclipse events occur at almost the same time every night, making them the ideal target for regular follow up study. When the fainter of the two passes in front of the brighter one, we call that a primary eclipse , and when the brighter companion passes in front of the fainter one, we call it a secondary eclipse . According to the AAVSO ephemeris https milwaukeeastro.org EB MAS EB 2025 10.pdf for this system, primary eclipse is predicted to occur around 6 00 7 00 UTC , depending on what part of the month we are in. Due to the similar sizes and spectral types of each star, the eclipse depths for both are fairly similar and can vary by almost a whole apparent magnitude With a total duration of about three hours, the entire light curve for a given eclipse can be captured in a single night. tip For more on reading eclipsing binary ephemerides, please see this AAVSO resource https www.aavso.org how use eb ephemeris . \"\"\" md\"\"\" Data inspection 🔎 For this lab, we will be using eVscope 2 data collected for this target on the night of March 25th, 2024. Observations were taken in the exoplanet science mode https science.unistellar.com exoplanets tutorial with the following observation parameters ``` Observing mode Exoplanets Eclipse mid point 23 00 PT Eclipse duration 3 hrs Ra 09h 43m 45.47s Dec 55° 57' 09.07\" Duration 3 hrs Exposure time ms 1400 Cadence ms 4000 Recommended Gain dB 0 Max Gain dB 1.78 ``` note The sample data for this lab can be downloaded here https drive.google.com drive folders 1P7PTtx9LUnR QF SWjszTBjCwpJHZ7AN?usp sharing . \"\"\" md\"\"\" Here is a summary of the header information for each science frame taken \"\"\" Resource \"https raw.githubusercontent.com Unistellar science SETI Education refs heads main data timeseries ebs finder WUMa.jpg\" md\"\"\" Here is the associated header information for our science frame \"\"\" bind reset Button \"Reset\" md\"\"\" Uh oh, we see that our images are literally rotating out from under us This field rotation https calgary.rasc.ca field rotation.htm and also some drift that needed to be manually corrected partway through the observation are normal effects of taking long duration observations on an alt az mount. Fortunately, it is fairly manageable to handle this as we will see in the next section. \"\"\" md\"\"\" A note on image calibration A critical step in analyzing astronomical data is accounting for sources of noise that may impact our final image. This process is known as calibration, and its purpose is to increase the signal to noise ratio of our science images. Here is a nice summary modified from Practical Astrophotography https practicalastrophotography.com a brief guide to calibration frames of three of the main sources of noise that we typically try to calibrate for note \"\" Bias frames \"Your camera inherently has a base level of read out noise as it reads the values of each pixel of the sensor, called bias. When averaged out, basically it’s an inherent gradient to the sensor. Bias Frames are meant to capture this so it can be removed.\" Dark frames \"When taking a long exposure, the chip will introduce \"thermal\" noise. Its level is magnified by three things – temperature, exposure time, and ISO. Dark frames are used to subtract this sensor noise from your image and mitigate \"hot or cold\" pixels. Some modern sensors automatically calculate dark levels and don't need dark frames . Dark Frames also will calibrate the chip so all pixels give the same value when not exposed to light.\" Flat frames \"I've seen people say flats help with light pollution. NOT TRUE AT ALL. Flat frames allow you to calculate the correction factor for each pixel so they all give the same value when exposed to the same quantity of light for a given optical path. Things like dust motes, lens vignetting consistently reduce the light to a given pixel, flat frames allow you to mathematically remove them to give a smooth evenly illuminated image.\" \"\"\" md\"\"\" Different flat fielding techniques are being examined by our team, but in general this has not been oberserved to be a significant source of noise in science mode observations. In practice, the sensor calibration https help.unistellar.com hc en us articles 360011333600 Sensor calibration Dark Frame How and Why step that is required at the end of science observations are set to the same gain and exposure time as your science images. By doing this, the bias frame is automatically built into the dark frames collected during this step, so no separate bias acquisition is needed. We find that the contribution from dark noise does not impact our observations significantly, so we have excluded this calibration step for simplicity. Stay tuned for a future calibration notebook though where we will explore these procedures in more detail \"\"\" md\"\"\" Image alignment 📐 A typical astronomical observation might use the know RA and Dec of the field to plate solve https astrobackyard.com plate solving each frame against background sources see, e.g., astrometry.net https astrometry.net . This then gives a coordinate transformation e.g., with the World Coordinate System WCS standard https fits.gsfc.nasa.gov fits wcs.html that can be applied to each frame to align them to a common grid with open source tools like AstroImageJ https www.astro.louisville.edu software astroimagej . Unfortunately, plate solving is a computationally expensive process that can take quite a while, especially if we have a large number of frames. Fortunately, there is a nice alternative that we can use if we do not care about the WCS information asterisms https en.wikipedia.org wiki Asterism astronomy . In this process, one frame is aligned to another in much the same way that the human brain might try to by matching common shapes between each frame to each other. This works indpendently of WCS information, so it completely avoids the need to plate solve our images. We will use this method to align our science frames. \"\"\" md\"\"\" Let's see how our aligned frames look below \"\"\" md\"\"\" Nice The rotation looks to have been successfuly transformed out. We turn next to computing the photometry for our aligned series of frames. \"\"\" md\"\"\" Aperture photometry 🔾 This process is very similar to what was shown in our previous notebook. Only now, instead of computing the photometry for a single image, we will compute it for a series of images and store the results \"\"\" md\"\"\" By convention, `t` is our observation time, `x1` is for our target star, and `x2` is for our comparison star. We also scaled the flux of each star by its median observed flux to make the numbers more comfortable to work with. We can now visualize the light curve of our target from our photometry table above \"\"\" md\"\"\" And divide by our comparison star \"\"\" md\"\"\" To try this analysis on you own data 1. Place your data folder into the same folder as this notebook 1. Type the name of your data folder below e.g., `my data` 1. Click `Enter` \"\"\" begin reset bind DATA DIR local confirm TextField label \"Enter\" end DATA DIR if isempty DATA DIR local datadep\"sample data\" else joinpath DIR , DATA DIR local end df sci all let df fitscollection DATA DIR abspath false transform df \"DATE OBS\" DateTime. \"DATE OBS\" end begin N max 200 N sci all nrow df sci all md\"\"\" note This can be a pretty large number of data files depending on the observation, so for simplicity we will just use a subset for our lab, which we can specify. \"\"\" end cm\"\"\" note \"File selection\" Move the slider below, then click `Select` to specify the total number of equally spaced observations in time to use bind nrows max confirm Slider 2 N sci all show value true, default min N sci all, 200 , Heads up, as max steps goes up, performance goes down max steps 4 000, label \"Select\", For safety, this will default to N max if you have more than this number of files to process, but you can of course raise this limit with the slider if your computer can handle it. \"\"\" df sci let rows to use round. Int, range 1, N sci all length nrows max df sci all rows to use, end imgs sci map eachrow df sci do f img load f.path mapwindow median , similar img , img, 3, 3 Good for catching hot pixels end arrs aligned align frames imgs sci box size 3, 3 imgs sci aligned let imgs aligned map imgs sci, arrs aligned do img0, img shareheader img0, img end imgs sci 1 , imgs aligned... end let obs start, obs end df sci , \"DATE OBS\" | extrema .| string md\"\"\" We see that we have nrow df sci fits files taken from obs start obs end UTC. Here's what that first image looks like compared to its finder chart https astro.swarthmore.edu transits finding charts.cgi \"\"\" end img sci load first df sci .path The semicolon hides automatic output img size, img eltype size img sci , eltype img sci md\"\"\" It looks like this image is first img size x last img size pixels, with the ADU counts for each pixel stored as a img eltype to reduce memory storage. Now that we know that we are pointing at the right place in the sky, let's take at look at how these images change over time. Use the display below to scroll through each of our science frames. Note for the rest of this notebook that we will be using the default image orientation in the plotting software \"\"\" begin X max, Y max img size X mid, Y mid img size .÷ 2 bind coords PlutoUI.combine do Child md\"\"\" | | X pixels | Y pixels | radius pixels | | | | | | target | Child \"x\", NumberField 1 X max default 1029 | Child \"y\", NumberField 1 Y max default 779 | Child \"r\", NumberField 1 1000 default 50 | | comparison | Child \"x comp\", NumberField 1 X max default 1153 | Child \"y comp\", NumberField 1 Y max default 711 | Child \"r comp\", NumberField 1 1000 default 50 note \"Apertures and comparison stars\" To better show the frame to frame differences, we also added some sample target and comparison star aperturess in green and orange, respectively centered on the first frame in our image series. We use comparison stars to divide out common systematics like atmospheric turbulence and other changes in seeing conditions so that ideally only the target signal will be left. \"\"\" end end img sci reverse begin end , begin end header img sci h header img sci md\"\"\" note To reload the original sample data, clear the field above and click `Enter` again. \"\"\" Aperture object that will be used for photometry x center, y center, radius ap target CircularAperture coords.x, coords.y, coords.r ap comp1 CircularAperture coords.x comp, coords.y comp, coords.r comp aps ap target, ap comp1 aperture sums map imgs sci aligned do img Returns x center, y center, aperture sum for each aperture p photometry aps, img Just store the aperture sum for each frame p.aperture sum end df phot let `stack` converts to a Matrix ` auto` names the columns for us `copycols` sets whether we want a view or copy of the source matrix data stack aperture sums dims 1 data . median data dims 1 df DataFrame data, auto copycols false transform df begin x1 x1 median x1 x2 x2 median x2 end Place the observation time in the first column insertcols df, 1, t df sci. \"DATE OBS\" end let Switch to long \"tidy\" format to use convenient plotting syntax p plot stack df phot x t, y value, color variable, mode markers, layout Layout xaxis attr title \"Date UTC \" , yaxis attr title \"Relative aperture sum\" , title \"Raw light curves\", legend title text \"Source\", relayout p, layout p end let sc scatter x df phot.t, y df phot.x1 . df phot.x2, mode markers layout Layout xaxis attr title \"Date UTC \" , yaxis attr title \"Relative aperture sum\" , title string \"Divided light curve br \", h \"PURPOSE\" , \" observation \", h \"DATE OBS\" , legend title text \"Source\", plot sc, layout end md\"\"\" Wrapping up We now have a light curve of an eclipsing binary captured at the predicted time By eye, totality looks to have lasted for about half an hour, and the total eclipse duration looks to be close to the three hours estimated by the ephemeris. Not too bad for a quick observation taken from a backyard in the middle of a light polluted city. Since the total period for this system is about 8 hours, we only caught one of the eclipses, in this case the secondary eclipse. With a more careful treatment of the calibration and data reduction procedures, we might also be able to measure the eclipse depth as well as get a more precise estimate on the \"time of minimum\" ToM . The former allows us to determine the size of the eclipsing object relative to its companion, and the latter is the precise time that the two objects are exactly aligned. Measuring the ToM over time create so called \" O C curves https www.aavso.org analysis times minima o c diagram \", or observed minus calculated predicted times over time, which allow us to not only measure the periods of binary systems, but also characterize the stellar and orbital evolution of these dynamic systems. \"\"\" md\"\"\" Extensions 🌱 \"\"\" md\"\"\" Other systematics Although this was a fairly bright target with a relatively large signal to noise ratio http spiff.rit.edu classes ast613 lectures signal signal illus.html , its resulting light curve still contains systematics that can be addressed. \"\"\" md\"\"\" Observing other eclipsing binary systems The AAVSO has a great web interface https targettool.aavso.org for finding other potential eclipsing binary targets. Below, we briefly show how this could be accessed in a programmatic fashion using their API https targettool.aavso.org TargetTool api . If there is interest, we may publish a separate lab on just this topic. \"\"\" md\"\"\" We start by creating an account https targettool.aavso.org init default user register? next init default index on AAVSO. This will allow us to access their API and set our observing location. Once we are logged in, our API key will be displayed as a string of numbers and letters across the top of the API webpage https targettool.aavso.org TargetTool api . Copy this key into a text file in your data folder, and name it `.aavso key`. Select the `Query` button below to submit your query to AAVSO. \"\"\" bind submit query Button \"Submit Query\" begin submit query username if isfile \".aavso key\" debug \"API key found\" readline \".aavso key\" else debug \"Please load your API key using the instructions above.\" \"\" end end md\"\"\" note This is your personal key. Do not share this with others. \"\"\" md\"\"\" We are now ready to query AAVSO for eclipsing binaries observable from our location. Using the HTTP.jl https juliaweb.github.io HTTP.jl stable package, we send our query using the following format ```julia HTTP.get url query ``` where `url` is entry point into the API essentially what we would manually type into our browser window ```julia url \"https your api key here api token targettool.aavso.org TargetTool api v1 targets\" ``` and `query` is a key, value map dictionary of settings that we would like to pass to the API ```julia query latitude 37.76329102360394, longitude 122.41190624779506, obs section \"eb\", observable true, orderby \"period\", ``` Below is a list from the API page of what each of the inputs mean tip \"\" `obs section` An array with observing sections of interest. You may use one or more of ac,ep,cv,eb,spp,lpv,yso,het,misc,all. Default is \\ 'ac'\\ Alerts & Campaigns . `observable` If true, filters out targets which are visible at the telescope location during the following nighttime period. Default is false. `orderby` Order by any of the output fields below, except for observability\\ times and solar\\ conjunction. `reverse` If true, reverses the order. Default is false. `latitude` Latitude of telescope. South is negative, North is positive. If not provided, the user's settings are assumed. `longitude` Longitude of telescope. West is negative, East is positive. If not provided, the user's settings are assumed. `targetaltitude` Minimum altitude that the telescope can observe in degrees relative to the horizon. If not provided, the user's settings are assumed. `sunaltitude` Altitude of sun at dusk and dawn in degrees. If not provided, the user's settings are assumed. \"\"\" md\"\"\" Feel free to uncomment the lat long fields below to override the default location set in your profile, or add any additional settings. We store our query in a DataFrame https dataframes.juliadata.org stable to view the first 10 results \"\"\" df all if isempty username DataFrame else api \"targettool.aavso.org TargetTool api v1 targets\" url \"https username api token api \" query latitude 37.76329102360394, longitude 122.41190624779506, obs section \"eb\", observable true, orderby \"period\", r HTTP.get url query The table under the `target` field of the JSONTable does not seem to convert nulls to missings, so using the raw string directly instead DataFrame jsontable chop String r.body head 12 end md\"\"\" It looks like we have nrow df all hits. Let's filter these for targets that are easily observable, i.e., with our following criteria 1. Large change in brightness at least half a mag 2. Fairly short period period 3 days 3. Includes an ephemeris the `other info` column must include this link note We also prioritize dimmer targets V 9.0 . The reason for this is that we are taking a time series over the course of hours, which would lead to an unfeasable number of total science frames taken if the exposure time for each one needed to be dialed down for bright targets. Instead, we fix our exposure time to the maximum on eVscopes 4 seconds , and select targets that would not be overexposed at this level. Lastly, we select the columns that we care about and make some visual transforms for convenience e.g., including units, converting decimal RA and Dec to ` h m s `, and ` ° ' \" ` format, respectively, for easy copy pasting into the Unistellar app \"\"\" md\"\"\" Determining observation parameters Once a target has been found, here's how we might estimate an observing setup for it based on the Unistellar Exposure Time and Gain Calculator https docs.google.com spreadsheets d 1niBg5LOkWyR8lCCOOcIo6OHt5kwlc3vnsBsazo7YfXQ edit gid 0 . \"\"\" bind target PlutoUI.combine do Child cm\"\"\" tip \"Observation inputs\" Enter your target's visual magnitude and desired exposure time in milliseconds below |``V \\mathrm mag ``|``t \\mathrm exp ``| | | | | Child v mag, NumberField 1 0.1 20 default 11.7 | Child t exp, NumberField 100 100 4 000 default 3 200 ms \"\"\" end nrow df all Total number of targets in our list md\"\"\" Notebook setup 🔧 \"\"\" TableOfContents depth 4 Keep these values untouched const baseline v mag 11.7, V mag t exp 3200.0, Exptime ms gain 25.0, Gain dB peak px 3000, Peak Pixel ADU md\"\"\" Helper functions \"\"\" function to hms ra deci hms round. deg2hms ra deci digits 2 return format angle hms delim \"h \", \"m \", \"s\" end function to dms ra deci dms round. deg2dms ra deci digits 2 return format angle dms delim \"° \", \"' \", \"\\\"\" end function get url s return url chain s begin split \"Ephemeris info \" last split \" \" first end end get shapes aps line color lightgreen circle ap.x ap.r 2, ap.x ap.r 2, ap.y ap.r 2, ap.y ap.r 2 line color, for ap in aps function ephem url st scrape tables url ephem blob st 3 .rows if length ephem blob 2 4 error \"Expected ephemeris to have Epoch, Start, Mid, and End. Received \", ephem blob 2 end ephem title, ephem data... filter x length x 4, ephem blob return ephem title, ephem data end function deep link mission \"transit\", ra 0.0, dec 0.0, c 4 000, et 4 000, g 0.0, d 0.0, t 0.0, scitag \"scitag\", link join \"unistellar science mission ?ra ra \", \"dec dec \", \"c c \", \"et et \", \"g g \", \"d d \", \"t t \", \"scitag scitag \", , '&' return Markdown.parse \" link link \" end Make the table view a bit nicer in the browser pretty df DataFrames.PrettyTables.pretty table HTML, df maximum column width \"max width\", nosubheader true, alignment c, function flux factor target, baseline f mag target.v mag baseline.v mag 2.5 | exp10 f exp target.t exp baseline.t exp return f mag f exp end Maximum gain max gain baseline, f baseline.gain log10 f log10 1.122 Recommended gain rec gain g Int round g, RoundDown 1.0 df candidates if isempty username DataFrame star name \"Sol\" else chain df all begin dropmissing rsubset begin min mag 9.0 && min mag max mag ≥ 0.5 && min mag band \"V\" && max mag band \"V\" && period ≤ 3.0 && startswith other info, \" Ephemeris\" end rtransform ephem url get url other info rtransform begin star name period round Minute, period u\"d\" | canonicalize ra to hms ra ra deci ra dec to dms dec dec deci dec min mag min mag band max mag V mag min mag max mag 2.0 max mag band var type min mag max mag ephem link Markdown.parse \" link ephem url \" ephem url unix timestamp last ∘ first observability times end rtransform begin gain let target v mag V mag, t exp 4 000 Default to max exp f factor flux factor target, baseline gain max max gain baseline, f factor rec gain gain max end end sort period select begin star name period ra ra deci dec dec deci V mag gain ephem link ephem url end end end md\"\"\" We now have nrow df candidates prime candidates that we can plan our observations for. Clicking on the `ephem link` in the last column should take us to a table on AAVSO with the predicted eclipse times for the next month. For convenience, we can also select one of the targets below to generate a table of deep links note This will only work for targets that have a complete ephemeris. All times are in UTC. \"\"\" bind star name Select df candidates.star name df selected rsubset df candidates star name star name df ephem if isempty username DataFrame else ephem title, ephem data ephem only df selected.ephem url df DataFrame stack ephem data dims 1 , ephem title, fmt dateformat\"dd u YYYY HH MM\" chain df begin rtransform begin Epoch parse Float64, Epoch star name only df selected.star name Start DateTime Start, fmt Mid DateTime Mid, fmt End DateTime End, fmt end rtransform begin Duration canonicalize End Start Duration s Second End Start .value unix timestamp ms 1 000 datetime2unix Mid end end end df obs if isempty username DataFrame else rselect leftjoin df selected, df ephem on star name begin star name Start Mid End Duration deep link deep link ra ra deci, dec dec deci, g gain, d round Int, 1.5 Duration s , t round Int, unix timestamp ms , scitag join \"e\", Dates.format Mid, dateformat\"yymmdd\" , replace star name, \" \" \"\" , , end end let f factor flux factor target, baseline gain max max gain baseline, f factor gain recommended rec gain gain max debug \"Observing params\" gain max gain recommended end Helpful for not having ginormous plot objects r2 img restrict ∘ restrict img function htrace img zmin 2 400, zmax 3 200, title \"ADU\", restrict true, if restrict img small r2 img else img small img end img small permutedims img small return heatmap x img small.dims 1 .val, y img small.dims 2 .val, z img small.data, zmin, zmax, colorbar attr title , colorscale Cividis, end function circ ap line color lightgreen return circle ap.x ap.r, x min ap.x ap.r, x max ap.y ap.r, y min ap.y ap.r y max line color, end Convert to plotly objects for plotting shapes circ ap target , circ ap comp1 line color orange , circ ap comp2 line color orange , timestamp img header img \"DATE OBS\" function plot img i, img zmin 2400, zmax 3200, restrict true hm htrace img zmin, zmax, restrict l Layout width, height, title string \"Frame i \", timestamp img , xaxis attr title \"X\", constrain domain , yaxis attr title \"Y\", scaleanchor x, constrain domain , uirevision 1, return plot hm, l end function plot anim imgs N length imgs zmin, zmax AstroImages.PlotUtils.zscale first imgs frames map 1 N do i p plot img i, imgs i zmin, zmax frame data collect p.data , name \"frame i \", layout attr title text p.layout.title , traces 0 , end layout Layout title first frames .layout.title, shapes, width 500, height 500, margin b 90, updatemenus attr type \"buttons\", direction \"left\", x 0.5, y 0, xanchor \"center\", yanchor \"top\", pad t 90, buttons attr label \"▶ Play\", method \"animate\", args nothing, attr fromcurrent true, transition duration 0, frame attr duration 200, redraw true , , , attr label \"⏸ Pause\", method \"animate\", args nothing , attr mode \"immediate\", frame attr duration 0, redraw true , , , , , , sliders attr active 0, pad t 10, steps attr method \"animate\", label \" i \", args \"frame i \" , attr mode \"immediate\", transition duration 0, frame attr duration 5, redraw true , , for i in 1 N , , , return plot first frames .data, layout, frames end plot anim imgs sci plot anim imgs sci aligned md\"\"\" Packages \"\"\" "},{"url":"timeseries/occultations_lab/","title":"Asteroid Occultations Lab","description":"Observe a nearby asteroid occulting a background source.","tags":["time series","asteroids"],"text":" A Pluto.jl notebook v0.20.28 frontmatter image \"https www.seti.org media h3ejkrf3 image 0.png\" title \"Asteroid Occultations Lab\" date \"2025 08 01\" tags \"time series\", \"asteroids\" description \"Observe a nearby asteroid occulting a background source.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils This Pluto notebook uses bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of bind gives bound variables a default value instead of an error . macro bind def, element format off return quote local iv try Base.loaded modules Base.PkgId Base.UUID \"6e696c72 6542 2067 7265 42206c756150\" , \"AbstractPlutoDingetjes\" .Bonds.initial value catch b missing end local el esc element global esc def Core.applicable Base.get, el ? Base.get el iv el el end format on end begin Notebook UI using PlutoUI, CommonMark Data wrangling using CCDReduction, DataDeps, DataFramesMeta Visualization and analysis using AstroImages, PlutoPlotly, Photometry, ImageCore, ImageFiltering, Statistics using AstroImages restrict using Astroalign using Dates, Unitful, UnitfulAstro, Measurements AstroImages.set cmap cividis Use DataDeps.jl for dataset management Auto download data to current directory by default ENV \"DATADEPS ALWAYS ACCEPT\" \"true\" ENV \"DATADEPS LOAD PATH\" DIR DataDep \"data\", \"\"\" UCAN Data Files Website https www.seti.org education ucan unistellar education materials \"\"\", \"https www.dropbox.com scl fo go5ensqkpuumkhimuzy2p ANQJTr6oGTsyz1y0hLbPHIc?rlkey bqcvalmv9mxptpp5duirj1auv&st u59r839k&dl 1\" , \"9ad9e40401024482672a79dcb59da2a11d4ec4ebd4d185b35abb79eb9adef334\" , post fetch method unpack, | register end md\"\"\" 🪨 Asteroid Occultations Lab In this lab we will observe an asteroid passing in front of a star in real time and explore how to produce and analyze its resulting light curve. For more on taking these types of observations, see our Unistellar Science page here https science.unistellar.com asteroid occultations . \"\"\" md\"\"\" Background 📖 Asteroids are small, rocky bodies orbiting our Sun, primarily in a circular orbit between Mars and Jupiter know as the asteroid belt . There are millions of these bodies present in our Solar System, and they are thought to be the remnants of our early Solar System during its formation. For this reason, understanding more about these dark wanderers can give us insight into our origins. Asteroids do not emit their own light, so we must rely on other methods to observe them. One such method is to wait for an asteroid to pass in front of a background star from our point of view. When this happens, the light from the star is momentarily blocked out in what is known as an occultation event. Resource \"https science.unistellar.com wp content uploads 2023 03 90Antiope shadow cords v1.png\" Simplified diagram of an asteroid occultation. Each colored band represents a chord of the asteroid's shadow that an observer on Earth might catch. In aggregate, these observations can give us an idea of the asteroid's shape and size. The duration of this event, combined with how fast the asteroid is moving, can then give us an estimate of the asteroid's size. In this lab, we will step through this process using eVscope data collected from an occulting asteroid. \"\"\" md\"\"\" Data inspection 🔎 We start by loading in the raw sample data \"\"\" const DATA DIR datadep\"data\" df sci let df fitscollection \"data \" abspath false transform df \"DATE OBS\" DateTime. \"DATE OBS\" end Semicolon hides automatic output Just show the first 10 rows first df sci, 10 md\"\"\" It looks like we have nrow df sci science frames of our \"mystery\" target gathered between the following times in UTC \"\"\" t start, t end extrema df sci. \"DATE OBS\" md\"\"\" or about \"\"\" t end t start | canonicalize md\"\"\" That's pretty quick Let's see how each image frame looks note that in the online version of this notebook that the slider will not work \"\"\" imgs sci load f for f in df sci.path imgs sci map eachrow df sci do f img load f.path mapwindow median , similar img , img, 3, 3 Good for catching hot pixels end bind frame i Slider 1 length imgs sci show value true md\"\"\" tip \"Plotting aside\" We opted to use plotly https plotly.com javascript for our visualizations because it as a javascript library that integrates very well this notebook via PlutoPlotly.jl https github.com JuliaPluto PlutoPlotly.jl . We've included the helper functions used to make these visualizations below. Another fantastic choice is Makie.jl https docs.makie.org v0.21 , which is more composable, modern, and simpler to develop with. Unfortunately, its web support still has a few rough edges, but they are quickly being ironed out. \"\"\" md\"\"\" There's definitely some wiggling going on due to our alt az tracking. If we were really being careful, we would plate solve each frame and use the WCS information to align all of our images. This is computationally expensive and overkill for what we are trying to do, so instead we will align our images without WCS. \"\"\" md\"\"\" Frame alignment 📐 To accomplish this, we will just align on asterisms instead. We will use Astroalign.jl https juliaastro.org Astroalign stable to accomplish this \"\"\" arrs aligned align frames imgs sci box size 3, 3 imgs sci aligned let imgs aligned map imgs sci, arrs aligned do img0, img shareheader img0, img end imgs sci begin , imgs aligned... end md\"\"\" With these aligned images, we can now pop some static apertures onto our frames to perform our photometry more reliably. The target is in the green aperture near the center of the frame, and for fun a sample comparison star is in the orange aperture. We went for a fairly tight aperture size to boost the signal to noise ratio of our final light curve. \"\"\" bind frame i aligned Slider 1 length imgs sci aligned show value true df sci md\"\"\" Ok, let's do some photometry next \"\"\" md\"\"\" Aperture photometry 🔾 Based on the visualization above, we were able to make some pretty good guesses for our target and comparison star apertures \"\"\" x center, y center, radius ap target CircularAperture 668, 510, 11 ap comp1 CircularAperture 147, 577, 11 md\"\"\" We defined our apertures with the Photometry.jl http juliaastro.org dev modules Photometry package, e.g., `ap target`, for analysis in Julia, and their corresponding plot object, e.g., `circ ap target `, for visualization in plotly. Now, we just call the `photometry` http juliaastro.org dev modules Photometry apertures Photometry.Aperture.photometry function from Photometry.jl and store our results in a table \"\"\" df phot let Run photometry phot map imgs sci aligned do img photometry ap target, ap comp1 , img .aperture sum end Create table df DataFrame stack phot dims 1 , auto insertcols df, 1, t df sci.\"DATE OBS\" transform df xdiv x1 . x2 end md\"\"\" note The first column is time, `x1` is the target flux, `x2` is the comparison star flux, and `xdiv` is the target flux divided by the comparison star flux. \"\"\" md\"\"\" Below is the resulting light curve for our target. The occultation signal is quite striking \"\"\" let sc scatter df phot x t, y xdiv, mode markers l Layout xaxis attr title \"Time UTC \" , yaxis attr title \"Counts\" , title \"Divided light curve\", plot sc, l end md\"\"\" We now have everything we need to make a size estimate for this asteroid \"\"\" md\"\"\" Size estimation 🪨 Given the following system parameters that we know about the Sun's mass https en.wikipedia.org wiki Solar mass and general location of the asteroid belt https en.wikipedia.org wiki Asteroid belt Orbits \"\"\" GMsun 1 ± 0.00007 u\"GMsun\" r 2.7 ± 0.5 u\"AU\" md\"\"\" we can back out the asteroid's rough size `` d \\mathrm asteroid `` based on our timing measurements ```math \\begin align d \\mathrm asteroid & v \\mathrm asteroid \\Delta t \\\\ & \\sqrt \\frac G M \\mathrm sun r \\Delta t \\quad . \\end align ``` \"\"\" Estimated from graph Δt 5 ± 0.5 u\"s\" v √ GMsun r | u\"km s\" d asteroid v Δt | u\"km\" md\"\"\" Alright, it looks like we have a size estimate of d asteroid for our mystery asteroid. Scroll over the box below to see how we did. \"\"\" md\"\"\" hint \"Mystery asteroid\" Name 389 Industria https en.wikipedia.org wiki 389 Industria Location Asteroid belt, central region Diameter 79 km \"\"\" md\"\"\" tip \"Pedagogy aside\" To get our estimates above, we used the following background information The target probably lives in the asteroid belt The asteroid belt roughly spans from 2.2 AU 3.2 AU from the Sun Units and error propagation can be handled nicely for us in the following packages Unitful.jl https painterqubits.github.io Unitful.jl stable , UnitfulAstro.jl http juliaastro.org UnitfulAstro.jl stable , Measurements.jl https juliaphysics.github.io Measurements.jl stable We were only sampling over a single chord, so getting different answers than the published result is to be expected \"\"\" md\"\"\" Next steps We have now successfuly characterized our occulting asteroid Here are some other items to consider note \"\" How could these kinds of observations be combined to get a better estimate of the size and or shape of the asteroid? What other constraints might we be able to make? What kinds of observations would be needed to determine other properties of the asteroid e.g., mass, composition, reflectivity, rotation ? \"\"\" md\"\"\" Notebook setup 🔧 \"\"\" TableOfContents md\"\"\" Convenience functions \"\"\" Set nice colorbar limit for visualizations const zmin, zmax AstroImages.PlotUtils.zscale first imgs sci timestamp img header img \"DATE OBS\" msg x title \"Details\" details title, x md\"\"\" note \"\" `fitscollection` Function from CCDReductions.jl http juliaastro.org CCDReduction.jl stable to quickly summarize fits header info note \"\" ` transform` Macro from DataFramesMeta.jl https juliadata.org DataFramesMeta.jl stable to make changes to our data frames. In this case, converting one of the columns from string format to DateTime format so we can work with dates later note \"\" `| ` Also known as the pipe operator https docs.julialang.org en v1 manual functions Function composition and piping , this is a convenient way to pass the output of one function as input to the next. For example, ```julia sqrt sum 1, 4, 5, 6 4.0 ``` is equivalent to ```julia 1, 4, 5, 6 | sum | sqrt 4.0 ``` \"\"\" | msg Helpful for preventing ginormous plot objects r2 img restrict ∘ restrict img Plotly heatmap trace of img function htrace img zmin zmin, zmax zmax, title \"ADU\", restrict true, Reduce image, creates an offset array with different axis limits img AstroImage img if restrict img small r2 img else img small img end Account for plotly orientation convention img small permutedims img small dims is used here to convert back from an offset array to a simple array that JS can ingest return heatmap x img small.dims 1 .val, y img small.dims 2 .val, z Matrix Float32 img small.data , zmin, zmax, colorbar attr title , colorscale Cividis, end Combines plotly trace and layout into a plot object function plot img i, img restrict true hm htrace img restrict width, height size img if restrict width 2 height 2 else width 2 height 2 end l Layout width, height, title string \"Frame i \", timestamp img , xaxis attr title \"X\", constrain domain , yaxis attr title \"Y\", scaleanchor x, constrain domain , uirevision 1, return plot hm, l end let p plot img frame i, imgs sci frame i end Julia photometry aperture object plotly shape object function circ ap line color lightgreen return circle ap.x ap.r, x min ap.x ap.r, x max ap.y ap.r, y min ap.y ap.r y max line color, end let p plot img frame i aligned, imgs sci aligned frame i aligned shapes circ ap target , circ ap comp1 line color orange relayout p shapes p end function plot anim imgs N length imgs zmin, zmax AstroImages.PlotUtils.zscale first imgs frames map 1 N do i p plot img i, imgs i zmin, zmax frame data collect p.data , name \"frame i \", layout attr title text p.layout.title , traces 0 , end layout Layout title first frames .layout.title, shapes, width 500, height 500, margin b 90, updatemenus attr type \"buttons\", direction \"left\", x 0.5, y 0, xanchor \"center\", yanchor \"top\", pad t 90, buttons attr label \"▶ Play\", method \"animate\", args nothing, attr fromcurrent true, transition duration 0, frame attr duration 200, redraw true , , , attr label \"⏸ Pause\", method \"animate\", args nothing , attr mode \"immediate\", frame attr duration 0, redraw true , , , , , , sliders attr active 0, pad t 10, steps attr method \"animate\", label \" i \", args \"frame i \" , attr mode \"immediate\", transition duration 0, frame attr duration 5, redraw true , , for i in 1 N , , , return plot first frames .data, layout, frames end md\"\"\" Data handling \"\"\" md\"\"\" Packages \"\"\" "},{"url":"timeseries/stellar_pulsators_lab/","title":"Stellar Pulsators Lab","description":"Observe a nearby asteroid occulting a background source.","tags":["time series","pulsatords"],"text":" A Pluto.jl notebook v0.20.28 frontmatter image \"https www.seti.org media h3ejkrf3 image 0.png\" title \"Stellar Pulsators Lab\" date \"2026 04 17\" tags \"time series\", \"pulsatords\" description \"Observe a nearby asteroid occulting a background source.\" layout \"layout.jlhtml\" using Markdown using InteractiveUtils using Astrometry, PlutoUI, Unitful, Dates, AstroTime, LinearAlgebra md\"\"\" Stellar Pulsators Lab While out observing a pulsating star, we notice something odd. The time of maximum brightness that we measure from our telescope is several minutes behind the expected time reported by the AAVSO ephemeris. After carefully accounting for potential sources of error, we find that this discrepancy is real, and that it has a physically meaningful interpretation. To unravel this mystery, we start by introducing our characters. \"\"\" md\"\"\" Ephemeris Given an initial time of maximum brightness `` t 0 `` and uniform period `` P `` between each peak, we would expect subsequent measurements of the time of maximum brightness `` t `` to occur at ```math t t 0 N \\times P\\ , ``` where ``N`` is the integer number of cycles after the initial peak brightness. \"\"\" ephem N, t0, P t0 N P t0 2453766.839 | julian2datetime P 0.12053525u\"d\" md\"\"\" Introduction Our target, SZ Lyn https vsx.aavso.org index.php?view detail.top&oid 17922 , is part of a well studied class of pulsating stars known as Delta Scuti https www.nasa.gov universe nasas tess enables breakthrough study of perplexing stellar pulsations . These stars can rotate at least a dozen times faster than our own Sun, which contributes to complex patterns in the expansion and contraction of its outer layers, akin to a jumble of different musical chords. Out of this chaos, order can emerge in the form of regular pulsation periods, as seen in SZ Lyn's approximately round u\"hr\", P digits 3 period between times of maximum brightness. This period is so regular in fact, that we can set our clocks to it, as we will see next. \"\"\" Number of cycles since t0 N t, t0, P round Int, t ephem 0, t0, P P N now N now , t0, P \"\"\" This simple formula is known as a variable star's ephemeris, and its values are reported by AAVSO on each target's page for a range of dates. Using the reported values for Sz Lyn https vsx.aavso.org index.php?view detail.top&oid 17922 ```math t 0 t0 | datetime2julian ,\\\\ P P ``` we find that `` N now `` cycles have passed since the first recorded measurement of this target, to the current writing of this notebook. \"\"\" | Markdown.parse md\"\"\" Now that we have a handle on working with ephemerides, we turn next to applying it to our observations. note Technically, we should be taking things like leap seconds and light travel time into account when dealing with timescales in astronomy, but for quick estimates like the above, this is fine. We will give a more careful treatment later in this notebook. \"\"\" t obs UTC DateTime 2026, 02, 21, 23, 0, 0 N obs N t obs UTC, t0, P md\"\"\" Data note \"Observations\" Consistent observations from two of our ASP workshop https astrosociety.org education outreach amateur astronomers smartscopes101 asp unistellar smartscope.html participants. https raw.githubusercontent.com Unistellar science SETI Education refs heads main data timeseries pulsators lc an.png \\ Credit Anouchka N. https raw.githubusercontent.com Unistellar science SETI Education refs heads main data timeseries pulsators lc ib.png \\ Credit Ingo B. Based on the measurements made above, we see that the time of maximum brightness occurred at approximately t obs UTC UTC, which corresponds to N obs cycles since ``t 0``. \"\"\" md\"\"\" Taking a look at the corresponding predicted time in our ephemeris, we hit a problem \"\"\" t expected UTC ephem N obs, t0, P md\"\"\" Perhaps this is due to the light travel time between the Sun and the Earth, which can take up to ~ 8 minutes. This is the purpose of working in Heliocentric Julian Date HJD , which is the date format used by AAVSO. Let's try converting our observed time of maximum brightness from UTC to HJD to see if this can account for the missing time. \"\"\" md\"\"\" HJD It turns out that where a measurement was taken can be just as important as when . https www.mathpages.com home kmath203 kmath203 files image001.png \\ KMath203 https www.mathpages.com home kmath203 kmath203.htm todo Outline Rømer Delay, relevant time standards UTI, TAI, TDB, etc. . Eastman et al. 2010 https arxiv.org pdf 1005.4415 , Section 2.2. Jason's calculator lost hosting at OSU https lweb.cfa.harvard.edu ~jeastman astroutils.html , but a non functional version is still available on the Wayback machine https web.archive.org web 20260209055926 https astroutils.astronomy.osu.edu time . Alternative shared https arbiter.nextastro.org toolkit bjd converter \"\"\" Convert UTC BJD TDB or HJD TDB function ltt corrected t, ra, dec kind heliocentric Astrometry jdtdb from utc t scale TDB | julian | value astrom SOFA.apcg13 jdtdb, 0.0 gcra, gcdec SOFA.atciq deg2rad ra , deg2rad dec , 0.0, 0.0, 0.0, 0.0, astrom Light travel time delay r⃗ if kind heliocentric astrom.em astrom.eh Earth Sun vec, AU elseif kind barycentric astrom.eb Earth SS barycenter vec, AU else throw ArgumentError \"kind must be heliocentric or barycentric, got kind \" end n̂ SOFA.s2c gcra, gcdec Earth source unit vec c SOFA.LIGHTSPEED SOFA.AU SOFA.SECPERDAY AU day Δt r⃗ ⋅ n̂ c Store results jd corrected jdtdb Δt ep TDBEpoch jd corrected days origin julian return ep end md\"\"\" Converting to HJD, we get the following note \"📜 Historical note\" HJD was actually deprecated by the IAU in favor of BJD back in 1991. AAVSO just uses HJD for historical and pragmatic reasons easier to calculate, precise enough within ~ 8 seconds for most variable star needs. For our particular system, HJD and BJD differ by less than 2 seconds, so we will continue with HJD for consistency. \"\"\" t obs ltt corrected t obs UTC, 122.39896, 44.47156 t aavso TDBEpoch 2461093.454 days origin julian md\"\"\" This corresponds to a discrepancy of \"\"\" md\"\"\" Clearly, something else is up. \"\"\" md\"\"\" A silent companion After accounting for the different timescales at play, we seem no closer to our goal. Going back through the AAVSO target page, we see the following note left behind https raw.githubusercontent.com Unistellar science SETI Education refs heads main data timeseries pulsators aavso note.png This note indicates that our pulsating star is actually part of a binary And not only that, the General Catalog of Variable Stars https heasarc.gsfc.nasa.gov w3browse all gcvs.html Team was nice enough to leave a correction term that we can apply to AAVSO's reported ephemeris, copied below ``` There is a periodical term in the elements 0.00573d cos 2pi EP0 P1 0.007 P0 0.120534920d, P1 1150d. ``` \"\"\" ephem correction let P0 0.12053492 Pulsation period days P1 1150 Binary period days 0.00573 cos 2π N obs P0 P1 0.007 days end md\"\"\" This corresponds to an additional correction of ephem correction to our initial linear ephemeris estimate. \"\"\" t expected corrected t aavso ephem correction md\"\"\" This is a difference of \"\"\" md\"\"\" from our measured observation time. Not bad \"\"\" md\"\"\" tip \"Extension projects\" See the Eclipsing Binaries Lab https unistellar science.github.io SETI Education timeseries eb lab for more on observing your own variable star systems. Below are a couple extensions ideas based on what was explored in this lab. 1. Reproduce the GCVS Team note. This appears to be an empirical approximation that they use. We could always do better by considering the elliptical motion of Sz Lyn about its barycenter based on updated orbital elements from, e.g., Table 3 Gazeas 2004 1. There also appears to be a slight slowing in the pulsation period over time for this target discussed there as well, much smaller contribution than we can likely readily measure . 1. Visualize the 3D orientation of the Earth, Sun, and variable star system to help visualize the impact of light travel time. EphemerisSources.jl https juliaastro.org EphemerisSources.jl docs stable may be useful for this. \"\"\" md\"\"\" Notebook setup 🔧 \"\"\" TableOfContents Maybe upstream this function Dates.canonicalize dt AstroTime.AstroPeriod ms value ∘ seconds dt 1 000 return round Int, ms | Millisecond | canonicalize end t obs diff UTC t obs UTC t expected UTC canonicalize t obs diff UTC md\"\"\" While this matches the time reported by AAVSO, this differs from our observed time by almost round t obs diff UTC, Minute . \"\"\" t obs diff t obs t aavso t obs diff | canonicalize t obs t expected corrected | canonicalize "}]