Friday, June 13, 2025

Date Conversion in VStar

Currently, VStar's "JD to BJD_TDB" and "BJD_TDB Converter" plug-ins rely on the Ohio State University time conversion service (see https://astroutils.astronomy.osu.edu/time/). like the "HJD Converter" plug-in, they do not have an intrinsic (built-in) conversion procedure. This makes the converter strictly dependent on internet services, which may sometimes be unavailable.

An obvious solution is to create a local conversion procedure. An example of this approach is the AstroImageJ software, which uses a flexible method: it can utilize both local and internet-based time conversion services, including the one mentioned above.

However, this is like reinventing the wheel. I propose an approach (perhaps not very elegant) that uses a Python microservice, allowing us to leverage the AstroPy library for the conversion.

With this approach, we do not need constantly available internet services (and even the internet connection). AstroPy loads the required ephemeris files once at startup (and probably updates them periodically). After that, an internet connection is no longer required.

In the development build (https://drive.google.com/drive/folders/1ctXp1Ddb5BK9YdFuUg1XCRW0Lm308BRi) I included the modified "JD to BJD_TDB" and "BJD_TDB Converter" plug-ins, and a Python (Flask) micro-service.

To install it under Linux:

1) Download vstar-bash.zip and vstar-plugins.zip.

2) Unpack the archives into your home directory.

3) Create the .vstar sub-directory in your home dir.

4) Copy a file vstar.properties from the vstar directory to .vstar

5) Open .vstar/vstar.properties in a text editor and set the value of the localJDconverter.active parameter to y.

Then navigate to vstar/PyMicroService/ and run "astro_time_convert.py":

>python astro_time_convert.py

Prerequisites: AstroPy and Flask packages must be installed. I installed Anaconda, which already contains these packages preinstalled.

After a successful launch, you should see something like this:

You may ignore the warning; it simply means that you are running a single-threaded internal Flask web server, which is perfectly sufficient for our needs.

To test the microservice,  run VStar, load some data (for example, V405 Dra, all times, Johnson V). Invoke the [Tools] -> [BJD_TDB Converter] command and press OK.

In a few seconds, you should see the following message (number of observations may differ):

You can try deactivating the local service (via vstar.properties), restarting VStar, and attempting the conversion again (the Ohio State University service will be used).





Wednesday, May 14, 2025

A Python script for downloading TESS light curves as text files

[Revised 2025-07-06] 

Generally, TESS light curves are accessible via the MAST portal, which provides references to FITS files containing light curves in the standard TESS-SPOC format, the QLP format, and other variations (such as TASOC). FITS files in the TESS-SPOC and QLP formats can be read using VStar software. However, some other useful software packages (e.g., MCV by I. L. Andronov) do not support FITS files. 

To streamline the use of TESS data in programs that cannot directly read FITS files, an interactive Python script, TESSdata.py, was developed. It is a part of the 'Light Curve Viewer' project, and can be used completely independently.

The script requires the Lightkurve package, which can be installed using pip

>pip install lightkurve

 Upon launching TESSdata.py, the user is presented with the following text menu:



To select a star of interest, the user must press '1' and then ENTER. After that, they must enter either the star's name or its coordinates (RA and Dec):

After pressing ENTER, the program attempts to load a list of available light curves for the TESS mission. This process may take some time. If a network error occurs at this step, simply retry. Once loading is successful, the following extended menu appears:

 

Press '2' and then ENTER to view the list of available light curves:

 


To load one or more light curves, use menu options '3' or '4'. Option '3' loads observations as fluxes without any transformation. Please note that the units in which the fluxes are measured may vary depending on the data 'flavor' (see the 'author' column). Option '4' additionally transforms fluxes into TESS magnitudes, assuming that the median star magnitude corresponds to the TESSMAG parameter of the light curve. Note: Sometimes, the data contains negative fluxes (this is common in TASOC data). In this case, the conversion to magnitudes causes an error.

Let's load QLP light curves for sectors 58 and 59 as TESS magnitudes. To do this, press '4' and enter the numbers of the required light curves (found in the leftmost column of the table):


If a network error occurs, simply repeat the step.

After successfully loading the data, additional information appears at the top: the numbers of the light curves, the magnitude of the star, and other relevant details:

Now, the user can preview the data by selecting menu option '6'. An interactive plot will then be displayed:


To continue, close the plot window.

Now, the data can be saved to a text file. To do this, select menu option '5'. The user can either enter a file name directly or press '?' to open the file chooser:


The file is saved in text format, with an additional header that follows the Flexible Text Format used in VStar:

  

