Compare commits
2 Commits
feature/di
...
trace-pdf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
080192204e | ||
|
|
b8607b3fe2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -114,4 +114,3 @@ dist
|
|||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
*.zip
|
|
||||||
|
|||||||
45
DOCKER.md
45
DOCKER.md
@@ -1,45 +0,0 @@
|
|||||||
# Menjalankan proxy dengan Docker
|
|
||||||
|
|
||||||
## Build images
|
|
||||||
```sh
|
|
||||||
cd /path/to/cloned/dicomweb-proxy
|
|
||||||
docker build -t dicomweb-proxy:2.0 -f dicomweb.Dockerfile .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run Images
|
|
||||||
Cek `docker-compose.yml` lalu pastikan **image** sesuai dengan nama_image hasil build.
|
|
||||||
|
|
||||||
```Dockerfile
|
|
||||||
services:
|
|
||||||
dicomweb-proxy:
|
|
||||||
image: dicomweb-proxy:2.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Lalu jalankan dengan:
|
|
||||||
```sh
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Jika berhenti, **jangan lupa**:
|
|
||||||
```sh
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
## Memakai Image di Server PACS
|
|
||||||
1. Pull Images
|
|
||||||
```sh
|
|
||||||
docker pull devone.aplikasi.web.id/one/dicomweb-proxy:2.0
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Unduh template `docker-compose.yml` dan `/config/default.js` disini:
|
|
||||||
[GDrive dicomweb-proxy starter template](https://drive.google.com/file/d/1pteS1l9vz50HhPpYfoE6hZkMofyE_WDD/view?usp=drive_link)
|
|
||||||
|
|
||||||
3. Jalankan dengan:
|
|
||||||
```sh
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Jika berhenti, **jangan lupa**:
|
|
||||||
```sh
|
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
14
README.md
14
README.md
@@ -23,6 +23,20 @@ Note: Since OHIF 3 is still beta you can also switch back to OHIF 2 version: jus
|
|||||||
|
|
||||||
* nodejs 12 or newer
|
* nodejs 12 or newer
|
||||||
|
|
||||||
|
## Setup Instructions - npm
|
||||||
|
|
||||||
|
* install in empty directory:
|
||||||
|
```npm init -y```
|
||||||
|
```npm install dicomweb-proxy```
|
||||||
|
|
||||||
|
* update config file located in:
|
||||||
|
```./node_modules/dicomweb-proxy/config```
|
||||||
|
|
||||||
|
* or better: create config override, see: [config](https://www.npmjs.com/package/config)
|
||||||
|
|
||||||
|
* start proxy:
|
||||||
|
```npx dicomweb-proxy```
|
||||||
|
|
||||||
## Setup Instructions - source
|
## Setup Instructions - source
|
||||||
|
|
||||||
* clone repository and install dependencies:
|
* clone repository and install dependencies:
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"source": {
|
"source": {
|
||||||
"aet": "MARIO",
|
"aet": "DICOMWEB_PROXY",
|
||||||
"ip": "localhost",
|
"ip": "128.199.154.150",
|
||||||
"port": 21112
|
"port": 8888
|
||||||
},
|
},
|
||||||
"peers": [
|
"peers": [
|
||||||
{
|
{
|
||||||
"aet": "ABPACS",
|
"aet": "ABPACS",
|
||||||
"ip": "152.42.173.210",
|
"ip": "128.199.154.150",
|
||||||
"port": 11112
|
"port": 11112
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"storagePath": "./data",
|
"storagePath": "./data",
|
||||||
"cacheRetentionMinutes": 60,
|
"cacheRetentionMinutes": 60,
|
||||||
"webserverPort": 5000,
|
"webserverPort": 5000,
|
||||||
"useCget": true,
|
"useCget": false,
|
||||||
"useFetchLevel": "SERIES",
|
"useFetchLevel": "SERIES",
|
||||||
"maxAssociations": 4,
|
"maxAssociations": 4,
|
||||||
"qidoMinChars": 0,
|
"qidoMinChars": 0,
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
FROM node:18-bullseye
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install DCMTK dan dependensi yang dibutuhkan
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
dcmtk \
|
|
||||||
unzip \
|
|
||||||
curl \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Membuat folder untuk DCMTK
|
|
||||||
RUN mkdir -p /usr/local/share/dcmtk-3.6.8
|
|
||||||
|
|
||||||
# Copy DCMTK files
|
|
||||||
COPY ./usr-dcmtk368/* /usr/local/share/dcmtk-3.6.8/
|
|
||||||
|
|
||||||
# Set environment variable untuk DCMTK
|
|
||||||
ENV DCMDICTPATH=/usr/local/share/dcmtk-3.6.8/dicom.dic
|
|
||||||
|
|
||||||
# Create app directory structure
|
|
||||||
RUN mkdir -p /app/dicomweb-proxy/config
|
|
||||||
|
|
||||||
# Copy only necessary files from your project
|
|
||||||
COPY ./bin /app/dicomweb-proxy/bin
|
|
||||||
COPY ./public /app/dicomweb-proxy/public
|
|
||||||
COPY ./src /app/dicomweb-proxy/src
|
|
||||||
COPY ./package.json /app/dicomweb-proxy/
|
|
||||||
COPY ./tsconfig.json /app/dicomweb-proxy/
|
|
||||||
|
|
||||||
# Masuk ke directory dicomweb-proxy
|
|
||||||
WORKDIR /app/dicomweb-proxy
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# Membuat volume untuk konfigurasi
|
|
||||||
VOLUME /app/dicomweb-proxy/config
|
|
||||||
|
|
||||||
# Expose port yang dibutuhkan (sesuai dengan konfigurasi dicomweb-proxy)
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
# Command untuk menjalankan aplikasi
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
|
||||||
dicomweb-proxy:
|
|
||||||
image: dicomweb-proxy:3.0.1
|
|
||||||
container_name: dicomweb-proxy
|
|
||||||
ports:
|
|
||||||
- "5000:5000"
|
|
||||||
volumes:
|
|
||||||
- "/home/mario/Works/dicomweb-proxy/config/:/app/dicomweb-proxy/config/"
|
|
||||||
restart: unless-stopped
|
|
||||||
1919
package-lock.json
generated
1919
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
"start": "ts-node src/app.ts",
|
"start": "ts-node src/app.ts",
|
||||||
"format": "npx prettier ./src",
|
"format": "npx prettier ./src",
|
||||||
"build": "tsc --p ./tsconfig.json",
|
"build": "tsc --p ./tsconfig.json",
|
||||||
"test": "node --test -r ts-node/register/transpile-only tests/*.test.ts",
|
|
||||||
"pkg:win": "npm run build && npx pkg --targets node16-win-x64 package.json"
|
"pkg:win": "npm run build && npx pkg --targets node16-win-x64 package.json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -37,7 +36,7 @@
|
|||||||
"close-with-grace": "^1.2.0",
|
"close-with-grace": "^1.2.0",
|
||||||
"config": "^3.3.10",
|
"config": "^3.3.10",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"dicom-dimse-native": "2.4.2",
|
"dicom-dimse-native": "2.4.5",
|
||||||
"dicom-parser": "^1.8.21",
|
"dicom-parser": "^1.8.21",
|
||||||
"fastify": "^4.26.0",
|
"fastify": "^4.26.0",
|
||||||
"shelljs": "^0.8.5",
|
"shelljs": "^0.8.5",
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
# Report: DICOMweb proxy recommendation
|
|
||||||
|
|
||||||
- Todo: `todo/01-dicom-web-proxy-recommendation.md`
|
|
||||||
- Detail: `todo/01-dicom-web-proxy-recommendation.detail.md`
|
|
||||||
- Branch: `feature/dicomweb-proxy-recommendation`
|
|
||||||
- Commits: `0c680b6`, `3ac04f9`
|
|
||||||
- Status: implemented, build + lint + tests pass
|
|
||||||
|
|
||||||
## What was implemented
|
|
||||||
|
|
||||||
### P0 - metadata display correctness
|
|
||||||
|
|
||||||
The hand-built metadata subset in `parseMeta.ts` was replaced with a
|
|
||||||
VR-aware DICOM-to-DICOM-JSON converter (`src/dimse/dicomJson.ts`).
|
|
||||||
|
|
||||||
| Requirement | Result |
|
|
||||||
| :------------------------------------------- | :----- |
|
|
||||||
| Stop fabricating Window Center (0028,1050) | Done |
|
|
||||||
| Stop fabricating Window Width (0028,1051) | Done |
|
|
||||||
| Stop fabricating Rescale Intercept (0028,1052)| Done |
|
|
||||||
| Stop fabricating Rescale Slope (0028,1053) | Done |
|
|
||||||
| Preserve multi-valued WC/WW and rescale | Done |
|
|
||||||
| Preserve VOI/LUT/presentation/padding tags | Done |
|
|
||||||
| Omit absent optional tags (no `[undefined]`) | Done |
|
|
||||||
|
|
||||||
Converter behavior:
|
|
||||||
|
|
||||||
- Uses explicit `element.vr`, then the DICOM dictionary as fallback.
|
|
||||||
- Resolves ambiguous dictionary VRs (for example `US or SS` for padding).
|
|
||||||
- Handles string, numeric, `DS`/`IS`, `AT`, `PN` and binary VRs.
|
|
||||||
- Recursively converts `SQ` items and keeps LUT sequences.
|
|
||||||
- Encodes binary LUT payloads as `InlineBinary` (base64).
|
|
||||||
- Excludes Pixel Data, float pixel data and overlay data.
|
|
||||||
- Excludes private (odd-group) tags by default.
|
|
||||||
|
|
||||||
### P1 - WADO-RS frame correctness
|
|
||||||
|
|
||||||
| Requirement | Result |
|
|
||||||
| :-------------------------------------------- | :----- |
|
|
||||||
| Validate frame as positive 1-based integer | Done |
|
|
||||||
| Return 400 for invalid values | Done |
|
|
||||||
| Thread frame into `doWadoRs`/buffer builder | Done |
|
|
||||||
| Return only requested uncompressed frame | Done |
|
|
||||||
| Explicit unsupported response for compressed | Done (415) |
|
|
||||||
| Never fall back to full Pixel Data | Done |
|
|
||||||
| JPEG only on `/rendered` and `/thumbnail` | Done |
|
|
||||||
| Frame log with `jpegRendererUsed=false` | Done |
|
|
||||||
|
|
||||||
Frame slicing details:
|
|
||||||
|
|
||||||
- `NumberOfFrames` (0028,0008) defaults to 1 only when absent.
|
|
||||||
- Validates `1 <= frame <= NumberOfFrames`.
|
|
||||||
- Frame size uses Rows, Columns, SamplesPerPixel and BitsAllocated.
|
|
||||||
- Supports 1-bit and byte-aligned BitsAllocated.
|
|
||||||
- Too-short Pixel Data is a 500 server error, not a full-buffer fallback.
|
|
||||||
- Compressed or encapsulated Pixel Data is rejected with 415.
|
|
||||||
- Frame `Content-Location` uses the canonical `/instances/.../frames/{n}` URL.
|
|
||||||
|
|
||||||
### P2 - tests and verification
|
|
||||||
|
|
||||||
| Requirement | Result |
|
|
||||||
| :------------------------------------------- | :----- |
|
|
||||||
| Synthetic non-PHI CR and multi-frame fixtures| Done |
|
|
||||||
| Absent WC/WW stays absent | Done |
|
|
||||||
| Absent rescale stays absent | Done |
|
|
||||||
| Multi-valued WC/WW preserved | Done |
|
|
||||||
| `/frames/1` and `/frames/2` differ | Done |
|
|
||||||
| Invalid frame numbers return 400 | Done |
|
|
||||||
| Compressed extraction returns 415/501 | Done |
|
|
||||||
| `npm run build` and test suite | Done |
|
|
||||||
|
|
||||||
## Files changed
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
| :------------------------------ | :----- |
|
|
||||||
| `src/dimse/dicomJson.ts` | New DICOM-to-DICOM-JSON converter |
|
|
||||||
| `src/dimse/frameExtractor.ts` | New native frame slicing + param parser |
|
|
||||||
| `src/dimse/errors.ts` | New status-bearing error types |
|
|
||||||
| `src/dimse/parseMeta.ts` | Uses the converter instead of a static map |
|
|
||||||
| `src/dimse/wadoRs.ts` | Threads frame, logs, frame Content-Location |
|
|
||||||
| `src/routes/routes.ts` | Validates frame, maps error status codes |
|
|
||||||
| `tests/dicomJson.test.ts` | Metadata regression tests |
|
|
||||||
| `tests/frameExtractor.test.ts` | Frame extraction tests |
|
|
||||||
| `tests/helpers/dicomWriter.ts` | Synthetic DICOM writer (test-only) |
|
|
||||||
| `package.json` | Added `npm test` script |
|
|
||||||
| `tsconfig.json` | Added `skipLibCheck` |
|
|
||||||
|
|
||||||
## Deviations from the plan
|
|
||||||
|
|
||||||
- The plan's "preferred end state" converter is implemented, but the
|
|
||||||
metadata route still returns an array of per-instance datasets to keep
|
|
||||||
the existing `fetchMeta` contract intact.
|
|
||||||
- Private tags are excluded from metadata output. The plan did not require
|
|
||||||
them; excluding them keeps responses small and avoids site-private data.
|
|
||||||
- `convertToJpeg` remains untouched on the rendered/thumbnail paths.
|
|
||||||
- No integration test exercises the Fastify HTTP routes, because that path
|
|
||||||
imports `dicom-dimse-native` and needs a live PACS. Route-level frame
|
|
||||||
validation is covered through `parseFrameParam` and service errors.
|
|
||||||
- `skipLibCheck` was enabled in `tsconfig.json`. Without it `npm run build`
|
|
||||||
fails on a pre-existing `@types/glob`/`minimatch` mismatch in dependencies.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `npm run build` -> pass
|
|
||||||
- `npm test` -> 16 tests, 16 pass, 0 fail
|
|
||||||
- `npx eslint` on changed files -> no errors
|
|
||||||
|
|
||||||
Manual visual verification of a Jam-like CR fixture was not possible in this
|
|
||||||
environment: the original study data is not available. The synthetic CR
|
|
||||||
fixture proves no `40/80` window is fabricated, which is the stated root cause.
|
|
||||||
|
|
||||||
## Documentation Updates Needed
|
|
||||||
|
|
||||||
This repository has no `docs/` directory. Optional updates:
|
|
||||||
|
|
||||||
### README.md
|
|
||||||
|
|
||||||
Add a test section after the "Setup Instructions - source" block:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
* run the unit tests (uses node:test):
|
|
||||||
```npm test```
|
|
||||||
```
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
import dicomParser, { DataSet, Element, readFixedString } from 'dicom-parser';
|
|
||||||
import { get_element } from '@iwharris/dicom-data-dictionary';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimal DICOM JSON model as defined by DICOM PS3.18 F.
|
|
||||||
* See https://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.html
|
|
||||||
*/
|
|
||||||
export interface DicomJsonElement {
|
|
||||||
vr: string;
|
|
||||||
Value?: unknown[];
|
|
||||||
InlineBinary?: string;
|
|
||||||
BulkDataURI?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DicomJsonDataSet = Record<string, DicomJsonElement>;
|
|
||||||
|
|
||||||
export interface DicomJsonOptions {
|
|
||||||
/**
|
|
||||||
* Include private (odd group) data elements. Defaults to false to keep
|
|
||||||
* responses small and to avoid leaking site-specific private data.
|
|
||||||
*/
|
|
||||||
includePrivate?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** String VRs that map to a JSON array of strings. */
|
|
||||||
const STRING_VRS = new Set([
|
|
||||||
'AE', 'AS', 'CS', 'DA', 'DT', 'LO', 'LT', 'SH', 'ST', 'TM', 'UC', 'UI', 'UR', 'UT',
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** Numeric VRs and the byte-array parser reader used for each. */
|
|
||||||
const NUMBER_VRS: Record<string, 'readUint16' | 'readInt16' | 'readUint32' | 'readInt32' | 'readFloat' | 'readDouble'> = {
|
|
||||||
US: 'readUint16',
|
|
||||||
SS: 'readInt16',
|
|
||||||
UL: 'readUint32',
|
|
||||||
SL: 'readInt32',
|
|
||||||
FL: 'readFloat',
|
|
||||||
FD: 'readDouble',
|
|
||||||
};
|
|
||||||
|
|
||||||
/** VRs whose payload is binary and must be encoded as InlineBinary/BulkDataURI. */
|
|
||||||
const BINARY_VRS = new Set(['OB', 'OW', 'OF', 'OD', 'OL', 'OV', 'UN']);
|
|
||||||
|
|
||||||
/** Pixel data and float pixel data are never part of the metadata response. */
|
|
||||||
const EXCLUDED_TAGS = new Set(['7FE00010', '7FE00008', '7FE00009']);
|
|
||||||
|
|
||||||
function formatKey(key: string): string {
|
|
||||||
return key.replace(/^x/i, '').toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupOf(tag: string): number {
|
|
||||||
return parseInt(tag.substring(0, 4), 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrivateTag(tag: string): boolean {
|
|
||||||
return (groupOf(tag) & 0x0001) === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isExcludedTag(tag: string): boolean {
|
|
||||||
if (groupOf(tag) === 0x0002) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (EXCLUDED_TAGS.has(tag)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Overlay data lives in groups 6000-60FF, element 3000.
|
|
||||||
const group = groupOf(tag);
|
|
||||||
if (group >= 0x6000 && group <= 0x60ff && tag.substring(4) === '3000') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveAmbiguousVr(vr: string, tag: string, dataSet: DataSet): string {
|
|
||||||
const options = vr.split(/\s+or\s+/i).map((v) => v.trim().toUpperCase());
|
|
||||||
if (options.length === 1) {
|
|
||||||
return options[0];
|
|
||||||
}
|
|
||||||
// Pixel Padding Value/Range Limit are US or SS depending on PixelRepresentation.
|
|
||||||
if ((tag === '00280120' || tag === '00280121') && options.includes('US') && options.includes('SS')) {
|
|
||||||
const pixelRepresentation = dataSet.uint16('x00280103');
|
|
||||||
return pixelRepresentation === 1 ? 'SS' : 'US';
|
|
||||||
}
|
|
||||||
return options[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveVr(element: Element, tag: string, dataSet: DataSet): string {
|
|
||||||
let vr = element.vr;
|
|
||||||
if (!vr) {
|
|
||||||
const dictionaryEntry = get_element(tag);
|
|
||||||
if (dictionaryEntry) {
|
|
||||||
vr = dictionaryEntry.vr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vr = (vr || 'UN').toUpperCase();
|
|
||||||
if (/\bor\b/i.test(vr)) {
|
|
||||||
vr = resolveAmbiguousVr(vr, tag, dataSet);
|
|
||||||
}
|
|
||||||
return vr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function byteArrayParserFor(element: Element, dataSet: DataSet) {
|
|
||||||
return element.parser ?? dataSet.byteArrayParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readBinary(dataSet: DataSet, element: Element): Buffer {
|
|
||||||
const byteArray = dataSet.byteArray;
|
|
||||||
if (Buffer.isBuffer(byteArray)) {
|
|
||||||
return byteArray.subarray(element.dataOffset, element.dataOffset + element.length);
|
|
||||||
}
|
|
||||||
return Buffer.from(byteArray.buffer, byteArray.byteOffset + element.dataOffset, element.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripTrailingNul(value: string): string {
|
|
||||||
let end = value.length;
|
|
||||||
while (end > 0 && value.charCodeAt(end - 1) === 0) {
|
|
||||||
end -= 1;
|
|
||||||
}
|
|
||||||
return value.slice(0, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readStringValues(dataSet: DataSet, element: Element): string[] {
|
|
||||||
const raw = readFixedString(dataSet.byteArray, element.dataOffset, element.length);
|
|
||||||
const padded = stripTrailingNul(raw).replace(/ +$/g, '');
|
|
||||||
if (padded.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return padded.split('\\').map((value) => value.replace(/ +$/g, ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePersonName(value: string): Record<string, string> {
|
|
||||||
const [alphabetic, ideographic, phonetic] = value.split('=');
|
|
||||||
const personName: Record<string, string> = {};
|
|
||||||
if (alphabetic) {
|
|
||||||
personName.Alphabetic = alphabetic;
|
|
||||||
}
|
|
||||||
if (ideographic) {
|
|
||||||
personName.Ideographic = ideographic;
|
|
||||||
}
|
|
||||||
if (phonetic) {
|
|
||||||
personName.Phonetic = phonetic;
|
|
||||||
}
|
|
||||||
return personName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readNumericValues(dataSet: DataSet, element: Element, vr: string): number[] {
|
|
||||||
const readerName = NUMBER_VRS[vr];
|
|
||||||
const reader = byteArrayParserFor(element, dataSet)[readerName];
|
|
||||||
const width = readerName === 'readFloat' || readerName === 'readInt32' || readerName === 'readUint32'
|
|
||||||
? 4
|
|
||||||
: readerName === 'readDouble'
|
|
||||||
? 8
|
|
||||||
: 2;
|
|
||||||
const count = Math.floor(element.length / width);
|
|
||||||
const values: number[] = [];
|
|
||||||
for (let i = 0; i < count; i += 1) {
|
|
||||||
values.push(reader(dataSet.byteArray, element.dataOffset + i * width));
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readAttributeTags(dataSet: DataSet, element: Element): string[] {
|
|
||||||
const parser = byteArrayParserFor(element, dataSet);
|
|
||||||
const count = Math.floor(element.length / 4);
|
|
||||||
const values: string[] = [];
|
|
||||||
for (let i = 0; i < count; i += 1) {
|
|
||||||
const offset = element.dataOffset + i * 4;
|
|
||||||
const group = parser.readUint16(dataSet.byteArray, offset);
|
|
||||||
const elementNumber = parser.readUint16(dataSet.byteArray, offset + 2);
|
|
||||||
values.push(
|
|
||||||
`${
|
|
||||||
group.toString(16).padStart(4, '0')
|
|
||||||
}${
|
|
||||||
elementNumber.toString(16).padStart(4, '0')}`.toUpperCase(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readDecimalStrings(dataSet: DataSet, element: Element): unknown[] {
|
|
||||||
return readStringValues(dataSet, element).map((value) => {
|
|
||||||
const parsed = Number(value);
|
|
||||||
return Number.isFinite(parsed) ? parsed : value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function readIntegerStrings(dataSet: DataSet, element: Element): unknown[] {
|
|
||||||
return readStringValues(dataSet, element).map((value) => {
|
|
||||||
const parsed = Number.parseInt(value, 10);
|
|
||||||
return Number.isFinite(parsed) ? parsed : value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertSequence(dataSet: DataSet, element: Element, options: DicomJsonOptions): DicomJsonElement {
|
|
||||||
const items: DicomJsonDataSet[] = [];
|
|
||||||
if (element.items) {
|
|
||||||
for (const item of element.items) {
|
|
||||||
if (item.dataSet) {
|
|
||||||
items.push(convertDataSetToJson(item.dataSet, options));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { vr: 'SQ', Value: items };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a parsed DICOM dataset into DICOM JSON (PS3.18 F).
|
|
||||||
*
|
|
||||||
* The conversion is VR aware: it uses the explicit VR stored on the element
|
|
||||||
* when available and falls back to the DICOM data dictionary otherwise. Absent
|
|
||||||
* attributes are never synthesized, so a source file without Window Center or
|
|
||||||
* Rescale tags will not gain fabricated values.
|
|
||||||
*/
|
|
||||||
export function convertElementToJson(
|
|
||||||
dataSet: DataSet,
|
|
||||||
element: Element,
|
|
||||||
options: DicomJsonOptions = {},
|
|
||||||
): DicomJsonElement {
|
|
||||||
const tag = formatKey(element.tag);
|
|
||||||
const vr = resolveVr(element, tag, dataSet);
|
|
||||||
|
|
||||||
if (vr === 'SQ') {
|
|
||||||
return convertSequence(dataSet, element, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.length === 0) {
|
|
||||||
return { vr, Value: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vr === 'PN') {
|
|
||||||
return { vr, Value: readStringValues(dataSet, element).map(parsePersonName) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vr === 'DS') {
|
|
||||||
return { vr, Value: readDecimalStrings(dataSet, element) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vr === 'IS') {
|
|
||||||
return { vr, Value: readIntegerStrings(dataSet, element) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vr === 'AT') {
|
|
||||||
return { vr, Value: readAttributeTags(dataSet, element) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NUMBER_VRS[vr]) {
|
|
||||||
return { vr, Value: readNumericValues(dataSet, element, vr) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (STRING_VRS.has(vr)) {
|
|
||||||
return { vr, Value: readStringValues(dataSet, element) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BINARY_VRS.has(vr)) {
|
|
||||||
return { vr, InlineBinary: readBinary(dataSet, element).toString('base64') };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown VR: fall back to an inline binary payload rather than guessing.
|
|
||||||
return { vr, InlineBinary: readBinary(dataSet, element).toString('base64') };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertDataSetToJson(
|
|
||||||
dataSet: DataSet,
|
|
||||||
options: DicomJsonOptions = {},
|
|
||||||
): DicomJsonDataSet {
|
|
||||||
const result: DicomJsonDataSet = {};
|
|
||||||
for (const key of Object.keys(dataSet.elements)) {
|
|
||||||
const element = dataSet.elements[key];
|
|
||||||
const tag = formatKey(key);
|
|
||||||
if (isExcludedTag(tag)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!options.includePrivate && isPrivateTag(tag)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result[tag] = convertElementToJson(dataSet, element, options);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseDicomToJson(
|
|
||||||
buffer: Buffer | Uint8Array,
|
|
||||||
options: DicomJsonOptions = {},
|
|
||||||
): DicomJsonDataSet {
|
|
||||||
const dataSet = dicomParser.parseDicom(buffer);
|
|
||||||
return convertDataSetToJson(dataSet, options);
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* Typed, status-bearing errors so that routes can translate service failures
|
|
||||||
* into the right HTTP status code instead of always returning 500.
|
|
||||||
*/
|
|
||||||
export class HttpError extends Error {
|
|
||||||
public readonly statusCode: number;
|
|
||||||
|
|
||||||
constructor(statusCode: number, message: string) {
|
|
||||||
super(message);
|
|
||||||
this.name = 'HttpError';
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
// Restore prototype chain when targeting ES5-ish runtimes.
|
|
||||||
Object.setPrototypeOf(this, new.target.prototype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BadRequestError extends HttpError {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(400, message);
|
|
||||||
this.name = 'BadRequestError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnsupportedMediaTypeError extends HttpError {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(415, message);
|
|
||||||
this.name = 'UnsupportedMediaTypeError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NotImplementedError extends HttpError {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(501, message);
|
|
||||||
this.name = 'NotImplementedError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function statusCodeForError(error: unknown): number {
|
|
||||||
if (error instanceof HttpError) {
|
|
||||||
return error.statusCode;
|
|
||||||
}
|
|
||||||
return 500;
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
import dicomParser, { DataSet, Element } from 'dicom-parser';
|
|
||||||
import { BadRequestError, HttpError, UnsupportedMediaTypeError } from './errors';
|
|
||||||
|
|
||||||
/** Transfer syntaxes whose Pixel Data can be sliced directly as native frames. */
|
|
||||||
export const UNCOMPRESSED_TRANSFER_SYNTAXES = new Set([
|
|
||||||
'1.2.840.10008.1.2', // Implicit VR Little Endian
|
|
||||||
'1.2.840.10008.1.2.1', // Explicit VR Little Endian
|
|
||||||
'1.2.840.10008.1.2.2', // Explicit VR Big Endian
|
|
||||||
]);
|
|
||||||
|
|
||||||
const PIXEL_DATA_TAG = 'x7fe00010';
|
|
||||||
|
|
||||||
export interface ExtractedFrame {
|
|
||||||
/** The 1-based frame number that was requested. */
|
|
||||||
frame: number;
|
|
||||||
/** NumberOfFrames from the dataset, 1 when the tag is absent. */
|
|
||||||
numberOfFrames: number;
|
|
||||||
/** Transfer syntax UID of the parsed object, if present. */
|
|
||||||
transferSyntax: string;
|
|
||||||
/** Size in bytes of a single native frame. */
|
|
||||||
frameSize: number;
|
|
||||||
/** Exactly one frame worth of Pixel Data bytes. */
|
|
||||||
buffer: Buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a 1-based frame number from a route parameter. Returns undefined for
|
|
||||||
* missing, non-numeric, zero, negative or fractional values so callers can
|
|
||||||
* respond with 400.
|
|
||||||
*/
|
|
||||||
export function parseFrameParam(value: string | undefined): number | undefined {
|
|
||||||
if (value === undefined || !/^\d+$/.test(value)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const frame = Number.parseInt(value, 10);
|
|
||||||
if (!Number.isInteger(frame) || frame < 1) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEncapsulated(pixelDataElement: Element): boolean {
|
|
||||||
return pixelDataElement.encapsulatedPixelData === true
|
|
||||||
|| pixelDataElement.hadUndefinedLength === true
|
|
||||||
|| (Array.isArray(pixelDataElement.fragments) && pixelDataElement.fragments.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readNumberOfFrames(dataSet: DataSet): number {
|
|
||||||
const fromString = dataSet.intString('x00280008');
|
|
||||||
if (typeof fromString === 'number' && Number.isFinite(fromString)) {
|
|
||||||
return fromString;
|
|
||||||
}
|
|
||||||
const fromUint16 = dataSet.uint16('x00280008');
|
|
||||||
if (typeof fromUint16 === 'number' && Number.isFinite(fromUint16)) {
|
|
||||||
return fromUint16;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function requireUint16(dataSet: DataSet, tag: string, name: string): number {
|
|
||||||
const value = dataSet.uint16(tag);
|
|
||||||
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
||||||
throw new HttpError(500, `cannot extract frame: missing or invalid ${name}`);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract a single native (uncompressed) frame from a parsed DICOM dataset.
|
|
||||||
*
|
|
||||||
* The returned buffer contains exactly one frame worth of bytes; it never
|
|
||||||
* falls back to returning the full Pixel Data element. Encapsulated/compressed
|
|
||||||
* Pixel Data is rejected with an explicit unsupported response until real
|
|
||||||
* compressed frame extraction is implemented.
|
|
||||||
*/
|
|
||||||
export function extractFrame(dataSet: DataSet, frame: number): ExtractedFrame {
|
|
||||||
if (!Number.isInteger(frame) || frame < 1) {
|
|
||||||
throw new BadRequestError(`frame must be a positive integer, received '${frame}'`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const transferSyntaxElement = dataSet.elements.x00020010;
|
|
||||||
const transferSyntax = transferSyntaxElement
|
|
||||||
? (dataSet.string('x00020010') ?? '')
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const pixelDataElement = dataSet.elements[PIXEL_DATA_TAG];
|
|
||||||
if (!pixelDataElement) {
|
|
||||||
throw new HttpError(500, 'cannot extract frame: Pixel Data (7FE0,0010) not present');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transferSyntax && !UNCOMPRESSED_TRANSFER_SYNTAXES.has(transferSyntax)) {
|
|
||||||
throw new UnsupportedMediaTypeError(
|
|
||||||
`compressed frame extraction is not supported for transfer syntax ${transferSyntax}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEncapsulated(pixelDataElement)) {
|
|
||||||
throw new UnsupportedMediaTypeError(
|
|
||||||
'encapsulated (compressed) Pixel Data frame extraction is not supported',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberOfFrames = readNumberOfFrames(dataSet);
|
|
||||||
if (frame > numberOfFrames) {
|
|
||||||
throw new BadRequestError(
|
|
||||||
`frame ${frame} is out of range, instance has ${numberOfFrames} frame(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = requireUint16(dataSet, 'x00280010', 'Rows (0028,0010)');
|
|
||||||
const columns = requireUint16(dataSet, 'x00280011', 'Columns (0028,0011)');
|
|
||||||
const samplesPerPixel = requireUint16(dataSet, 'x00280002', 'SamplesPerPixel (0028,0002)');
|
|
||||||
const bitsAllocated = requireUint16(dataSet, 'x00280100', 'BitsAllocated (0028,0100)');
|
|
||||||
|
|
||||||
let frameSize: number;
|
|
||||||
if (bitsAllocated === 1) {
|
|
||||||
frameSize = Math.ceil((rows * columns * samplesPerPixel) / 8);
|
|
||||||
}
|
|
||||||
else if (bitsAllocated % 8 === 0) {
|
|
||||||
frameSize = rows * columns * samplesPerPixel * (bitsAllocated / 8);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new HttpError(500, `cannot extract frame: unsupported BitsAllocated ${bitsAllocated}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requiredLength = frame * frameSize;
|
|
||||||
if (pixelDataElement.length < requiredLength) {
|
|
||||||
throw new HttpError(
|
|
||||||
500,
|
|
||||||
`cannot extract frame ${frame}: Pixel Data is too short `
|
|
||||||
+ `(${pixelDataElement.length} bytes, need ${requiredLength})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = pixelDataElement.dataOffset + (frame - 1) * frameSize;
|
|
||||||
const byteArray = dataSet.byteArray;
|
|
||||||
const buffer = Buffer.isBuffer(byteArray)
|
|
||||||
? byteArray.subarray(offset, offset + frameSize)
|
|
||||||
: Buffer.from(byteArray.buffer, byteArray.byteOffset + offset, frameSize);
|
|
||||||
|
|
||||||
return {
|
|
||||||
frame,
|
|
||||||
numberOfFrames,
|
|
||||||
transferSyntax,
|
|
||||||
frameSize,
|
|
||||||
buffer: Buffer.from(buffer),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractFrameFromBuffer(buffer: Buffer | Uint8Array, frame: number): ExtractedFrame {
|
|
||||||
const dataSet = dicomParser.parseDicom(buffer);
|
|
||||||
return extractFrame(dataSet, frame);
|
|
||||||
}
|
|
||||||
@@ -1,33 +1,96 @@
|
|||||||
import { LoggerSingleton } from '../utils/logger';
|
import { LoggerSingleton } from '../utils/logger';
|
||||||
import { ConfParams, config } from '../utils/config';
|
import { ConfParams, config } from '../utils/config';
|
||||||
import { fileExists } from '../utils/fileHelper';
|
import { fileExists } from '../utils/fileHelper';
|
||||||
|
import dicomParser from 'dicom-parser';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { DicomJsonDataSet, parseDicomToJson } from './dicomJson';
|
|
||||||
|
|
||||||
function parseFile(filename: string): Promise<DicomJsonDataSet> {
|
interface ValueType {
|
||||||
|
Value: string[] | number[] | unknown[];
|
||||||
|
vr: string;
|
||||||
|
}
|
||||||
|
type ElementType = Record<string, ValueType>;
|
||||||
|
|
||||||
|
function parseFile(filename: string): Promise<ElementType> {
|
||||||
const logger = LoggerSingleton.Instance;
|
const logger = LoggerSingleton.Instance;
|
||||||
return new Promise<DicomJsonDataSet>((resolve, reject) => {
|
return new Promise<ElementType>((resolve, reject) => {
|
||||||
fileExists(filename).then((success: boolean) => {
|
fileExists(filename).then((success: boolean) => {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
logger.error(`file does not exist: ${filename}`);
|
logger.error(`file does not exist: ${filename}`);
|
||||||
return reject(new Error(`file does not exist: ${filename}`));
|
return reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.promises.readFile(filename)
|
fs.promises.readFile(filename).then((data: Uint8Array) => {
|
||||||
.then((data: Uint8Array) => {
|
const dataset = dicomParser.parseDicom(data);
|
||||||
try {
|
|
||||||
resolve(parseDicomToJson(data));
|
// parse additional needed attributes
|
||||||
}
|
const patientName = dataset.string('x00100010');
|
||||||
catch (error) {
|
const patentID = dataset.string('x00100020');
|
||||||
logger.error(`failed to parse metadata from ${filename}: ${error}`);
|
const studyInstanceUID = dataset.string('x0020000d');
|
||||||
reject(error);
|
const studyDate = dataset.string('x00080020');
|
||||||
}
|
const studyTime = dataset.string('x00080030');
|
||||||
})
|
const seriesInstanceUID = dataset.string('x0020000e');
|
||||||
.catch((error: unknown) => {
|
const seriesNumber = dataset.string('x00200011');
|
||||||
logger.error(`failed to read ${filename}: ${error}`);
|
const sopInstanceUID = dataset.string('x00080018');
|
||||||
reject(error);
|
const sopClassUID = dataset.string('x00080016');
|
||||||
});
|
const bitsAllocated = dataset.uint16('x00280100');
|
||||||
|
const bitsStored = dataset.uint16('x00280101');
|
||||||
|
const highBit = dataset.uint16('x00280102');
|
||||||
|
const rows = dataset.uint16('x00280010');
|
||||||
|
const cols = dataset.uint16('x00280011');
|
||||||
|
const pixelSpacingString = dataset.string('x00280030');
|
||||||
|
const pixelSpacing = pixelSpacingString ? pixelSpacingString.split('\\').map((e: string) => parseFloat(e)) : [1, 1];
|
||||||
|
const modality = dataset.string('x00080060');
|
||||||
|
const samplesPerPixel = dataset.uint16('x00280002');
|
||||||
|
const photometricInterpretation = dataset.string('x00280004');
|
||||||
|
const pixelRepresentation = dataset.uint16('x00280103');
|
||||||
|
const windowCenter = dataset.string('x00281050');
|
||||||
|
const wc = windowCenter ? parseFloat(windowCenter.split('\\')[0]) : 40;
|
||||||
|
const windowWidth = dataset.string('x00281051');
|
||||||
|
const ww = windowWidth ? parseFloat(windowWidth.split('\\')[0]) : 80;
|
||||||
|
const rescaleIntercept = parseFloat(dataset.string('x00281052') || '1');
|
||||||
|
const rescaleSlope = parseFloat(dataset.string('x00281053') || '1');
|
||||||
|
const iopString = dataset.string('x00200037');
|
||||||
|
const iop = iopString ? iopString.split('\\').map((e: string) => parseFloat(e)) : null;
|
||||||
|
const ippString = dataset.string('x00200032');
|
||||||
|
const ipp = ippString ? ippString.split('\\').map((e: string) => parseFloat(e)) : null;
|
||||||
|
const instanceNumber = dataset.string('x00200013');
|
||||||
|
const sliceThickness = dataset.string('x00180050');
|
||||||
|
const sliceLocation = dataset.string('x00201041');
|
||||||
|
|
||||||
|
// append to all results
|
||||||
|
const result: ElementType = {
|
||||||
|
'00100010': { Value: [{ Alphabetic: patientName }], vr: 'PN' },
|
||||||
|
'00100020': { Value: [patentID], vr: 'LO' },
|
||||||
|
'0020000D': { Value: [studyInstanceUID], vr: 'UI' },
|
||||||
|
'00080020': { Value: [studyDate], vr: 'DA' },
|
||||||
|
'00080030': { Value: [studyTime], vr: 'TM' },
|
||||||
|
'0020000E': { Value: [seriesInstanceUID], vr: 'UI' },
|
||||||
|
'00200011': { Value: [seriesNumber], vr: 'IS' },
|
||||||
|
'00080018': { Value: [sopInstanceUID], vr: 'UI' },
|
||||||
|
'00080016': { Value: [sopClassUID], vr: 'UI' },
|
||||||
|
'00080060': { Value: [modality], vr: 'CS' },
|
||||||
|
'00280002': { Value: [samplesPerPixel], vr: 'US' },
|
||||||
|
'00280004': { Value: [photometricInterpretation], vr: 'CS' },
|
||||||
|
'00280010': { Value: [rows], vr: 'US' },
|
||||||
|
'00280011': { Value: [cols], vr: 'US' },
|
||||||
|
'00280030': { Value: pixelSpacing, vr: 'DS' },
|
||||||
|
'00280100': { Value: [bitsAllocated], vr: 'US' },
|
||||||
|
'00280101': { Value: [bitsStored], vr: 'US' },
|
||||||
|
'00280102': { Value: [highBit], vr: 'US' },
|
||||||
|
'00280103': { Value: [pixelRepresentation], vr: 'US' },
|
||||||
|
'00281050': { Value: [wc], vr: 'DS' },
|
||||||
|
'00281051': { Value: [ww], vr: 'DS' },
|
||||||
|
'00281052': { Value: [rescaleIntercept], vr: 'DS' },
|
||||||
|
'00281053': { Value: [rescaleSlope], vr: 'DS' },
|
||||||
|
...(iop && { '00200037': { Value: iop, vr: 'DS' } }),
|
||||||
|
...(ipp && { '00200032': { Value: ipp, vr: 'DS' } }),
|
||||||
|
'00200013': { Value: [instanceNumber], vr: 'IS' },
|
||||||
|
'00180050': { Value: [sliceThickness], vr: 'DS' },
|
||||||
|
'00201041': { Value: [sliceLocation], vr: 'DS' },
|
||||||
|
};
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -36,12 +99,13 @@ export function parseMeta(json: object, studyInstanceUID: string, seriesInstance
|
|||||||
const logger = LoggerSingleton.Instance;
|
const logger = LoggerSingleton.Instance;
|
||||||
logger.info(`parsing series ${seriesInstanceUID}`);
|
logger.info(`parsing series ${seriesInstanceUID}`);
|
||||||
|
|
||||||
const parsing = new Array<Promise<DicomJsonDataSet>>();
|
const parsing = new Array<Promise<ElementType>>();
|
||||||
const storagePath = config.get(ConfParams.STORAGE_PATH) as string;
|
const storagePath = config.get(ConfParams.STORAGE_PATH) as string;
|
||||||
for (const [key] of Object.entries(json)) {
|
for (const [key] of Object.entries(json)) {
|
||||||
const sopInstanceUid = json[key]['00080018'].Value[0];
|
const sopInstanceUid = json[key]['00080018'].Value[0];
|
||||||
const pathname = path.join(storagePath, studyInstanceUID, sopInstanceUid);
|
const pathname = path.join(storagePath, studyInstanceUID, sopInstanceUid);
|
||||||
parsing.push(parseFile(pathname));
|
parsing.push(parseFile(pathname));
|
||||||
|
|
||||||
}
|
}
|
||||||
return Promise.all(parsing);
|
return Promise.all(parsing);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import deepmerge from 'deepmerge';
|
|||||||
import dicomParser from 'dicom-parser';
|
import dicomParser from 'dicom-parser';
|
||||||
import combineMerge from '../utils/combineMerge';
|
import combineMerge from '../utils/combineMerge';
|
||||||
import { fileExists } from '../utils/fileHelper';
|
import { fileExists } from '../utils/fileHelper';
|
||||||
import { extractFrame } from './frameExtractor';
|
|
||||||
import { execFile as exFile } from 'child_process';
|
import { execFile as exFile } from 'child_process';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ type WadoRsArgs = {
|
|||||||
seriesInstanceUid?: string;
|
seriesInstanceUid?: string;
|
||||||
sopInstanceUid?: string;
|
sopInstanceUid?: string;
|
||||||
dataFormat?: DataFormat;
|
dataFormat?: DataFormat;
|
||||||
frame?: number;
|
frame?: number | number[];
|
||||||
};
|
};
|
||||||
type WadoRsResponse = {
|
type WadoRsResponse = {
|
||||||
contentType: string;
|
contentType: string;
|
||||||
@@ -92,10 +91,9 @@ interface AddFileToBuffer {
|
|||||||
filename: string,
|
filename: string,
|
||||||
instanceInfo: InstanceInfo,
|
instanceInfo: InstanceInfo,
|
||||||
dataFormat?: DataFormat,
|
dataFormat?: DataFormat,
|
||||||
frame?: number,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addFileToBuffer({ pathname, filename, dataFormat, instanceInfo, frame }: AddFileToBuffer): Promise<Buffer> {
|
async function addFileToBuffer({ pathname, filename, dataFormat, instanceInfo }: AddFileToBuffer): Promise<Buffer> {
|
||||||
const logger = LoggerSingleton.Instance;
|
const logger = LoggerSingleton.Instance;
|
||||||
const filepath = path.join(pathname, filename);
|
const filepath = path.join(pathname, filename);
|
||||||
const buffArray: Buffer[] = [];
|
const buffArray: Buffer[] = [];
|
||||||
@@ -110,11 +108,7 @@ async function addFileToBuffer({ pathname, filename, dataFormat, instanceInfo, f
|
|||||||
contentLocation += `/series/${instanceInfo.series}`
|
contentLocation += `/series/${instanceInfo.series}`
|
||||||
}
|
}
|
||||||
if (instanceInfo.instance) {
|
if (instanceInfo.instance) {
|
||||||
// Frame requests use the canonical WADO-RS frame URL so clients can
|
contentLocation += `/instance/${instanceInfo.instance}`
|
||||||
// resolve the exact resource that was returned.
|
|
||||||
contentLocation += typeof frame === 'number'
|
|
||||||
? `/instances/${instanceInfo.instance}/frames/${frame}`
|
|
||||||
: `/instance/${instanceInfo.instance}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress the file
|
// Compress the file
|
||||||
@@ -129,7 +123,8 @@ async function addFileToBuffer({ pathname, filename, dataFormat, instanceInfo, f
|
|||||||
const data = await fs.readFile(filepath);
|
const data = await fs.readFile(filepath);
|
||||||
let returnData;
|
let returnData;
|
||||||
switch (dataFormat) {
|
switch (dataFormat) {
|
||||||
case 'bulkdata': {
|
case 'bulkdata':
|
||||||
|
case 'pixeldata': {
|
||||||
// Get the pixeldata from the DICOM and add it to the buffer.
|
// Get the pixeldata from the DICOM and add it to the buffer.
|
||||||
const dataset = dicomParser.parseDicom(data);
|
const dataset = dicomParser.parseDicom(data);
|
||||||
const pixeldataElement = dataset.elements.x7fe00010;
|
const pixeldataElement = dataset.elements.x7fe00010;
|
||||||
@@ -137,26 +132,6 @@ async function addFileToBuffer({ pathname, filename, dataFormat, instanceInfo, f
|
|||||||
returnData = Buffer.from(dataset.byteArray.buffer, pixeldataElement.dataOffset, pixeldataElement.length);
|
returnData = Buffer.from(dataset.byteArray.buffer, pixeldataElement.dataOffset, pixeldataElement.length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'pixeldata': {
|
|
||||||
// Get the pixeldata from the DICOM and add it to the buffer.
|
|
||||||
const dataset = dicomParser.parseDicom(data);
|
|
||||||
buffArray.push(Buffer.from(`Content-Type:application/octet-stream;${term}`));
|
|
||||||
if (typeof frame === 'number') {
|
|
||||||
const extracted = extractFrame(dataset, frame);
|
|
||||||
logger.info(
|
|
||||||
`frame request study=${instanceInfo.study} series=${instanceInfo.series} `
|
|
||||||
+ `instance=${instanceInfo.instance} frame=${extracted.frame} `
|
|
||||||
+ `numberOfFrames=${extracted.numberOfFrames} transferSyntax=${extracted.transferSyntax} `
|
|
||||||
+ 'contentType=application/octet-stream jpegRendererUsed=false'
|
|
||||||
);
|
|
||||||
returnData = extracted.buffer;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const pixeldataElement = dataset.elements.x7fe00010;
|
|
||||||
returnData = Buffer.from(dataset.byteArray.buffer, pixeldataElement.dataOffset, pixeldataElement.length);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'rendered': {
|
case 'rendered': {
|
||||||
// Convert the DCM file to a JPEG and return that
|
// Convert the DCM file to a JPEG and return that
|
||||||
buffArray.push(Buffer.from(`Content-Type:image/jpeg;${term}`));
|
buffArray.push(Buffer.from(`Content-Type:image/jpeg;${term}`));
|
||||||
@@ -182,7 +157,7 @@ type InstanceInfo = {
|
|||||||
instance?: string
|
instance?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doWadoRs({ studyInstanceUid, seriesInstanceUid, sopInstanceUid, dataFormat, frame }: WadoRsArgs): Promise<WadoRsResponse> {
|
export async function doWadoRs({ studyInstanceUid, seriesInstanceUid, sopInstanceUid, dataFormat }: WadoRsArgs): Promise<WadoRsResponse> {
|
||||||
const logger = LoggerSingleton.Instance;
|
const logger = LoggerSingleton.Instance;
|
||||||
// Set up all the paths and query levels.
|
// Set up all the paths and query levels.
|
||||||
const storagePath = config.get(ConfParams.STORAGE_PATH) as string;
|
const storagePath = config.get(ConfParams.STORAGE_PATH) as string;
|
||||||
@@ -262,14 +237,14 @@ export async function doWadoRs({ studyInstanceUid, seriesInstanceUid, sopInstanc
|
|||||||
buffers = await Promise.all(files.map(async (file) => {
|
buffers = await Promise.all(files.map(async (file) => {
|
||||||
const instanceInfo = foundInstances.find((i) => i.instance === file)
|
const instanceInfo = foundInstances.find((i) => i.instance === file)
|
||||||
if (instanceInfo) {
|
if (instanceInfo) {
|
||||||
return addFileToBuffer({ pathname, filename: file, dataFormat, instanceInfo, frame });
|
return addFileToBuffer({ pathname, filename: file, dataFormat, instanceInfo });
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Attach the one file that we need to the return buffer
|
// Attach the one file that we need to the return buffer
|
||||||
const instanceInfo = { study: studyInstanceUid, series: seriesInstanceUid, instance: sopInstanceUid }
|
const instanceInfo = { study: studyInstanceUid, series: seriesInstanceUid, instance: sopInstanceUid }
|
||||||
buffers = [await addFileToBuffer({ pathname: studyPath, filename, dataFormat, instanceInfo, frame: typeof frame === 'number' ? frame : undefined })];
|
buffers = [await addFileToBuffer({ pathname: studyPath, filename, dataFormat, instanceInfo })];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the boundaries and join together all of the file buffers to form
|
// Set up the boundaries and join together all of the file buffers to form
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { doFind } from '../dimse/findData';
|
|||||||
import { QUERY_LEVEL } from '../dimse/querLevel';
|
import { QUERY_LEVEL } from '../dimse/querLevel';
|
||||||
import { doWadoRs } from '../dimse/wadoRs';
|
import { doWadoRs } from '../dimse/wadoRs';
|
||||||
import { doWadoUri } from '../dimse/wadoUri';
|
import { doWadoUri } from '../dimse/wadoUri';
|
||||||
import { parseFrameParam } from '../dimse/frameExtractor';
|
|
||||||
import { statusCodeForError } from '../dimse/errors';
|
|
||||||
import { LoggerSingleton } from '../utils/logger';
|
import { LoggerSingleton } from '../utils/logger';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
@@ -27,10 +25,6 @@ interface IParamsImage extends IParamsSeries {
|
|||||||
sopInstanceUid: string;
|
sopInstanceUid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IParamsFrame extends IParamsImage {
|
|
||||||
frame: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IQueryImage {
|
interface IQueryImage {
|
||||||
studyUID: string;
|
studyUID: string;
|
||||||
seriesUID: string;
|
seriesUID: string;
|
||||||
@@ -46,41 +40,19 @@ module.exports = function (server: FastifyInstance, opts: unknown, done: () => v
|
|||||||
Querystring: QueryParams;
|
Querystring: QueryParams;
|
||||||
}>('/rs/studies', async (req, reply) => {
|
}>('/rs/studies', async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
const { query } = req;
|
var { query } = req;
|
||||||
|
|
||||||
// Tambahkan filter StudyDate hanya ketika tidak ada: Medrec, AccessionNo, PatientName, StudyInstanceUID, dan StudyDate
|
// * Untuk Limit Study List Agar Tidak Query Semua Data DICOM (kasus overload di PANAM)
|
||||||
// Params: &00100020=MRN, AccessionNumber=, PatientName=, StudyDate=
|
// Jika req url tidak ada:
|
||||||
const hasMedicalRecord = query['00100020'] !== undefined; // Patient ID/Medical Record
|
// StudyInstanceUID, StudyDate, dan PatientID(00100020)
|
||||||
const hasAccessionNumber = query.AccessionNumber !== undefined;
|
// maka tambahkan default StudyDate (yyyymmdd) kemarin dan hari ini
|
||||||
const hasPatientName = query.PatientName !== undefined;
|
if (!query.StudyInstanceUID && !query.StudyDate && !query['00100020']) {
|
||||||
const hasStudyInstanceUID = query.StudyInstanceUID !== undefined;
|
const startDate = moment().subtract(1, 'day').format('YYYYMMDD');
|
||||||
const hasStudyDate = query.StudyDate !== undefined;
|
|
||||||
|
|
||||||
if (!hasMedicalRecord && !hasAccessionNumber && !hasPatientName && !hasStudyInstanceUID && !hasStudyDate) {
|
|
||||||
const startDate = moment().subtract(7, 'days').format('YYYYMMDD');
|
|
||||||
const endDate = moment().format('YYYYMMDD');
|
const endDate = moment().format('YYYYMMDD');
|
||||||
query.StudyDate = `${startDate}-${endDate}`;
|
query.StudyDate = `${startDate}-${endDate}`;
|
||||||
|
|
||||||
// Add time range filter (entire day)
|
|
||||||
const startTime = '000000';
|
|
||||||
const endTime = '235959';
|
|
||||||
query['00080030'] = `${startTime}-${endTime}`; // StudyTime (0008,0030)
|
|
||||||
|
|
||||||
logger.info(`Adding default date range filter: ${query.StudyDate}`);
|
|
||||||
logger.info(`Adding default time range filter: ${query['00080030']}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Querying studies with filters: ${JSON.stringify(query)}`);
|
|
||||||
|
|
||||||
const json = deepmerge.all(await doFind(QUERY_LEVEL.STUDY, query), options);
|
const json = deepmerge.all(await doFind(QUERY_LEVEL.STUDY, query), options);
|
||||||
|
|
||||||
// Karena by default hasilnya urut StudyTime (ascending),
|
|
||||||
// Maka, jika butuh latest first (descending), maka dibalik urutannya
|
|
||||||
if (Array.isArray(json) && json.length > 0) {
|
|
||||||
logger.info(`Reversing order of ${json.length} studies to show latest first`);
|
|
||||||
json.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return reply.send(json);
|
return reply.send(json);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
@@ -366,25 +338,17 @@ module.exports = function (server: FastifyInstance, opts: unknown, done: () => v
|
|||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
server.get<{
|
server.get<{
|
||||||
Params: IParamsFrame;
|
Params: IParamsImage;
|
||||||
}>('/rs/studies/:studyInstanceUid/series/:seriesInstanceUid/instances/:sopInstanceUid/frames/:frame', async (req, reply) => {
|
}>('/rs/studies/:studyInstanceUid/series/:seriesInstanceUid/instances/:sopInstanceUid/frames/:frame', async (req, reply) => {
|
||||||
const { studyInstanceUid, seriesInstanceUid, sopInstanceUid, frame: frameParam } = req.params;
|
const { studyInstanceUid, seriesInstanceUid, sopInstanceUid } = req.params;
|
||||||
|
|
||||||
const frame = parseFrameParam(frameParam);
|
|
||||||
if (frame === undefined) {
|
|
||||||
return reply.code(400).send({ error: `frame must be a positive integer, received '${frameParam}'` });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const rsp = await doWadoRs({ studyInstanceUid, seriesInstanceUid, sopInstanceUid, dataFormat: 'pixeldata', frame });
|
const rsp = await doWadoRs({ studyInstanceUid, seriesInstanceUid, sopInstanceUid, dataFormat: 'pixeldata' });
|
||||||
reply.header('Content-Type', rsp.contentType);
|
reply.header('Content-Type', rsp.contentType);
|
||||||
return reply.send(rsp.buffer);
|
return reply.send(rsp.buffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const statusCode = statusCodeForError(error);
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return reply.code(statusCode).send({
|
return reply.send(500);
|
||||||
error: statusCode === 500 ? 'internal error' : (error as Error).message,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -424,25 +388,6 @@ module.exports = function (server: FastifyInstance, opts: unknown, done: () => v
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.get<{
|
|
||||||
Params: IParamsImage;
|
|
||||||
Querystring: QueryParams;
|
|
||||||
}>('/rs/studies/:studyInstanceUid/series/:seriesInstanceUid/instances/:sopInstanceUid/metadata', async (req, reply) => {
|
|
||||||
const { studyInstanceUid, seriesInstanceUid, sopInstanceUid } = req.params;
|
|
||||||
const { query } = req;
|
|
||||||
query.StudyInstanceUID = studyInstanceUid;
|
|
||||||
query.SeriesInstanceUID = seriesInstanceUid;
|
|
||||||
query.SOPInstanceUID = sopInstanceUid;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const rsp = await fetchMeta(query, studyInstanceUid, seriesInstanceUid);
|
|
||||||
return reply.send(rsp);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return reply.send(500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import { test } from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import dicomParser, { Element } from 'dicom-parser';
|
|
||||||
import { convertElementToJson, DicomJsonDataSet, parseDicomToJson } from '../src/dimse/dicomJson';
|
|
||||||
import { buildMonochromeImage, Dataset } from './helpers/dicomWriter';
|
|
||||||
|
|
||||||
const BASE_PIXELS = [0, 1, 2, 3, 4, 5, 6, 7];
|
|
||||||
|
|
||||||
function crLike(extra: Dataset = [], bufferExtra: Partial<Parameters<typeof buildMonochromeImage>[0]> = {}) {
|
|
||||||
return buildMonochromeImage({
|
|
||||||
rows: 2,
|
|
||||||
columns: 4,
|
|
||||||
pixelData: BASE_PIXELS,
|
|
||||||
extra,
|
|
||||||
...bufferExtra,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test('omits Window Center/Width when the source tags are absent', () => {
|
|
||||||
const json = parseDicomToJson(crLike());
|
|
||||||
assert.equal(json['00281050'], undefined);
|
|
||||||
assert.equal(json['00281051'], undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('omits Rescale Intercept/Slope when the source tags are absent', () => {
|
|
||||||
const json = parseDicomToJson(crLike());
|
|
||||||
assert.equal(json['00281052'], undefined);
|
|
||||||
assert.equal(json['00281053'], undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('preserves multi-valued Window Center/Width in source order', () => {
|
|
||||||
const json = parseDicomToJson(crLike([
|
|
||||||
{ tag: '00281050', vr: 'DS', value: '40\\400' },
|
|
||||||
{ tag: '00281051', vr: 'DS', value: '80\\800' },
|
|
||||||
]));
|
|
||||||
|
|
||||||
assert.deepEqual(json['00281050'].Value, [40, 400]);
|
|
||||||
assert.deepEqual(json['00281051'].Value, [80, 800]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('preserves rescale values when present', () => {
|
|
||||||
const json = parseDicomToJson(crLike([
|
|
||||||
{ tag: '00281052', vr: 'DS', value: '-1024' },
|
|
||||||
{ tag: '00281053', vr: 'DS', value: '2' },
|
|
||||||
]));
|
|
||||||
|
|
||||||
assert.deepEqual(json['00281052'].Value, [-1024]);
|
|
||||||
assert.deepEqual(json['00281053'].Value, [2]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('preserves VOI, LUT, presentation LUT and pixel padding metadata', () => {
|
|
||||||
const json = parseDicomToJson(crLike([
|
|
||||||
{ tag: '00281055', vr: 'LO', value: 'WINDOW' },
|
|
||||||
{ tag: '00281056', vr: 'CS', value: 'LINEAR' },
|
|
||||||
{ tag: '20500020', vr: 'CS', value: 'IDENTITY' },
|
|
||||||
{ tag: '00280120', vr: 'US', value: 5 },
|
|
||||||
{ tag: '00280121', vr: 'US', value: 10 },
|
|
||||||
{
|
|
||||||
tag: '00283010',
|
|
||||||
vr: 'SQ',
|
|
||||||
items: [
|
|
||||||
[
|
|
||||||
{ tag: '00283002', vr: 'US', value: [256, 0, 8] },
|
|
||||||
{ tag: '00283006', vr: 'US', value: [1, 2, 3] },
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]));
|
|
||||||
|
|
||||||
assert.deepEqual(json['00281055'].Value, ['WINDOW']);
|
|
||||||
assert.deepEqual(json['00281056'].Value, ['LINEAR']);
|
|
||||||
assert.deepEqual(json['20500020'].Value, ['IDENTITY']);
|
|
||||||
assert.deepEqual(json['00280120'].Value, [5]);
|
|
||||||
assert.deepEqual(json['00280121'].Value, [10]);
|
|
||||||
|
|
||||||
const sequence = json['00283010'];
|
|
||||||
assert.equal(sequence.vr, 'SQ');
|
|
||||||
const item = (sequence.Value as DicomJsonDataSet[])[0];
|
|
||||||
assert.deepEqual(item['00283002'].Value, [256, 0, 8]);
|
|
||||||
assert.deepEqual(item['00283006'].Value, [1, 2, 3]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('never emits Value: [undefined] for absent optional tags', () => {
|
|
||||||
const json = parseDicomToJson(crLike());
|
|
||||||
for (const [tag, element] of Object.entries(json)) {
|
|
||||||
if (Array.isArray(element.Value)) {
|
|
||||||
for (const value of element.Value) {
|
|
||||||
assert.notEqual(value, undefined, `${tag} contains an undefined value`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('excludes Pixel Data from metadata output', () => {
|
|
||||||
const json = parseDicomToJson(crLike());
|
|
||||||
assert.equal(json['7FE00010'], undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('falls back to the data dictionary when VR is not explicit', () => {
|
|
||||||
const buffer = crLike([
|
|
||||||
{ tag: '00281050', vr: 'DS', value: '40' },
|
|
||||||
]);
|
|
||||||
const dataSet = dicomParser.parseDicom(buffer);
|
|
||||||
const explicit = dataSet.elements.x00281050;
|
|
||||||
const implicitLike: Element = {
|
|
||||||
tag: explicit.tag,
|
|
||||||
length: explicit.length,
|
|
||||||
dataOffset: explicit.dataOffset,
|
|
||||||
};
|
|
||||||
|
|
||||||
const json = convertElementToJson(dataSet, implicitLike);
|
|
||||||
assert.equal(json.vr, 'DS');
|
|
||||||
assert.deepEqual(json.Value, [40]);
|
|
||||||
});
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import { test } from 'node:test';
|
|
||||||
import assert from 'node:assert/strict';
|
|
||||||
import { extractFrameFromBuffer, parseFrameParam } from '../src/dimse/frameExtractor';
|
|
||||||
import { BadRequestError, HttpError, statusCodeForError, UnsupportedMediaTypeError } from '../src/dimse/errors';
|
|
||||||
import { buildMonochromeImage } from './helpers/dicomWriter';
|
|
||||||
|
|
||||||
function twoFrameImage() {
|
|
||||||
return buildMonochromeImage({
|
|
||||||
rows: 2,
|
|
||||||
columns: 2,
|
|
||||||
bitsAllocated: 8,
|
|
||||||
samplesPerPixel: 1,
|
|
||||||
numberOfFrames: 2,
|
|
||||||
pixelData: [1, 2, 3, 4, 5, 6, 7, 8],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
test('/frames/1 returns only the first frame byte range', () => {
|
|
||||||
const extracted = extractFrameFromBuffer(twoFrameImage(), 1);
|
|
||||||
assert.deepEqual([...extracted.buffer], [1, 2, 3, 4]);
|
|
||||||
assert.equal(extracted.frameSize, 4);
|
|
||||||
assert.equal(extracted.numberOfFrames, 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('/frames/2 returns only the second frame byte range', () => {
|
|
||||||
const extracted = extractFrameFromBuffer(twoFrameImage(), 2);
|
|
||||||
assert.deepEqual([...extracted.buffer], [5, 6, 7, 8]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('/frames/2 for a single-frame instance is rejected with 400', () => {
|
|
||||||
const singleFrame = buildMonochromeImage({
|
|
||||||
rows: 2,
|
|
||||||
columns: 2,
|
|
||||||
bitsAllocated: 8,
|
|
||||||
pixelData: [1, 2, 3, 4],
|
|
||||||
});
|
|
||||||
assert.throws(
|
|
||||||
() => extractFrameFromBuffer(singleFrame, 2),
|
|
||||||
(error: unknown) => error instanceof BadRequestError && statusCodeForError(error) === 400,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('extracts 16-bit frames with the correct byte offsets', () => {
|
|
||||||
const image = buildMonochromeImage({
|
|
||||||
rows: 2,
|
|
||||||
columns: 2,
|
|
||||||
bitsAllocated: 16,
|
|
||||||
samplesPerPixel: 1,
|
|
||||||
numberOfFrames: 2,
|
|
||||||
pixelData: [256, 257, 258, 259, 260, 261, 262, 263],
|
|
||||||
});
|
|
||||||
const second = extractFrameFromBuffer(image, 2);
|
|
||||||
const values = [0, 2, 4, 6].map((offset) => second.buffer.readUInt16LE(offset));
|
|
||||||
assert.deepEqual(values, [260, 261, 262, 263]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rejects out-of-range and zero frame numbers with 400', () => {
|
|
||||||
const image = twoFrameImage();
|
|
||||||
assert.throws(
|
|
||||||
() => extractFrameFromBuffer(image, 0),
|
|
||||||
(error: unknown) => error instanceof BadRequestError,
|
|
||||||
);
|
|
||||||
assert.throws(
|
|
||||||
() => extractFrameFromBuffer(image, 3),
|
|
||||||
(error: unknown) => error instanceof BadRequestError,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('rejects compressed transfer syntax with an explicit unsupported response', () => {
|
|
||||||
const image = buildMonochromeImage({
|
|
||||||
rows: 2,
|
|
||||||
columns: 2,
|
|
||||||
bitsAllocated: 8,
|
|
||||||
numberOfFrames: 2,
|
|
||||||
pixelData: [1, 2, 3, 4, 5, 6, 7, 8],
|
|
||||||
transferSyntaxUID: '1.2.840.10008.1.2.4.80',
|
|
||||||
});
|
|
||||||
assert.throws(
|
|
||||||
() => extractFrameFromBuffer(image, 2),
|
|
||||||
(error: unknown) => error instanceof UnsupportedMediaTypeError && statusCodeForError(error) === 415,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('treats a too-short Pixel Data element as a 500 data error', () => {
|
|
||||||
const image = buildMonochromeImage({
|
|
||||||
rows: 4,
|
|
||||||
columns: 4,
|
|
||||||
bitsAllocated: 8,
|
|
||||||
pixelData: [1, 2, 3, 4],
|
|
||||||
});
|
|
||||||
assert.throws(
|
|
||||||
() => extractFrameFromBuffer(image, 1),
|
|
||||||
(error: unknown) => error instanceof HttpError && statusCodeForError(error) === 500,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parseFrameParam accepts only positive base-10 integers', () => {
|
|
||||||
assert.equal(parseFrameParam('1'), 1);
|
|
||||||
assert.equal(parseFrameParam('42'), 42);
|
|
||||||
assert.equal(parseFrameParam('0'), undefined);
|
|
||||||
assert.equal(parseFrameParam('-1'), undefined);
|
|
||||||
assert.equal(parseFrameParam('abc'), undefined);
|
|
||||||
assert.equal(parseFrameParam('1.5'), undefined);
|
|
||||||
assert.equal(parseFrameParam(' 1'), undefined);
|
|
||||||
assert.equal(parseFrameParam(''), undefined);
|
|
||||||
assert.equal(parseFrameParam(undefined), undefined);
|
|
||||||
});
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
/**
|
|
||||||
* Minimal, dependency-free DICOM Part 10 writer used to build synthetic,
|
|
||||||
* non-PHI fixtures for unit tests. Only the explicit VR little endian
|
|
||||||
* transfer syntax is produced, which is enough to exercise the metadata
|
|
||||||
* converter and native frame extraction.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface VrElement {
|
|
||||||
/** Tag in 'ggggeeee' hex form, e.g. '00281050'. */
|
|
||||||
tag: string;
|
|
||||||
vr: string;
|
|
||||||
value?: string | number | number[] | Buffer;
|
|
||||||
/** Sequence items, only used when vr === 'SQ'. Each item is itself a dataset. */
|
|
||||||
items?: Dataset[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Dataset = VrElement[];
|
|
||||||
|
|
||||||
const LONG_VRS = new Set(['OB', 'OD', 'OF', 'OL', 'OV', 'OW', 'SQ', 'SV', 'UC', 'UN', 'UR', 'UT', 'UV']);
|
|
||||||
|
|
||||||
const NUMERIC_VRS: Record<string, { width: number; write: (buf: Buffer, value: number, offset: number) => void }> = {
|
|
||||||
US: { width: 2, write: (b, v, o) => b.writeUInt16LE(v, o) },
|
|
||||||
SS: { width: 2, write: (b, v, o) => b.writeInt16LE(v, o) },
|
|
||||||
UL: { width: 4, write: (b, v, o) => b.writeUInt32LE(v, o) },
|
|
||||||
SL: { width: 4, write: (b, v, o) => b.writeInt32LE(v, o) },
|
|
||||||
FL: { width: 4, write: (b, v, o) => b.writeFloatLE(v, o) },
|
|
||||||
FD: { width: 8, write: (b, v, o) => b.writeDoubleLE(v, o) },
|
|
||||||
};
|
|
||||||
|
|
||||||
function paddingByte(vr: string): number {
|
|
||||||
return vr === 'UI' ? 0x00 : 0x20;
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeValue(element: VrElement): Buffer {
|
|
||||||
const { vr, value } = element;
|
|
||||||
|
|
||||||
if (vr === 'SQ') {
|
|
||||||
const items = Array.isArray(element.items) ? element.items : [];
|
|
||||||
return Buffer.concat(items.map(encodeItem));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === undefined) {
|
|
||||||
return Buffer.alloc(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NUMERIC_VRS[vr]) {
|
|
||||||
const numbers = Array.isArray(value) ? value : [value as number];
|
|
||||||
const { width, write } = NUMERIC_VRS[vr];
|
|
||||||
const buffer = Buffer.alloc(numbers.length * width);
|
|
||||||
numbers.forEach((number, index) => write(buffer, number, index * width));
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = Array.isArray(value) ? value.join('\\') : String(value);
|
|
||||||
return Buffer.from(text, 'ascii');
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeItem(dataset: Dataset): Buffer {
|
|
||||||
const body = encodeDataset(dataset);
|
|
||||||
const header = Buffer.alloc(8);
|
|
||||||
header.writeUInt16LE(0xfffe, 0);
|
|
||||||
header.writeUInt16LE(0xe000, 2);
|
|
||||||
header.writeUInt32LE(body.length, 4);
|
|
||||||
return Buffer.concat([header, body]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function encodeElement(element: VrElement): Buffer {
|
|
||||||
const group = Number.parseInt(element.tag.substring(0, 4), 16);
|
|
||||||
const elementNumber = Number.parseInt(element.tag.substring(4, 8), 16);
|
|
||||||
|
|
||||||
let value = encodeValue(element);
|
|
||||||
if (value.length % 2 !== 0) {
|
|
||||||
value = Buffer.concat([value, Buffer.from([paddingByte(element.vr)])]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLong = LONG_VRS.has(element.vr);
|
|
||||||
const header = Buffer.alloc(isLong ? 12 : 8);
|
|
||||||
header.writeUInt16LE(group, 0);
|
|
||||||
header.writeUInt16LE(elementNumber, 2);
|
|
||||||
header.write(element.vr, 4, 2, 'ascii');
|
|
||||||
if (isLong) {
|
|
||||||
header.writeUInt32LE(value.length, 8);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
header.writeUInt16LE(value.length, 6);
|
|
||||||
}
|
|
||||||
return Buffer.concat([header, value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function encodeDataset(dataset: Dataset): Buffer {
|
|
||||||
return Buffer.concat(dataset.map(encodeElement));
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Part10Options {
|
|
||||||
transferSyntaxUID?: string;
|
|
||||||
sopClassUID?: string;
|
|
||||||
sopInstanceUID?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildPart10(dataset: Dataset, options: Part10Options = {}): Buffer {
|
|
||||||
const transferSyntaxUID = options.transferSyntaxUID ?? '1.2.840.10008.1.2.1';
|
|
||||||
const sopClassUID = options.sopClassUID ?? '1.2.840.10008.5.1.4.1.1.7';
|
|
||||||
const sopInstanceUID = options.sopInstanceUID ?? '1.2.826.0.1.3680043.8.498.1';
|
|
||||||
|
|
||||||
const metaElements: Dataset = [
|
|
||||||
{ tag: '00020001', vr: 'OB', value: Buffer.from([0x00, 0x01]) },
|
|
||||||
{ tag: '00020002', vr: 'UI', value: sopClassUID },
|
|
||||||
{ tag: '00020003', vr: 'UI', value: sopInstanceUID },
|
|
||||||
{ tag: '00020010', vr: 'UI', value: transferSyntaxUID },
|
|
||||||
{ tag: '00020012', vr: 'UI', value: '1.2.826.0.1.3680043.8.498.1' },
|
|
||||||
];
|
|
||||||
const metaBody = encodeDataset(metaElements);
|
|
||||||
const groupLength = encodeElement({ tag: '00020000', vr: 'UL', value: metaBody.length });
|
|
||||||
|
|
||||||
const preamble = Buffer.alloc(128);
|
|
||||||
const magic = Buffer.from('DICM', 'ascii');
|
|
||||||
const datasetBytes = encodeDataset(dataset);
|
|
||||||
|
|
||||||
return Buffer.concat([preamble, magic, groupLength, metaBody, datasetBytes]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Build a synthetic single or multi frame MONOCHROME2 image. */
|
|
||||||
export function buildMonochromeImage(options: {
|
|
||||||
rows: number;
|
|
||||||
columns: number;
|
|
||||||
bitsAllocated?: number;
|
|
||||||
samplesPerPixel?: number;
|
|
||||||
numberOfFrames?: number;
|
|
||||||
pixelData: number[];
|
|
||||||
extra?: Dataset;
|
|
||||||
transferSyntaxUID?: string;
|
|
||||||
photometricInterpretation?: string;
|
|
||||||
}): Buffer {
|
|
||||||
const bitsAllocated = options.bitsAllocated ?? 8;
|
|
||||||
const samplesPerPixel = options.samplesPerPixel ?? 1;
|
|
||||||
const dataset: Dataset = [
|
|
||||||
{ tag: '00080016', vr: 'UI', value: '1.2.840.10008.5.1.4.1.1.7' },
|
|
||||||
{ tag: '00080018', vr: 'UI', value: '1.2.826.0.1.3680043.8.498.1' },
|
|
||||||
{ tag: '00080060', vr: 'CS', value: 'CR' },
|
|
||||||
{ tag: '0020000D', vr: 'UI', value: '1.2.826.0.1.3680043.8.498.100' },
|
|
||||||
{ tag: '0020000E', vr: 'UI', value: '1.2.826.0.1.3680043.8.498.200' },
|
|
||||||
{ tag: '00280002', vr: 'US', value: samplesPerPixel },
|
|
||||||
{ tag: '00280004', vr: 'CS', value: options.photometricInterpretation ?? 'MONOCHROME2' },
|
|
||||||
{ tag: '00280010', vr: 'US', value: options.rows },
|
|
||||||
{ tag: '00280011', vr: 'US', value: options.columns },
|
|
||||||
{ tag: '00280100', vr: 'US', value: bitsAllocated },
|
|
||||||
{ tag: '00280101', vr: 'US', value: bitsAllocated },
|
|
||||||
{ tag: '00280102', vr: 'US', value: bitsAllocated - 1 },
|
|
||||||
{ tag: '00280103', vr: 'US', value: 0 },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (options.numberOfFrames !== undefined) {
|
|
||||||
dataset.push({ tag: '00280008', vr: 'IS', value: String(options.numberOfFrames) });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.extra) {
|
|
||||||
dataset.push(...options.extra);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pixelData = bitsAllocated <= 8
|
|
||||||
? Buffer.from(options.pixelData)
|
|
||||||
: (() => {
|
|
||||||
const buffer = Buffer.alloc(options.pixelData.length * 2);
|
|
||||||
options.pixelData.forEach((value, index) => buffer.writeUInt16LE(value, index * 2));
|
|
||||||
return buffer;
|
|
||||||
})();
|
|
||||||
dataset.push({ tag: '7FE00010', vr: bitsAllocated <= 8 ? 'OB' : 'OW', value: pixelData });
|
|
||||||
|
|
||||||
return buildPart10(dataset, { transferSyntaxUID: options.transferSyntaxUID });
|
|
||||||
}
|
|
||||||
@@ -69,7 +69,6 @@
|
|||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||||
"skipLibCheck": true, /* Skip type checking of declaration files from dependencies. */
|
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (C) 1994-2014, OFFIS e.V.
|
|
||||||
# All rights reserved. See COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# This software and supporting documentation were developed by
|
|
||||||
#
|
|
||||||
# OFFIS e.V.
|
|
||||||
# R&D Division Health
|
|
||||||
# Escherweg 2
|
|
||||||
# D-26121 Oldenburg, Germany
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Module: dcmdata
|
|
||||||
#
|
|
||||||
# Author: Andrew Hewett, Marco Eichelberg, Joerg Riesmeier
|
|
||||||
#
|
|
||||||
# Purpose: ACR/NEMA data dictionary for the DICOM toolkit DCMTK
|
|
||||||
#
|
|
||||||
# This file contains the complete data dictionary from the DICOM predecessor
|
|
||||||
# ACR/NEMA. Please note that the file "dicom.dic" contains more or less the
|
|
||||||
# same tag definitions since they are now officially part of the DICOM standard
|
|
||||||
# (introduced with CP-607), so there is usually no need to use this file. It
|
|
||||||
# is mainly provided for backward compatibility with previous versions of the
|
|
||||||
# DCMTK.
|
|
||||||
#
|
|
||||||
# Each line represents an entry in the data dictionary. Each line has 5 fields
|
|
||||||
# (Tag, VR, Name, VM, Version). Entries need not be in ascending tag order.
|
|
||||||
#
|
|
||||||
# Entries may override existing entries.
|
|
||||||
#
|
|
||||||
# Each field must be separated by a single tab. The tag values (gggg,eeee)
|
|
||||||
# must be in hexadecimal and must be surrounded by parentheses. Repeating
|
|
||||||
# groups are represented by indicating the range (gggg-gggg,eeee). By default
|
|
||||||
# the repeating notation only represents even numbers. A range where only
|
|
||||||
# odd numbers are valid is represented using the notation (gggg-o-gggg,eeee).
|
|
||||||
# A range can represent both even and odd numbers using the notation
|
|
||||||
# (gggg-u-gggg,eeee). The element part of the tag can also be a range.
|
|
||||||
#
|
|
||||||
# Comments have a '#' at the beginning of the line.
|
|
||||||
#
|
|
||||||
# Tag VR Name VM Version
|
|
||||||
#
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Retired data elements from ACR/NEMA 2 (1988)
|
|
||||||
#
|
|
||||||
(0000,0001) UL ACR_NEMA_CommandGroupLengthToEnd 1 ACR/NEMA2
|
|
||||||
(0000,0010) CS ACR_NEMA_CommandRecognitionCode 1 ACR/NEMA2
|
|
||||||
(0000,0200) LO ACR_NEMA_Initiator 1 ACR/NEMA2
|
|
||||||
(0000,0300) LO ACR_NEMA_Receiver 1 ACR/NEMA2
|
|
||||||
(0000,0400) LO ACR_NEMA_FindLocation 1 ACR/NEMA2
|
|
||||||
(0000,0850) US ACR_NEMA_NumberOfMatches 1 ACR/NEMA2
|
|
||||||
(0000,0860) US ACR_NEMA_ResponseSequenceNumber 1 ACR/NEMA2
|
|
||||||
(0000,4000) LO ACR_NEMA_DialogReceiver 1 ACR/NEMA2
|
|
||||||
(0000,4010) LO ACR_NEMA_TerminalType 1 ACR/NEMA2
|
|
||||||
(0000,5010) LO ACR_NEMA_MessageSetID 1 ACR/NEMA2
|
|
||||||
(0000,5020) LO ACR_NEMA_EndMessageSet 1 ACR/NEMA2
|
|
||||||
(0000,5110) LO ACR_NEMA_DisplayFormat 1 ACR/NEMA2
|
|
||||||
(0000,5120) LO ACR_NEMA_PagePositionID 1 ACR/NEMA2
|
|
||||||
(0000,5130) LO ACR_NEMA_TextFormatID 1 ACR/NEMA2
|
|
||||||
(0000,5140) CS ACR_NEMA_NormalReverse 1 ACR/NEMA2
|
|
||||||
(0000,5150) CS ACR_NEMA_AddGrayScale 1 ACR/NEMA2
|
|
||||||
(0000,5160) CS ACR_NEMA_Borders 1 ACR/NEMA2
|
|
||||||
(0000,5170) IS ACR_NEMA_Copies 1 ACR/NEMA2
|
|
||||||
(0000,5180) LO ACR_NEMA_MagnificationType 1 ACR/NEMA2
|
|
||||||
(0000,5190) LO ACR_NEMA_Erase 1-n ACR/NEMA2
|
|
||||||
(0000,51A0) CS ACR_NEMA_Print 1 ACR/NEMA2
|
|
||||||
(0000,51B0) US ACR_NEMA_Overlays 1-n ACR/NEMA2
|
|
||||||
(0008,0001) UL ACR_NEMA_IdentifyingGroupLengthToEnd 1 ACR/NEMA2
|
|
||||||
(0008,0010) LO ACR_NEMA_RecognitionCode 1 ACR/NEMA2
|
|
||||||
(0008,0040) US ACR_NEMA_OldDataSetType 1 ACR/NEMA2
|
|
||||||
(0008,0041) LO ACR_NEMA_DataSetSubtype 1 ACR/NEMA2
|
|
||||||
(0008,1000) LO ACR_NEMA_NetworkID 1 ACR/NEMA2
|
|
||||||
(0008,4000) LT ACR_NEMA_IdentifyingComments 1-n ACR/NEMA2
|
|
||||||
(0010,1050) LT ACR_NEMA_InsurancePlanIdentification 1-n ACR/NEMA2
|
|
||||||
(0018,1240) IS ACR_NEMA_UpperLowerPixelValues 1-n ACR/NEMA2
|
|
||||||
(0018,4000) LT ACR_NEMA_AcquisitionComments 1-n ACR/NEMA2
|
|
||||||
(0018,5030) DS ACR_NEMA_DynamicRange 1 ACR/NEMA2
|
|
||||||
(0018,5040) DS ACR_NEMA_TotalGain 1 ACR/NEMA2
|
|
||||||
(0020,0030) DS ACR_NEMA_ImagePosition 3 ACR/NEMA2
|
|
||||||
(0020,0035) DS ACR_NEMA_ImageOrientation 6 ACR/NEMA2
|
|
||||||
(0020,0050) DS ACR_NEMA_Location 1 ACR/NEMA2
|
|
||||||
(0020,0070) LO ACR_NEMA_ImageGeometryType 1 ACR/NEMA2
|
|
||||||
(0020,0080) LO ACR_NEMA_MaskingImage 1-n ACR/NEMA2
|
|
||||||
(0020,1001) IS ACR_NEMA_AcquisitionsInSeries 1 ACR/NEMA2
|
|
||||||
(0020,1003) IS ACR_NEMA_ImagesInSeries 1 ACR/NEMA2
|
|
||||||
(0020,1005) IS ACR_NEMA_ImagesInStudy 1 ACR/NEMA2
|
|
||||||
(0020,1020) LO ACR_NEMA_Reference 1-n ACR/NEMA2
|
|
||||||
(0020,3100-31FF) LO ACR_NEMA_SourceImageID 1-n ACR/NEMA2
|
|
||||||
(0020,3401) LO ACR_NEMA_ModifyingDeviceID 1 ACR/NEMA2
|
|
||||||
(0020,3402) LO ACR_NEMA_ModifiedImageID 1 ACR/NEMA2
|
|
||||||
(0020,3403) DA ACR_NEMA_ModifiedImageDate 1 ACR/NEMA2
|
|
||||||
(0020,3404) LO ACR_NEMA_ModifyingDeviceManufacturer 1 ACR/NEMA2
|
|
||||||
(0020,3405) TM ACR_NEMA_ModifiedImageTime 1 ACR/NEMA2
|
|
||||||
(0020,3406) LO ACR_NEMA_ModifiedImageDescription 1 ACR/NEMA2
|
|
||||||
(0020,5000) AT ACR_NEMA_OriginalImageIdentification 1-n ACR/NEMA2
|
|
||||||
(0020,5002) LO ACR_NEMA_OriginalImageIdentificationNomenclature 1-n ACR/NEMA2
|
|
||||||
(0028,0005) US ACR_NEMA_ImageDimensions 1 ACR/NEMA2
|
|
||||||
(0028,0040) CS ACR_NEMA_ImageFormat 1 ACR/NEMA2
|
|
||||||
(0028,0050) LO ACR_NEMA_ManipulatedImage 1-n ACR/NEMA2
|
|
||||||
(0028,0060) CS ACR_NEMA_CompressionCode 1 ACR/NEMA2
|
|
||||||
(0028,0104) xs ACR_NEMA_SmallestValidPixelValue 1 ACR/NEMA2
|
|
||||||
(0028,0105) xs ACR_NEMA_LargestValidPixelValue 1 ACR/NEMA2
|
|
||||||
(0028,0200) US ACR_NEMA_ImageLocation 1 ACR/NEMA2
|
|
||||||
(0028,1080) CS ACR_NEMA_GrayScale 1 ACR/NEMA2
|
|
||||||
(0028,1100) xs ACR_NEMA_GrayLookupTableDescriptor 3 ACR/NEMA2
|
|
||||||
(0028,1200) xs ACR_NEMA_GrayLookupTableData 1-n ACR/NEMA2
|
|
||||||
(0028,4000) LT ACR_NEMA_ImagePresentationComments 1-n ACR/NEMA2
|
|
||||||
(4000,0000) UL ACR_NEMA_TextGroupLength 1 ACR/NEMA2
|
|
||||||
(4000,0010) LT ACR_NEMA_TextArbitrary 1-n ACR/NEMA2
|
|
||||||
(4000,4000) LT ACR_NEMA_TextComments 1-n ACR/NEMA2
|
|
||||||
(6000-60FF,0110) CS ACR_NEMA_OverlayFormat 1 ACR/NEMA2
|
|
||||||
(6000-60FF,0200) US ACR_NEMA_OverlayLocation 1 ACR/NEMA2
|
|
||||||
(6000-60FF,4000) LT ACR_NEMA_OverlayComments 1-n ACR/NEMA2
|
|
||||||
#
|
|
||||||
#---------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Retired data elements from the ACR/NEMA 2 compression enhancements
|
|
||||||
#
|
|
||||||
(0028,005F) CS ACR_NEMA_2C_CompressionRecognitionCode 1 ACR/NEMA2C
|
|
||||||
(0028,0061) SH ACR_NEMA_2C_CompressionOriginator 1 ACR/NEMA2C
|
|
||||||
(0028,0062) SH ACR_NEMA_2C_CompressionLabel 1 ACR/NEMA2C
|
|
||||||
(0028,0063) SH ACR_NEMA_2C_CompressionDescription 1 ACR/NEMA2C
|
|
||||||
(0028,0065) CS ACR_NEMA_2C_CompressionSequence 1-n ACR/NEMA2C
|
|
||||||
(0028,0066) AT ACR_NEMA_2C_CompressionStepPointers 1-n ACR/NEMA2C
|
|
||||||
(0028,0068) US ACR_NEMA_2C_RepeatInterval 1 ACR/NEMA2C
|
|
||||||
(0028,0069) US ACR_NEMA_2C_BitsGrouped 1 ACR/NEMA2C
|
|
||||||
(0028,0070) US ACR_NEMA_2C_PerimeterTable 1-n ACR/NEMA2C
|
|
||||||
(0028,0071) xs ACR_NEMA_2C_PerimeterValue 1 ACR/NEMA2C
|
|
||||||
(0028,0080) US ACR_NEMA_2C_PredictorRows 1 ACR/NEMA2C
|
|
||||||
(0028,0081) US ACR_NEMA_2C_PredictorColumns 1 ACR/NEMA2C
|
|
||||||
(0028,0082) US ACR_NEMA_2C_PredictorConstants 1-n ACR/NEMA2C
|
|
||||||
(0028,0090) CS ACR_NEMA_2C_BlockedPixels 1 ACR/NEMA2C
|
|
||||||
(0028,0091) US ACR_NEMA_2C_BlockRows 1 ACR/NEMA2C
|
|
||||||
(0028,0092) US ACR_NEMA_2C_BlockColumns 1 ACR/NEMA2C
|
|
||||||
(0028,0093) US ACR_NEMA_2C_RowOverlap 1 ACR/NEMA2C
|
|
||||||
(0028,0094) US ACR_NEMA_2C_ColumnOverlap 1 ACR/NEMA2C
|
|
||||||
(0028,0400) CS ACR_NEMA_2C_TransformLabel 1 ACR/NEMA2C
|
|
||||||
(0028,0401) CS ACR_NEMA_2C_TransformVersionNumber 1 ACR/NEMA2C
|
|
||||||
(0028,0402) US ACR_NEMA_2C_NumberOfTransformSteps 1 ACR/NEMA2C
|
|
||||||
(0028,0403) CS ACR_NEMA_2C_SequenceOfCompressedData 1-n ACR/NEMA2C
|
|
||||||
(0028,0404) AT ACR_NEMA_2C_DetailsOfCoefficients 1-n ACR/NEMA2C
|
|
||||||
# according to the DICOM standard, the following 4 attributes
|
|
||||||
# should have a tag in the range of (0028,04x1) to (0028,04x3)
|
|
||||||
(0028,0410) US ACR_NEMA_2C_RowsForNthOrderCoefficients 1 ACR/NEMA2C
|
|
||||||
(0028,0411) US ACR_NEMA_2C_ColumnsForNthOrderCoefficients 1 ACR/NEMA2C
|
|
||||||
(0028,0412) CS ACR_NEMA_2C_CoefficientCoding 1-n ACR/NEMA2C
|
|
||||||
(0028,0413) AT ACR_NEMA_2C_CoefficientCodingPointers 1-n ACR/NEMA2C
|
|
||||||
(0028,0700) CS ACR_NEMA_2C_DCTLabel 1 ACR/NEMA2C
|
|
||||||
(0028,0701) CS ACR_NEMA_2C_DataBlockDescription 1-n ACR/NEMA2C
|
|
||||||
(0028,0702) AT ACR_NEMA_2C_DataBlock 1-n ACR/NEMA2C
|
|
||||||
(0028,0710) US ACR_NEMA_2C_NormalizationFactorFormat 1 ACR/NEMA2C
|
|
||||||
(0028,0720) US ACR_NEMA_2C_ZonalMapNumberFormat 1 ACR/NEMA2C
|
|
||||||
(0028,0721) AT ACR_NEMA_2C_ZonalMapLocation 1-n ACR/NEMA2C
|
|
||||||
(0028,0722) US ACR_NEMA_2C_ZonalMapFormat 1 ACR/NEMA2C
|
|
||||||
(0028,0730) US ACR_NEMA_2C_AdaptiveMapFormat 1 ACR/NEMA2C
|
|
||||||
(0028,0740) US ACR_NEMA_2C_CodeNumberFormat 1 ACR/NEMA2C
|
|
||||||
# according to the DICOM standard, the following 5 attributes
|
|
||||||
# should have a tag in the range of (0028,08x0) to (0028,08x8)
|
|
||||||
(0028,0800) CS ACR_NEMA_2C_CodeLabel 1-n ACR/NEMA2C
|
|
||||||
(0028,0802) US ACR_NEMA_2C_NumberOfTables 1 ACR/NEMA2C
|
|
||||||
(0028,0803) AT ACR_NEMA_2C_CodeTableLocation 1-n ACR/NEMA2C
|
|
||||||
(0028,0804) US ACR_NEMA_2C_BitsForCodeWord 1 ACR/NEMA2C
|
|
||||||
(0028,0808) AT ACR_NEMA_2C_ImageDataLocation 1-n ACR/NEMA2C
|
|
||||||
(1000,0000) UL ACR_NEMA_2C_CodeTableGroupLength 1 ACR/NEMA2C
|
|
||||||
# according to the DICOM standard, the following 6 attributes
|
|
||||||
# should have a tag in the range of (1000,xxx0) to (1000,xxx5)
|
|
||||||
(1000,0010) US ACR_NEMA_2C_EscapeTriplet 3 ACR/NEMA2C
|
|
||||||
(1000,0011) US ACR_NEMA_2C_RunLengthTriplet 3 ACR/NEMA2C
|
|
||||||
(1000,0012) US ACR_NEMA_2C_HuffmanTableSize 1 ACR/NEMA2C
|
|
||||||
(1000,0013) US ACR_NEMA_2C_HuffmanTableTriplet 3 ACR/NEMA2C
|
|
||||||
(1000,0014) US ACR_NEMA_2C_ShiftTableSize 1 ACR/NEMA2C
|
|
||||||
(1000,0015) US ACR_NEMA_2C_ShiftTableTriplet 3 ACR/NEMA2C
|
|
||||||
(1010,0000) UL ACR_NEMA_2C_ZonalMapGroupLength 1 ACR/NEMA2C
|
|
||||||
# according to the DICOM standard, the following attribute
|
|
||||||
# should have the tag (1010,xxxx) where "x" is "0" to "F"
|
|
||||||
(1010,0004) US ACR_NEMA_2C_ZonalMap 1-n ACR/NEMA2C
|
|
||||||
(6000-60FF,0060) CS ACR_NEMA_2C_OverlayCompressionCode 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0061) SH ACR_NEMA_2C_OverlayCompressionOriginator 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0062) SH ACR_NEMA_2C_OverlayCompressionLabel 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0063) SH ACR_NEMA_2C_OverlayCompressionDescription 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0066) AT ACR_NEMA_2C_OverlayCompressionStepPointers 1-n ACR/NEMA2C
|
|
||||||
(6000-60FF,0068) US ACR_NEMA_2C_OverlayRepeatInterval 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0069) US ACR_NEMA_2C_OverlayBitsGrouped 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0800) CS ACR_NEMA_2C_OverlayCodeLabel 1-n ACR/NEMA2C
|
|
||||||
(6000-60FF,0802) US ACR_NEMA_2C_OverlayNumberOfTables 1 ACR/NEMA2C
|
|
||||||
(6000-60FF,0803) AT ACR_NEMA_2C_OverlayCodeTableLocation 1-n ACR/NEMA2C
|
|
||||||
(6000-60FF,0804) US ACR_NEMA_2C_OverlayBitsForCodeWord 1 ACR/NEMA2C
|
|
||||||
(7F00-7FFF,0000) UL ACR_NEMA_2C_VariablePixelDataGroupLength 1 ACR/NEMA2C
|
|
||||||
(7F00-7FFF,0010) ox ACR_NEMA_2C_VariablePixelData 1 ACR/NEMA2C
|
|
||||||
(7F00-7FFF,0011) AT ACR_NEMA_2C_VariableNextDataGroup 1 ACR/NEMA2C
|
|
||||||
(7F00-7FFF,0020) OW ACR_NEMA_2C_VariableCoefficientsSDVN 1-n ACR/NEMA2C
|
|
||||||
(7F00-7FFF,0030) OW ACR_NEMA_2C_VariableCoefficientsSDHN 1-n ACR/NEMA2C
|
|
||||||
(7F00-7FFF,0040) OW ACR_NEMA_2C_VariableCoefficientsSDDN 1-n ACR/NEMA2C
|
|
||||||
(7FE0,0020) OW ACR_NEMA_2C_CoefficientsSDVN 1-n ACR/NEMA2C
|
|
||||||
(7FE0,0030) OW ACR_NEMA_2C_CoefficientsSDHN 1-n ACR/NEMA2C
|
|
||||||
(7FE0,0040) OW ACR_NEMA_2C_CoefficientsSDDN 1-n ACR/NEMA2C
|
|
||||||
#
|
|
||||||
# end of acrnema.dic
|
|
||||||
#
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Camera characteristic file
|
|
||||||
|
|
||||||
# maximum DDL value
|
|
||||||
|
|
||||||
max 4095
|
|
||||||
|
|
||||||
# DDL LumVal
|
|
||||||
|
|
||||||
0 11.588656
|
|
||||||
16 16.934737
|
|
||||||
32 23.521660
|
|
||||||
48 32.963072
|
|
||||||
96 46.394017
|
|
||||||
145 61.407916
|
|
||||||
241 82.615611
|
|
||||||
353 110.237447
|
|
||||||
514 151.589157
|
|
||||||
723 200.998517
|
|
||||||
996 261.785082
|
|
||||||
1461 357.560166
|
|
||||||
2023 489.766584
|
|
||||||
2794 657.187314
|
|
||||||
3758 863.159038
|
|
||||||
4095 1160.879875
|
|
||||||
|
|
||||||
# end of file
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<!-- Document Type Definition for DCMTK tools dcm2xml and xml2dcm.
|
|
||||||
Copyright (C) 2002-2013, OFFIS e.V.
|
|
||||||
All rights reserved. See COPYRIGHT file for details. -->
|
|
||||||
|
|
||||||
<!ELEMENT file-format ( meta-header, data-set ) >
|
|
||||||
<!ATTLIST file-format xmlns CDATA #IMPLIED >
|
|
||||||
|
|
||||||
<!ELEMENT meta-header ( element+ ) >
|
|
||||||
<!ATTLIST meta-header xfer NMTOKEN #REQUIRED >
|
|
||||||
<!ATTLIST meta-header name CDATA #IMPLIED >
|
|
||||||
|
|
||||||
<!ELEMENT data-set ( element | sequence )* >
|
|
||||||
<!ATTLIST data-set xfer NMTOKEN #REQUIRED >
|
|
||||||
<!ATTLIST data-set name CDATA #IMPLIED >
|
|
||||||
|
|
||||||
<!ELEMENT element ( #PCDATA ) >
|
|
||||||
<!ATTLIST element binary ( yes | no | hidden | base64 | file ) "no" >
|
|
||||||
<!ATTLIST element len NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST element loaded ( yes | no ) "yes" >
|
|
||||||
<!ATTLIST element name CDATA #IMPLIED >
|
|
||||||
<!ATTLIST element tag CDATA #REQUIRED >
|
|
||||||
<!ATTLIST element vm NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST element vr NMTOKEN #REQUIRED >
|
|
||||||
|
|
||||||
<!ELEMENT sequence ( item* | pixel-item* ) >
|
|
||||||
<!ATTLIST sequence card NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST sequence len NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST sequence name CDATA #IMPLIED >
|
|
||||||
<!ATTLIST sequence tag CDATA #REQUIRED >
|
|
||||||
<!ATTLIST sequence vr NMTOKEN #REQUIRED >
|
|
||||||
|
|
||||||
<!ELEMENT item ( element | sequence )* >
|
|
||||||
<!ATTLIST item card NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST item len NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST item offset NMTOKEN #IMPLIED >
|
|
||||||
|
|
||||||
<!ELEMENT pixel-item ( #PCDATA ) >
|
|
||||||
<!ATTLIST pixel-item binary ( yes | hidden | base64 | file ) "yes" >
|
|
||||||
<!ATTLIST pixel-item len NMTOKEN #IMPLIED >
|
|
||||||
<!ATTLIST pixel-item loaded ( yes | no ) "yes" >
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,173 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (C) 2010-2014, OFFIS e.V.
|
|
||||||
# All rights reserved. See COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# This software and supporting documentation were developed by
|
|
||||||
#
|
|
||||||
# OFFIS e.V.
|
|
||||||
# R&D Division Health
|
|
||||||
# Escherweg 2
|
|
||||||
# D-26121 Oldenburg, Germany
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Module: dcmdata
|
|
||||||
#
|
|
||||||
# Author: Joerg Riesmeier
|
|
||||||
#
|
|
||||||
# Purpose: DICONDE data dictionary for the DICOM toolkit DCMTK
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Dictionary of Digital Imaging and Communication in Nondestructive
|
|
||||||
# Evaluation (DICONDE) tags. Please note that the file "dicom.dic"
|
|
||||||
# contains the non-private DICONDE tag definitions since they are now
|
|
||||||
# official part of the DICOM standard (beginning with 2011 edition).
|
|
||||||
#
|
|
||||||
# Each line represents an entry in the data dictionary. Each line
|
|
||||||
# has 5 fields (Tag, VR, Name, VM, Version). Entries need not be
|
|
||||||
# in ascending tag order.
|
|
||||||
#
|
|
||||||
# Entries may override existing entries.
|
|
||||||
#
|
|
||||||
# Each field must be separated by a single tab.
|
|
||||||
# The tag value may take one of three forms:
|
|
||||||
# (gggg,eeee)
|
|
||||||
# (gggg,"CREATOR",ee)
|
|
||||||
# (gggg,"CREATOR",eeee) [eeee >= 1000]
|
|
||||||
# The first form represents a standard tag with group and element number.
|
|
||||||
# The second form describes a private tag that may be used with different
|
|
||||||
# element numbers as reserved by the private creator element. The third
|
|
||||||
# form describes a private tag that may only occur with a certain fixed
|
|
||||||
# element number. In all cases, the tag values must be in hexadecimal.
|
|
||||||
#
|
|
||||||
# Comments have a '#' at the beginning of the line.
|
|
||||||
#
|
|
||||||
# Tag VR Name VM Version
|
|
||||||
#
|
|
||||||
(0008,0080) LO CompanyName 1 DICONDE10
|
|
||||||
(0008,0081) ST CompanyAddress 1 DICONDE10
|
|
||||||
(0008,0090) PN ComponentOwnerName 1 DICONDE10
|
|
||||||
(0008,1040) LO DepartmentName 1 DICONDE10
|
|
||||||
(0008,1048) PN InspectingCompanyName 1-n DICONDE10
|
|
||||||
(0008,1050) PN InspectorName 1-n DICONDE10
|
|
||||||
(0008,1060) PN CertifyingInspectorName 1-n DICONDE10
|
|
||||||
(0010,0010) PN ComponentName 1 DICONDE10
|
|
||||||
(0010,0020) LO ComponentIDNumber 1 DICONDE10
|
|
||||||
(0010,0030) DA ComponentManufacturingDate 1 DICONDE10
|
|
||||||
(0010,1000) LO OtherComponentIDs 1-n DICONDE10
|
|
||||||
(0010,1001) PN OtherComponentNames 1-n DICONDE10
|
|
||||||
(0010,2160) SH MaterialName 1 DICONDE10
|
|
||||||
(0010,4000) LT ComponentNotes 1 DICONDE10
|
|
||||||
(0018,1008) LO ScannerID 1 DICONDE10
|
|
||||||
(0032,4000) LT ExaminationNotes 1 DICONDE10
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/ComponentStudy",20) DA ExpiryDate 1 DICONDE10
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/ComponentSeries",10) ST ActualEnvironmentalConditions 1 DICONDE10
|
|
||||||
(0009,"astm.org/diconde/iod/ComponentSeries",40) ST EnvironmentalConditions 1 DICONDE10
|
|
||||||
|
|
||||||
(0011,"astm.org/diconde/iod/Component",23) ST CADFileFormat 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",24) ST ComponentRefSystem 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",25) ST ComponentManufacturingProcedure 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",28) ST ComponentManufacturer 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",30) DS Thickness 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",32) DS MaterialPipeDiameter 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",34) DS MaterialIsolationDiameter 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",42) ST MaterialGrade 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",44) ST MaterialPropertiesFileID 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",45) ST MaterialPropertiesFileFormat 1-n DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",46) LT MaterialNotes 1 DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",50) CS Shape 1 DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",52) CS CurvatureType 1 DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",54) DS OuterDiameter 1 DICONDE10
|
|
||||||
(0011,"astm.org/diconde/iod/Component",56) DS InnerDiameter 1 DICONDE10
|
|
||||||
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",02) SQ EvaluatorSequence 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",04) IS EvaluatorNumber 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",06) PN EvaluatorName 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",08) IS EvaluationAttempt 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",12) SQ IndicationSequence 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",14) IS IndicationNumber 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",16) SH IndicationLabel 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",18) ST IndicationDesription 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",1A) CS IndicationType 1-n DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",1C) CS IndicationDisposition 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",1E) SQ IndicationROISequence 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",20) CS IndicationROIGeometricType 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",22) IS NumberOfROIContourPoints 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",24) DS IndicationROIContourData 3-3n DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",26) CS IndicationROIContourPointUnits 3 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",28) IS IndicationROIDimensionality 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",30) SQ IndicationPhysicalPropertySequence 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",32) SH PropertyLabel 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",34) DS PropertyValue 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NdeIndication",36) CS PropertyUnits 1 DICONDE10
|
|
||||||
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",02) IS NumberOfAxes 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",04) SQ AxesSequence 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",06) ST AxisDescription 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",08) CS DataSetMapping 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",0A) IS AxisNumber 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",0C) CS AxisType 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",0E) CS AxisUnits 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",10) OB AxisValues 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",20) SQ TransformSequence 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",22) ST TransformDescription 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",24) IS NumberOfAxes 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",26) IS OrderOfAxes 1-n DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",28) CS TransformedAxisUnits 1 DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",2A) DS RotationAndScaleMatrix 1-n DICONDE10
|
|
||||||
(0021,"astm.org/diconde/iod/NDEGeometry",2C) DS TranslationMatrix 11 DICONDE10
|
|
||||||
|
|
||||||
# X-ray Computed Tomography (CT)
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCTImage",02) IS LINACEnergy 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCTImage",04) IS LINACOutput 1 DICONDE10_CT
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",11) DS InternalDetectorFrameTime 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",12) DS NumberOfFramesIntegrated 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",20) SQ DetectorTemperatureSequence 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",22) DS SensorName 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",24) DS HorizontalOffset 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",26) DS VerticalOffset 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtDetector",28) DS Temperature 1 DICONDE10_CT
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",40) SQ DarkCurrentSequence 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",50) ox DarkCurrentCounts 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",60) SQ GainCorrectionReferenceSequence 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",70) ox AirCounts 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",71) DS KVUsedinGainCalibration 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",72) DS MAsUsedInGainCalibration 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",73) DS NumberOfFrames 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",74) LO FilterMaterialUsedInGainCalibration 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",75) DS FilterThicknessUsedInGainCalibration 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",76) DA DateOfGainCalibration 1-n DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",77) TM TimeOfGainCalibration 1-n DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",80) OB BadPixelImage 1 DICONDE10_CT
|
|
||||||
(0009,"astm.org/diconde/iod/NdeCtCalibrationData",99) LT CalibrationNotes 1 DICONDE10_CT
|
|
||||||
|
|
||||||
# Digital Radiographic (DR)
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",11) DS InternalDetectorFrameTime 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",12) DS NumberOfFramesIntegrated 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",20) SQ DetectorTemperatureSequence 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",22) DS SensorName 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",24) DS HorizontalOffset 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",26) DS VerticalOffset 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxDetector",28) DS Temperature 1 DICONDE10_DR
|
|
||||||
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",40) SQ DarkCurrentSequence 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",50) ox DarkCurrentCounts 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",60) SQ GainCorrectionReferenceSequence 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",70) ox AirCounts 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",71) DS KVUsedinGainCalibration 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",72) DS MAsUsedInGainCalibration 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",73) DS NumberOfFrames 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",74) LO FilterMaterialUsedInGainCalibration 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",75) DS FilterThicknessUsedInGainCalibration 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",76) DA DateOfGainCalibration 1-n DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",77) TM TimeOfGainCalibration 1-n DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",80) OB BadPixelImage 1 DICONDE10_DR
|
|
||||||
(0009,"astm.org/diconde/iod/NdeDxCalibrationData",99) LT CalibrationNotes 1 DICONDE10_DR
|
|
||||||
#
|
|
||||||
# end of diconde.dic
|
|
||||||
#
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
--search PatientName
|
|
||||||
--search PatientID
|
|
||||||
--search PatientBirthDate
|
|
||||||
--search PatientSex
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
# Monitor characteristic file
|
|
||||||
|
|
||||||
# maximum DDL value
|
|
||||||
|
|
||||||
max 255
|
|
||||||
|
|
||||||
# ambient light (optional)
|
|
||||||
|
|
||||||
amb 1.0
|
|
||||||
|
|
||||||
# DDL LumVal
|
|
||||||
|
|
||||||
0 0.186260
|
|
||||||
1 0.199540
|
|
||||||
2 0.213000
|
|
||||||
3 0.226850
|
|
||||||
4 0.241260
|
|
||||||
5 0.256440
|
|
||||||
6 0.272560
|
|
||||||
7 0.289820
|
|
||||||
8 0.308410
|
|
||||||
9 0.328520
|
|
||||||
10 0.350340
|
|
||||||
11 0.374050
|
|
||||||
12 0.399850
|
|
||||||
13 0.427930
|
|
||||||
14 0.458480
|
|
||||||
15 0.491690
|
|
||||||
16 0.527740
|
|
||||||
17 0.566800
|
|
||||||
18 0.608900
|
|
||||||
19 0.654050
|
|
||||||
20 0.702250
|
|
||||||
21 0.753510
|
|
||||||
22 0.807840
|
|
||||||
23 0.865250
|
|
||||||
24 0.925730
|
|
||||||
25 0.989300
|
|
||||||
26 1.055950
|
|
||||||
27 1.125710
|
|
||||||
28 1.198560
|
|
||||||
29 1.274530
|
|
||||||
30 1.353610
|
|
||||||
31 1.435800
|
|
||||||
32 1.521130
|
|
||||||
33 1.609590
|
|
||||||
34 1.701190
|
|
||||||
35 1.795960
|
|
||||||
36 1.893890
|
|
||||||
37 1.995020
|
|
||||||
38 2.099340
|
|
||||||
39 2.206880
|
|
||||||
40 2.317650
|
|
||||||
41 2.431660
|
|
||||||
42 2.548930
|
|
||||||
43 2.669460
|
|
||||||
44 2.793280
|
|
||||||
45 2.920390
|
|
||||||
46 3.050810
|
|
||||||
47 3.184560
|
|
||||||
48 3.321650
|
|
||||||
49 3.462090
|
|
||||||
50 3.605910
|
|
||||||
51 3.753140
|
|
||||||
52 3.903820
|
|
||||||
53 4.057970
|
|
||||||
54 4.215620
|
|
||||||
55 4.376800
|
|
||||||
56 4.541540
|
|
||||||
57 4.709880
|
|
||||||
58 4.881840
|
|
||||||
59 5.057460
|
|
||||||
60 5.236750
|
|
||||||
61 5.419770
|
|
||||||
62 5.606520
|
|
||||||
63 5.797050
|
|
||||||
64 5.991380
|
|
||||||
65 6.189550
|
|
||||||
66 6.391550
|
|
||||||
67 6.597390
|
|
||||||
68 6.807060
|
|
||||||
69 7.020590
|
|
||||||
70 7.237950
|
|
||||||
71 7.459160
|
|
||||||
72 7.684220
|
|
||||||
73 7.913130
|
|
||||||
74 8.145890
|
|
||||||
75 8.382510
|
|
||||||
76 8.622980
|
|
||||||
77 8.867310
|
|
||||||
78 9.115500
|
|
||||||
79 9.367550
|
|
||||||
80 9.623470
|
|
||||||
81 9.883250
|
|
||||||
82 10.146910
|
|
||||||
83 10.414440
|
|
||||||
84 10.685870
|
|
||||||
85 10.961190
|
|
||||||
86 11.240410
|
|
||||||
87 11.523550
|
|
||||||
88 11.810600
|
|
||||||
89 12.101570
|
|
||||||
90 12.396480
|
|
||||||
91 12.695330
|
|
||||||
92 12.998130
|
|
||||||
93 13.304880
|
|
||||||
94 13.615600
|
|
||||||
95 13.930280
|
|
||||||
96 14.248940
|
|
||||||
97 14.571590
|
|
||||||
98 14.898230
|
|
||||||
99 15.228890
|
|
||||||
100 15.563570
|
|
||||||
101 15.902290
|
|
||||||
102 16.245070
|
|
||||||
103 16.591910
|
|
||||||
104 16.942840
|
|
||||||
105 17.297860
|
|
||||||
106 17.656990
|
|
||||||
107 18.020250
|
|
||||||
108 18.387640
|
|
||||||
109 18.759180
|
|
||||||
110 19.134890
|
|
||||||
111 19.514770
|
|
||||||
112 19.898850
|
|
||||||
113 20.287130
|
|
||||||
114 20.679590
|
|
||||||
115 21.076230
|
|
||||||
116 21.477030
|
|
||||||
117 21.881960
|
|
||||||
118 22.291010
|
|
||||||
119 22.704160
|
|
||||||
120 23.121390
|
|
||||||
121 23.542700
|
|
||||||
122 23.968050
|
|
||||||
123 24.397430
|
|
||||||
124 24.830830
|
|
||||||
125 25.268230
|
|
||||||
126 25.709610
|
|
||||||
127 26.154950
|
|
||||||
128 26.604230
|
|
||||||
129 27.057450
|
|
||||||
130 27.514600
|
|
||||||
131 27.975710
|
|
||||||
132 28.440770
|
|
||||||
133 28.909800
|
|
||||||
134 29.382810
|
|
||||||
135 29.859810
|
|
||||||
136 30.340810
|
|
||||||
137 30.825820
|
|
||||||
138 31.314850
|
|
||||||
139 31.807910
|
|
||||||
140 32.305020
|
|
||||||
141 32.806170
|
|
||||||
142 33.311390
|
|
||||||
143 33.820680
|
|
||||||
144 34.334050
|
|
||||||
145 34.851510
|
|
||||||
146 35.373040
|
|
||||||
147 35.898610
|
|
||||||
148 36.428200
|
|
||||||
149 36.961790
|
|
||||||
150 37.499360
|
|
||||||
151 38.040870
|
|
||||||
152 38.586320
|
|
||||||
153 39.135660
|
|
||||||
154 39.688880
|
|
||||||
155 40.245950
|
|
||||||
156 40.806860
|
|
||||||
157 41.371570
|
|
||||||
158 41.940060
|
|
||||||
159 42.512310
|
|
||||||
160 43.088300
|
|
||||||
161 43.668010
|
|
||||||
162 44.251470
|
|
||||||
163 44.838720
|
|
||||||
164 45.429800
|
|
||||||
165 46.024740
|
|
||||||
166 46.623600
|
|
||||||
167 47.226410
|
|
||||||
168 47.833210
|
|
||||||
169 48.444030
|
|
||||||
170 49.058920
|
|
||||||
171 49.677930
|
|
||||||
172 50.301070
|
|
||||||
173 50.928410
|
|
||||||
174 51.559970
|
|
||||||
175 52.195800
|
|
||||||
176 52.835940
|
|
||||||
177 53.480410
|
|
||||||
178 54.129180
|
|
||||||
179 54.782210
|
|
||||||
180 55.439460
|
|
||||||
181 56.100870
|
|
||||||
182 56.766410
|
|
||||||
183 57.436030
|
|
||||||
184 58.109700
|
|
||||||
185 58.787350
|
|
||||||
186 59.468950
|
|
||||||
187 60.154460
|
|
||||||
188 60.843840
|
|
||||||
189 61.537030
|
|
||||||
190 62.233990
|
|
||||||
191 62.934680
|
|
||||||
192 63.639060
|
|
||||||
193 64.347100
|
|
||||||
194 65.058820
|
|
||||||
195 65.774270
|
|
||||||
196 66.493510
|
|
||||||
197 67.216560
|
|
||||||
198 67.943490
|
|
||||||
199 68.674330
|
|
||||||
200 69.409130
|
|
||||||
201 70.147940
|
|
||||||
202 70.890790
|
|
||||||
203 71.637750
|
|
||||||
204 72.388850
|
|
||||||
205 73.144130
|
|
||||||
206 73.903650
|
|
||||||
207 74.667450
|
|
||||||
208 75.435570
|
|
||||||
209 76.208050
|
|
||||||
210 76.984850
|
|
||||||
211 77.765920
|
|
||||||
212 78.551230
|
|
||||||
213 79.340710
|
|
||||||
214 80.134330
|
|
||||||
215 80.932040
|
|
||||||
216 81.733790
|
|
||||||
217 82.539530
|
|
||||||
218 83.349220
|
|
||||||
219 84.162800
|
|
||||||
220 84.980240
|
|
||||||
221 85.801480
|
|
||||||
222 86.626480
|
|
||||||
223 87.455180
|
|
||||||
224 88.287560
|
|
||||||
225 89.123560
|
|
||||||
226 89.963230
|
|
||||||
227 90.806620
|
|
||||||
228 91.653780
|
|
||||||
229 92.504770
|
|
||||||
230 93.359640
|
|
||||||
231 94.218430
|
|
||||||
232 95.081200
|
|
||||||
233 95.948010
|
|
||||||
234 96.818900
|
|
||||||
235 97.693920
|
|
||||||
236 98.573140
|
|
||||||
237 99.456590
|
|
||||||
238 100.344340
|
|
||||||
239 101.236440
|
|
||||||
240 102.132930
|
|
||||||
241 103.033820
|
|
||||||
242 103.938850
|
|
||||||
243 104.847740
|
|
||||||
244 105.760200
|
|
||||||
245 106.675910
|
|
||||||
246 107.594590
|
|
||||||
247 108.515940
|
|
||||||
248 109.439670
|
|
||||||
249 110.365470
|
|
||||||
250 111.293050
|
|
||||||
251 112.222110
|
|
||||||
252 113.152360
|
|
||||||
253 114.083500
|
|
||||||
254 115.015230
|
|
||||||
255 115.947260
|
|
||||||
|
|
||||||
# eof of file
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
# Philips Medical Systems Presentation LUT
|
|
||||||
# Difference between GSDF and Philips Standard Display Curve
|
|
||||||
# y = 523.31018 * (1 - exp(-x * 0.0026257106)), with x = 0..255
|
|
||||||
|
|
||||||
# number of entries (x,y)
|
|
||||||
count 256
|
|
||||||
|
|
||||||
# maximum x value (optional)
|
|
||||||
#xMax 255
|
|
||||||
|
|
||||||
# maximum y value (optional)
|
|
||||||
#yMax 255
|
|
||||||
|
|
||||||
# table data
|
|
||||||
# x y
|
|
||||||
|
|
||||||
0 0
|
|
||||||
1 1.372258721
|
|
||||||
2 2.740919014
|
|
||||||
3 4.105990316
|
|
||||||
4 5.467482036
|
|
||||||
5 6.825403563
|
|
||||||
6 8.179764257
|
|
||||||
7 9.530573457
|
|
||||||
8 10.87784048
|
|
||||||
9 12.2215746
|
|
||||||
10 13.5617851
|
|
||||||
11 14.8984812
|
|
||||||
12 16.23167214
|
|
||||||
13 17.56136709
|
|
||||||
14 18.88757523
|
|
||||||
15 20.21030569
|
|
||||||
16 21.52956761
|
|
||||||
17 22.84537007
|
|
||||||
18 24.15772214
|
|
||||||
19 25.46663288
|
|
||||||
20 26.7721113
|
|
||||||
21 28.07416642
|
|
||||||
22 29.37280719
|
|
||||||
23 30.66804259
|
|
||||||
24 31.95988153
|
|
||||||
25 33.24833293
|
|
||||||
26 34.53340566
|
|
||||||
27 35.81510859
|
|
||||||
28 37.09345055
|
|
||||||
29 38.36844037
|
|
||||||
30 39.64008681
|
|
||||||
31 40.90839867
|
|
||||||
32 42.17338467
|
|
||||||
33 43.43505354
|
|
||||||
34 44.69341397
|
|
||||||
35 45.94847466
|
|
||||||
36 47.20024423
|
|
||||||
37 48.44873134
|
|
||||||
38 49.69394458
|
|
||||||
39 50.93589254
|
|
||||||
40 52.17458378
|
|
||||||
41 53.41002684
|
|
||||||
42 54.64223024
|
|
||||||
43 55.87120247
|
|
||||||
44 57.09695201
|
|
||||||
45 58.31948731
|
|
||||||
46 59.5388168
|
|
||||||
47 60.75494888
|
|
||||||
48 61.96789194
|
|
||||||
49 63.17765434
|
|
||||||
50 64.38424442
|
|
||||||
51 65.58767049
|
|
||||||
52 66.78794087
|
|
||||||
53 67.98506381
|
|
||||||
54 69.17904758
|
|
||||||
55 70.36990041
|
|
||||||
56 71.5576305
|
|
||||||
57 72.74224605
|
|
||||||
58 73.92375522
|
|
||||||
59 75.10216616
|
|
||||||
60 76.27748699
|
|
||||||
61 77.44972582
|
|
||||||
62 78.61889072
|
|
||||||
63 79.78498977
|
|
||||||
64 80.94803099
|
|
||||||
65 82.1080224
|
|
||||||
66 83.26497201
|
|
||||||
67 84.41888779
|
|
||||||
68 85.5697777
|
|
||||||
69 86.71764966
|
|
||||||
70 87.8625116
|
|
||||||
71 89.00437141
|
|
||||||
72 90.14323695
|
|
||||||
73 91.27911609
|
|
||||||
74 92.41201665
|
|
||||||
75 93.54194644
|
|
||||||
76 94.66891326
|
|
||||||
77 95.79292486
|
|
||||||
78 96.91398901
|
|
||||||
79 98.03211343
|
|
||||||
80 99.14730583
|
|
||||||
81 100.2595739
|
|
||||||
82 101.3689253
|
|
||||||
83 102.4753677
|
|
||||||
84 103.5789087
|
|
||||||
85 104.6795559
|
|
||||||
86 105.777317
|
|
||||||
87 106.8721994
|
|
||||||
88 107.9642107
|
|
||||||
89 109.0533585
|
|
||||||
90 110.1396503
|
|
||||||
91 111.2230935
|
|
||||||
92 112.3036956
|
|
||||||
93 113.3814641
|
|
||||||
94 114.4564064
|
|
||||||
95 115.52853
|
|
||||||
96 116.5978421
|
|
||||||
97 117.6643502
|
|
||||||
98 118.7280617
|
|
||||||
99 119.7889838
|
|
||||||
100 120.8471239
|
|
||||||
101 121.9024892
|
|
||||||
102 122.9550871
|
|
||||||
103 124.0049249
|
|
||||||
104 125.0520096
|
|
||||||
105 126.0963487
|
|
||||||
106 127.1379492
|
|
||||||
107 128.1768183
|
|
||||||
108 129.2129633
|
|
||||||
109 130.2463912
|
|
||||||
110 131.2771091
|
|
||||||
111 132.3051243
|
|
||||||
112 133.3304437
|
|
||||||
113 134.3530745
|
|
||||||
114 135.3730237
|
|
||||||
115 136.3902983
|
|
||||||
116 137.4049053
|
|
||||||
117 138.4168517
|
|
||||||
118 139.4261446
|
|
||||||
119 140.4327908
|
|
||||||
120 141.4367973
|
|
||||||
121 142.4381711
|
|
||||||
122 143.436919
|
|
||||||
123 144.4330479
|
|
||||||
124 145.4265647
|
|
||||||
125 146.4174762
|
|
||||||
126 147.4057893
|
|
||||||
127 148.3915107
|
|
||||||
128 149.3746474
|
|
||||||
129 150.355206
|
|
||||||
130 151.3331933
|
|
||||||
131 152.3086161
|
|
||||||
132 153.281481
|
|
||||||
133 154.2517949
|
|
||||||
134 155.2195643
|
|
||||||
135 156.1847959
|
|
||||||
136 157.1474965
|
|
||||||
137 158.1076726
|
|
||||||
138 159.0653309
|
|
||||||
139 160.0204779
|
|
||||||
140 160.9731203
|
|
||||||
141 161.9232646
|
|
||||||
142 162.8709174
|
|
||||||
143 163.8160852
|
|
||||||
144 164.7587745
|
|
||||||
145 165.6989918
|
|
||||||
146 166.6367436
|
|
||||||
147 167.5720364
|
|
||||||
148 168.5048766
|
|
||||||
149 169.4352707
|
|
||||||
150 170.363225
|
|
||||||
151 171.2887459
|
|
||||||
152 172.2118399
|
|
||||||
153 173.1325133
|
|
||||||
154 174.0507724
|
|
||||||
155 174.9666237
|
|
||||||
156 175.8800733
|
|
||||||
157 176.7911276
|
|
||||||
158 177.6997929
|
|
||||||
159 178.6060754
|
|
||||||
160 179.5099814
|
|
||||||
161 180.4115171
|
|
||||||
162 181.3106888
|
|
||||||
163 182.2075025
|
|
||||||
164 183.1019647
|
|
||||||
165 183.9940812
|
|
||||||
166 184.8838585
|
|
||||||
167 185.7713024
|
|
||||||
168 186.6564193
|
|
||||||
169 187.5392152
|
|
||||||
170 188.4196961
|
|
||||||
171 189.2978682
|
|
||||||
172 190.1737374
|
|
||||||
173 191.04731
|
|
||||||
174 191.9185917
|
|
||||||
175 192.7875888
|
|
||||||
176 193.6543071
|
|
||||||
177 194.5187526
|
|
||||||
178 195.3809313
|
|
||||||
179 196.2408492
|
|
||||||
180 197.0985121
|
|
||||||
181 197.953926
|
|
||||||
182 198.8070968
|
|
||||||
183 199.6580304
|
|
||||||
184 200.5067325
|
|
||||||
185 201.3532092
|
|
||||||
186 202.1974661
|
|
||||||
187 203.0395092
|
|
||||||
188 203.8793443
|
|
||||||
189 204.716977
|
|
||||||
190 205.5524133
|
|
||||||
191 206.3856588
|
|
||||||
192 207.2167193
|
|
||||||
193 208.0456006
|
|
||||||
194 208.8723083
|
|
||||||
195 209.6968482
|
|
||||||
196 210.5192259
|
|
||||||
197 211.3394471
|
|
||||||
198 212.1575175
|
|
||||||
199 212.9734427
|
|
||||||
200 213.7872283
|
|
||||||
201 214.59888
|
|
||||||
202 215.4084032
|
|
||||||
203 216.2158037
|
|
||||||
204 217.021087
|
|
||||||
205 217.8242586
|
|
||||||
206 218.6253241
|
|
||||||
207 219.424289
|
|
||||||
208 220.2211588
|
|
||||||
209 221.0159389
|
|
||||||
210 221.808635
|
|
||||||
211 222.5992523
|
|
||||||
212 223.3877965
|
|
||||||
213 224.1742729
|
|
||||||
214 224.958687
|
|
||||||
215 225.7410441
|
|
||||||
216 226.5213496
|
|
||||||
217 227.299609
|
|
||||||
218 228.0758276
|
|
||||||
219 228.8500107
|
|
||||||
220 229.6221638
|
|
||||||
221 230.392292
|
|
||||||
222 231.1604007
|
|
||||||
223 231.9264953
|
|
||||||
224 232.6905809
|
|
||||||
225 233.452663
|
|
||||||
226 234.2127466
|
|
||||||
227 234.9708371
|
|
||||||
228 235.7269397
|
|
||||||
229 236.4810596
|
|
||||||
230 237.2332019
|
|
||||||
231 237.983372
|
|
||||||
232 238.7315749
|
|
||||||
233 239.4778158
|
|
||||||
234 240.2220999
|
|
||||||
235 240.9644323
|
|
||||||
236 241.7048181
|
|
||||||
237 242.4432624
|
|
||||||
238 243.1797702
|
|
||||||
239 243.9143468
|
|
||||||
240 244.6469971
|
|
||||||
241 245.3777262
|
|
||||||
242 246.1065392
|
|
||||||
243 246.833441
|
|
||||||
244 247.5584367
|
|
||||||
245 248.2815312
|
|
||||||
246 249.0027296
|
|
||||||
247 249.7220368
|
|
||||||
248 250.4394578
|
|
||||||
249 251.1549976
|
|
||||||
250 251.868661
|
|
||||||
251 252.580453
|
|
||||||
252 253.2903784
|
|
||||||
253 253.9984423
|
|
||||||
254 254.7046494
|
|
||||||
255 255.4090047
|
|
||||||
|
|
||||||
# eof
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Printer characteristic file
|
|
||||||
|
|
||||||
# maximum DDL value
|
|
||||||
|
|
||||||
max 255
|
|
||||||
|
|
||||||
# illumination (optional)
|
|
||||||
|
|
||||||
lum 2000
|
|
||||||
|
|
||||||
# reflected ambient light (optional)
|
|
||||||
|
|
||||||
amb 10
|
|
||||||
|
|
||||||
# order of the polynomial curve fitting algorithm (optional)
|
|
||||||
|
|
||||||
ord 5
|
|
||||||
|
|
||||||
# DDL OD Val
|
|
||||||
|
|
||||||
0 3.00
|
|
||||||
16 2.40
|
|
||||||
33 2.09
|
|
||||||
51 1.86
|
|
||||||
67 1.68
|
|
||||||
84 1.52
|
|
||||||
102 1.37
|
|
||||||
119 1.24
|
|
||||||
135 1.10
|
|
||||||
153 0.97
|
|
||||||
170 0.85
|
|
||||||
187 0.72
|
|
||||||
204 0.59
|
|
||||||
221 0.46
|
|
||||||
238 0.33
|
|
||||||
255 0.19
|
|
||||||
|
|
||||||
# eof of file
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,91 +0,0 @@
|
|||||||
/* Copyright (C) 2000-2010, OFFIS e.V.
|
|
||||||
All rights reserved. See COPYRIGHT file for details.
|
|
||||||
Sample CSS for HTML output of "dsr2html"
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #dddddd;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
background-color: #000099;
|
|
||||||
color: white;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
padding: 4px;
|
|
||||||
font-size: x-large;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
padding: 3px;
|
|
||||||
font-size: large;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3, h4, h5, h6 {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4, h5, h6 {
|
|
||||||
font-size: medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 a[name] {
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
background-color: white;
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
div small {
|
|
||||||
width: 100%;
|
|
||||||
color: black;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
background-color: #ccccff;
|
|
||||||
font-family: sans-serif;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a[href] {
|
|
||||||
color: maroon;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
a[name]:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footnote {
|
|
||||||
background-color: #dddddd;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footnote a, div.footnote small {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footnote a:hover {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.under {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
color: silver;
|
|
||||||
background: transparent;
|
|
||||||
border: thin;
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
/* Copyright (C) 2000-2010, OFFIS e.V.
|
|
||||||
All rights reserved. See COPYRIGHT file for details.
|
|
||||||
Sample CSS for XHTML output of "dsr2html"
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #dddddd;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
background-color: #000099;
|
|
||||||
color: white;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
padding: 4px;
|
|
||||||
font-size: x-large;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
padding: 3px;
|
|
||||||
font-size: large;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3, h4, h5, h6 {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4, h5, h6 {
|
|
||||||
font-size: medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 a[id] {
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
background-color: #ccccff;
|
|
||||||
font-family: sans-serif;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a[href] {
|
|
||||||
color: maroon;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
a[id]:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.para {
|
|
||||||
background-color: white;
|
|
||||||
text-align: justify;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.small {
|
|
||||||
width: 100%;
|
|
||||||
color: black;
|
|
||||||
text-align: left;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footnote {
|
|
||||||
background-color: #dddddd;
|
|
||||||
color: gray;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footnote a {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footnote a:hover {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.under, span.relation, span.date, span.time, span.datetime, span.pname, span.num, span.code {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.observe {
|
|
||||||
color: gray;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.super {
|
|
||||||
font-size: x-small;
|
|
||||||
vertical-align: super;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
color: silver;
|
|
||||||
background: transparent;
|
|
||||||
border: thin;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Scanner characteristic file
|
|
||||||
|
|
||||||
# maximum DDL value
|
|
||||||
|
|
||||||
max 4095
|
|
||||||
|
|
||||||
# illumination (optional)
|
|
||||||
|
|
||||||
lum 2000
|
|
||||||
|
|
||||||
# reflected ambient light (optional)
|
|
||||||
|
|
||||||
amb 10
|
|
||||||
|
|
||||||
# DDL OD Val
|
|
||||||
|
|
||||||
0 3.10
|
|
||||||
16 2.46
|
|
||||||
32 2.17
|
|
||||||
48 1.94
|
|
||||||
96 1.74
|
|
||||||
145 1.59
|
|
||||||
241 1.44
|
|
||||||
353 1.30
|
|
||||||
514 1.15
|
|
||||||
723 1.02
|
|
||||||
996 0.90
|
|
||||||
1461 0.76
|
|
||||||
2023 0.62
|
|
||||||
2794 0.49
|
|
||||||
3758 0.37
|
|
||||||
4095 0.24
|
|
||||||
|
|
||||||
# eof of file
|
|
||||||
Reference in New Issue
Block a user