15–19 Sept 2025
JIVE
Europe/Amsterdam timezone

Data reduction guide

VLBI Data Reduction and Imaging Tutorial

Advanced Tutorial 3: Introduction to VLBI data reduction and imaging

Originally developed for the 10th European Radio Interferometry School in Granada (IAA-CSIC, September 30 – October 4, 2024)


Overview

This is a hands-on introduction to VLBI data reduction using CASA. The tutorial dataset will be updated with data from the e-EVN observation conducted during the school.

Note: This is a simplified introduction. For more detailed guides, please refer to:

 


Data Download

https://archive.jive.eu/scripts/arch.php?exp=RSM07

You can go to the fits idi page where you will find the information to download the FITS-IDI files containing the data.

The credentials for accessing the data are:
  username: rsm07
  password: m7PQOTdy2Mgl

 

Download the a-priori flag table from here.

Download the a-priori calibration table from here.


Dataset Information

Observation: RSM07 continuum dataset 

Type: EVN network monitoring test with phase-referencing 

Duration: 3 hours 

Method: Telescopes nodding between calibrator (C) and target (T) in C-T-C-T pattern

Source Type Source Name
Calibrator (C) J1848+3219
Target (T)

3C395

Fringe-finder (FF)

3C345


Data Reduction in CASA

For extra details on what these steps do, check the slides for the calibration and flagging.

Setup

Navigate to your working directory and start CASA:

 
bash
cd ~/ERIS/eris-evn/
casa

Step 1: Loading and Inspecting Data

Load the data:

default(importfitsidi)
fitsidifile= ['rsm07_1_1.IDI1','rsm07_1_1.IDI2']
vis='rsm07.ms'
constobsid=True
scanreindexgap_s=15
importfitsidi()

 

Load flag table:

default(flagdata)
vis='rsm07.ms'
mode='list'
inpfile='rsm07_casa.flags'
flagdata()

Note that the rsm07_casa.flags file that we provide here is just the ANTAB file provided in the EVN Archive (under Pipeline) and converted to CASA format, which can be done via scripts following the steps in the EVN data reduction guide. Here we have already done it for you.

Understand your data:

default(listobs)
vis='rsm07.ms'
listfile='rsm07.listobs'
overwrite=True
listobs()

The output will provide to you the information about the sources, antennas that were participating in the observation, and the different scans times, and frequency setup.

Inspect uncalibrated data:

default(plotms)
vis='rsm07.ms'
xaxis='frequency'
yaxis='amp'
field='3C345'
avgtime='3600'
antenna='EF&*'
gridrows=3
gridcols=3
iteraxis='antenna'
coloraxis='scan'
plotms()

 

Step 2: A Priori Amplitude Calibration

Generate system temperature information:

default(gencal)
vis='rsm07.ms'
caltable='rsm07.tsys'
caltype='tsys'
uniform=False
gencal()

 

Generate gain curves (SKIP THIS STEP!):

default(gencal)
vis="rsm07.ms"
caltable= "rsm07.gcal"
caltype="gc"
gencal()

WARNING: instead of creating the gain curve table, we will be using the one linked above (download it from here and uncompress). There is a small bug in the GC information that we appended in this observation.

Inspect Tsys corrections:

default(plotms)
vis='rsm07.tsys'
xaxis='frequency'
yaxis='tsys'
gridrows=3
gridcols=3
coloraxis='corr'
iteraxis='antenna'
plotms()
xaxis='time'
plotms()

As we are analyzing the project before the observing run actually finished, you will see that all Tsys values are the same along time, but different for each telescope. This is because the information is only generated after the run finishes. The Tsys values there are proportional to the sensitivity of each antenna.

Step 3: Data Inspection and Flagging

Plot amplitude vs frequency on sensitive baseline:

default(plotms)
vis='rsm07.ms'
xaxis='channel'
yaxis='amp'
field = '3C345'
avgtime='3600'
antenna='EF&JB'
iteraxis='spw'
gridrows=1
gridcols=4
plotms()

 

Flag band edges:

default(flagdata)
vis='rsm07.ms'
mode='manual'
spw='*:0~3;60~63'
flagdata()

We remove the channels that are at the edge of each subbands, as the sensitivity there quickly drops to zero.

Flag autocorrelations:

default(flagdata)
vis='rsm07.ms'
mode='manual'
autocorr=True
flagdata()


Flag Onsala (O8) as did not observe due to winds:

default(flagdata)
vis='rsm07.ms'
mode='manual'
antenna='O8'
flagdata()

 

 

Step 4: Fringe Fitting

Identify good timerange and check data:

 
default(plotms)
vis='rsm07.ms'
xaxis='frequency'
yaxis='phase'
ydatacolumn='data'
antenna='EF&*'
coloraxis='corr'
timerange='15:48:00~15:50:00'
averagedata=True
avgtime='120'
iteraxis='antenna'
gridrows=3
gridcols=3
plotms()

You can check different scans on the fringe finder and the time range to pick would be a few minutes that contain data that look stable and without bad data.

Calculate delays (SBD - Single Band Delays):

default(fringefit)
vis='rsm07.ms'
caltable='rsm07.sbd'
timerange='15:48:00~15:50:00'
solint='inf'
zerorates=True
refant='EF'
minsnr=10
gaintable=['rsm07.gcal','rsm07.tsys']
interp=['nearest','nearest,nearest']
parang=True
fringefit()

 