This format is recognized by MCV software:


 Also, data in this format can be read by VStar (via the Flexible Text plug-in):

 

and LCV:

 


Note: The current version of TESSdata.py attempts to extract flux errors, which are converted to magnitude errors during the transformation to magnitudes. Lightkurve version 2.4.2 does not provide error values, whereas version 2.5.0 does.

Thursday, October 31, 2024

The First Attempt to Use the 'Lightkurve' Package

Перша спроба використати пакет Lightkurve (https://colab.research.google.com/github/christinahedges/KepSciConWorkshop/blob/master/Workshop.ipynb) для отримання фотометрії з TESS Full-Frame Images.

Зоря типу HADS CSS_J102714.3+205943 = TIC 171599792 = Gaia DR3 720971253464796288 спостерігалась TESS, але для неї не існує попередньо підготовленої кривої блиску (принаймні на 30 жовтня 2024 року, див. https://mast.stsci.edu/portal/Mashup/Clients/Mast/Portal.html). В таких випадках можна спробувати зробити фотометрію самостійно, використовуючи пакет Lightkurve.

Згідно порталу https://mast.stsci.edu/portal/Mashup/Clients/Mast/Portal.html, зоря спостерігалась у секторах 45, 46, 48, 72. У секторі 72 спостереження проводилися з найкоротшою експозицією 158 секунд, що прийнятно для зорі з періодом у ~99 хвилин (насправді, інтервал між точками 200 секунд).


Спочатку перевіримо наявність спостережень:

import lightkurve as lk

search_result = lk.search_tesscut('Gaia DR3720971253464796288')
print(search_result)

SearchResult containing 4 data products.

 #     mission     year  author exptime        target_name         distance
                                   s                                arcsec
--- -------------- ---- ------- ------- -------------------------- --------
  0 TESS Sector 46 2021 TESScut     475 Gaia DR3720971253464796288      0.0
  1 TESS Sector 45 2021 TESScut     475 Gaia DR3720971253464796288      0.0
  2 TESS Sector 48 2022 TESScut     475 Gaia DR3720971253464796288      0.0
  3 TESS Sector 72 2023 TESScut     158 Gaia DR3720971253464796288      0.0

 

Далі отримуємо маленькі фрагменти зображень навколо зорі (target pixels):

search_result1 = lk.search_tesscut('Gaia DR3720971253464796288', sector=72)
tpf = search_result1.download(cutout_size=11)
print(tpf.flux.shape)
tpf.plot()

Розмір пікселя TESS дорівнює 21", тому зображення розміром 11 пікселів охоплює ділянку у майже 4 кутові хвилини. На щастя, в цій околиці немає зірок з порівняною яскравістю, інакше наша задача би помітно ускладнилась.

Генеруємо апертуру за допомогою create_threshold_mask ("this method will identify the pixels in the TPF which show a median flux that is brighter than threshold times the standard deviation above the overall median")

aper = tpf.create_threshold_mask(threshold=5)
tpf.plot(aperture_mask=aper)

 

Далі генеруємо криву блиску:

lc = tpf.to_lightcurve(aperture_mask=aper)

 Результат, однак, не дуже... Фон дає сильний паразитний сигнал.


Виходячи зі здорового глузду, з зображень треба відняти фоновий сигнал, як це зазвичай робиться у апертурній фотометрії.

Я використав таку апертуру фона для оцінки інтенсивності фонового сигналу:

Після фотометрії з відніманням фонового сигналу результат (тут світловий потік) значно краще:



Після перетворення у зоряну величину (фрагмент, зоряна величина відносна):


і фазова крива (зоряна величина відносна):


Процедури, описані вище (фотометрія з відніманням фону) я робив 'вручну', за допомогою Python-скріпту, попередньо перетворивши набір міні-зображень target pixels у текстовий файл для подальших маніпуляцій.

Однак, можливий 'автоматизований' підхід, див. наприклад, https://www.youtube.com/watch?v=54osKe2E_sM&t=5923s

import lightkurve as lk
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import statistics
import math
import sys

###############################################################################

#c_sz = 11

# search_result = lk.search_tesscut('Gaia DR3720971253464796288')
# print(search_result)

# search_result1 = lk.search_tesscut('Gaia DR3720971253464796288', sector=72)
# tpf = search_result1.download(cutout_size=c_sz)
# print(tpf.flux.shape)
# tpf.plot()

#tpf.to_fits('tpf.fits')

###############################################################################

tpf = lk.read('tpf.fits')
tpf.plot()

#See https://www.youtube.com/watch?v=54osKe2E_sM&t=5923s
# background-subtracted images
flux_sub = []

n = 0
for image in tpf.hdu[1].data['FLUX']:
    #n += 1
    #print(n)
    # here, we estimate the background as the average brightness
    # of the faintest 10% of pixels in each frame.
    background = image[image <= np.percentile(image, 10)].mean()
    #print(background)
    flux_sub.append(image - background)
# we now update the flux of the TPF
tpf.hdu[1].data['FLUX'] = np.array(flux_sub)
tpf.plot()

aper = tpf.create_threshold_mask(threshold=5)
tpf.plot(aperture_mask=aper)

lc = tpf.to_lightcurve(aperture_mask=aper)
lc.plot()

lc.to_fits('lc.fits')

В скрипті закоментована частина завантажує target pixels з серверу і зберегає його у файл 'tpf.fits' для подальшого використання.

В циклі від кожного мінізображення віднімається фоновий сигнал (див. коментарі).

Результатом є крива блиску у форматі FITS, яку можна відкрити у VStar як 'Lightkurve FITS'

 

 

За амплітудою змінності цей результат майже такий самий, як і отриманий 'вручну'. Різниця обумовлена різним підходом до оцінки фону. В 'ручному' варанті бралась фіксована апертура фону, а 'автоматизованому' сигнал фону оцінювався як середня інтенсивність 10% найтьмяніших пікселів зображення.

 

 

 ---------------------------

І, наприкінець, гібрід двох підходів (ефективна маніпуляція target pixels + user-defined background mask):

import lightkurve as lk
import numpy as np
#import sys

###############################################################################

def read_mask(file_name):
    mask = []
    with open(file_name, 'r') as f:
        for line in f.readlines():
            t = line[:-1].split('\t')
            t = list(map(int, t))
            mask.append(t)
    mask = np.array(mask)
    return mask

def get_bk_level(f, m, sz):
    s = 0.0
    n = 0
    for i in range(0, sz):
        for j in range(0, sz):
            if m[i, j] != 0:
                s += f[i, j]
                n += 1
    if n > 0:
        return s / n
    else:
        return None

###############################################################################

c_sz = 11

search_result = lk.search_tesscut('Gaia DR3720971253464796288')
print(search_result)

search_result1 = lk.search_tesscut('Gaia DR3720971253464796288', sector=72)
tpf = search_result1.download(cutout_size=c_sz)
print(tpf.flux.shape)
tpf.plot()

tpf.to_fits('tpf.fits')

# ###############################################################################

tpf = lk.read('tpf.fits')
tpf.plot()

bk_mask = read_mask('bk_mask.txt')

#test
#image = tpf.hdu[1].data['FLUX'][0]
#bk_level = get_bk_level(image, bk_mask, 11)
#sys.exit(0)

#See https://www.youtube.com/watch?v=54osKe2E_sM&t=5923s
# background-subtracted images
flux_sub = []

n = 0
for image in tpf.hdu[1].data['FLUX']:
    # background value (per pixel)
    background = get_bk_level(image, bk_mask, c_sz)
    flux_sub.append(image - background)
# we now update the flux of the TPF
tpf.hdu[1].data['FLUX'] = np.array(flux_sub)
tpf.plot()

aper = tpf.create_threshold_mask(threshold=5)
tpf.plot(aperture_mask=aper)

lc = tpf.to_lightcurve(aperture_mask=aper)
lc.plot()

lc.to_fits('lc-v3.fits')

 ----------------

З результуючим fits-файлом можна працювати у VStar (відкривши його як 'Lightkurve FITS').

Текстовий файл маски фону bk_mask.txt виглядає так:

0    0    0    0    0    0    0    0    0    0    0
0    1    1    1    1    1    1    1    1    1    1
0    1    1    1    1    1    1    1    1    1    1
0    1    1    1    0    0    0    0    0    1    1
0    1    1    0    0    0    0    0    0    0    1
0    1    1    0    0    0    0    0    0    0    1
0    1    1    0    0    0    0    0    0    0    1
0    1    1    0    0    0    0    0    0    0    1
0    1    1    0    0    0    0    0    0    0    1
0    1    1    1    0    0    0    0    0    1    1
0    1    1    1    1    1    1    1    1    1    1

в графічному вигляді:

Посилання:

https://heasarc.gsfc.nasa.gov/docs/tess/Target-Pixel-File-Tutorial.html
https://heasarc.gsfc.nasa.gov/docs/tess/Full-Frame-Image-Tutorial.html
https://colab.research.google.com/github/christinahedges/KepSciConWorkshop/blob/master/Workshop.ipynb

https://www.youtube.com/watch?v=54osKe2E_sM&t=5923s