Files
ohif-viewer/platform/core/src/classes/metadata
ladeirarodolfo e7e1a8a6cd fix: 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases (#862)
* fix: 🐛 Local file: failing when retrieving segmentation data

Fix segmentation data retrieval issues for local file. Changed from
fecthing to use cornerstone loadAndCache method

BREAKING CHANGE: DICOM Seg

Closes: part of #838

* Switch SEG retrieval to WADO-RS

* Forgot a debugger

* refactor: 💡 Code refactor. Minor changes into methods

* fix: 🐛 Load local files: PDF

Items:
1. FileLoaderService: used for serveral operations on local files(load it,  get list of studies, group them, accepting dicom and pdf)
2. DicomLoaderService: used for loading dicom based on dataset and studies. Depending on type of dicom loader might change. WIP
3. Refactor PDF and handleSegmentationStorage to use DicomLoaderService

* fix: 🐛 Code review

* fix: 🐛 Code review. Changed:Folder organization and dicom file

Move fileLoaderService and others to a specific folder. When loading
dicom file change to only retrieve the file(not use cornerstone to cache
or anything else).

* fix: 🐛 Code review. Move dicomLoaderService to core

Moved dicomLoaderService to ohif/core and localFileLoaders to a specific
folder.

* fix: 🐛 Code review

Simplified method to get study for dicom file. Added error handling on
file loading. DicomLoaderService to be exposed on ohif/core/utils
instead.

* fix: 🐛 Reduce local load to one method only

Reduced local file load to one method only

* fix: 🐛 HTML to use dicomLoaderService. Prefer wadors than (uri)

* fix: 🐛 Code implementation for multiframe files

* fix: 🐛 Code review. Default local loader to dicom

Closes: 838

* fix: 🐛 Code review. Use relative path to require DICOMWeb

Closes: 838

* fix: 🐛 Code review. Fix unit test. Added DicomLoaderService mod

Closes: 838

* fix: 🐛 Code review. Add 'Seg' on left thumb

When getting/creating dataset get modality for file/image read

Closes: 838
2019-09-27 13:47:08 +02:00
..
2019-08-13 15:36:46 -04:00
2019-08-13 15:36:46 -04:00
2019-08-13 15:36:46 -04:00

Study Metadata Module

This module defines the API/Data-Model by which OHIF Viewerbase package and possibly distinct viewer implementations can access studies metadata. This module does not attempt to define any means of loading study metadata from any data end-point but only how the data that has been previously loaded into the application context will be accessed by any of the routines or algorithm implementations that need the data.

Intro

For various reasons like sorting, grouping or simply rendering study information, OHIF Viewerbase package and applications depending on it usually have the need to access study metadata. Before the current initiative there was no uniform way of achieving that since each implementation provides study metadata on its own specific ways. The application and the package itself needed to have a deep knowledge of the data structures provided by the data endpoint to perform any of the operations mentioned above, meaning that any data access code needed to be adapted or rewritten.

The intent of the current module is to provide a fairly consistent and flexible API/Data-Model by which OHIF Viewerbase package (and different viewer implementations that depend on it) can manipulate DICOM matadata retrieved from distinct data end points (e.g., a proprietary back end servers) in uniform ways with minor to no modifications needed.

Implementation

The current API implementation defines three classes of objects: StudyMetadata, SeriesMetadata and InstanceMetadata. Inside OHIF Viewerbase package, every access to Study, Series or SOP Instance metadata is achieved by the interface exposed by these three classes. By inheriting from them and overriding or extending their methods, different applications with different data models can adapt even the most peculiar data structures to the uniform interface defined by those classes. Together these classes define a flexible and extensible data manipulation layer leaving routines and algorithms that depend on that data untouched.

Design Decisions & "Protected" Members

In order to provide for good programming practices, attributes and methods meant to be used exclusively by the classes themselves (for internal purposes only) were written with an initial '_' character, being thus treated as "protected" members. The idea behind this practice was never to hide them from the programmers (what makes debugging tasks painful) but only advise for something that's not part of the official public API and thus should not be relied on. Usage of "protected" members makes the code less readable and prone to compatibility issues.

As an example, the initial implementation of the StudyMetadata class defined the attribute _studyInstanceUID and the method getStudyInstanceUID. This implies that whenever the StudyInstanceUID of a given study needs to be retrieved the getStudyInstanceUID method should be called instead of directly accessing the attribute _studyInstanceUID (which might not even be populated since getStudyInstanceUID can be possiblity overriden by a subclass to satisfy specific implementation needs, leaving the attribute _studyInstanceUID unused).

Ex:

let studyUID = myStudy.getStudyInstanceUID(); // GOOD! :-)
[ ... ]
let otherStudyUID = anotherStudy._studyInstanceUID; // BAD... :-(

Another important topic is the preference of methods over attributes on the public API. This design decision was made to ensure extensibility and flexibility (methods are extensible while standalone attributes are not, and can be adapted – through overrides, for example – to support even the most peculiar data models) even though the overhead a few additional function calls may incur.

Abstract Classes

Some classes defined in this module are "abstract" classes (even though JavaScript does not officially support such programming facility). They are abstract in the sense that a few methods (very important ones, by the way) were left "blank" (unimplemented, or more precisely implemented as empty NOP functions) in order to be implemented by specialized subclasses. Methods believed to be more generic were implemented in an attempt to satify most implementation needs but nothing prevents a subclass from overriding them as well (again, flexibility and extensibility are design goals). Most implemented methods rely on the implementation of an unimplemented method. For example, the method getStringValue from InstanceMetadata class, which has indeed been implemented and is meant to retrieve a metadata value as a string, internally calls the getRawValue method which was NOT implemented and is meant to query the internal data structures for the requested metadata value and return it as is. Used in that way, an application would not benefit much from the already implemented methods. On the other hand, by simply overriding the getRawValue method on a specialized class to deal with the intrinsics of its internal data structures, this very application would now benefit from all already implemented methods.

The following code snippet tries to illustrate the idea:


// -- InstanceMetadata.js

class InstanceMetadata {
    [ ... ]
    getRawValue(tagOrProperty, defaultValue) {
        // Please implement this method in a specialized subclass...
    }
    [ ... ]
    getStringValue(tagOrProperty, index, defaultValue) {
        let rawValue = this.getRawValue(tagOrProperty, '');
        // parse the returned value into a string...
        [ ... ]
        return stringValue;
    }
    [ ... ]
}

// -- MyFancyAppInstanceMetadata.js

class MyFancyAppInstanceMetadata extends InstanceMetadata {
    // Overriding this method will make all methods implemented in the super class
    // that rely on it to be immediately available...
    getRawValue(tagOrProperty, defaultValue) {
        let rawValue;
        // retrieve raw value from internal data structures...
        [ ... ]
        return rawValue;
    }
}

// -- main.js

[ ... ]
let sopInstaceMetadata = new MyFancyAppInstanceMetadata(myInternalData);
if (sopInstaceMetadata instanceof MyFancyAppInstanceMetadata) { // true
    // this code will be executed...
}
if (sopInstaceMetadata instanceof InstanceMetadata) { // also true
    // this code will also be executed...
}
// The following will also work since the internal "getRawValue" call inside
// "getStringValue" method will now be satisfied... (thanks to the override)
let patientName = sopInstaceMetadata.getStringValue('PatientName', '');
[ ... ]

Copyright © 2016 nucleushealth™. All rights reserved