129 lines
6.5 KiB
Markdown
129 lines
6.5 KiB
Markdown
# 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 usualy 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 exclusevily 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:
|
||
|
||
```javascript
|
||
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:
|
||
|
||
```javascript
|
||
|
||
// -- 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_
|