#ifndef __ctkDICOMDatabase_h
#define __ctkDICOMDatabase_h
// Qt includes
#include <QObject>
#include <QStringList>
#include <QSqlDatabase>
#include "ctkDICOMItem.h"
#include "ctkDICOMCoreExport.h"
class QDateTime;
class ctkDICOMDatabasePrivate;
class DcmDataset;
class ctkDICOMAbstractThumbnailGenerator;
class ctkDICOMDisplayedFieldGenerator;
// / \ingroup DICOM_Core
// /
// / 处理 DICOM 对象数据库的类。到目前为止,底层是SQLITE 数据库。
// / 通常,添加的 DICOM 对象也是存储在这个数据库文件中。
// / SQLITE数据库文件可以由用户指定。
// / SQLITE(和当前类) 还支持特殊的内存模式,即不创建数据库文件
// / 但数据库完全保存在内存中(并且在退出程序后,数据库文件消失)。
// / 如果在“memory mode”下,DICOM对象不会写入磁盘,
// / 否则它们存储在 SQLITE 数据库文件的子目录中名为“dicom”的目录。
// / 在内部,创建了一个文件夹结构,其中包含
// / 每个研究的目录,包含每个系列的目录,包含
// / 每个对象的文件。相应的 UID 用作文件名。
// / 可以创建每个图像的缩略图;它们存储在和“dicom”一个目录中
// / 名为“thumbs”的目录。
class CTK_DICOM_CORE_EXPORT ctkDICOMDatabase : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isOpen READ isOpen)
Q_PROPERTY(bool isInMemory READ isInMemory)
Q_PROPERTY(QString lastError READ lastError)
Q_PROPERTY(QString databaseFilename READ databaseFilename)
Q_PROPERTY(QString databaseDirectory READ databaseDirectory)
Q_PROPERTY(QStringList tagsToPrecache READ tagsToPrecache WRITE setTagsToPrecache)
Q_PROPERTY(QStringList tagsToExcludeFromStorage READ tagsToExcludeFromStorage WRITE setTagsToExcludeFromStorage)
Q_PROPERTY(QStringList patientFieldNames READ patientFieldNames)
Q_PROPERTY(QStringList studyFieldNames READ studyFieldNames)
Q_PROPERTY(QStringList seriesFieldNames READ seriesFieldNames)
Q_PROPERTY(bool useShortStoragePath READ useShortStoragePath WRITE setUseShortStoragePath)
public:
struct IndexingResult
{
QString filePath;
QSharedPointer<ctkDICOMItem> dataset;
bool copyFile;
bool overwriteExistingDataset;
};
explicit ctkDICOMDatabase(QObject *parent = 0);
explicit ctkDICOMDatabase(QString databaseFile);
virtual ~ctkDICOMDatabase();
const QSqlDatabase& database() const;
const QString lastError() const;
const QString databaseFilename() const;
static const char* defaultSchemaFile() { return ":/dicom/dicom-schema.sql"; };
///返回数据库目录的绝对路径
// /(数据库文件所在的位置)采用操作系统首选路径格式。
// / @return 数据库目录的绝对路径
const QString databaseDirectory() const;
///尝试打开数据库后应该检查
// / @Returns true 如果数据库是打开的
bool isOpen () const ;
///返回数据库是否只驻留在内存中,即
// / SQLITE DB 不写入存储到磁盘和 DICOM 对象不
// / 存储到文件系统。
// / @return 如果处于内存模式则为真,否则为假。
bool isInMemory () const ;
///设置缩略图生成器对象
Q_INVOKABLE void setThumbnailGenerator (ctkDICOMAbstractThumbnailGenerator* generator);
///获取缩略图生成器对象
Q_INVOKABLE ctkDICOMAbstractThumbnailGenerator* thumbnailGenerator ();
// / 在@param databaseFile 中打开 SQLite 数据库。如果文件不存在,默认创建并初始化一个新的数据库
// /
// / @param databaseFile 存储SQLITE数据库的文件。
// / 如果用“:memory:”指定,则数据库完全不写入磁盘,只保存在内存中(因此在此对象之后会被销毁)。
// / @param connectionName 数据库连接名称。如果没有指定,然后生成一个随机名称(重复使用连接名称
// / 必须避免,因为使用相同的连接名称会破坏之前创建的数据库对象)。
// / @param 如果发现模式过期则更新模式
Q_INVOKABLE virtual void openDatabase ( const QString databaseFile,
const QString& connectionName = " " );
// / 关闭数据库。之后不得使用。
Q_INVOKABLE void closeDatabase ();
// / 删除所有数据并(重新)初始化数据库。
Q_INVOKABLE bool initializeDatabase ( const char * schemaFile = ctkDICOMDatabase::defaultSchemaFile());
// /更新数据库模式并重新插入所有现有文件
// / \param schemaFile SQL 文件包含模式定义的
// / \param newDatabaseDir 更新数据库的新数据库目录路径。
// / 默认值为空,意味着目录将保持不变;
// / 注意:需要“即时”切换数据库文件夹,以便复制数据库可以简单地通过 createBackupFileList 和一系列插入操作
// / \返回值为真 :如果模式已更新
Q_INVOKABLE bool updateSchema(
const char* schemaFile = ctkDICOMDatabase::defaultSchemaFile(),
const char* newDatabaseDir = nullptr);
// / 仅当版本不匹配时才更新数据库模式
// / \param schemaFile 包含模式定义的 SQL 文件
// / \param newDatabaseDir 更新数据库的新数据库目录路径。
// / 默认为空,意味着目录将保持不变
// / 注意:需要“即时”切换数据库文件夹,以便复制
// / 数据库可以简单地通过 createBackupFileList 和以下插入来完成
// / \如果架构已更新则返回真
Q_INVOKABLE bool updateSchemaIfNeeded (
const char * schemaFile = ctkDICOMDatabase::defaultSchemaFile(),
const char* newDatabaseDir = nullptr);
///返回这段代码当前版本需要的schema版本
Q_INVOKABLE QString schemaVersion ();
// / 返回当前打开的数据库的模式版本
// / 为了支持模式更新
Q_INVOKABLE QString schemaVersionLoaded ();
// / 在使用非标准模式的情况下在外部设置模式版本
Q_INVOKABLE void setSchemaVersion (QString schemaVersion);
// / \数据库访问
Q_INVOKABLE QStringList patients();
Q_INVOKABLE QStringList studiesForPatient ( const QString patientUID);
Q_INVOKABLE QStringList seriesForStudy ( const QString studyUID);
// / 由于一个系列可能包含数百个实例,因此此方法可能很慢。
// / 如果指定了 hits > 0,则返回的实例将被限制为该数量。
// / 这对于检索第一个文件很有用,例如用于访问该文件中的字段
// / 使用 instanceValue() 方法。
Q_INVOKABLE QStringList instancesForSeries ( const QString seriesUID, int hits = - 1 );
Q_INVOKABLE QString studyForSeries (QString seriesUID);
Q_INVOKABLE QString patientForStudy (QString studyUID);
// / 由于系列可能包含数百个文件,因此此方法可能很慢。
// / 如果指定了 hits > 0,则返回的文件名将被限制为该数字。
// / 这对于检索第一个文件很有用,例如用于访问该文件中的字段
// / 使用 fileValue() 方法。
Q_INVOKABLE QStringList filesForSeries ( const QString seriesUID, int hits=- 1 );
Q_INVOKABLE QHash<QString,QString> descriptionsForFile (QString fileName);
Q_INVOKABLE QString descriptionForSeries ( const QString seriesUID);
Q_INVOKABLE QString descriptionForStudy ( const QString studyUID);
Q_INVOKABLE QString nameForPatient ( const QString patientUID);
Q_INVOKABLE QString displayedNameForPatient ( const QString patientUID);
Q_INVOKABLE QString fieldForPatient ( const QString field, const QString patientUID);
Q_INVOKABLE QString fieldForStudy ( const QString field, const QString studyInstanceUID);
Q_INVOKABLE QString fieldForSeries ( const QString field, const QString seriesInstanceUID);
QStringList patientFieldNames () const ;
QStringList studyFieldNames () const ;
QStringList seriesFieldNames () const ;
Q_INVOKABLE QString fileForInstance ( const QString sopInstanceUID);
Q_INVOKABLE QString seriesForFile (QString fileName);
Q_INVOKABLE QString instanceForFile ( const QString fileName);
Q_INVOKABLE QDateTime insertDateTimeForInstance ( const QString fileName);
Q_INVOKABLE int patientsCount ();
Q_INVOKABLE int studiesCount ();
Q_INVOKABLE int seriesCount ();
Q_INVOKABLE int imagesCount ();
Q_INVOKABLE QStringList allFiles ();
bool allFilesModifiedTimes (QMap<QString, QDateTime>& modifiedTimeForFilepath);
// / \brief 从文件加载标题并允许访问元素
// / @param sopInstanceUID 具有给定实例的 uid 的字符串
// /(会通过数据库找到对应的文件)
// / @param fileName 要加载的 dicom 文件的完整路径。
// / @param key A group, element tag in zero-filled hex
Q_INVOKABLE void loadInstanceHeader ( const QString sopInstanceUID);
Q_INVOKABLE void loadFileHeader ( const QString fileName);
Q_INVOKABLE QStringList headerKeys ();
Q_INVOKABLE QString headerValue ( const QString key);
// / \brief 应用程序定义的感兴趣的标签
// / 这个标签列表在导入时被添加到内部标签缓存中
// / 操作。该列表应由应用程序准备为
// / 向数据库提示这些标签很可能被访问
// / 稍后。在内部,数据库会缓存这些值
// / 标记,以便后续调用 fileValue 或 instanceValue
// / 能够使用缓存而不是重新读取文件。
// / @param 标签应该是 ascii 十六进制组/元素标签的列表
// / 类似于 instanceValue 和 fileValue 调用中的“0008,0008”
void setTagsToPrecache ( const QStringList tags);
const QStringList tagsToPrecache ();
// / \brief 不得存储在标签缓存中的标签。
///如果标签不适合存储在数据库中(例如,二进制数据),则可能会从存储中排除
// / 或者如果该字段的内容通常非常大。
// / 仍然可以使用 instanceValueExists 或 fileValueExists 检查是否存在非空标签。
// / 默认情况下,只有 PixelData 标签被排除在存储之外。
void setTagsToExcludeFromStorage ( const QStringList tags);
const QStringList tagsToExcludeFromStorage ();
// / 如果不存在则插入到数据库中。
// / @param dataset 要存储到数据库中的数据集。通常,这是
// / 是一个完整的 DICOM 对象,就像一个完整的图像。然而
// / 数据库也插入部分对象,比如study
///信息到数据库,即使没有图像数据
// / 包含。这有助于存储来自
// / 查询 PACS 中的患者/研究/系列或图像
///信息,这里只构造了一个完整的层次结构
// / 经过一些查询。
// / @param storeFile 如果设置了存储文件(默认),那么数据集将
// / 被存储到磁盘。请注意,在仅内存的情况下
// / 数据库,忽略此标志。通常,这个标志
// / 只有在接收到完整对象时才有意义。
// / @param @generateThumbnail 如果为真,则生成缩略图。
// /
Q_INVOKABLE void insert ( const ctkDICOMItem& ctkDataset,
bool storeFile, bool generateThumbnail);
void insert ( DcmItem *item,
bool storeFile = true , bool generateThumbnail = true );
Q_INVOKABLE void insert ( const QString& filePath,
bool storeFile = true , bool generateThumbnail = true ,
bool createHierarchy = true ,
const QString& destinationDirectoryName = QString() );
Q_INVOKABLE void insert ( const QString& filePath, const ctkDICOMItem& ctkDataset,
bool storeFile = true , bool generateThumbnail = true );
Q_INVOKABLE void insert ( const QList<ctkDICOMDatabase::IndexingResult>& indexingResults);
// / 当一个 DICOM 文件存储在数据库中时(使用 storeFile=true 调用插入)然后
// / 路径由研究、系列和 SOP 实例 UID 构成。
// / 如果 useShortStoragePath 为 false,则完整的 UID 将用作子文件夹和文件名。
// / 如果 useShortStoragePath 为 true(这是默认值)则路径被缩短到大约 40 个字符
// / ,通过将 UID 替换为从它们生成的哈希值。
// / 每个 UID 的长度可以是 40-60 个字符,因此是总路径(包括数据库文件夹基本路径)
// / 在某些文件系统上可以超过最大路径长度。建议启用 useShortStoragePath
// / 为了更好的兼容性,除非可以保证文件系统可以存储完整的 UID。
void setUseShortStoragePath ( bool useShort);
bool useShortStoragePath () const ;
///更新数据库中用于显示信息的字段
// / 来自存储在标签缓存中的信息。
// / 如果原始 DICOM 标签不是人类可读的,则显示的字段很有用,或者
// / 当我们想要显示派生的信息时(例如图像大小或
// / 患者的研究数量)。
Q_INVOKABLE virtual void updateDisplayedFields ();
// / 如果显示的字段已定义,则获取。对于使用旧模式创建的数据库,它返回 false
// / 不包含 ColumnDisplayProperties 表。
Q_INVOKABLE bool isDisplayedFieldsTableAvailable () const ;
// / 获取显示的字段生成器,以便能够在外部设置新规则
ctkDICOMDisplayedFieldGenerator* displayedFieldGenerator () const ;
// / 重置缓存的项目 ID 以确保以前
// / 插入不会干扰即将到来的插入操作。
// / 通常,它应该在一批文件之前调用
// / 插入开始。
// /
// / 如果有机会,这必须在 insert() 调用之前调用
// / 该项目已从数据库中删除,因为
// / 最后一次 insert() 调用。如果自那以后没有任何 insert() 调用
///连接到数据库,那么应该先调用它
// / 插入()。
Q_INVOKABLE void prepareInsert ();
// / 检查文件是否已经在数据库中并且是最新的
Q_INVOKABLE bool fileExistsAndUpToDate ( const QString& filePath);
///从数据库中取出系列,包括图片和缩略图
// / 如果 clearCachedTags 设置为 true 则删除与该系列关联的缓存标签,
// / 如果设置为 False,它们将保留在数据库中不变。
// / 默认情况下禁用 clearCachedTags,因为它会显着增加删除时间
// / 在大型数据库上。
Q_INVOKABLE bool removeSeries ( const QString& seriesInstanceUID, bool clearCachedTags= false );
Q_INVOKABLE bool removeStudy ( const QString& studyInstanceUID);
Q_INVOKABLE bool removePatient ( const QString& patientID);
// / 删除所有没有关联图像的患者、研究、系列。
// / 如果 vacuum 设置为 true 那么整个数据库内容都会被尝试
// / 从文件中清除所有先前删除的数据的残余。
// / 如果有多个数据库连接,Vacuuming 可能会失败。
Q_INVOKABLE bool cleanup ( bool vacuum= false );
// / \brief 访问给定实例的元素值
// / @param sopInstanceUID 具有给定实例的 uid 的字符串
// /(会通过数据库找到对应的文件)
// / @param fileName 要加载的 dicom 文件的完整路径。
// / @param group 标签的组部分作为一个整数
// / @param element 标记的元素部分为整数
// / @如果元素丢失或从存储中排除,则返回空字符串。
Q_INVOKABLE QString instanceValue ( const QString sopInstanceUID, const QString tag);
Q_INVOKABLE QString instanceValue ( const QString sopInstanceUID, const unsigned short group, const unsigned short element);
Q_INVOKABLE QString fileValue ( const QString fileName, const QString tag);
Q_INVOKABLE QString fileValue ( const QString fileName, const unsigned short group, const unsigned short element);
Q_INVOKABLE bool tagToGroupElement ( const QString tag, unsigned short & group, unsigned short & element);
Q_INVOKABLE QString groupElementToTag ( const unsigned short & group, const unsigned short & element);
// / \brief 检查具有给定属性标签的元素是否存在于数据集中并且具有非空值。
// / @param sopInstanceUID 具有给定实例的 uid 的字符串
// /(会通过数据库找到对应的文件)
// / @param fileName 要加载的 dicom 文件的完整路径。
// / @param group 标签的组部分作为一个整数
// / @param element 标记的元素部分为整数
// / @Returns true 如果标签存在并且具有非空值。即使该值已从数据库中的存储中排除,也返回 true。
Q_INVOKABLE bool instanceValueExists ( const QString sopInstanceUID, const QString tag);
Q_INVOKABLE bool instanceValueExists ( const QString sopInstanceUID, const unsigned short group, const unsigned short element);
Q_INVOKABLE bool fileValueExists ( const QString fileName, const QString tag);
Q_INVOKABLE bool fileValueExists ( const QString fileName, const unsigned short group, const unsigned short element);
// / \brief 存储先前请求的实例元素的值
// / 这些是 instanceValue 和 fileValue 使用的内部方法
// / 方法,但它们可以通过调用类来填充或访问
// / 根据需要实例标记值。
// / @param sopInstanceUID 具有给定实例的 uid 的字符串
// /(会通过数据库找到对应的文件)
// / @param key A group, element tag in zero-filled hex
// / @如果 uid 的元素从缓存中丢失,则返回空字符串
// /
// / 标签缓存存在的轻量级检查(每个运行时检查一次数据库)
Q_INVOKABLE bool tagCacheExists ();
///在当前数据库中创建一个tagCache。如果存在,请删除现有的。
Q_INVOKABLE bool initializeTagCache ();
///返回缓存标签的值
Q_INVOKABLE QString cachedTag ( const QString sopInstanceUID, const QString tag);
// / 返回指定 sopInstanceUID 的所有缓存标签和值的列表。如果标签不在缓存中,则返回空字符串。
Q_INVOKABLE void getCachedTags ( const QString sopInstanceUID, QMap<QString, QString> &cachedTags);
// / 将实例标签的值插入到缓存中
Q_INVOKABLE bool cacheTag ( const QString sopInstanceUID, const QString tag, const QString value);
///将标签列表作为批量查询操作插入缓存
Q_INVOKABLE bool cacheTags ( const QStringList sopInstanceUIDs, const QStringList tags, const QStringList values);
///移除一个SOP实例UID对应的所有标签
Q_INVOKABLE void removeCachedTags ( const QString sopInstanceUID);
///获取给定字段的显示名称
Q_INVOKABLE QString displayedNameForField (QString table, QString field) const ;
// / 设置给定字段的显示名称
Q_INVOKABLE void setDisplayedNameForField (QString table, QString field, QString displayedName);
///获取给定字段的可见性
Q_INVOKABLE bool visibilityForField (QString table, QString field) const ;
// / 设置给定字段的可见性
Q_INVOKABLE void setVisibilityForField (QString table, QString field, bool visibility);
// / 获取给定字段的权重。
///权重指定表中字段列的顺序。较小的值位于左侧(“最重的下沉”)
Q_INVOKABLE int weightForField (QString table, QString field) const ;
///设置给定字段的权重
///权重指定表中字段列的顺序。较小的值位于左侧(“最重的下沉”)
Q_INVOKABLE void setWeightForField (QString table, QString field, int weight);
///获取给定字段的格式
// / 它包含一个包含以下字段的 json 文档:
// / - resizeMode:列调整模式。可接受的值为:“interactive”(默认)、“stretch”或“resizeToContents”。
// / - 排序:默认排序顺序。可接受的值为:空(默认)、“升序”或“降序”。
// / 每个表中只有一列(或没有)应具有非空排序顺序。
Q_INVOKABLE QString formatForField (QString table, QString field) const ;
///设置给定字段的格式
Q_INVOKABLE void setFormatForField(QString table, QString field, QString format);
// / DICOM中没有患者UID,所以我们需要使用这个复合ID,用一个字符串来唯一标识一个患者。
// / 在插入患者(InsertedPatientsCompositeIDCache)和显示字段更新过程中使用。
// / 注意:这不是一个比用于决定是否应该进行研究的标准更严格的问题
// / 插入同一患者下。
Q_INVOKABLE static QString compositePatientID ( const QString& patientID, const QString& patientsName, const QString& patientsBirthDate);
Q_SIGNALS:
/// Things inserted to database.
/// patientAdded arguments:
/// - int: database index of patient (unique) within CTK database
/// - QString: patient ID (not unique across institutions)
/// - QString: patient Name (not unique)
/// - QString: patient Birth Date (not unique)
void patientAdded(int, QString, QString, QString);
/// studyAdded arguments:
/// - studyUID (unique)
void studyAdded(QString);
/// seriesAdded arguments:
/// - seriesUID (unique)
void seriesAdded(QString);
/// instance UID is provided
/// instanceAdded arguments:
/// - instanceUID (unique)
void instanceAdded(QString);
/// This signal is emitted when the database has been opened.
void opened();
/// This signal is emitted when the database has been closed.
void closed();
/// Indicate that an in-memory database has been updated
void databaseChanged();
/// Indicate that tagsToPrecache list changed
void tagsToPrecacheChanged();
/// Indicate that tagsToExcludeFromStorage list changed
void tagsToExcludeFromStorageChanged();
/// Indicate that the schema is about to be updated and how many files will be processed
void schemaUpdateStarted(int);
/// Indicate progress in updating schema (int is file number, string is file name)
void schemaUpdateProgress(int);
void schemaUpdateProgress(QString);
/// Indicate schema update finished
void schemaUpdated();
/// Trigger showing progress dialog for displayed fields update
void displayedFieldsUpdateStarted();
/// Indicate progress in updating displayed fields (int is step number)
void displayedFieldsUpdateProgress(int);
/// Indicate displayed fields update finished
void displayedFieldsUpdated();
protected:
QScopedPointer<ctkDICOMDatabasePrivate> d_ptr;
private:
Q_DECLARE_PRIVATE(ctkDICOMDatabase);
Q_DISABLE_COPY(ctkDICOMDatabase);
};
#endif
ex:
DICOMUtils.importDicom(dstDir, db)
databaseDirectory = db.databaseDirectory
patientUIDs = db.patients()
print("*******************1********************")
print('dicomDataDir :', dicomDataDir)
print('dstDir :', dstDir)
print('db.patients() :', db.patients())
print('db.patientsCount() :', db.patientsCount())
print('db.studiesCount() :', db.studiesCount())
print('db.seriesCount() :', db.seriesCount())