-
Notifications
You must be signed in to change notification settings - Fork 2
Plugin internals
Configuration.json specifies two folders, the storage and the index folder.
When it receives dicom files through the Rest API/GUI or DICOM server, it places the dicoms in the storage folder, and updates the SQLite database in the index folder
As Orthanc docs state, the storage folder is designed for exclusive use by Orthanc
Orthanc assigns each unique DICOM file a UUID (in hexadecimal with hyphens) and this is the filename
The standard Orthanc directory structure is a three layer directory structure to help filesystems that don't handle flat directories internally as computationally efficient trees
For the file labeled "cd062f5a-0...63f5", it's expected to have the path: /orthanc-storagedir/cd/06/cd062f5a-0...63f5
This plugin serves the usecase that one would like to make a directory of dicoms available through a dicom server/GUI/rest API
When a folder is added in Configuration.json to be indexed, its subfolders and their subfolders and deeper ones are scanned for dicom files as well.
Hence now there are two points of entry for DICOM files to Orthanc: 1) a dicom file placed inside the indexed folder 2) Orthanc API/GUI/DICOM server
The plugin mimics the usual Orthanc data model for files received through the latter.
In this case, when Orthanc would like to read, the path is simply the storage path plus the UUID of the file (plus the subdirs directly inferred from only the UUID)
For the former, Orthanc requests file bytes by UUID, so the plugin stores in its database the UUID->filepath mapping
The plugin also works well if the storage folder is also one of the folders listed to be indexed. This is what we would like for keeping dicoms in a single folder (or its subfolders), since caMicroscope keeps a single folder to be shared between Orthanc and files uploaded through its interface
Note that the indexer's behavior, when requested through the GUI/Rest API/DICOM server, for deleting a linked file, is to unlink it and not to delete it, and this can be changed by adding lines, in the LookupExternalDicom branch of StorageRemove, to check (using boot::filesystem) if the "externalPath" still exists, and if so, to delete the file and notify caMicroscope.
The plugin works by doing these two things:
- Registers its StorageCreate, StorageReadWhole, StorageReadRange, StorageRemove
- Makes a thread that scans the indexed directories and their recursive subdirectories
The four storage functions are data handlers for Orthanc for when Orthanc would like to save a dicom file, access it or to delete it.
These are needed because accessing a file is now no longer as simple as appending the file UUID to the storage directory path.
It might be in the storage directory, if the file was added not through scanning, but perhaps the file Orthanc requested is one of the indexed folders (which might include the storage folder as well, in which case simple append might not work)
Hence the plugin needs to handle all file accesses.
To understand controlflow of the plugin, one should note that: There are multiple plugins for Orthanc storage handling (such as SQL drivers) but it's unusual for a plugin to add a DICOM file to Orthanc. That's why adding files from indexed folders involves calling Orthanc's Rest API.
Orthanc Plugins API does not have a specialized function for this, so instead, the indexer plugin uses the standard Rest API to add dicom files from indexed folders. Hence when considering controlflow, note that StorageCreate may have been called because 1) User uploaded it via Rest API/GUI/DICOM Server 2) The plugin found a new file in indexed folders hence ProcessFile did RestApiPost. (After RestApiPost, Orthanc processes the file, saves some metadata to its own database, then calls StorageCreate, as if to save a new file) Likewise, StorageRemove may have been called because 1) e.g. user chose from the GUI to delete it 2) the scanner found it removed, hence called ExecuteDelete (that does the cleanup after it vanished from the indexed folder) To remember if a UUID is for a file stored conventionally or if it refers to a file in the indexed folders directory, the plugins keeps two databases: "Files" keeps track of all files encountered in indexed folders, "Attachments" keeps track of all files in indexed folders Orthanc also knows. Attachments hence contains (almost?) only DICOM files. For example, this is how adding new files from a directory works:
- ProcessFile finds a new DICOM file in an indexed folder and adds it to "Files".
- Then it uses the Rest API to "upload" it to Orthanc.
- Orthanc calls StorageCreate of the plugin
- the plugin matches it to that file hence adds it to "Attachments".
The plugin uses a hash it calls "instanceid" which covers SOPInstanceUID as well, hence instanceid is different for every DICOM file and this is how matching works. Matching a DICOM's contents to its instanceid allows the plugin to choose which of the two storage branches to run for StorageXXX functions. The codepath for files whose location is inferred from UUID differs from those from indexed folders, which must have their exact path stored in the plugin's database.
caMicroscope uses a single directory where DICOMs are in subdirectories, hence the flat directory is both the storage directory and a directory to be indexed. (Hence the caMicroscope /images directory must be listed as both the storage directory and an element in the indexed folders array)
The reason a patch is needed is that the StorageCreate function for new dicom files (not indexed files) uses the conventional append to storagedir, whereas this needs to match caMicroscope.
Firstly, for reader compatibility DICOM file names need to end in ".dcm" or ".dic" or ".dicom" and cannot be extensionless.
Secondly, first 10 characters of MD5 of Series Instance UID is the subdir name for caMicroscope, which allows us to keep related DICOM files in the same folder.
The caMicroscope patch works by, on receiving new DICOM (not through scanning - they work well already): Infers the subdirectory name from the file, saves the file in that directory, then saves to the plugin's database for files found through indexing, hence for that file all the branches that run next are the branches for files found through indexing. This means that our plugin now has control of paths for new files as well and these are now consistent with how SlideLoader places DICOMs in subfolders.