Skip to content
David Sarrut edited this page Nov 4, 2015 · 23 revisions

This is a simplified documentation of the c++ API. Start a new project by using the template CMakeFile.txt (TODO).

Two sections here: first the core functions common to all types (schema) of database, and the standard functions specific to the standard database. Those classes and functions (should) works for any type of databases derivated from the StandardDatabase.

Core

In the following code, we omit the syd namespace, we use:

    using namespace syd;

Database manager

Get the unique instance of the database manager:

    DatabaseManager * m = DatabaseManager::GetInstance();

Create a database:

    Database * db = m->Create("StandardDatabase", dbname, folder);

Read a database:

    // Read a database, get a generic pointer syd::Database
    Database * db = m->Read(dbname);
    // or if you know the type (do the cast for you)
    StandardDatabase * db = m->Read<StandardDatabase>(dbname);

Other function: auto & list = m->GetDatabaseSchemas(); to get a string list of all known database schema. Those schemas are dynamically loaded at runtime via plugin read in the SYD_PLUGIN environment variable.

Functions common to all databases (CRUD)

Basics operations are 'CRUD': Create, Read, Update and Delete.

Get information:

    // Get the name of the sql schema, i.e. "StandardDatabase"
    std::string s = db->GetDatabaseSchema();
    // Get the filename i.e. test.db
    std::string s = db->GetFilename();
    // Get the image folder of the db, relative to the test.db folder or absolute
    std::string s = db->GetDatabaseRelativeFolder();
    std::string s = db->GetDatabaseAbsoluteFolder();

Create (New + Insert)

Insert an element (with table Patient as example but could be other tables):

    // For a generic database (unknown type)
    syd::Record::pointer r = db->New("Patient");
    // r is shared_ptr<syd::Record>
    // Also possible :
    auto r  = db->New("Patient");
    db->Set(r, args);
    db->Insert(r);

    // For a standard database (or derived from)
    syd::Patient::pointer r;
    db->New(r);
    // r is a syd::Patient::pointer (or inherit from that)
    p->name = "hello";
    db->Insert(p);

Using the New operator from the database (db->New(r))is mandatory. Because of polymorphism, a database that inherit from StandardDatabase may manage Patient with class extended from syd::Patient.

Query:

    // Get all patients
    syd::Patient::vector v;
    db->Query(v);

    // Get all patients matching a query
    typedef odb::query<Patient> QueryType;
    QueryType q = (QueryType::name == "toto" and QueryType::age < 20)
    db->Query(q, v);

    // Get only one patient matching the query
    syd::Patient::pointer patient;
    db->QueryOne(patient, q);

    // Get only one patient with its id
    db->QueryOne(patient, id);

    // Others functions
    int n = db->GetNumberOfElements<syd::Patient>(); // slow

Update:

Update records in the database.

    db->Update(patient);
    db->Update(vector_of_patients);

Delete:

    syd::Patient::pointer patient = ...;
    syd::Patient::vector patients = ...;
    syd::Record::vector records = ...;
    db->Delete(patient);
    db->Delete(patients);
    db->Delete(records);

Deleting a record can be complicated due to foreign keys (FK) in some tables. Two mecanisms are provided:

  • On cascade delete. When a table has a FK, it is possible to choose to delete the record if the record corresponding to the FK is deleted. This is done with the following pragma : #pragma db on_delete(cascade). See table DicomFile in standard database for example: we decide to automatically delete a DicomFile if the associated File is deleted.
  • In other circumstance, additional tasks must be performed when a record is deleted (for example removing the file on disk when an image is deleted). This is done with the callback mecanism:
    void Callback(odb::callback_event, odb::database&) const;
    void Callback(odb::callback_event, odb::database&);

Exceptions

Every access to a database can fail for one reason, you can test the result with a try/catch. See here:

    try {
        // Here, access to the database (insert, query etc)
    }
    catch (const Exception & e) {
        std::cout << e->what(); // Go here if something wrong
    }

For debug, you can access to the last SQL query with:

    std::string s = db->GetLastSQLQuery();

When creating a new table and the corresponding class

  • First step
  • inherit from syd::Record (or a class that inherit from)
  • Use this pragma (with abc the namespace and 'MyTable' the name of the table:
   #pragma db object polymorphic pointer(std::shared_ptr) table("abc::MyTable") callback(Callback)
  • Second step

  • constructor ? not needed ?

  • add friend class odb::access; if the constructor is protected

  • define typedef pointer and vector

  • implement GetTableName and GetStaticTableName

  • implement New

  • All previous can be automatically done with TABLE_DEFINE(MyTable)

  • Third step

  • implement ToString FIXME --> provide default ?

  • implement IsEqual FIXME --> provide default ?

  • implement CopyFrom FIXME --> provide default ?

  • implement Callback or use macro TABLE_DEFAULT_CALLBACK

  • Fourth Step (optional, if not overloaded, default exist)

  • implement Set

  • implement InitPrintTable and DumpInTable

  • implement Sort


Standard Database

List of tables: TODO

Some functions related to tables

Convenient functions for common queries (to retrieve a patient from his name for example):

    // Let db be a syd::Database
    Patient patient;
    FindPatientByNameOrStudyId(patient, db, "toto");
    std::vector<Tag> tags;
    std::string tag_names; // separated by space
    FindTags(tags, db, tag_names);

Records deletion

When records are delete, a mecanism takes care about dependencies.

Policy:

  • Image: also delete Files.
  • Files: also delete the files on disk, the Image and the DicomFile
  • DicomFile: also delete the File, the DicomSerie
  • DicomSerie: also delete the DicomFiles
  • Injection: also delete DicomSeries and Timepoint
  • Radionuclide: also delete the Injection
  • Patient: also delete the Injection
  • Tag : TODO
  • Timepoint: TODO

DicomSerieBuilder

Create an object DicomSerieBuilder and set the basics informations:

    DicomSerieBuilder b(db);
    b.SetInjection(injection);
    b.SetForcePatientFlag(true);
    b.SearchForFilesInFolder(folder, files);

Main function to create a DicomSerie from a file. Checks if the file is not already in the db, guess if the file should be added to an existing DicomSerie or if a new DicomSerie. The DicomSerie is not yet inserted into the db. See next function.

    for(auto f:files) b.CreateDicomSerieFromFile(f.c_str());

Once the files have been created (previous function), insert all created series in the db.

    b.InsertDicomSeries();

ImageBuilder

The class ImageBuilder allow to to create record in the Image table and the associated mhd/raw files.

Convert a dicom image to a mhd (and insert an Image)

    ImageBuilder b(db);
    syd::Image image = builder.InsertImage(d);

Create and insert a ROI as a binary mask: RoiMaskImage. Need a filename (mhd) and a DicomSerie to be linked with.

    ImageBuilder b(db);
    syd::Image image = b.InsertRoiMaskImage(dicom, roitype, filename);

It is also possible to create an image by stitching two dicom together:

    ImageBuilder b(db);
    syd::Image image = builder.InsertStitchedImage(tag, dicom1, dicom2);

It is recommended to add tag to images:

    Tag t; // get a tag somehow
    image.AddTag(t);
    db->Update(image);