Apply SBD solutions:

default(applycal)
vis='rsm07.ms'
gaintable=['rsm07.gcal','rsm07.tsys','rsm07.sbd']
interp=['nearest','nearest,nearest','nearest']
parang=True
applycal()

 

Check corrected data:

tget(plotms)
ydatacolumn='corrected'
plotms()

Phases should not look around zero for the time when we corrected via the SBD.

Multi-band delays and rates (MBD):

default(fringefit)
vis='rsm07.ms'
caltable='rsm07.mbd'
field='J1848+3219,3C345'
solint='120s'
zerorates=False
refant='EF,MC'
combine='spw'
minsnr=7
gaintable=['rsm07.gcal','rsm07.tsys','rsm07.sbd']
interp=['nearest','nearest,nearest','nearest']
parang=True
fringefit()

 

Inspect MBD solutions:

default(plotms)
vis='rsm07.mbd'
xaxis='time'
yaxis='delay'
gridrows=4
gridcols=4
coloraxis='corr'
iteraxis='antenna'
plotms()

 

Apply phase referencing:

default(applycal)
vis='rsm07.ms'
field='J1848+3219,3C395,3C345'
gaintable=['rsm07.gcal','rsm07.tsys','rsm07.sbd','rsm07.mbd']
interp=['nearest', 'nearest,nearest','nearest','linear']
spwmap=[[],[],[],[0,0,0,0]]
parang=True
applycal()

 

Step 5: Bandpass Calibration

Calculate bandpass:

 
default(bandpass)
vis='rsm07.ms'
caltable='rsm07.bpass'
field='3C345'
gaintable=['rsm07.gcal','rsm07.tsys','rsm07.sbd','rsm07.mbd']
interp=['nearest','nearest,nearest','nearest','linear']
solnorm=True
solint='inf'
refant='EF'
bandtype='B'
spwmap=[[],[],[],[0,0,0,0]]
parang=True
bandpass()

 

Inspect bandpass solutions:

default(plotms)
vis='rsm07.bpass'
xaxis='frequency'
yaxis='amp'
coloraxis='corr'
iteraxis='antenna'
plotms()

 

Apply all calibrations:

default(applycal)
vis='rsm07.ms'
field='J1848+3219, 3C395'
gaintable=['rsm07.gcal','rsm07.tsys','rsm07.sbd','rsm07.mbd','rsm07.bpass']
interp=['nearest','nearest,nearest','nearest','linear','nearest,nearest']
spwmap=[[],[],[],[0,0,0,0],[]]
parang=True
applycal()

 

Step 6: Export Data

Create calibrated datasets:

# Calibrator
default(mstransform)
vis='rsm07.ms'
outputvis='rsm07_J1848+3219.ms'
field='J1848+3219'
datacolumn='corrected'
keepflags=True
chanaverage=True
chanbin=64
mstransform()

# Target
default(mstransform)
vis='rsm07.ms'
outputvis='rsm07_3C395.ms'
field='3C395'
datacolumn='corrected'
keepflags=True
chanaverage=True
chanbin=64
mstransform()
 
 

Step 7: Imaging the data

For more details on this process, look at the slides on imaging and self-calibration.

In Linux:
 
tclean(vis='***',
       imagename='***',
       specmode='mfs',
       niter=1500,
       cycleniter=300,
       threshold=0,
       imsize=['**','**'],
       cell='**',
       weighting='**',
       deconvolver='**',
       savemodel='modelcolumn',
       interactive=True)

In MacOS:

from casagui.apps import run_iclean
run_iclean(vis=target+'.ms',
	   imagename='***',
	   specmode='mfs',
	   niter=1500,
	   cycleniter=300,
	   threshold=0,
	   imsize=['**','**'],
	   cell='**',
	   weighting='**',
	   deconvolver='**',
	   savemodel='modelcolumn')
ft(vis=target+'.ms', 
   model='***.model',
   usescratch=True) 

 

 

Step 6: Self Calibration

After imaging, the MS will now contain the model that you computed for your source. 

 

You can look at the model in plots:

plotms(vis=target+'.ms', xaxis='uvdist', yaxis='amp', ydatacolumn='model', correlation='RR,LL', avgchannel='16', coloraxis='spw', plotfile='', overwrite=True)

 

Then you can derive the calibration table. First start with a long solution interval and to correct only for the phases.

gaincal(vis=target+'.ms', calmode='p',caltable='target-sc-p1', field=target, solint='10min',refant='EF', minblperant=3, minsnr=5)

The calibration table should contain corrections that are on a small level, and you can check with:

plotms(vis='target-sc-p1', gridcols=2, gridrows=3, xaxis='time', yaxis='phase', iteraxis='antenna', coloraxis='spw', plotrange=[-1,-1,-180,180])

Apply the calibration:

applycal(vis=target+'.ms', gaintable=['target-sc-p1'])

Then you can repeat the cycle of imaging (it should look slightly better now), and self calibration in phase with a shorted interval (2min, as you do not expect faster changes due to the atmosphere).

After another imaging round, if the solutions (in plots) seem to converge to just small improvements, one can run the self-calibration in amplitude and phase. In this case, running gaincal with calmode='ap' . For amplitude, you do not expect fast changes, but only on the order of ~30 min or more. Therefore a longer solution interval of half an hour or one hour is enough.

The final image with this corrections should look much better!