DCMDIR

// cl dcmdir.cpp /std:c++17 /EHsc /Zi /D_WINDOWS /pdb:dcmdir.pdb /release /debug gdi32.lib user32.lib kernel32.lib // Dec 11, 2020 // Koji Ota // DICOMDIR を解析するプログラム #define PUBLIC #include <windows.h> #include <Windowsx.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <stdint.h> #include <locale.h> #include <errno.h> #include <fcntl.h> #include <io.h> #include <process.h> #include <cstdint> #include <vector> #include <algorithm> #include <memory> #include <map> using flt32_t = float; using flt64_t = double; #define PROGRAM_NAME "dcmdir" #define PROGRAM_VERSION "0.0.7 (20210220a)" #define PROGRAM_COPYRIGHT "Copyright(C) 2010-2021 Koji Ota All Rights Reserved." //////////////////////////////////////////////////////////////////////////////// // クラス定義 namespace ko2 { // #define KO2_WORD(a, b) ((a << 8) | (b)) #define KO2_DWORD(a, b, c, d) ((a << 24) | (b << 16) | (c << 8) | d) // enum class TextColor { Black = 0, DarkBlue = 1, DarkGreen = 2, LightBlue = 3, DarkRed = 4, Magenta = 5, Orange = 6, LightGray = 7, Gray = 8, Blue = 9, Green = 10, Cyan = 11, Red = 12, Pink = 13, Yellow = 14, White = 15 }; class TerminalColor { public: TerminalColor(TextColor fg, TextColor bg); ~TerminalColor(); void SetColor(TextColor fg, TextColor bg); #ifdef _WINDOWS std::uint16_t m_saved_color; #endif }; // class Terminal { public: static void Printf(TextColor fg, TextColor bg, char* fmt, ...); static void Panic(char* fmt, ...); }; // class Trace { public: Trace(char* module, bool activated=true); ~Trace(); void Printf(char* fmt, ...); void Println(char* fmt, ...); void Warning(char* fmt, ...); void Error(char* fmt, ...); void Flush(); private: char* m_module; bool m_activated; }; // class PUBLIC MemoryStream { public: MemoryStream() = delete; MemoryStream(const MemoryStream&) = delete; MemoryStream& operator=(const MemoryStream&) = delete; MemoryStream(unsigned char* buf, size_t bytes); void load(unsigned char* buf, size_t bytes); bool isEnd(); void rewind(); void rewind(int delta); unsigned char readByte(); std::int16_t readAsInt16(); std::uint16_t readAsUInt16(); std::int32_t readAsInt32(); std::uint32_t readAsUInt32(); float readAsFlt32(); double readAsFlt64(); void skip(int n); void copyTo(unsigned char* des, int length); size_t getPosition(); void setPosition(size_t pos); unsigned char* getCurrentAddress(); private: unsigned char* m_buf; size_t m_bytes; int m_cursor; }; // class Arg { public: Arg(); Arg(char* buf, uint32_t bytes, char delimiter = ' '); Arg(const char* line); ~Arg(); void Parse(char* buf, uint32_t bytes, char delimiter = ' '); int Count(); const char* GetAt(int i); private: int GetChar(); void UngetChar(); bool IsEnd(); private: void SkipWhitespace(); char* m_buf; uint32_t m_bytes; std::vector<std::string> m_args; uint32_t m_cursor; }; // struct CommandLineOption_s { // spec char* name; bool hasValue; char* default_value; // value bool valid; char* value; }; using CommandLineOption_t = struct CommandLineOption_s; // class CommandLineArg { public: CommandLineArg() = default; CommandLineArg(const CommandLineArg&) = delete; CommandLineArg& operator=(const CommandLineArg&) = delete; void Spec(char* name, bool hasValue = false, char* default_value = nullptr); int Parse(int argc, char** argv); const char* operator[](int i); bool HasOption(char* name); char* GetOptionValue(char* name); private: CommandLineOption_t* Find(char* name); private: std::vector<CommandLineOption_t> m_options; std::vector<std::string> m_args; }; // namespace io::file { // class ImageDataCollection { }; // class PUBLIC ImageDataLoader { public: virtual ImageDataCollection* load(char* path) = 0; }; // struct PUBLIC DicomField { size_t offset; std::uint16_t gId; std::uint16_t eId; std::uint8_t vr0; std::uint8_t vr1; std::uint32_t length; unsigned char* dataP; std::vector<DicomField> seq; DicomField() : offset(0), gId(0), eId(0), vr0('.'), vr1('.'), length(0), dataP(nullptr) {} void print() { Terminal::Printf(TextColor::Gray, TextColor::Black, "%08zx %04x %04x %c%c %08x", offset, gId, eId, vr0, vr1, length); } }; // class PUBLIC DicomVR { public: DicomVR(const DicomVR&) = delete; DicomVR& operator=(const DicomVR&) = delete; DicomVR(); DicomVR(DicomField* f); ~DicomVR(); void load(DicomField* f); std::int16_t readAsInt16(int id = 0); std::uint16_t readAsUInt16(int id = 0); std::int32_t readAsInt32(int id = 0); std::uint32_t readAsUInt32(int id = 0); float readAsFlt32(int id = 0); double readAsFlt64(int id = 0); void print(); const char* toString(); private: DicomField* m_field; MemoryStream* m_msP; std::string m_string; }; // class PUBLIC DicomRecord { public: DicomRecord() = default; DicomRecord(const DicomRecord&) = delete; DicomRecord& operator=(const DicomRecord&) = delete; void addField(DicomField f); size_t countOfFields(); DicomField* getFieldAt(int id); DicomField* find(std::uint16_t gId, std::uint16_t eId); private: std::vector<DicomField> m_fields; }; // class PUBLIC DicomContainer : public DicomRecord { public: DicomContainer() = default; DicomContainer(const DicomContainer&) = delete; DicomContainer& operator=(const DicomContainer&) = delete; ~DicomContainer(); void addChild(DicomContainer* container); size_t countOfChildren(); DicomContainer* getChildAt(int id); private: std::vector<DicomContainer*> m_children; }; // class PUBLIC DicomScanner { public: DicomScanner() = delete; DicomScanner(const DicomScanner&) = delete; DicomScanner& operator=(const DicomScanner&) = delete; DicomScanner(MemoryStream& ms); MemoryStream& getMemoryStream(); virtual bool next() = 0; DicomField& field(); virtual DicomField* getDirEntry(); protected: MemoryStream& m_ms; DicomField m_field; }; // class PUBLIC DicomScannerLiIm : public DicomScanner { public: DicomScannerLiIm() = delete; DicomScannerLiIm(const DicomScannerLiIm&) = delete; DicomScannerLiIm& operator=(const DicomScannerLiIm&) = delete; DicomScannerLiIm(MemoryStream& ms); bool next(); }; class PUBLIC DicomScannerLiEx : public DicomScanner { public: DicomScannerLiEx() = delete; DicomScannerLiEx(const DicomScannerLiEx&) = delete; DicomScannerLiEx& operator=(const DicomScannerLiEx&) = delete; DicomScannerLiEx(MemoryStream& ms); bool next(); void scanSequence(DicomField& parent); void scanSequenceItem(DicomField& parent, unsigned long length); DicomField* getDirEntry(); }; class PUBLIC DicomScannerBiEx : public DicomScanner { public: DicomScannerBiEx() = delete; DicomScannerBiEx(const DicomScannerBiEx&) = delete; DicomScannerBiEx& operator=(const DicomScannerBiEx&) = delete; DicomScannerBiEx(MemoryStream& ms); bool next(); }; // class PUBLIC ImageDataLoaderDicom { public: ImageDataLoaderDicom(); ~ImageDataLoaderDicom(); DicomContainer* load(char* path); private: DicomContainer* loadDir(char* path); void loadTree(DicomContainer* parent, DicomScanner* scanner, size_t offset); void printNode(DicomContainer* node); private: DicomContainer* loadFile(char* path); char* m_data; unsigned long m_bytes; }; } } // class PixelConverter { public: PixelConverter(int dbpp, int sbpp, int w, int h, int pr); ~PixelConverter(); char* convert(char* srcP); int bytes(); private: int m_dbpp; char* m_desP; int m_sbpp; char* m_srcP; int m_w; int m_h; int m_pr; }; // class ImageData { public: ImageData(); ~ImageData(); ImageData* Clone(); void load(int w, int h, int bpp, int pixel_representation, char* pixels); int minPixelValue(); int maxPixelValue(); int width(); int height(); int bitPerPixel(); int pixelRepresentation(); char* pixels(); private: int m_w; int m_h; int m_bpp; int m_pixel_representation; char* m_pixels; int m_pmin; int m_pmax; }; // class Statistics { public: Statistics(); void load(ImageData* data); private: template<typename T> void _load_pixels(T* pixels, int pcount) { m_data.clear(); m_histogram.clear(); m_sig2 = 0; m_sumX = 0; m_sumX2 = 0; for(int i=0; i<pcount; i++) { int pvalue = pixels[i]; m_data[pvalue]++; m_sumX += pvalue; m_sumX2 += (pvalue * pvalue); if (m_data.size() == 0) { m_pmin = pvalue; m_pmax = pvalue; } else { if (m_pmin > pvalue) m_pmin = pvalue; if (m_pmax < pvalue) m_pmax = pvalue; } } flt32_t n = m_data.size(); flt32_t aveX2 = m_sumX2 / n; m_sig2 = (m_sumX2 - n * aveX2) / (n - 1.0); } private: std::map<int, int> m_data; std::vector<int> m_histogram; int m_pmin; int m_pmax; int m_prange[2]; flt32_t m_sig2; flt32_t m_sumX; flt32_t m_sumX2; }; // class ImageModule { public: ImageModule(); ImageModule(char* home, ko2::io::file::DicomContainer* node); ~ImageModule(); void add(ImageData* imgData); int count(); ImageData* getFrameAt(int i = 0); private: std::vector<ImageData*> m_frames; }; // #define WM_APP_DISPLAY_IMAGE (WM_APP + 1) #define WM_APP_QUIT (WM_APP + 2) // #define FRAMEBUFFER_RGB(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | ((b) << 0)) // class ImageViewWindow { public: ImageViewWindow(); ~ImageViewWindow(); void closeWindow(); void displayImage(ImageModule* module); int wl(); int wl(int wl); int ww(); int ww(int ww); void update(); private: void _wakeupThread(); void _loadCmap(); void _rebuildLut(); void _showImage(ImageModule* module); void _drawImage(HDC hDC); void _moveCursor(int inc); static LRESULT CALLBACK _wndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); BOOL _setClientSize(int w, int h); static unsigned __stdcall _thread(void *pArg); private: HANDLE m_guiThread; unsigned int m_threadId; HANDLE m_wakeup; HWND m_hWnd; int m_w; int m_h; HINSTANCE m_hInst; int m_wl; int m_ww; int m_pixel_representation; int m_bias; char m_lut[65536]; unsigned long m_cmap[256]; ImageModule* m_module; int m_cursor; char* m_fb; bool m_need_to_rebuild_lut; }; // static struct { char home[1024]; ko2::io::file::DicomContainer *root; struct { int depth; int index[4]; // 0=patient, 1=study, 2=series, 3=image } path; ImageViewWindow imgview; } pcontext; // //////////////////////////////////////////////////////////////////////////////// // クラス実装 namespace ko2 { // TerminalColor::TerminalColor(TextColor fg, TextColor bg) { SetColor(fg, bg); } TerminalColor::~TerminalColor() { #ifdef _WINDOWS SetColor(TextColor::Gray, TextColor::Black); #endif } void TerminalColor::SetColor(TextColor fg, TextColor bg) { #ifdef _WINDOWS std::uint16_t flags = 0; switch(fg) { case TextColor::Black: flags |= 0; break; case TextColor::DarkBlue: flags |= FOREGROUND_BLUE; break; case TextColor::DarkGreen: flags |= FOREGROUND_GREEN; break; case TextColor::LightBlue: flags |= FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; case TextColor::DarkRed: flags |= FOREGROUND_RED; break; case TextColor::Magenta: flags |= FOREGROUND_RED | FOREGROUND_BLUE; break; case TextColor::Orange: flags |= FOREGROUND_BLUE; break; case TextColor::LightGray: flags |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; case TextColor::Gray: flags |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; case TextColor::Blue: flags |= FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; case TextColor::Green: flags |= FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; case TextColor::Cyan: flags |= FOREGROUND_GREEN | FOREGROUND_BLUE; break; case TextColor::Red: flags |= FOREGROUND_RED | FOREGROUND_INTENSITY;; break; case TextColor::Pink: flags |= FOREGROUND_RED | FOREGROUND_BLUE; break; case TextColor::Yellow: flags |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; case TextColor::White: flags |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; default: flags |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;; break; } switch(bg) { case TextColor::Black: flags |= 0; break; case TextColor::DarkBlue: flags |= BACKGROUND_BLUE; break; case TextColor::DarkGreen: flags |= BACKGROUND_GREEN; break; case TextColor::LightBlue: flags |= BACKGROUND_BLUE | BACKGROUND_INTENSITY; break; case TextColor::DarkRed: flags |= BACKGROUND_RED; break; case TextColor::Magenta: flags |= BACKGROUND_RED | BACKGROUND_BLUE; break; case TextColor::Orange: flags |= BACKGROUND_BLUE; break; case TextColor::LightGray: flags |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY; break; case TextColor::Gray: flags |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; break; case TextColor::Blue: flags |= BACKGROUND_BLUE | BACKGROUND_INTENSITY; break; case TextColor::Green: flags |= BACKGROUND_GREEN | BACKGROUND_INTENSITY; break; case TextColor::Cyan: flags |= BACKGROUND_GREEN | BACKGROUND_BLUE; break; case TextColor::Red: flags |= BACKGROUND_RED | BACKGROUND_INTENSITY;; break; case TextColor::Pink: flags |= BACKGROUND_RED | BACKGROUND_BLUE; break; case TextColor::Yellow: flags |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY; break; case TextColor::White: flags |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY; break; default: flags |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY;; break; } SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), flags); #endif } // void Terminal::Printf(TextColor fg, TextColor bg, char* fmt, ...) { TerminalColor tcolor(fg, bg); va_list ap; char buf[512]; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); } void Terminal::Panic(char* fmt, ...) { TerminalColor tcolor(TextColor::Gray, TextColor::Red); int tid = GetCurrentThreadId(); printf("TID=0x%04x:**PANIC**:", tid); va_list ap; char buf[512]; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); exit(1); } // Trace::Trace(char* module, bool activated) { m_module = module; m_activated = activated; if (m_activated == false) { return; } TerminalColor tcolor(TextColor::DarkGreen, TextColor::Black); int tid = GetCurrentThreadId(); printf("TID=0x%04x:Enter:%s\r\n", tid, m_module); fflush(stdout); } Trace::~Trace() { if (m_activated == false) { return; } TerminalColor tcolor(TextColor::DarkGreen, TextColor::Black); int tid = GetCurrentThreadId(); printf("TID=0x%04x:Leave:%s\r\n", tid, m_module); fflush(stdout); } void Trace::Printf(char* fmt, ...) { if (m_activated == false) { return; } TerminalColor tcolor(TextColor::DarkGreen, TextColor::Black); va_list ap; char buf[512]; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); } void Trace::Println(char* fmt, ...) { if (m_activated == false) { return; } TerminalColor tcolor(TextColor::DarkGreen, TextColor::Black); int tid = GetCurrentThreadId(); printf("TID=0x%04x:%s:", tid, m_module); va_list ap; char buf[512]; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } void Trace::Warning(char* fmt, ...) { TerminalColor tcolor(TextColor::Yellow, TextColor::Black); if (m_activated == false) { int tid = GetCurrentThreadId(); printf("TID=0x%04x:%s:Warning:", tid, m_module); } va_list ap; char buf[512]; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } void Trace::Error(char* fmt, ...) { TerminalColor tcolor(TextColor::Magenta, TextColor::Black); if (m_activated == false) { int tid = GetCurrentThreadId(); printf("TID=0x%04x:%s:Error:", tid, m_module); } va_list ap; char buf[512]; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); printf("\n"); } void Trace::Flush() { fflush(stdout); } // MemoryStream::MemoryStream(unsigned char* buf, size_t bytes) : m_buf(buf), m_bytes(bytes), m_cursor(0) { } void MemoryStream::load(unsigned char* buf, size_t bytes) { m_buf = buf; m_bytes = bytes; m_cursor = 0; } bool MemoryStream::isEnd() { return m_cursor >= m_bytes; } void MemoryStream::rewind() { m_cursor = 0; } void MemoryStream::rewind(int delta) { m_cursor -= delta; if (m_cursor < 0) { m_cursor = 0; } } unsigned char MemoryStream::readByte() { return m_buf[m_cursor++]; } std::int16_t MemoryStream::readAsInt16() { int d0 = readByte(); int d1 = readByte(); return static_cast<std::int16_t>(KO2_WORD(d1, d0)); } std::uint16_t MemoryStream::readAsUInt16() { int d0 = readByte(); int d1 = readByte(); return static_cast<std::uint16_t>(KO2_WORD(d1, d0)); } std::int32_t MemoryStream::readAsInt32() { int d0 = readByte(); int d1 = readByte(); int d2 = readByte(); int d3 = readByte(); return static_cast<std::int32_t>(KO2_DWORD(d3, d2, d1, d0)); } std::uint32_t MemoryStream::readAsUInt32() { int d0 = readByte(); int d1 = readByte(); int d2 = readByte(); int d3 = readByte(); return static_cast<std::uint32_t>(KO2_DWORD(d3, d2, d1, d0)); } float MemoryStream::readAsFlt32() { unsigned char d[4]; d[0] = readByte(); d[1] = readByte(); d[2] = readByte(); d[3] = readByte(); return *(float*)(d); } double MemoryStream::readAsFlt64() { unsigned char d[8]; d[0] = readByte(); d[1] = readByte(); d[2] = readByte(); d[3] = readByte(); d[4] = readByte(); d[5] = readByte(); d[6] = readByte(); d[7] = readByte(); return *(double*)(d); } void MemoryStream::skip(int n) { m_cursor += n; } void MemoryStream::copyTo(unsigned char* des, int length) { memcpy(des, &m_buf[m_cursor], length); m_cursor += length; } size_t MemoryStream::getPosition() { return m_cursor; } void MemoryStream::setPosition(size_t pos) { m_cursor = pos; } unsigned char* MemoryStream::getCurrentAddress() { return &m_buf[m_cursor]; } // Arg::Arg() { } Arg::Arg(char* buf, uint32_t bytes, char delimiter) { Parse(buf, bytes, delimiter); } Arg::~Arg() { } void Arg::Parse(char* buf, uint32_t bytes, char delimiter) { m_buf = buf; m_bytes = bytes; m_cursor = 0; char word[128]; int i = 0; while(IsEnd() == false) { int ch = GetChar(); if (ch == delimiter) { if (i > 0) { word[i] = 0; m_args.push_back(std::string(word)); i = 0; word[i] = 0; } SkipWhitespace(); } else { word[i++] = ch; } } if (i > 0) { word[i] = 0; m_args.push_back(std::string(word)); } } int Arg::Count() { return m_args.size(); } const char* Arg::GetAt(int i) { return m_args[i].c_str(); } void Arg::SkipWhitespace() { int ch = GetChar(); while(ch == ' ') { if (IsEnd()) { return; } ch = GetChar(); } UngetChar(); } int Arg::GetChar() { return m_buf[m_cursor++]; } void Arg::UngetChar() { m_cursor--; if (m_cursor < 0) { m_cursor = 0; } } bool Arg::IsEnd() { return m_cursor >= m_bytes; } // void CommandLineArg::Spec(char* name, bool hasValue, char* default_value) { CommandLineOption_t spec; spec.name = name; spec.hasValue = hasValue; spec.default_value = default_value; spec.valid = false; spec.value = nullptr; m_options.push_back(spec); } int CommandLineArg::Parse(int argc, char** argv) { Trace t("CommandLineArg::Parse", false); for(int i=1; i < argc; i++) { if (argv[i][0] != '-') { break; } CommandLineOption_t* opt = Find(argv[i]); if (opt == nullptr) { t.Warning("%s : unknown option, ignored.\n", argv[i]); continue; } opt->valid = true; if (opt->hasValue) { opt->value = argv[++i]; } } return m_args.size(); } const char* CommandLineArg::operator[](int i) { if (i < m_args.size()) { return m_args[i].c_str(); } return nullptr; } bool CommandLineArg::HasOption(char* name) { CommandLineOption_t* opt = Find(name); if (opt == nullptr) { return false; } return opt->valid; } char* CommandLineArg::GetOptionValue(char* name) { CommandLineOption_t* opt = Find(name); if (opt == nullptr) { return false; } return opt->value; } CommandLineOption_t* CommandLineArg::Find(char* name) { for(int i=0; i<m_options.size(); i++) { if (!strcmp(name, m_options[i].name)) { return &m_options[i]; } } return nullptr; } // namespace io::file { // DicomVR::DicomVR() : m_field(nullptr), m_msP(nullptr) { } DicomVR::DicomVR(DicomField* f) : m_field(f), m_msP(nullptr) { if (f == nullptr) { return; } m_msP = new MemoryStream(m_field->dataP, m_field->length); } DicomVR::~DicomVR() { delete m_msP; } void DicomVR::load(DicomField* f) { delete m_msP; m_field = f; m_msP = new MemoryStream(m_field->dataP, m_field->length); } std::int16_t DicomVR::readAsInt16(int id) { if (m_msP == nullptr) { return 0; } m_msP->skip(id * sizeof(std::int16_t)); return m_msP->readAsInt16(); } std::uint16_t DicomVR::readAsUInt16(int id) { if (m_msP == nullptr) { return 0; } m_msP->skip(id * sizeof(std::uint16_t)); return m_msP->readAsUInt16(); } std::int32_t DicomVR::readAsInt32(int id) { if (m_msP == nullptr) { return 0; } m_msP->skip(id * sizeof(std::int32_t)); return m_msP->readAsInt32(); } std::uint32_t DicomVR::readAsUInt32(int id) { if (m_msP == nullptr) { return 0; } m_msP->skip(id * sizeof(std::uint32_t)); return m_msP->readAsUInt32(); } float DicomVR::readAsFlt32(int id) { if (m_msP == nullptr) { return 0; } m_msP->skip(id * sizeof(float)); return m_msP->readAsFlt32(); } double DicomVR::readAsFlt64(int id) { if (m_msP == nullptr) { return 0; } m_msP->skip(id * sizeof(double)); return m_msP->readAsFlt64(); } void DicomVR::print() { if (m_msP == nullptr) { printf("\n"); return; } auto print_chars = [this]() { for(int i=0; i<m_field->length;i++) { printf("%c", m_field->dataP[i]); } printf("\n"); }; auto print_binary = [this]() { int length = m_field->length; if (length > 16) { length = 16; } for(int i=0; i<length;i++) { printf("%02x ", m_field->dataP[i]); } printf("(%d bytes)\n", m_field->length); }; unsigned char vr0 = m_field->vr0; unsigned char vr1 = m_field->vr1; if ((vr0 == 'A') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'C') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'D') && (vr1 == 'A')) { print_chars(); } else if ((vr0 == 'D') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'I') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'L') && (vr1 == 'O')) { print_chars(); } else if ((vr0 == 'L') && (vr1 == 'T')) { print_chars(); } else if ((vr0 == 'O') && (vr1 == 'B')) { print_binary(); } else if ((vr0 == 'O') && (vr1 == 'W')) { print_binary(); } else if ((vr0 == 'P') && (vr1 == 'N')) { print_chars(); } else if ((vr0 == 'S') && (vr1 == 'H')) { print_chars(); } else if ((vr0 == 'S') && (vr1 == 'L')) { printf("%d\n", readAsInt32()); } else if ((vr0 == 'S') && (vr1 == 'S')) { printf("%d\n", readAsInt16()); } else if ((vr0 == 'S') && (vr1 == 'T')) { print_chars(); } else if ((vr0 == 'T') && (vr1 == 'M')) { print_chars(); } else if ((vr0 == 'U') && (vr1 == 'I')) { print_chars(); } else if ((vr0 == 'U') && (vr1 == 'L')) { print_chars(); } else if ((vr0 == 'U') && (vr1 == 'S')) { printf("%d\n", readAsUInt16()); } else if ((vr0 == 'U') && (vr1 == 'T')) { print_chars(); } else { printf("unsupported vr %c%c\n", vr0, vr1); } } const char* DicomVR::toString() { m_string.clear(); if (m_msP == nullptr) { return m_string.c_str(); } auto print_chars = [this]() { for(int i=0; i<m_field->length;i++) { m_string += m_field->dataP[i]; } }; auto print_binary = [this]() { int length = m_field->length; if (length > 16) { length = 16; } char strbuf[32]; for(int i=0; i<length;i++) { sprintf_s(strbuf, sizeof(strbuf), "%02x ", m_field->dataP[i]); m_string += strbuf; } sprintf_s(strbuf, sizeof(strbuf), "(%d bytes)\n", m_field->length); m_string += strbuf; }; unsigned char vr0 = m_field->vr0; unsigned char vr1 = m_field->vr1; char strbuf[128]; if ((vr0 == 'A') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'C') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'D') && (vr1 == 'A')) { print_chars(); } else if ((vr0 == 'D') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'I') && (vr1 == 'S')) { print_chars(); } else if ((vr0 == 'L') && (vr1 == 'O')) { print_chars(); } else if ((vr0 == 'L') && (vr1 == 'T')) { print_chars(); } else if ((vr0 == 'O') && (vr1 == 'B')) { print_binary(); } else if ((vr0 == 'O') && (vr1 == 'W')) { print_binary(); } else if ((vr0 == 'P') && (vr1 == 'N')) { print_chars(); } else if ((vr0 == 'S') && (vr1 == 'H')) { print_chars(); } else if ((vr0 == 'S') && (vr1 == 'L')) { sprintf_s(strbuf, sizeof(strbuf), "%d", readAsInt32()); m_string += strbuf; } else if ((vr0 == 'S') && (vr1 == 'S')) { sprintf_s(strbuf, sizeof(strbuf), "%d", readAsInt16()); m_string += strbuf; } else if ((vr0 == 'S') && (vr1 == 'T')) { print_chars(); } else if ((vr0 == 'T') && (vr1 == 'M')) { print_chars(); } else if ((vr0 == 'U') && (vr1 == 'I')) { print_chars(); } else if ((vr0 == 'U') && (vr1 == 'L')) { print_chars(); } else if ((vr0 == 'U') && (vr1 == 'S')) { sprintf_s(strbuf, sizeof(strbuf), "%d\n", readAsUInt16()); m_string += strbuf; } else if ((vr0 == 'U') && (vr1 == 'T')) { print_chars(); } else { sprintf_s(strbuf, sizeof(strbuf), "unsupported vr %c%c\n", vr0, vr1); m_string += strbuf; } return m_string.c_str(); } // void DicomRecord::addField(DicomField f) { m_fields.push_back(f); } size_t DicomRecord::countOfFields() { return m_fields.size(); } DicomField* DicomRecord::getFieldAt(int id) { return &m_fields[id]; } DicomField* DicomRecord::find(std::uint16_t gId, std::uint16_t eId) { for(int i=0; i<m_fields.size(); i++) { if ((gId == m_fields[i].gId) && (eId == m_fields[i].eId)) { return &m_fields[i]; } } return nullptr; } // DicomContainer::~DicomContainer() { for(int i=0; i<m_children.size(); i++) { delete m_children[i]; } } void DicomContainer::addChild(DicomContainer* container) { m_children.push_back(container); } size_t DicomContainer::countOfChildren() { return m_children.size(); } DicomContainer* DicomContainer::getChildAt(int id) { if (id >= m_children.size()) { return nullptr; } return m_children[id]; } // DicomScanner::DicomScanner(MemoryStream& ms) : m_ms(ms) { } MemoryStream& DicomScanner::getMemoryStream() { return m_ms; } DicomField& DicomScanner::field() { return m_field; } DicomField* DicomScanner::getDirEntry() { return nullptr; } // DicomScannerLiIm::DicomScannerLiIm(MemoryStream& ms) : DicomScanner(ms) { } bool DicomScannerLiIm::next() { return false; } // DicomScannerLiEx::DicomScannerLiEx(MemoryStream& ms) : DicomScanner(ms) { } bool DicomScannerLiEx::next() { Trace t("DicomScannerLiEx::next", false); if (m_ms.isEnd() == true) { return false; } int length = 0; m_field.offset = m_ms.getPosition(); m_field.gId = m_ms.readAsUInt16(); m_field.eId = m_ms.readAsUInt16(); m_field.vr0 = m_ms.readByte(); m_field.vr1 = m_ms.readByte(); t.Printf(" %04x %04x %c%c ", m_field.gId, m_field.eId, m_field.vr0, m_field.vr1); m_field.length = 0; m_field.dataP = nullptr; switch(m_field.vr0) { case 'O': m_ms.skip(2); m_field.length = m_ms.readAsUInt32(); t.Printf("%d\n", m_field.length); break; case 'S': switch(m_field.vr1) { case 'Q': m_ms.skip(2); length = m_ms.readAsUInt32(); //m_ms.skip(4); // FFFF,FFFF t.Printf("%d\n", length); if (length == 0xffffffff) { scanSequence(m_field); } else { m_field.length = length; scanSequence(m_field); } break; default: m_field.length = m_ms.readAsUInt16(); t.Printf("%d\n", m_field.length); } break; default: m_field.length = m_ms.readAsUInt16(); t.Printf("%d\n", m_field.length); break; } m_field.dataP = m_ms.getCurrentAddress(); m_ms.skip(m_field.length); return true; } void DicomScannerLiEx::scanSequence(DicomField& parent) { Trace t("DicomScannerLiEx::scanSequence", false); DicomField child; while(m_ms.isEnd() != true) { child.offset = m_ms.getPosition(); child.gId = m_ms.readAsUInt16(); child.eId = m_ms.readAsUInt16(); t.Printf("]>%04x %04x\n", child.gId, child.eId); if ((child.gId == 0xfffe) && (child.eId == 0xe0dd)) { m_ms.skip(4); // length = 00000000H t.Printf("BREAK!!\n"); break; } unsigned long length = m_ms.readAsUInt32(); scanSequenceItem(parent, length); } } void DicomScannerLiEx::scanSequenceItem(DicomField& parent, unsigned long length) { Trace t("DicomScannerLiEx::scanSequenceItem", false); DicomField child; while(m_ms.isEnd() != true) { child.offset = m_ms.getPosition(); child.gId = m_ms.readAsUInt16(); child.eId = m_ms.readAsUInt16(); t.Printf(">>%04x %04x\n", child.gId, child.eId); if ((child.gId == 0xfffe) && (child.eId == 0xe00d)) { m_ms.skip(4); // length = 00000000H break; } child.vr0 = m_ms.readByte(); child.vr1 = m_ms.readByte(); child.length = 0; child.dataP = nullptr; switch(child.vr0) { case 'O': m_ms.skip(2); child.length = m_ms.readAsUInt32(); break; case 'S': switch(child.vr1) { case 'Q': m_ms.skip(2); m_ms.skip(4); // FFFF,FFFF scanSequence(child); child.length = 0; break; default: child.length = m_ms.readAsUInt16(); } break; default: child.length = m_ms.readAsUInt16(); break; } child.dataP = m_ms.getCurrentAddress(); m_ms.skip(child.length); parent.seq.push_back(child); } } DicomField* DicomScannerLiEx::getDirEntry() { Trace t("DicomScannerLiEx::getDirEntry", false); if (m_ms.isEnd() == true) { return nullptr; } m_field.offset = m_ms.getPosition(); m_field.gId = m_ms.readAsUInt16(); m_field.eId = m_ms.readAsUInt16(); t.Printf(" %04x %04x\n", m_field.gId, m_field.eId); if ((m_field.gId == 0x0fffe) && (m_field.eId == 0xe000)) { m_field.length = m_ms.readAsUInt32(); return &m_field; } if ((m_field.gId == 0x0fffe) && (m_field.eId == 0xe00d)) { m_field.length = m_ms.readAsUInt32(); return &m_field; } m_field.vr0 = m_ms.readByte(); m_field.vr1 = m_ms.readByte(); m_field.length = 0; m_field.dataP = nullptr; switch(m_field.vr0) { case 'O': m_ms.skip(2); m_field.length = m_ms.readAsUInt32(); t.Printf("%d\n", m_field.length); break; case 'S': switch(m_field.vr1) { case 'Q': m_ms.skip(2); m_ms.skip(4); // FFFF,FFFF t.Printf("\n"); scanSequence(m_field); break; default: m_field.length = m_ms.readAsUInt16(); t.Printf("%d\n", m_field.length); } break; default: m_field.length = m_ms.readAsUInt16(); t.Printf("%d\n", m_field.length); break; } m_field.dataP = m_ms.getCurrentAddress(); m_ms.skip(m_field.length); return &m_field; } // DicomScannerBiEx::DicomScannerBiEx(MemoryStream& ms) : DicomScanner(ms) { } bool DicomScannerBiEx::next() { return false; } // ImageDataLoaderDicom::ImageDataLoaderDicom() : m_data(nullptr), m_bytes(0) { } ImageDataLoaderDicom::~ImageDataLoaderDicom() { delete m_data; } DicomContainer* ImageDataLoaderDicom::load(char* path) { std::string file(path); if (file.find("DICOMDIR") != std::string::npos) { return loadDir(path); } else { return loadFile(path); } return nullptr; } DicomContainer* ImageDataLoaderDicom::loadDir(char* path) { Trace t("ImageDataLoaderDicom::loadDir", false); struct _stat sb; if (_stat(path, &sb) != 0) { t.Error("couldn't get file size. %s\n", path); switch (errno) { case ENOENT: t.Error("File %s not found.\n", path); break; case EINVAL: t.Error("Invalid parameter to _stat.\n"); break; default: /* Should never be reached. */ t.Error("Unexpected error in _stat.\n"); } return nullptr; } t.Printf("reading file size ... %d bytes.\n", sb.st_size); //std::unique_ptr<unsigned char[]> buf(new unsigned char[sb.st_size]); m_data = new char[sb.st_size]; m_bytes = sb.st_size; int fd = open(path, O_RDONLY | O_BINARY); if (read(fd, m_data, m_bytes) < 0) { t.Error("couldn't read.\n"); close(fd); return nullptr; } close(fd); //MemoryStream ms(buf.get(), sb.st_size); MemoryStream ms((unsigned char*)m_data, m_bytes); ms.skip(128); if ((ms.readByte() != 'D') || (ms.readByte() != 'I') || (ms.readByte() != 'C') || (ms.readByte() != 'M')) { t.Error("invalid data format.\n"); return nullptr; } DicomScanner* scanner = nullptr; // group2 DicomScannerLiEx group2(ms); while(group2.next() == true) { //group2.field().print(); printf("\n"); if (group2.field().gId != 0x0002) { break; } switch(group2.field().eId) { case 0x0010: if (!strcmp((const char*)(group2.field().dataP), "1.2.840.10008.1.2.1")) { scanner = reinterpret_cast<DicomScanner*>(new DicomScannerLiEx(ms)); t.Printf("transfer syntax: Li Ex\n"); } else if (!strcmp((const char*)(group2.field().dataP), "1.2.840.10008.1.2.2")) { scanner = reinterpret_cast<DicomScanner*>(new DicomScannerBiEx(ms)); t.Printf("transfer syntax: Bi Ex\n"); } else if (!strcmp((const char*)(group2.field().dataP), "1.2.840.10008.1.2")) { scanner = reinterpret_cast<DicomScanner*>(new DicomScannerLiIm(ms)); t.Printf("transfer syntax: Li Im\n"); } break; } } auto print_chars = [](unsigned char* cP, size_t length) { for(int i=0; i<length; i++) { printf("%c", cP[i]); } return length; }; auto print_word = [](unsigned char* cP, size_t length) { int d1 = cP[0]; int d2 = cP[1]; int d = (cP[1] << 8) | cP[0]; printf("%d (0x%04x)", d, d); return d; }; auto print_dword = [](unsigned char* cP, size_t length) { int d1 = cP[0]; int d2 = cP[1]; int d3 = cP[2]; int d4 = cP[3]; int d = (cP[3] << 24) | (cP[2] << 16) | (cP[1] << 8) | cP[0]; //printf("%d (0x%08x)", d, d); //printf("\n%d (0x%08x)\n", d, d); return d; }; // group4 bool done = false; DicomContainer* dir = new DicomContainer(); size_t offset = 0; while(scanner->next() == true) { //scanner->field().print(); if (scanner->field().gId != 0x0004) { break; } switch(scanner->field().eId) { case 0x1130: //print_chars(scanner->field().dataP, scanner->field().length); break; case 0x1200: offset = print_dword(scanner->field().dataP, scanner->field().length); loadTree(dir, scanner, offset); done = true; break; case 0x1202: //print_dword(scanner->field().dataP, scanner->field().length); break; case 0x1212: //print_word(scanner->field().dataP, scanner->field().length); break; } //t.Printf("\n"); if (done) { break; } } for(int i=0; i < dir->countOfChildren(); i++) { //printNode(dir->getChildAt(i)); } t.Printf("%s loaded.\n", path); return dir; } void ImageDataLoaderDicom::loadTree(DicomContainer* parent, DicomScanner* scanner, size_t offset) { Trace t("ImageDataLoaderDicom::loadTree", false); t.Printf("loadTree(%08zx)\n", offset); scanner->getMemoryStream().setPosition(offset); DicomContainer* child = new DicomContainer(); parent->addChild(child); DicomField* f = nullptr; while((f=scanner->getDirEntry()) != nullptr) { if (scanner->field().gId == 0xfffe) { if (scanner->field().eId == 0xe00d) { break; } } else { child->addField(scanner->field()); //printf("::"); scanner->field().print(); printf("\n"); } } // walk down to child. DicomField* grandChildP = child->find(0x0004, 0x1420); if (grandChildP == nullptr) { //printf("no children.\n"); return; } size_t grandChildP_offset = KO2_DWORD(grandChildP->dataP[3], grandChildP->dataP[2], grandChildP->dataP[1], grandChildP->dataP[0]); loadTree(child, scanner, grandChildP_offset); // move next block DicomField* nextP = child->find(0x0004, 0x1400); if (nextP == nullptr) { return; } size_t next_offset = KO2_DWORD(nextP->dataP[3], nextP->dataP[2], nextP->dataP[1], nextP->dataP[0]); if (next_offset != 0) { loadTree(parent, scanner, next_offset); } } struct DicomTagDictionary_s { std::uint16_t gId; std::uint16_t eId; const char* name; }; void ImageDataLoaderDicom::printNode(DicomContainer* node) { Trace t("ImageDataLoaderDicom::printNode", false); static struct DicomTagDictionary_s patient[] = { { 0x0010, 0x0010, "Patient Name" }, { 0x0010, 0x0020, "Patient ID" }, { 0x0010, 0x0030, "Birth Date" }, { 0, 0, nullptr } }; static struct DicomTagDictionary_s study[] = { { 0x0008, 0x0020, "Study Date" }, { 0x0008, 0x0030, "Study Time" }, { 0x0008, 0x0050, "Accession No" }, { 0x0008, 0x1030, "Study Description" }, { 0x0020, 0x000d, "Study Instance UID" }, { 0x0020, 0x0010, "Study UID" }, { 0, 0, nullptr } }; static struct DicomTagDictionary_s series[] = { { 0x0008, 0x0021, "Series Date" }, { 0x0008, 0x0031, "Series Time" }, { 0x0008, 0x0060, "Modality" }, { 0x0008, 0x103e, "Series Description" }, { 0x0020, 0x000e, "Series Instance UID" }, { 0x0020, 0x0011, "Series Number" }, { 0, 0, nullptr } }; static struct DicomTagDictionary_s image[] = { { 0x0004, 0x1500, "File Id" }, { 0x0004, 0x1510, "Ref. SOP Class UID" }, { 0x0004, 0x1511, "Ref. SOP Instance UID" }, { 0x0004, 0x1512, "Transfer Syntax" }, { 0x0008, 0x0016, "SOP Class UID" }, { 0x0008, 0x0018, "SOP Instance UID" }, { 0x0020, 0x0013, "Instance Number" }, { 0, 0, nullptr } }; struct DicomTagDictionary_s* infoP = nullptr; DicomField* typeP = node->find(0x0004, 0x1430); if (typeP == nullptr) { return; } if (!strncmp((char*)typeP->dataP, "PATIENT", 7)) { infoP = patient; } else if (!strncmp((char*)typeP->dataP, "STUDY", 5)) { infoP = study; } else if (!strncmp((char*)typeP->dataP, "SERIES", 6)) { infoP = series; } else if (!strncmp((char*)typeP->dataP, "IMAGE", 5)) { infoP = image; } else { t.Warning("unknown type.\n"); DicomVR vr(typeP); vr.print(); return; } DicomVR vr; DicomField* f = nullptr; for(int i=0; infoP[i].name != nullptr; i++) { f = node->find(infoP[i].gId, infoP[i].eId); if (f != nullptr) { t.Printf("[%-20s] ", infoP[i].name); vr.load(f); vr.print(); } } for(int i=0; i<node->countOfChildren(); i++) { printNode(node->getChildAt(i)); } } DicomContainer* ImageDataLoaderDicom::loadFile(char* path) { Trace t("ImageDataLoaderDicom::loadFile", false); struct _stat sb; if (_stat(path, &sb) != 0) { t.Error("couldn't get file size. %s\n", path); switch (errno) { case ENOENT: t.Error("File %s not found.\n", path); break; case EINVAL: t.Error("Invalid parameter to _stat.\n"); break; default: /* Should never be reached. */ t.Error("Unexpected error in _stat.\n"); } return nullptr; } t.Printf("load file size ... %d\n", sb.st_size); m_data = new char[sb.st_size]; m_bytes = sb.st_size; int fd = open(path, O_RDONLY | O_BINARY); if (read(fd, m_data, m_bytes) < 0) { t.Error("couldn't read.\n"); close(fd); return nullptr; } close(fd); MemoryStream ms((unsigned char*)m_data, m_bytes); ms.skip(128); if ((ms.readByte() != 'D') || (ms.readByte() != 'I') || (ms.readByte() != 'C') || (ms.readByte() != 'M')) { t.Error("invalid data format.\n"); return nullptr; } DicomScanner* scanner = nullptr; // group2 DicomScannerLiEx group2(ms); while(group2.next() == true) { //group2.field().print(); printf("\n"); if (group2.field().gId != 0x0002) { break; } switch(group2.field().eId) { case 0x0010: if (!strcmp((const char*)(group2.field().dataP), "1.2.840.10008.1.2.1")) { scanner = reinterpret_cast<DicomScanner*>(new DicomScannerLiEx(ms)); t.Printf("transfer syntax: Li Ex\n"); } else if (!strcmp((const char*)(group2.field().dataP), "1.2.840.10008.1.2.2")) { scanner = reinterpret_cast<DicomScanner*>(new DicomScannerBiEx(ms)); t.Printf("transfer syntax: Bi Ex\n"); } else if (!strcmp((const char*)(group2.field().dataP), "1.2.840.10008.1.2")) { scanner = reinterpret_cast<DicomScanner*>(new DicomScannerLiIm(ms)); t.Printf("transfer syntax: Li Im\n"); } break; } } bool done = false; DicomContainer* dir = new DicomContainer(); size_t offset; while(scanner->next() == true) { //scanner->field().print(); printf("\n"); dir->addField(scanner->field()); } return dir; } } } // PixelConverter::PixelConverter(int dbpp, int sbpp, int w, int h, int pr) : m_dbpp(dbpp), m_desP(nullptr), m_sbpp(sbpp), m_srcP(nullptr), m_w(w), m_h(h), m_pr(pr) { if (m_dbpp != m_sbpp) { m_desP = new char[m_w * m_h * m_dbpp]; } } PixelConverter::~PixelConverter() { delete[] m_desP; } char* PixelConverter::convert(char* srcP) { if (m_dbpp == m_sbpp) { return srcP; // nothing to do because the bpp is same. } else { if (m_sbpp == 1) { if (m_dbpp == 2) { // 1byte -> 2byte char* sP = m_srcP; unsigned short* dP = (unsigned short*)m_desP; for(int i=0; i<(m_w * m_h); i++) { dP[i] = (unsigned short)sP[i]; } } else { printf("unknown output image pixel format.\n"); return nullptr; } } else if (m_sbpp == 2) { if (m_dbpp == 1) { // 2byte -> 1byte unsigned short* sP = (unsigned short*)m_srcP; char* dP = m_desP; for(int i=0; i<(m_w * m_h); i++) { dP[i] = (char)sP[i]; } } else { printf("unknown output image pixel format.\n"); return nullptr; } } else { printf("unknown input image pixel format.\n"); return nullptr; } return m_desP; } } int PixelConverter::bytes() { return m_w * m_h * m_dbpp; } // ImageData::ImageData() : m_w(0), m_h(0), m_bpp(0), m_pixel_representation(0), m_pixels(nullptr), m_pmin(0), m_pmax(0) { } ImageData::~ImageData() { delete[] m_pixels; } ImageData* ImageData::Clone() { ImageData* clone = new ImageData(); clone->m_w = m_w; clone->m_h = m_h; clone->m_pixel_representation = m_pixel_representation; clone->m_pmin = m_pmin; clone->m_pmax = m_pmax; if (m_pixels == nullptr) { clone->m_pixels = nullptr; } else { int bytes = 2 * m_w * m_h; clone->m_pixels = new char[bytes]; memcpy(clone->m_pixels, m_pixels, bytes); } return clone; } void ImageData::load(int w, int h, int bpp, int pixel_representation, char* pixels) { m_w = w; m_h = h; m_bpp = bpp; m_pixel_representation = pixel_representation; PixelConverter pc(2, bpp/8, m_w, m_h, m_pixel_representation); m_pixels = new char[pc.bytes()]; memcpy(m_pixels, pc.convert(pixels), pc.bytes()); if (m_pixel_representation == 0) { // unsigned unsigned short* p = (unsigned short*)m_pixels; m_pmin = p[0]; m_pmax = p[0]; for(int i=0; i<m_w * m_h; i++) { if (m_pmin > p[i]) { m_pmin = p[i]; } if (m_pmax < p[i]) { m_pmax = p[i]; } } } else { // singned short* p = (short*)m_pixels; m_pmin = p[0]; m_pmax = p[0]; for(int i=0; i<m_w * m_h; i++) { if (m_pmin > p[i]) { m_pmin = p[i]; } if (m_pmax < p[i]) { m_pmax = p[i]; } } } printf("prange=[%d, %d]\n", m_pmin, m_pmax); } int ImageData::minPixelValue() { return m_pmin; } int ImageData::maxPixelValue() { return m_pmax; } int ImageData::width() { return m_w; } int ImageData::height() { return m_h; } int ImageData::bitPerPixel() { return m_bpp; } int ImageData::pixelRepresentation() { return m_pixel_representation; } char* ImageData::pixels() { return m_pixels; } // void Statistics::load(ImageData* data) { void* pixels = (void*)data->pixels(); int pcount = data->width() * data->height(); auto setup = [this](int lower, int upper) { m_prange[0] = lower; m_prange[1] = upper; for(int i=lower; i<=upper; i++) { m_data.insert(std::make_pair(i, 0)); } }; if (data->pixelRepresentation() == 0) { // unsigned if (data->bitPerPixel() == 8) { setup( 0, 255); _load_pixels<uint8_t>((uint8_t*)pixels, pcount); } else if (data->bitPerPixel() == 16) { setup( 0, 65535); load_pixels<uint16_t>((uint16_t*)pixels, pcount); } } else { // signed if (data->bitPerPixel() == 8) { setup(-128, 127); load_pixels<uint8_t>((int8_t*)pixels, pcount); } else if (data->bitPerPixel() == 16) { setup(-32768, 32767); load_pixels<uint8_t>((int16_t*)pixels, pcount); } } } // ImageModule::ImageModule() { } ImageModule::ImageModule(char* home, ko2::io::file::DicomContainer* node) { ko2::io::file::DicomVR vr; std::unique_ptr<char[]> path(new char[1024]); ko2::io::file::DicomField* f = node->find(0x0004, 0x1500); // file if (f == nullptr) { printf("No file found.\n"); return; } vr.load(f); const char* imgfile = vr.toString(); printf("loading ... %s\n", imgfile); sprintf_s(path.get(), 1024, "%s\\%s", home, imgfile); ko2::io::file::ImageDataLoaderDicom* loader = new ko2::io::file::ImageDataLoaderDicom(); ko2::io::file::DicomContainer *dcm = loader->load((char*)path.get()); int w = 0; int h = 0; int samples_per_pixel = 0; int allocated = 0; int stored = 0; int highbit = 0; char photometric[64]; unsigned long imgbytes = 0; char* imgdata = nullptr; int pixel_representation = 0; char done[48] = "...................."; int op = 0; // [ 0] Samples per Pixel, 画素あたりサンプル, 1, US, 2 f = dcm->find(0x0028,0x0002); if (f != nullptr) { vr.load(f); samples_per_pixel = vr.readAsUInt16(); done[op] = '#'; } op++; // [ 1] Photometric Interpretation, 光度測定解釈, 1, CS, 12 f = dcm->find(0x0028,0x0004); if (f != nullptr) { vr.load(f); strcpy(photometric, vr.toString()); done[op] = '#'; } op++; // [ 2] Rows 行 1 US 2 f = dcm->find(0x0028,0x0010); if (f != nullptr) { vr.load(f); h = vr.readAsUInt16(); done[op] = '#'; } op++; // [ 3] Columns 列 1 US 2 f = dcm->find(0x0028,0x0011); if (f != nullptr) { vr.load(f); w = vr.readAsUInt16(); done[op] = '#'; } op++; // [ ?] Pixel Spacing 画素間隔 3 DS 12 //f = dcm->find(0x0028,0x0030); // [ 4] Bits Allocated 割当ビット 1 US 2 f = dcm->find(0x0028,0x0100); if (f != nullptr) { vr.load(f); allocated = vr.readAsUInt16(); done[op] = '#'; } op++; // [ 5] Bits Stored 格納ビット 1 US 2 f = dcm->find(0x0028,0x0101); if (f != nullptr) { vr.load(f); stored = vr.readAsUInt16(); done[op] = '#'; } op++; // [ 6] High Bit 高位ビット 1 US 2 f = dcm->find(0x0028,0x0102); if (f != nullptr) { vr.load(f); highbit = vr.readAsUInt16(); done[op] = '#'; } op++; // [ 7] Pixel Representation 画素表現 1 US 2 f = dcm->find(0x0028,0x0103); if (f != nullptr) { vr.load(f); pixel_representation = vr.readAsUInt16(); done[op] = '#'; } op++; // [ 8] Number of Frames f = dcm->find(0x0028,0x0008); int nframes = 0; if (f != nullptr) { vr.load(f); nframes = strtol(vr.toString(), 0, 0); done[op] = '#'; } op++; // [ 9] Frame Increament Pointer unsigned short fiGid = 0; unsigned short fiEid = 0; f = dcm->find(0x0028,0x0009); if (f != nullptr) { vr.load(f); fiGid = vr.readAsUInt16(); fiEid = vr.readAsUInt16(1); done[op] = '#'; } op++; // [10] Pixel data f = dcm->find(0x7fe0,0x0010); if (f != nullptr) { imgdata = (char*)f->dataP; imgbytes = f->length; done[op] = '#'; } op++; printf("image:(%d,%d), %d(%04x,%04x), %d(%d,%d:%d), (%s,%d), (%d bytes)\n%s\n", w, h, nframes, fiGid, fiEid, allocated, stored, highbit, pixel_representation, photometric, samples_per_pixel, imgbytes, done); if (nframes == 0) { ImageData* id = new ImageData(); id->load(w, h, allocated, pixel_representation, imgdata); m_frames.push_back(id); } else { char* pframe = (char*)imgdata; int framesize = w * h * (allocated / 8); for(int j=0; j<nframes; j++) { ImageData* id = new ImageData(); id->load(w, h, allocated, pixel_representation, pframe); m_frames.push_back(id); pframe += framesize; } } printf("%d loaded.\n", count()); } ImageModule::~ImageModule() { for(int i=0; i<m_frames.size(); i++) { delete m_frames[i]; } } void ImageModule::add(ImageData* imgData) { m_frames.push_back(imgData); } int ImageModule::count() { return m_frames.size(); } ImageData* ImageModule::getFrameAt(int i) { return m_frames[i]; } // ImageViewWindow::ImageViewWindow() { m_guiThread = NULL; m_wakeup = CreateEvent(NULL , TRUE , FALSE, "WakeUp"); m_hWnd = NULL; m_w = 0; m_h = 0; m_wl = 0; m_ww = 1024; m_need_to_rebuild_lut = true; m_pixel_representation = 1; m_module = nullptr; m_fb = nullptr; _rebuildLut(); _loadCmap(); } ImageViewWindow::~ImageViewWindow() { if (m_guiThread != NULL) { WaitForSingleObject( m_guiThread, INFINITE ); } delete[] m_fb; } void ImageViewWindow::closeWindow() { SendMessage(m_hWnd, WM_APP_QUIT, 0, 0); } void ImageViewWindow::displayImage(ImageModule* module) { _wakeupThread(); SendMessage(m_hWnd, WM_APP_DISPLAY_IMAGE, (WPARAM)module, 0); } int ImageViewWindow::wl() { return m_wl; } int ImageViewWindow::wl(int v) { m_wl = v; m_need_to_rebuild_lut = true; return wl(); } int ImageViewWindow::ww() { return m_ww; } int ImageViewWindow::ww(int v) { m_ww = v; m_need_to_rebuild_lut = true; return ww(); } void ImageViewWindow::update() { InvalidateRect(m_hWnd, NULL, FALSE); UpdateWindow(m_hWnd); } void ImageViewWindow::_wakeupThread() { if (m_guiThread == NULL) { ResetEvent(m_wakeup); m_guiThread = (HANDLE)_beginthreadex( NULL, 0, ImageViewWindow::_thread, (void*)this, 0, &m_threadId ); WaitForSingleObject(m_wakeup, INFINITE); } ShowWindow(m_hWnd, SW_SHOW); } void ImageViewWindow::_loadCmap() { for(int i=0; i<255; i++) { m_cmap[i] = FRAMEBUFFER_RGB(255, i, i, i); } } void ImageViewWindow::_rebuildLut() { if (m_need_to_rebuild_lut == false) { return; } // value range int pmin = 0; int pmax = 0; if (m_pixel_representation == 0) { pmin = 0; pmax = 65536; } else { pmin = -32768; pmax = 32767; } m_bias = (-1) * pmin; // check lut if (m_ww < 1) { m_ww = 1; } if (m_wl < pmin) { m_wl = pmin; } else if (m_wl > pmax) { m_wl = pmax; } // build int lower = m_wl - (m_ww / 2); int upper = lower + m_ww; float slope = 255.0/(float)m_ww; float x = 0, y = 0; int i = 0; for(i=pmin; i<=lower; i++) { m_lut[i+m_bias] = 0; } x = 0; for(; i<=upper; i++, x++) { y = slope * x; m_lut[i+m_bias] = (int)y; } for(; i<pmax; i++) { m_lut[i+m_bias] = 255; } m_need_to_rebuild_lut = false; } void ImageViewWindow::_showImage(ImageModule* module) { m_module = module; m_cursor = 0; int pmin = m_module->getFrameAt(0)->minPixelValue(); int pmax = m_module->getFrameAt(0)->maxPixelValue(); int wl = (pmin + pmax)/2; int ww = pmax - pmin; m_wl = wl; m_ww = ww; m_need_to_rebuild_lut = true; update(); } void ImageViewWindow::_drawImage(HDC hDC) { ko2::Trace t("ImageViewWindow::_drawImage", false); char title[128]; sprintf_s(title, sizeof(title), "WL:%d, WW:%d", m_wl, m_ww); SetWindowText(m_hWnd, title); if (m_module == nullptr) { t.Error("no loaded image.\n"); return; } ImageData* imgdata = m_module->getFrameAt(m_cursor); if (imgdata == nullptr) { t.Error("no image data.\n"); return; } if ((m_need_to_rebuild_lut) || (m_pixel_representation != imgdata->pixelRepresentation())) { m_pixel_representation = imgdata->pixelRepresentation(); m_need_to_rebuild_lut = true; _rebuildLut(); } int iw = imgdata->width(); int ih = imgdata->height(); float scale = 1.0; // W = s * I float scaleW = (float)m_w / (float)iw; float scaleH = (float)m_h / (float)ih; if (scaleW > scaleH) { scale = scaleW; } else { scale = scaleH; } float ix0 = ((float)iw / 2.0) - ((float)m_w / 2.0) * (1.0/scale); float iy0 = ((float)ih / 2.0) - ((float)m_h / 2.0) * (1.0/scale); float xinc = 1.0 * (1.0 / scale); float yinc = 1.0 * (1.0 / scale); // I : W S : ix0 : xinc // 256 256 : 512 512 => 2.0 : 128 - 256 * 1.0/2.0 = 0 : 1 * 1.0/2.0 = 0.5 // 512 512 : 512 512 => 1.0 : 256 - 256 * 1.0/1.0 = 0 : 1 * 1.0/1.0 = 1.0 // 1024 1024 : 512 512 => 0.5 : 512 - 256 * 1.0/0.5 = 0 : 1 * 1.0/0.5 = 2.0 //printf("scale %f\n", scale); //printf("ix0 %f, iy0 %f\n", ix0, iy0); //printf("xinc %f, yinc %f\n", xinc, yinc); short *sP = (short*)imgdata->pixels(); unsigned long *dP = (unsigned long *)m_fb; float ix = ix0; float iy = iy0; for(int dy=0; dy<m_h; dy++) { for(int dx=0; dx<m_w; dx++) { int sx = int(ix + 0.5); int sy = int(iy + 0.5); if ((sx >= 0) && (sx < iw) && (sy >= 0) && (sy < ih)) { int pvalue = m_cmap[m_lut[m_bias + sP[sx + sy * iw]]]; dP[dx + dy * m_w + 0] = pvalue; } else { dP[dx + dy * m_w] = 0; } ix += xinc; } ix = ix0; iy += yinc; } BITMAPINFO bi; bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biWidth = m_w; bi.bmiHeader.biHeight = -m_h; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biSizeImage = m_w * m_h * 4; bi.bmiHeader.biXPelsPerMeter = 0; bi.bmiHeader.biYPelsPerMeter = 0; bi.bmiHeader.biClrUsed = 0; bi.bmiHeader.biClrImportant = 0; SetDIBitsToDevice(hDC, 0, 0, m_w, m_h, 0, 0, 0, m_h, m_fb, &bi, DIB_RGB_COLORS); } void ImageViewWindow::_moveCursor(int inc) { m_cursor += inc; if (m_cursor < 0) { m_cursor = 0; } else if (m_cursor >= m_module->count()) { m_cursor = m_module->count() - 1; } update(); } LRESULT CALLBACK ImageViewWindow::_wndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { ImageViewWindow* imgview = (ImageViewWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA); static int xmouse = 0, px = 0; static int ymouse = 0, py = 0; static int dragging = 0; switch (msg) { case WM_APP_DISPLAY_IMAGE: { ImageModule* module = (ImageModule*)wp; if (imgview != NULL) { imgview->_showImage(module); } } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hDC = BeginPaint(hWnd, &ps); if (imgview != NULL) { imgview->_drawImage(hDC); } EndPaint(hWnd, &ps); } break; case WM_TIMER: break; case WM_MOUSEWHEEL: { int zDelta = GET_WHEEL_DELTA_WPARAM(wp); if (imgview != NULL) { if (zDelta < 0) { imgview->_moveCursor(-1); } else { imgview->_moveCursor( 1); } } } break; case WM_RBUTTONDOWN: xmouse = GET_X_LPARAM(lp); ymouse = GET_Y_LPARAM(lp); dragging = TRUE; break; case WM_RBUTTONUP: dragging = FALSE; break; case WM_MOUSEMOVE: if (dragging == TRUE) { px = GET_X_LPARAM(lp); py = GET_Y_LPARAM(lp); int dx = xmouse - px; int dy = ymouse - py; if (imgview != NULL) { imgview->wl(imgview->wl() + dy); imgview->ww(imgview->ww() + dx); imgview->update(); } xmouse = px; ymouse = py; } break; case WM_APP_QUIT: DestroyWindow(hWnd); break; case WM_CLOSE: ShowWindow(hWnd, SW_HIDE); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; } BOOL ImageViewWindow::_setClientSize(int w, int h) { RECT rw, rc; ::GetWindowRect(m_hWnd, &rw); ::GetClientRect(m_hWnd, &rc); m_w = w; m_h = h; int nw = (rw.right - rw.left) - (rc.right - rc.left) + m_w; int nh = (rw.bottom - rw.top) - (rc.bottom - rc.top) + m_h; delete m_fb; m_fb = new char[m_w * m_h * 4]; return ::SetWindowPos(m_hWnd, NULL, 0, 0, nw, nh, SWP_NOMOVE | SWP_NOZORDER); } unsigned __stdcall ImageViewWindow::_thread(void *pArg) { ko2::Trace t("ImageViewWindow::_thread", false); ImageViewWindow *imgview = (ImageViewWindow*)pArg; static char* szClassName = "DcmDirImgWin"; imgview->m_hInst = GetModuleHandle(NULL); WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = ImageViewWindow::_wndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = imgview->m_hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; if (!RegisterClass(&wc)) { printf("couldn't register window class.\n"); } imgview->m_hWnd = CreateWindow( szClassName, NULL, //タイトルバーにこの名前が表示されます WS_CAPTION | WS_SYSMENU, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 512, //幅 512, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL imgview->m_hInst, //インスタンスハンドル NULL); if (!imgview->m_hWnd) { printf("couldn't create a window.\n"); } imgview->_setClientSize(512, 512); ShowWindow(imgview->m_hWnd, SW_SHOW); UpdateWindow(imgview->m_hWnd); SetWindowLongPtr(imgview->m_hWnd, GWLP_USERDATA, (LONG_PTR)imgview); SetEvent(imgview->m_wakeup); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } _endthreadex( msg.wParam ); return 0; } //////////////////////////////////////////////////////////////////////////////// // 処理本体 ko2::io::file::DicomContainer *findContainer(int depth, int* index) { ko2::io::file::DicomContainer *container0 = nullptr; ko2::io::file::DicomContainer *container1 = nullptr; ko2::io::file::DicomContainer *container2 = nullptr; ko2::io::file::DicomContainer *container3 = nullptr; switch(depth) { case 0: return pcontext.root; case 1: container0 = pcontext.root->getChildAt(index[0]); return container0; case 2: container0 = pcontext.root->getChildAt(index[0]); if (container0 == nullptr) { goto error; } container1 = container0->getChildAt(index[1]); return container1; case 3: container0 = pcontext.root->getChildAt(index[0]); if (container0 == nullptr) { goto error; } container1 = container0->getChildAt(index[1]); if (container1 == nullptr) { goto error; } container2 = container1->getChildAt(index[2]); return container2; case 4: container0 = pcontext.root->getChildAt(index[0]); if (container0 == nullptr) { goto error; } container1 = container0->getChildAt(index[1]); if (container1 == nullptr) { goto error; } container2 = container1->getChildAt(index[2]); if (container2 == nullptr) { goto error; } container3 = container2->getChildAt(index[3]); return container3; } error: return nullptr; } ko2::io::file::DicomContainer *findContainer() { return findContainer(pcontext.path.depth, pcontext.path.index); } const char* path2string(int depth, int* index) { static char path[128]; int index0 = index[0] + 1; int index1 = index[1] + 1; int index2 = index[2] + 1; int index3 = index[3] + 1; switch(depth) { case 0: strcpy(path, "/"); break; case 1: sprintf_s(path, sizeof(path), "/%d", index0); break; case 2: sprintf_s(path, sizeof(path), "/%d/%d", index0, index1); break; case 3: sprintf_s(path, sizeof(path), "/%d/%d/%d", index0, index1, index2); break; case 4: sprintf_s(path, sizeof(path), "/%d/%d/%d/%d", index0, index1, index2, index3); break; } return path; } const char* path2string() { return path2string(pcontext.path.depth, pcontext.path.index); } void proc_help(ko2::Arg& args) { static char* h[] = { "The following commands are available:", " config : display configuration.", " config <name> : display <name> configuration value.", " config <n> <v> : set <v> value to <n> name.", " tree : display tree.", " list : list data of current level.", " level : display current level.", " level <...> : change level to <...>.", " show <image> : display image.", " show <series> : display images of <series> if image is.", " info : display information of this level.", " export <outdir> : export images of <series> to output folder.", " show <img> : display image data.", " help : display this message.", " quit : quit this program", " exit : same as quit.", "The level is represented as the follows:", " /patient/study/series/image", " e.g.", " / : root", " /1 : patient=1", " /1/2 : patient=1, study=2", " /1/2/3 : patient=1, study=2, series=3", " /1/2/3/4 : patient=1, study=2, series=3, image=4", nullptr, }; for(int i=0; h[i] != nullptr; i++) { printf("%s\n", h[i]); } } void proc_config(ko2::Arg& arg) { switch(arg.Count()) { case 1: printf("home = '%s'\n", pcontext.home); break; case 2: if (!strcmp(arg.GetAt(1), "home")) { printf("home = '%s'\n", pcontext.home); } break; case 3: break; } } void proc_list(ko2::Arg& args) { const char *currentPath = path2string(); printf("%s:\n", path2string()); ko2::io::file::DicomContainer *dir = findContainer(); if (dir == nullptr) { printf("invalid path.\n"); return; } ko2::io::file::DicomContainer *node = nullptr; ko2::io::file::DicomField* f = nullptr; ko2::io::file::DicomVR vr; char tmp[128]; auto print_field = [tmp](ko2::io::file::DicomContainer *node, unsigned short gId, unsigned short eId) { ko2::io::file::DicomField* f = nullptr; ko2::io::file::DicomVR vr; f = node->find(gId, eId); if (f != nullptr) { vr.load(f); strcpy((char*)tmp, vr.toString()); return tmp; } else { return "<no data>"; } }; switch(pcontext.path.depth) { case 0: for(int i=0; i < dir->countOfChildren(); i++) { printf("%4d : ", i+1 ); node = dir->getChildAt(i); // patient name printf("%-40s ", print_field(node, 0x0010, 0x0010)); // patient id printf("[%-20s] ", print_field(node, 0x0010, 0x0020)); printf("\n"); } break; case 1: for(int i=0; i < dir->countOfChildren(); i++) { printf("%4d : ", i+1 ); node = dir->getChildAt(i); // study uid printf("%s ", print_field(node, 0x0020, 0x0010)); // study date printf("%s ", print_field(node, 0x0008, 0x0020)); // study time printf("%s ", print_field(node, 0x0008, 0x0030)); // study Description printf("%s ", print_field(node, 0x0008, 0x1030)); printf("\n"); } break; case 2: for(int i=0; i < dir->countOfChildren(); i++) { printf("%4d : ", i+1 ); node = dir->getChildAt(i); // Series Number printf("[%-10s] ", print_field(node, 0x0020, 0x0011)); // Modality printf("%s ", print_field(node, 0x0008, 0x0060)); // Series Date printf("%s ", print_field(node, 0x0008, 0x0021)); // Series Time printf("%s ", print_field(node, 0x0008, 0x0031)); // Series Description printf("%s ", print_field(node, 0x0008, 0x103e)); // Number of image printf("(%zd images) ", node->countOfChildren()); printf("\n"); } break; case 3: for(int i=0; i < dir->countOfChildren(); i++) { printf("%4d : ", i+1 ); node = dir->getChildAt(i); // File Id printf("%s ", print_field(node, 0x0004, 0x1500)); printf("\n"); } break; default: for(int i=0; i < dir->countOfChildren(); i++) { printf("%4d - \n", i+1 ); // } break; } } void tree_down(int depth, int* level, ko2::io::file::DicomContainer* dir) { //printf("tree_down(%d,[%d,%d,%d,%d], 0x%p)\n", depth, level[0],level[1],level[2],level[3], dir); const char* basepath = nullptr; if (depth == 0) { basepath = ""; } else { basepath = path2string(depth, level); } printf("%s\n", basepath); if (depth < 4) { for(int i=0; i<dir->countOfChildren(); i++) { level[depth] = i; tree_down(depth+1, level, dir->getChildAt(i)); } } } void proc_tree(ko2::Arg& args) { int level[4]; for(int i=0; i<4; i++) { level[i] = pcontext.path.index[i]; } ko2::io::file::DicomContainer *dir = findContainer(); if (dir == nullptr) { printf("invalid path.\n"); return; } tree_down(pcontext.path.depth, level, dir); } void proc_level(ko2::Arg& args) { if (args.Count() > 1) { char* arg1 = (char*)args.GetAt(1); if (args.GetAt(1)[0] != '/') { printf("format is wrong.\n"); return; } ko2::Arg path((char*)args.GetAt(1), strlen(args.GetAt(1)), '/'); pcontext.path.depth = path.Count(); for(int i=0; i<path.Count(); i++) { pcontext.path.index[i] = strtol(path.GetAt(i), 0, 0) - 1; } } printf("%s\n", path2string()); } void proc_info(ko2::Arg& args) { static struct ko2::io::file::DicomTagDictionary_s patient[] = { { 0x0010, 0x0010, "Patient Name" }, { 0x0010, 0x0020, "Patient ID" }, { 0x0010, 0x0030, "Birth Date" }, { 0, 0, nullptr } }; static struct ko2::io::file::DicomTagDictionary_s study[] = { { 0x0008, 0x0020, "Study Date" }, { 0x0008, 0x0030, "Study Time" }, { 0x0008, 0x0050, "Accession No" }, { 0x0008, 0x1030, "Study Description" }, { 0x0020, 0x000d, "Study Instance UID" }, { 0x0020, 0x0010, "Study UID" }, { 0, 0, nullptr } }; static struct ko2::io::file::DicomTagDictionary_s series[] = { { 0x0008, 0x0021, "Series Date" }, { 0x0008, 0x0031, "Series Time" }, { 0x0008, 0x0060, "Modality" }, { 0x0008, 0x103e, "Series Description" }, { 0x0020, 0x000e, "Series Instance UID" }, { 0x0020, 0x0011, "Series Number" }, { 0, 0, nullptr } }; static struct ko2::io::file::DicomTagDictionary_s image[] = { { 0x0004, 0x1500, "File Id" }, { 0x0004, 0x1510, "Ref. SOP Class UID" }, { 0x0004, 0x1511, "Ref. SOP Instance UID" }, { 0x0004, 0x1512, "Transfer Syntax" }, { 0x0008, 0x0016, "SOP Class UID" }, { 0x0008, 0x0018, "SOP Instance UID" }, { 0x0020, 0x0013, "Instance Number" }, { 0, 0, nullptr } }; ko2::io::file::DicomContainer *node = findContainer(); if (node == nullptr) { printf("invalid level.\n"); return; } /* printf("node--\n"); for(int i=0; i<node->countOfFields(); i++) { ko2::io::file::DicomField* f1 = node->getFieldAt(i); ko2::io::file::DicomVR vr1; f1->print();printf("\n"); vr1.load(f1); vr1.print(); } printf("--node\n"); */ ko2::io::file::DicomField* typeP = node->find(0x0004, 0x1430); if (typeP == nullptr) { return; } printf("%s:\n", path2string()); struct ko2::io::file::DicomTagDictionary_s* infoP = nullptr; switch(pcontext.path.depth) { case 0: return; case 1: infoP = patient; break; case 2: infoP = study; break; case 3: infoP = series; break; case 4: infoP = image; break; } ko2::io::file::DicomVR vr; ko2::io::file::DicomField* f = nullptr; for(int i=0; infoP[i].name != nullptr; i++) { f = node->find(infoP[i].gId, infoP[i].eId); if (f != nullptr) { printf("[%-20s] ", infoP[i].name); vr.load(f); vr.print(); } } } void proc_export(ko2::Arg& args) { ko2::Trace t("proc_export", false); if (pcontext.path.depth < 3) { printf("invalid level. please specify the series level.\n"); return; } if (args.Count() < 2) { printf("output folder is not specified.\n"); printf("usage: export <outdir> [<no-op>]\n"); printf(" <no-op>; 0=checking, 1=exporting(default)\n"); return; } bool operation = true; if (args.Count() == 3) { if (strtol(args.GetAt(2), 0, 0) == 0) { operation = false; printf("checking only\n"); } else { operation = true; } } ko2::io::file::DicomContainer *dir = findContainer(); if (dir == nullptr) { printf("invalid level.\n"); return; } ko2::io::file::DicomVR vr; std::unique_ptr<char[]> path(new char[1024]); for(int i=0; i<dir->countOfChildren(); i++) { ko2::io::file::DicomContainer *node = dir->getChildAt(i); ko2::io::file::DicomField* f = node->find(0x0004, 0x1500); // file if (f != nullptr) { ko2::io::file::DicomVR vr; vr.load(f); const char* imgfile = vr.toString(); printf("loading ... %s\n", imgfile); sprintf_s(path.get(), 1024, "%s\\%s", pcontext.home, imgfile); ko2::io::file::ImageDataLoaderDicom* loader = new ko2::io::file::ImageDataLoaderDicom(); ko2::io::file::DicomContainer *dcm = loader->load((char*)path.get()); ko2::io::file::DicomField *f = nullptr; int w = 0; int h = 0; int samples_per_pixel = 0; int allocated = 0; int stored = 0; int highbit = 0; char photometric[64]; unsigned long imgbytes = 0; unsigned char* imgdata = nullptr; int pixel_representation = 0; // Samples per Pixel, 画素あたりサンプル, 1, US, 2 f = dcm->find(0x0028,0x0002); if (f != nullptr) { vr.load(f); samples_per_pixel = vr.readAsUInt16(); } // Photometric Interpretation, 光度測定解釈, 1, CS, 12 f = dcm->find(0x0028,0x0004); if (f != nullptr) { vr.load(f); strcpy(photometric, vr.toString()); } // Rows 行 1 US 2 f = dcm->find(0x0028,0x0010); if (f != nullptr) { vr.load(f); h = vr.readAsUInt16(); } // Columns 列 1 US 2 f = dcm->find(0x0028,0x0011); if (f != nullptr) { vr.load(f); w = vr.readAsUInt16(); } // Pixel Spacing 画素間隔 3 DS 12 //f = dcm->find(0x0028,0x0030); // Bits Allocated 割当ビット 1 US 2 f = dcm->find(0x0028,0x0100); if (f != nullptr) { vr.load(f); allocated = vr.readAsUInt16(); } // Bits Stored 格納ビット 1 US 2 f = dcm->find(0x0028,0x0101); if (f != nullptr) { vr.load(f); stored = vr.readAsUInt16(); } // High Bit 高位ビット 1 US 2 f = dcm->find(0x0028,0x0102); if (f != nullptr) { vr.load(f); highbit = vr.readAsUInt16(); } // Pixel Representation 画素表現 1 US 2 f = dcm->find(0x0028,0x0103); if (f != nullptr) { vr.load(f); pixel_representation = vr.readAsUInt16(); } // Number of Frames f = dcm->find(0x0028,0x0008); int nframes = 0; if (f != nullptr) { vr.load(f); nframes = strtol(vr.toString(), 0, 0); } // Frame Increament Pointer unsigned short fiGid = 0; unsigned short fiEid = 0; f = dcm->find(0x0028,0x0009); if (f != nullptr) { vr.load(f); fiGid = vr.readAsUInt16(); fiEid = vr.readAsUInt16(1); } // Pixel data f = dcm->find(0x7fe0,0x0010); if (f != nullptr) { imgdata = f->dataP; imgbytes = f->length; } /* printf("image data: (%d,%d), %d(%d,%d: %d), (%s,%d) %p(%d bytes)\n", w, h, allocated, stored, highbit, pixel_representation, photometric, samples_per_pixel, imgdata, imgbytes); */ printf("image data: (%d,%d), %d(%04x,%04x) %d(%d,%d: %d), (%s,%d) (%d bytes)\n", w, h, nframes, fiGid, fiEid, allocated, stored, highbit, pixel_representation, photometric, samples_per_pixel, imgbytes); if (operation) { PixelConverter pc(2, allocated / 8, w, h, pixel_representation); if (nframes == 0) { sprintf_s(path.get(), 1024, "%s\\%d.img", args.GetAt(1), (i+1)); int fd = _open(path.get(), _O_RDWR | _O_BINARY | _O_CREAT | _O_EXCL); if (fd >= 0) { write(fd, pc.convert((char*)imgdata), pc.bytes()); close(fd); printf("%s ... wrote\n", path.get()); } else { t.Error("couldn't create a file. %s\n", path.get()); } } else { char* pframe = (char*)imgdata; int framesize = w * h * (allocated / 8); for(int j=0; j<nframes; j++) { sprintf_s(path.get(), 1024, "%s\\%d-%d.img", args.GetAt(1), (i+1), (j+1)); int fd = _open(path.get(), _O_RDWR | _O_BINARY | _O_CREAT | _O_EXCL); if (fd >= 0) { write(fd, pc.convert((char*)imgdata), pc.bytes()); close(fd); printf("%s ... wrote\n", path.get()); } else { t.Error("couldn't create a file. %s\n", path.get()); } pframe += framesize; } } } } } } void proc_show(ko2::Arg& args) { ko2::Trace t("proc_show", false); if (pcontext.path.depth == 4) { ko2::io::file::DicomContainer *node = findContainer(); if (node == nullptr) { t.Error("Error: couldn't find image module.\n"); return; } ImageModule* module = new ImageModule(pcontext.home, node); pcontext.imgview.displayImage(module); } else if (pcontext.path.depth == 3) { ImageModule* module = new ImageModule(); ko2::io::file::DicomContainer *serNode = findContainer(); for(int i=0; i<serNode->countOfChildren(); i++) { ko2::io::file::DicomContainer *imgNode = serNode->getChildAt(i); ImageModule* img = new ImageModule(pcontext.home, imgNode); module->add(img->getFrameAt()->Clone()); printf("image %4d ... loaded.\n", i); } pcontext.imgview.displayImage(module); } else { t.Error("Error: wrong level. requires a series or an image level.\n"); return; } } void proc_copyright(ko2::Arg& args) { printf("%s\n", PROGRAM_NAME); printf("version %s\n", PROGRAM_VERSION); printf("%s\n", PROGRAM_COPYRIGHT); } void proc_quit(ko2::Arg& args) { pcontext.imgview.closeWindow(); exit(0); } static struct { char* name; void (*proc)(ko2::Arg& args); } commands[] = { "config", proc_config, "list", proc_list, "tree", proc_tree, "level", proc_level, "info", proc_info, "export", proc_export, "show", proc_show, "copyright", proc_copyright, "help", proc_help, "quit", proc_quit, "exit", proc_quit, nullptr, nullptr }; static void usage(char* program_name, int format = 0) { printf("usage: %s [options] DICOMDIR\n", program_name); exit(1); } int main(int argc, char** argv) { if (argc == 1) { usage(argv[0]); } ko2::CommandLineArg opts; opts.Spec("-x", true, "0"); opts.Spec("-s"); opts.Spec("-v"); opts.Spec("-h"); int paramidx = opts.Parse(argc, argv); if (opts.HasOption("-v")) { printf("version %s\n", PROGRAM_VERSION); exit(0); } else if (opts.HasOption("-h")) { usage(argv[0], 1); } if (!opts.HasOption("-s")) { printf("%s, version %s\n", PROGRAM_NAME, PROGRAM_VERSION); } static char* path = argv[1]; if (_access(path, 0) < 0) { ko2::Terminal::Panic("%s: not found.", path); } ko2::io::file::ImageDataLoaderDicom* loader = new ko2::io::file::ImageDataLoaderDicom(); pcontext.root = loader->load(path); printf("%s loaded.\n", path); strcpy(pcontext.home, path); for(int i=strlen(pcontext.home); i>0; i--) { if (pcontext.home[i] == '\\') { pcontext.home[i] = 0; break; } } printf("folder: %s\n", pcontext.home); pcontext.path.depth = 0; pcontext.path.index[0] = 0; pcontext.path.index[1] = 0; pcontext.path.index[2] = 0; pcontext.path.index[3] = 0; char input[128]; while(true) { ko2::Terminal::Printf(ko2::TextColor::Pink, ko2::TextColor::Black, "dcmsh%% "); fflush(stdout); input[0] = 0; fgets(input, sizeof(input), stdin); for(int i=0; i<sizeof(input); i++) { if ((input[i] == 0x0a) || (input[i] == 0x0d)) { input[i] = 0; break; } } ko2::Arg args(input, strlen(input)); if (args.Count() > 0) { bool done = false; for(int i=0; commands[i].name != nullptr; i++) { if (!strcmp(commands[i].name, args.GetAt(0))) { commands[i].proc(args); done = true; break; } } if (done == false) { printf("unknown command: %s\n", args.GetAt(0)); } } } return 0; } //////////////////////////////////////////////////////////////////////////////// // 付録:資料 /* * DICOMDIR http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_F.3.html Record Type PATIENT STUDY SERIES IMAGE RT DOSE RT STRUCTURE SET RT PLAN RT TREAT RECORD PRESENTATION WAVEFORM SR DOCUMENT KEY OBJECT DOC SPECTROSCOPY RAW DATA REGISTRATION FIDUCIAL HANGING PROTOCOL ENCAP DOC HL7 STRUC DOC VALUE MAP STEREOMETRIC PALETTE IMPLANT IMPLANT GROUP IMPLANT ASSY MEASUREMENT SURFACE SURFACE SCAN PRIVATE * DICOM dictionary http://dicom.nema.org/Dicom/2011/11_06pu.pdf * DICOM transfer syntax: http://www.jira-net.or.jp/dicom/file/dicom_201110_paneldiscussion.pdf * VR型説明: http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html */ /* (0008,0005) Specific Character Set 特定文字集合 1C CS 16 (0008,0008) Image Type 画像タイプ 1 CS 18 (0008,0016) SOP Class UID SOPクラスUID 1 UI 28 (0008,0018) SOP Instance UID SOPインスタンスUID 1 UI 52 (0008,0020) Study Date 検査日付 2 DA 8 (0008,0023) Content Date 内容日付 2C DA 8 (0008,0030) Study Time 検査時刻 2 TM 6 (0008,0033) Content Time 内容時刻 2C TM 6 (0008,0050) Accession Number 受付番号 2 SH 16 (0008,0060) Modality モダリティ 1 CS 2 (0008,0068) Presentation Intent Type 提示意図タイプ 1 CS 16 (0008,0070) Manufacturer 製造者 2 LO 4 (0008,0080) Institution Name 施設名 3 LO 2 (0008,0090) Referring Physician's Name 照会医師の名前 2 PN 2 (0008,1090) Manufacturer's Model Name 製造者のモデル名 3 LO 10 (0010,0010) Patient's Name 患者の名前 2 PN 28 (0010,0020) Patient ID 患者ID 2 LO 16 (0010,0030) Patient's Birth Date 患者の誕生日 2 DA 8 (0010,0040) Patient's Sex 患者の性別 2 CS 2 (0018,0015) Body Part Examined 検査部位 2 CS 6 (0018,0060) KVP KVP 3 DS 2 (0018,1150) Exposure Time 曝射時間 3 IS 4 (0018,1151) X-Ray Tube Current X線管電流 3 IS 2 (0018,1164) Image Pixel Spacing イメージャ画素間隔 1 DS 12 (0018,1508) Positioner Type 位置決め装置タイプ 1 CS 12 (0018,1700) Collimator Shape 位置決め装置タイプ 1 CS 12 (0018,1702) Collimator Left Vertical Edge コリメータ形状 1C IS 2 (0018,1704) Collimator Right Vertical Edge コリメータの左垂直端 1C IS 4 (0018,1706) Collimator Upper Horizontal Ed コリメータの右垂直端 1C IS 2 (0018,1708) Collimator Lower Horizontal Ed コリメータの上水平端 1C IS 4 (0018,1710) Center of Circular Collimator コリメータの下水平端 1C IS (0018,1712) Radius of Circular Collimator 円形コリメータの中心 1C IS (0018,1720) Vertices of the Polygonal Collimator 円形コリメータの半径 1C IS (0020,000D) Study Instance UID 多角形コリメータの頂点 1 UI 50 (0020,000E) Series Instance UID シリーズインスタンスUID 1 UI 52 (0020,0010) Study ID 検査ID 2 SH 2 (0020,0011) Series Number シリーズ番号 2 IS 2 (0020,0013) Instance Number インスタンス番号 2 IS 6 (0020,0020) Patient Orientation 患者方向 1C CS 4 (0020,0060) Laterality 側性 2C CS 2 (0020,0062) Image Laterality 画像側性 1 CS 2 (0028,0002) Samples per Pixel 画素あたりサンプル 1 US 2 (0028,0004) Photometric Interpretation 光度測定解釈 1 CS 12 (0028,0010) Rows 行 1 US 2 (0028,0011) Columns 列 1 US 2 (0028,0030) Pixel Spacing 画素間隔 3 DS 12 (0028,0100) Bits Allocated 割当ビット 1 US 2 (0028,0101) Bits Stored 格納ビット 1 US 2 (0028,0102) High Bit 高位ビット 1 US 2 (0028,0103) Pixel Representation 画素表現 1 US 2 (0028,0301) Burned In Annotation 焼込済注釈 1 CS 2 (0028,1040) Pixel Intensity Relationship 画素強度関係 1 CS 4 (0028,1041) Pixel Intensity Relationship S 画素強度関係符号 1 SS 2 (0028,1050) Window Center ウィンドウ中心 1C DS 4 (0028,1051) Window Width ウィンドウ幅 1C DS 4 (0040,0316) Organ Dose 臓器線量 3 DS 6 (0040,0318) Organ Exposed 被曝臓器 1 CS 6 (0040,0555) Acquisition Context Sequence 収集コンテキストシーケンス 2 SQ 0 (0054,0220) View Code Sequence 視野コードシーケンス 1 SQ 66 --(0008,0100) Code Value コード値 1C SH 8 --(0008,0102) Coding Scheme Designator 符号化体系指定子 1C SH 4 --(0008,0104) Code Meaning コード意味 1C LO 14 --(0054,0222) View Modifer Code Sequence 収集コンテキストシーケンス 2 SQ 0 (2050,0020) Presentation LUT Shape 提示LUT形状 1 CS 8 (7FE0,0010) Pixel Data 画素データ 1 OW 11354112 */