- About XNAT
- News & Events
- XNAT Marketplace
- Contact Us
XNAT 1.6 provides native support for longitudinal studies through the concept of "Visits". Protocols, which is the structure that defines types of valid visits and intervals on a Project specific level can be enabled with the installation of a Module (available on XNAT Marketplace.) XNAT's support for "visits" is meant to be as generic as possible to allow multiple implementations (packaged as modules) to provide features. To that end, the 1.6 release simply adds a few data types and a few REST calls.
Typically, a longitudinal project will have a pre-defined list of Visits they expect their subjects to have. A Visit defines a "unit of research" that is to be completed on a Subject at a certain time point. In this case, we’ll call that a VisitDefinition. A Visit Type defines the "unit of research" by stating exactly what actions are meant to be taken at that point in time.
VisitDefinitions and Visit Types are separated for flexibility. Under this model, a protocol can have different actions happen at a visit depending on some pre-defined rule. For example, imagine a protocol that says a subject is to be screened for disease every 6 months for five years. If they test positive their subsequent visits will include a biopsy. In this case, the initial visit will only have one type. That is, "screening". All subsequent visits could then be either "screening" or "biopsy."
A "Visit" is a new data type called xnat:pVisitdata (Protocol Visit Data) which is an extension of another new datatype called xnat:genericData. Generic Data is an extension of xnat:experimentData which was created as a catch all for non-image assessor data types. pVisitData adds the concepts of a subject_id, visit_type, visit_name, and "open/closed."
xnat:experimentData has been extended to include a "visit" column which represents a foreign key to a pVisitData by way of a foreign key by pvisitdata label. Please do not be confused with experimentData's "visit_id" column. That column is a remnant of a past implementation of visits and has been retained for backward compatibility.
xnat:expermentdata has also been granted an additional (albiet, poorly named) property called "protocol." In this context, "protocol" means which part of the visit this particular experiment is associated. One can imagine a case where a subject gets an MR before a procedure, then another post procedure. In this context, the data-type would be "xnat:mrSessionData" for both experiments in this visit type. It would be impossible to distinguish which is the preProcedure and which should be associated with post; hence the need for a "protocol" field. Each allowedExpts has an unbounded list of Strings that represent rules that are meant to validate information associated with each experiment.
To create a visit, simply create a xnat:pVisitdata. Grab its ID and place it in your xnat:experimentData's visit property.
To maintain a longitudinal study, one must define the intervals for which a subject returns for follow-up data. XNAT uses the "visit_name" field to define the concept of intervals. It is a simple text string that can be used by any implementation to organize and essentially, tag a follow-up visit. The Protocols module stores the interval name in this field. For example, it could potentially store, "baseline," "1 year F/U," "2 Y" etc. in this property to distinguish a visit from others. Your implementation is allowed to store any textual data in this field.
We sometimes wrestle with the fact that one subject's follow-up visit might be of a different type than another's. You may run into this problem if your study has several arms. XNAT handles this case using the "visit_type" property. Again, a simple textual field, your implementation may store any data here. The Protocols module stores what type of visit this particular instance was. For example, a "diseased" subject may require a different set of tests at a follow-up than someone not showing disease. In that case, this field could be set to "comprehensive" while non-diseased subjects would be "checkup". Again, it is up to your implementation to define what data winds up in this field.
Built-in support for visits includes a simple REST API that can be used to CRUD visits and associate existing experiments with those visits. Documentation can be found here: XNAT REST API Directory under the "Visit Protocols Resources" section.
ProjectID - Unused, but helpful for bookkeeping.
versionDescription - Unused, but helpful for bookkeeping.
These define the types of visits available to use in the protocol. Typically, this would be things like, "diagnostic visit," "safety visit," "bi-weekly follow-up", etc.
name - the name of the visit type
description - a short description
deltaEnforce - whether or not the delta from previous visit is enforced with this type of visit. You may, for example, have a "subject died" type that could happen at any time. You wouldn't want to enforce a strict date on that.
deltaDrift - if delta is enforced, this value tells how much "wiggle room" to give for a visit (in days).
expectedExperiments - A list of experiments that are expected for this type of visit
type - the xft type of the experiment expected.
protocol - the poorly named string value that is used to distinguish multiple experiments of the same type that happen in the same visit. For example, in a "surgery visit" you require an MR before surgery and another after surgery. use the protocol column to distinguish pre-surgery MR and post-surgery MR. This is a varchar field.
createLink, deleteLink, editLink - Unimplemented. The idea is the UI would use these fields to send the user directly to the location to CRUD the experiment.
required - true/false. Tells the UI whether to display "required"
sortOrder - tells the UI in what order to display experiments.
imageUpload - tells the UI whether to provide a link to the LaunchUploader screen for this experiment.
labelFormat - Velocity templating language string that is used in LaunchUploadAppletProtocolAware to create an appropriate label for this experiment. It is passed to the Applet.
This is where your "visits" are actually defined.
name - The name of the visit. Varchar, can be anything. For example, V0, V1, V2, and V3 would be very descriptive visit names.
required - true/false. Tells the UI whether to note the visit is required or not.
delta - the length in days from the "0" delta visit this visit should occur. A "-1" delta is used as a "catch all" or "subject dossier" for various, non-visit related data. "0" is reserved for the first expected visit in the protocol. "365" is an example of a 1 Year Follow-up visit.
validTypes - a list of "visitTypes" (defined above) that are considered valid visit types for this particular visit.
This is implemented as a series of "data structures" (in quotes because some of the data structures are convoluted Maps of Maps of Maps of Sets.) The first data structure is "the protocol" which is built directly off the protocol JSON using the beans defined in the "protocol" package. This is straightforward enough. Rendering the protocol to the UI or figuring out how a subject's list of experiments maps into that protocol turns into a "big mess." We have to iterate over the subject's list of experiments and find out which ones fit neatly into the protocol, which experiments are missing, which visits are missing, and we have to figure out which experiments owned by the subject don't fit into anything at all. To make matters more compilcated, an experiment might be associated with a visit, but not defined in the protocol, so we have to flag that, as well.
The majority of this work is done in ProtocolVisitSubjectHelper.java. Its job is to load the protocol out of the configuration framework then generate this thing I call "the visit object" which is the map of maps of maps of maps that neatly organize all visits, valid visits, invalid visits (correct vistName, but of an invalid visitType), extra visits (those with a visitName that isn't defined in the protocol), and missing visits. Within the valid visits we organize experiments to figure out which are missing, valid, and extra.
One piece necessary to support building of the "visit object" is the ability to query the database for experiments that are joined to a pVisitdata. This portion of the code is derived from the code used to extract experiments and assessors for the standard subject details page.
Once we have "the visit object" created and populated, it is up to the velocity template to render it properly. This is the last "mess" of code. This is strictly messy only because there are so many edge cases (missing experiment in a visit that exists but isn't quite valid.) The code lives in xnat-templates/screens/xnat_subjectData/xnat_subjectData_visits.vm. We first iterate over the "all visits" portion of the visit object. For each, we query the visit object to see if it is in the Valid, Missing or Invalid. If it is valid, we write out a table with information about the visit, then iterate over all the expected experiments in that type of visit to display those experiments (and associated assessors) that are missing, extra, or valid. If the visit is "missing" a table is written out letting the user know of the missing visit and offering a link to create it. If the visit is invalid, we still have to display it and all associated experiments (noting they're invalid, of course) At the end, we then display all the "extra visits" along with all associated experiments.