CasaCore.Tables
Load this module by running
using CasaCore.Tables
The Tables
module is used to interact with CasaCore tables. This is a common data format in radio astronomy. For example CASA measurement sets and CASA calibration tables are simply CasaCore tables with a standard set of columns, keywords, and subtables.
Tables
CasaCore.Tables.Table
— Type.mutable struct Table
This type is used to interact with CasaCore tables (including measurement sets).
Fields:
path
- the path to the tablestatus
- the current status of the tableptr
- the pointer to the table object
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read/write)
julia> Tables.add_rows!(table, 3)
3
julia> table["DATA"] = Complex64[1+2im, 3+4im, 5+6im]
3-element Array{Complex{Float32},1}:
1.0+2.0im
3.0+4.0im
5.0+6.0im
julia> Tables.close(table)
closed::CasaCore.Tables.TableStatus = 0
julia> table = Tables.open("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read-only)
julia> table["DATA"]
3-element Array{Complex{Float32},1}:
1.0+2.0im
3.0+4.0im
5.0+6.0im
julia> Tables.delete(table)
See also: Tables.create
, Tables.open
, Tables.close
, Tables.delete
CasaCore.Tables.create
— Function.create(path)
Create a CasaCore table at the given path.
Arguments:
path
- the path where the table will be created
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read/write)
julia> Tables.delete(table)
See also: Tables.open
, Tables.close
, Tables.delete
CasaCore.Tables.open
— Function.open(path; write=false)
Open the CasaCore table at the given path.
Arguments:
path
- the path to the table that will be opened
Keyword Arguments:
write
- iffalse
(the default) the table will be opened read-only
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read/write)
julia> table′ = Tables.open("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read-only)
julia> table″ = Tables.open("/tmp/my-table.ms", write=true)
Table: /tmp/my-table.ms (read/write)
julia> Tables.close(table′)
Tables.close(table″)
Tables.delete(table)
See also: Tables.create
, Tables.close
, Tables.delete
CasaCore.Tables.close
— Function.close(table)
Close the given CasaCore table.
Arguments:
table
- the table to be closed
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read/write)
julia> Tables.close(table)
closed::CasaCore.Tables.TableStatus = 0
julia> Tables.delete(table)
See also: Tables.create
, Tables.open
, Tables.delete
CasaCore.Tables.delete
— Function.delete(table)
Close and delete the given CasaCore table.
Arguments:
table
- the table to be deleted
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Table: /tmp/my-table.ms (read/write)
julia> Tables.delete(table)
See also: Tables.create
, Tables.open
, Tables.create
CasaCore.Tables.num_rows
— Function.Tables.num_rows(table)
Returns the number of rows in the given table.
Arguments:
table
- the relevant table
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.num_rows(table)
0
julia> Tables.add_rows!(table, 10)
Tables.num_rows(table)
10
julia> Tables.remove_rows!(table, 1:2:10)
Tables.num_rows(table)
5
julia> Tables.delete(table)
See also: Tables.num_columns
, Tables.num_keywords
CasaCore.Tables.add_rows!
— Function.Tables.add_rows!(table, number)
Add the given number of rows to the table.
Arguments:
table
- the relevant tablenumber
- the number of rows that will be added to the table
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.add_rows!(table, 10)
Tables.num_rows(table)
10
julia> Tables.add_rows!(table, 123)
Tables.num_rows(table)
133
julia> Tables.delete(table)
See also: Tables.remove_rows!
CasaCore.Tables.remove_rows!
— Function.Tables.remove_rows!(table, rows)
Remove the specified rows from the table.
Arguments:
tables
- the relevant tablerows
- the row or rows that will be deleted from the table
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.add_rows!(table, 10)
Tables.remove_rows!(table, 1:2:10)
Tables.num_rows(table)
5
julia> Tables.remove_rows!(table, 4)
Tables.num_rows(table)
4
julia> Tables.remove_rows!(table, [1, 2, 3])
Tables.num_rows(table)
1
julia> Tables.delete(table)
See also: Tables.add_rows!
Columns
Columns are accessed by name. Some common table names (used in CASA measurement sets) are UVW
(the baseline coordinates), DATA
(the uncalibrated data), and CORRECTED_DATA
(the calibrated data).
For example to read and write the entire DATA
column from a measurement set:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.add_rows!(table, 100)
Npol = 4 # number of polarizations
Nfreq = 50 # number of frequency channels
Nbase = 100 # number of baselines
data = rand(Complex64, Npol, Nfreq, Nbase)
table["DATA"] = data # creates the DATA column if it doesn't already exist
data == table["DATA"]
true
julia> Tables.delete(table)
CasaCore.jl will throw a CasaCoreTablesError
exception if you try to overwrite a column with an array of the incorrect size or element type. A column that contains float
s cannot be overwritten with an array of int
s.
CasaCore.Tables.num_columns
— Function.Tables.num_columns(table)
Returns the number of columns in the given table.
Arguments:
table
- the relevant table
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.num_columns(table)
0
julia> Tables.add_rows!(table, 10)
table["TEST_COLUMN"] = randn(10)
Tables.num_columns(table)
1
julia> Tables.delete(table)
See also: Tables.num_rows
, Tables.num_keywords
CasaCore.Tables.remove_column!
— Function.Tables.remove_column!(table, column)
Remove the specified column from the table.
Arguments:
table
- the relevant tablecolumn
- the column that will be removed from the table
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.add_rows!(table, 10)
table["TEST"] = rand(Bool, 10)
Tables.num_columns(table)
1
julia> Tables.remove_column!(table, "TEST")
Tables.num_columns(table)
0
julia> Tables.delete(table)
See also: Tables.num_columns
Cells
If you do not want to read or write to an entire column, you can instead pick a single row of the column (ie. a cell). For example, the length of the 123rd baseline in a measurement set can be computed by:
julia> table = Tables.create("/tmp/my-table.ms")
Nbase = 500 # number of baselines
Tables.add_rows!(table, 500)
uvw = 100 .* randn(3, Nbase) # create a random set of baselines
table["UVW"] = uvw # creates the UVW column if it doesn't already exist
uvw[:, 123] == table["UVW", 123]
true
julia> table["UVW", 123] = [100., 50, 0.]
table["UVW", 123]
3-element Array{Float64,1}:
100.0
50.0
0.0
julia> Tables.delete(table)
The number of rows in the table can be obtained with Tables.num_rows
. Note also that the indexing order is column first, row second. This is opposite from the usual matrix convention where the first index specifies the row.
Julia is 1-indexed programming language. This means that the first element of an array x
is accessed with x[1]
instead of x[0]
(as is the case for C and Python). Similarly, the first row of a table is row number 1. Attempting to access row number 0 will throw a CasaCoreTablesError
because this row does not exist.
Keywords
Keywords are accessed using the kw"..."
string macro. For example:
julia> table = Tables.create("/tmp/my-table.ms")
table[kw"MS_VERSION"] = 2.0 # set the value of the "MS_VERSION" keyword
table[kw"MS_VERSION"] # read the value of the "MS_VERSION" keyword
2.0
julia> Tables.delete(table)
CasaCore.Tables.num_keywords
— Function.num_keywords(table)
Returns the number of keywords associated with the given table.
Arguments:
table
- the relevant table
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
Tables.num_keywords(table)
0
julia> table[kw"RICK_PERLEY_IS_A_BOSS"] = true
Tables.num_keywords(table)
1
julia> table[kw"NOT_SO_BAD"] = "yourself"
Tables.num_keywords(table)
2
julia> Tables.delete(table)
See also: Tables.num_rows
, Tables.num_columns
CasaCore.Tables.remove_keyword!
— Function.Tables.remove_keyword!(table, keyword)
Remove the specified keyword from the table.
Arguments:
table
- the relevant tablekeyword
- the keyword to be removed
Usage:
julia> table = Tables.create("/tmp/my-table.ms")
table[kw"HELLO"] = "world"
Tables.num_keywords(table)
1
julia> Tables.remove_keyword!(table, kw"HELLO")
Tables.num_keywords(table)
0
julia> Tables.delete(table)
See also: Tables.num_keywords
Subtables
Subtables will be automatically opened by reading the appropriate keyword. These tables need to be closed when you are done using them (just as for a regular table).
julia> table = Tables.create("/tmp/my-table.ms")
subtable = Tables.create("/tmp/my-sub-table.ms")
subtable[kw"SECRET_CODE"] = Int32(42)
table[kw"SUBTABLE"] = subtable
Tables.close(subtable)
closed::CasaCore.Tables.TableStatus = 0
julia> subtable = table[kw"SUBTABLE"] # re-open the subtable
subtable[kw"SECRET_CODE"]
42
julia> Tables.delete(table)
Tables.delete(subtable)
Best Practices
Type Stability
Julia is a dynamically typed language. Because of this we can write statements like column = table["column"]
without knowing the type of the column ahead of time. If the column contains float
s (Float32
), Julia will do the right thing. If the column contains double
s (Float64
), Julia will do the right thing. As a user, we did not need to know whether this column contains float
s or double
s ahead of time.
However Julia also performs "type-inference". This means that Julia will attempt to deduce the types of your variables. If the types of your variables can be inferred at compile time, Julia will generate more efficient machine code specialized on the types that it inferred. If the types of your variables cannot be inferred at compile time, Julia will need to generate less efficient generic code to account for the uncertainty in the types of your variables.
This concept is important for CasaCore.Tables
because the result of table["column"]
can be a wide variety of different types, and the actual type isn't known until run time. Now consider the following example:
function add_one_to_data_column(table)
column = table["DATA"] # type of `column` cannot be inferred
for idx in eachindex(column)
column[idx] += 1
end
table["DATA"] = column
end
This function will read the DATA
column from the given table, add one to each element, and then write the result back to the table. However because the type of column
cannot be inferred, the performance of the for
-loop will be sub-optimal. We can remedy this problem by moving the computational kernel into a separate function:
function add_one_to_data_column(table)
column = table["DATA"]
do_the_for_loop(column) # `do_the_for_loop` specializes on the actual type of `column`
table["DATA"] = column
end
function do_the_for_loop(column)
for idx in eachindex(column)
column[idx] += 1
end
end
When do_the_for_loop
is called, Julia will specialize the function on the actual type of column
. That is, the for
-loop will be compiled with the knowledge of the actual type of column
. This specialization ultimately means that the latter example will generally be faster.
For more information please refer to the performance tips section of the Julia manual.