In order to write a valid primitive certain steps must be adhered to in order to ensure that subsequent primitives have the correct information.
It assumes knowledge of the following Perl concepts: using Perl objects, lexical variables, Perl data structures.
Here is an example primitive showing the basic principles:
The following should be noted:
my
).
ARG1
key in the hash and read it if it exists else a default value
is copied in. Note the use of exists
rather than defined
for checking hash contents. If
defined
is used the key would automatically be created in the hash and set to a value of
undef
whereas exists
simply looks for the key in the hash without creating it. In general,
this is the more correct behaviour as it can distinguish between the key not being there at
all (i.e. never set) and the key being set explicitly to undef
5.
In some current primitives the following may be found for reading arguments:
This works in most normal cases but will fail if the value of the argument is desired to be ‘0’ since that evaluates to false and will cause the default to be returned rather than a ‘0’. This construct also causes the key to be created even if no argument was ever supplied (known as auto-vivification).
_sfx
”. Note that this command accepts a number as an optional argument.
This can be used to connect the file name with the sub-frame (a Frame object can store multiple
current filenames).
hdr()
method can also be used to set
header values (but does not change the header on disk).
%Mon
hash. The options string depends on the task at the other end of the message
bus and will therefore need to be changed if the algorithm engine is changed. An
important point is that error checking code is automatically inserted into the recipe when
-obeyw
is found in a line that is not preceeded by an equals sign or a comment. This means that line 51 is
translated to:
such that the recipe is aborted and an error message printed. Note that this block runs in its own
scope to prevent warnings from Perl concerning the masking of previous OBEYW_STATUS
variables.
If automatic checking is not required, simply check the return status. If the parser finds an equals
sign before the obeyw
the line will not be re-written:
ORAC_STATUS
variable in a primitive, code is automatically added after this line to check the value of
ORAC_STATUS
and compare it with ORAC__OK
. If the status is not good, the recipe aborts and an
error message is printed. This saves the primitive writer from having to worry about status
checking.
orac_print
command to send a message to the user. The orac_print
command is written to send the message to multiple output filehandles as defined by the user
with the -log
switch to Orac-dr.
display_data
method will ask
for the current frame to be displayed. Note that the display sub-system will only
display the data frame if the user has requested this by configuring the display system
(using, for example, the oracdisp
command) accordingly. The check to make sure the
display object is initialised is required in case the user has turned off the display
system.
It is sometimes desirable to write results to log files as data files are processed (for example, seeing
statistics, pointing offsets etc). Rather than force the primitive writer to check for the existence of log
files and decide whether or not to open or append to log files, the orac-dr system provides a
simplified access to log file creation via the ORAC:LogFile
class.
All that is required to write an entry to a log file is for the following methods to be invoked:
The header will only be written to the log file if the log file does not previously exist so it is safe to run
this command in a primitive without an explicit check. Both the header()
and addentry()
methods accept arrays, and newline characters will be appended to each item in the array
when written to the log file. The convention is that all log file names should start with
‘log.
’.
In many cases, it is necessary to make use of temporary files within a primitive, either for intermediate
data steps that are not relevant for the frame, or as text files input to external tasks. Since these are not
required once the primitive is finished a class is provided for dealing with temporary files
(ORAC::TempFile
).
This class will choose a filename and, optionally, open the file ready for read-write
access (when this facility is used it is guaranteed that the file is unique and will not
overwrite any existing file). The file, and any files of the same name but with a .sdf
extension6,
are removed when the variable goes out of scope.
The only files that should remain when a primitive completes should be those registered
with the current frame or the current group. All others should be temporary and
should be tidied up on leaving the primitive (which is automatic if ORAC::TempFile
is
used)7.
This allows the final tidyup primitive to be responsible solely for removing unwanted
intermediate frames that were the product of individual primitives (every time a the file name is
updated in a frame object the previous value is stored for possible later removal by the tidy
primitive).
Since each primitive is evaluated in its own scope, it is not possible (or even desirable) to pass simple variables between separate primitives. Two means are provided for doing this:
%_PRIMITIVE_NAME_
hash. The argument hash for each primitive is visible
to all other primitives at the same level. This is because primitives are translated
into:
This works but has a number of problems:
uhdr()
method to store arbitrary data (including references) in a hash. This is the
recommended way of transferring data between primitives when the information relates to the
current frame or group. By convention, the hdr()
method should be used for storing
FITS-like data. Checks should be made for the existence of data in the hash before using
it.The first method using the primitive hash only allows information to be passed within the current recipe whereas the frame header allows the information to be retained for subsequent frame processing.
5Although it is not possible for a user to specify a primitive argument value of undef
this is still good
programming practice.
6the extension used for Starlink N-Dimensional data format (NDF)
7This is not always a good idea when debugging. Future versions of the pipeline will disable the removal of temporary
files when the -debug
flag is in use
8It will not simply return undef
. The recipe will fail to run since the hash would not have been declared
previously.