├── .gitignore
├── DummyLoader
├── .gitignore
├── AppID.rgs
├── DummyLoader.def
├── DummyLoader.idl
├── DummyLoader.rc
├── DummyLoader.vcxproj
├── DummyLoader.vcxproj.filters
├── GenRgsFiles.py
├── Image3dFileLoader.cpp
├── Image3dFileLoader.hpp
├── Image3dSource.cpp
├── Image3dSource.hpp
├── Image3dStream.cpp
├── Image3dStream.hpp
├── LinAlg.hpp
├── Main.cpp
├── Resource.h
└── UNREGISTER_DummyLoader.bat
├── Guidelines.md
├── Image3dAPI.sln
├── Image3dAPI
├── .gitignore
├── ComSupport.hpp
├── IImage3d.idl
├── IImage3dTypeLibraryGenerator.idl
├── Image3dAPI.vcxproj
├── Image3dAPI.vcxproj.filters
├── RegistryCheck.hpp
└── UNREGISTER_Image3dAPI.bat
├── LICENSE.txt
├── PackagingGE
├── BuildNuGet.bat
├── DetermineNextTag.py
├── DummyLoader.redist.nuspec
├── DummyLoader.redist.targets
├── Image3dAPI.net.targets
├── Image3dAPI.nuspec
├── Image3dAPI.targets
├── PackagePublishNuget.bat
└── SetNuspecVersion.py
├── README.md
├── RegFreeTest
├── .gitignore
├── Main.cpp
├── RegFreeTest.vcxproj
└── RegFreeTest.vcxproj.filters
├── SandboxTest
├── .gitignore
├── LowIntegrity.hpp
├── Main.cpp
├── SandboxTest.vcxproj
└── SandboxTest.vcxproj.filters
├── TestPython
├── .gitignore
├── ITKExport.py
├── TestPython.py
├── TestPython.pyproj
└── utils.py
└── TestViewer
├── .gitignore
├── App.config
├── App.xaml
├── App.xaml.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.resx
├── Settings.Designer.cs
└── Settings.settings
└── TestViewer.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vs
2 | /x64
3 | /Win32
4 | /*.VC.db
5 | /*.VC.VC.opendb
6 | *.nupkg
7 | NEW_TAG.txt
8 | PREV_TAG.txt
9 | changelog.txt
10 |
--------------------------------------------------------------------------------
/DummyLoader/.gitignore:
--------------------------------------------------------------------------------
1 | /x64
2 | /Win32
3 | /*.rgs
4 | /DummyLoader.h
5 | /DummyLoader_i.c
6 |
--------------------------------------------------------------------------------
/DummyLoader/AppID.rgs:
--------------------------------------------------------------------------------
1 | HKCR
2 | {
3 | NoRemove AppID
4 | {
5 | ForceRemove '%APPID%' = s 'Image3dFileLoader Object'
6 | {
7 | val DllSurrogate = s ''
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/DummyLoader/DummyLoader.def:
--------------------------------------------------------------------------------
1 | EXPORTS
2 | DllCanUnloadNow PRIVATE
3 | DllGetClassObject PRIVATE
4 | DllRegisterServer PRIVATE
5 | DllUnregisterServer PRIVATE
6 | DllInstall PRIVATE
7 |
--------------------------------------------------------------------------------
/DummyLoader/DummyLoader.idl:
--------------------------------------------------------------------------------
1 | import "oaidl.idl";
2 | import "ocidl.idl";
3 | import "IImage3d.idl";
4 |
5 |
6 | [
7 | version(1.2),
8 | uuid(67E59584-3F6A-4852-8051-103A4583CA5E),
9 | helpstring("DummyLoader module")
10 | ]
11 | library DummyLoader
12 | {
13 | importlib("stdole2.tlb");
14 |
15 | [
16 | version(1.2),
17 | uuid(6FA82ED5-6332-4344-8417-DEA55E72098C),
18 | helpstring("3D image source")
19 | ]
20 | coclass Image3dSource
21 | {
22 | [default] interface IImage3dSource;
23 | };
24 |
25 | [
26 | version(1.2),
27 | uuid(8E754A72-0067-462B-9267-E84AF84828F1),
28 | helpstring("3D image file loader")
29 | ]
30 | coclass Image3dFileLoader
31 | {
32 | [default] interface IImage3dFileLoader;
33 | };
34 |
35 | };
36 |
--------------------------------------------------------------------------------
/DummyLoader/DummyLoader.rc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MedicalUltrasound/Image3dAPI/721eaa43179caa9f259168a8c5e6eaac4d84902c/DummyLoader/DummyLoader.rc
--------------------------------------------------------------------------------
/DummyLoader/DummyLoader.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}
23 | DummyLoader
24 | 10.0
25 |
26 |
27 |
28 | DynamicLibrary
29 | true
30 | v142
31 | MultiByte
32 |
33 |
34 | DynamicLibrary
35 | false
36 | v142
37 | true
38 | MultiByte
39 |
40 |
41 | DynamicLibrary
42 | true
43 | v142
44 | MultiByte
45 |
46 |
47 | DynamicLibrary
48 | false
49 | v142
50 | true
51 | MultiByte
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | $(SolutionDir)$(Platform)\$(Configuration)\
73 | $(Platform)\$(Configuration)\
74 | $(ProjectName)D
75 | false
76 |
77 |
78 | $(SolutionDir)$(Platform)\$(Configuration)\
79 | $(Platform)\$(Configuration)\
80 | false
81 |
82 |
83 | $(ProjectName)D
84 | false
85 |
86 |
87 | false
88 |
89 |
90 |
91 | Level3
92 | Disabled
93 | true
94 | MultiThreadedDebug
95 | $(ProjectDir)..\Image3dAPI
96 |
97 |
98 | true
99 | DummyLoader.def
100 |
101 |
102 | IF $(ConfigurationType) == DynamicLibrary (
103 | echo Registering DLL...
104 | regsvr32 /s "$(TargetPath)"
105 | ) ELSE (
106 | echo Registering EXE...
107 | "$(TargetPath)" /regserver
108 | )
109 |
110 | echo Registering component
111 |
112 |
113 | $(TargetName)$(TargetExt)
114 | Image3dFileLoader.rgs
115 |
116 |
117 | $(ProjectDir)..\Image3dAPI
118 | %(Filename).h
119 |
120 |
121 | $(IntDir)
122 |
123 |
124 | GenRgsFiles.py
125 | Generate RGS-files
126 |
127 |
128 |
129 |
130 | Level3
131 | Disabled
132 | true
133 | MultiThreadedDebug
134 | $(ProjectDir)..\Image3dAPI
135 |
136 |
137 | true
138 | DummyLoader.def
139 |
140 |
141 | IF $(ConfigurationType) == DynamicLibrary (
142 | echo Registering DLL...
143 | regsvr32 /s "$(TargetPath)"
144 | ) ELSE (
145 | echo Registering EXE...
146 | "$(TargetPath)" /regserver
147 | )
148 |
149 | echo Registering component
150 |
151 |
152 | $(TargetName)$(TargetExt)
153 | Image3dFileLoader.rgs
154 |
155 |
156 | $(ProjectDir)..\Image3dAPI
157 | %(Filename).h
158 |
159 |
160 | $(IntDir)
161 |
162 |
163 | GenRgsFiles.py
164 | Generate RGS-files
165 |
166 |
167 |
168 |
169 | Level3
170 | MaxSpeed
171 | true
172 | true
173 | true
174 | MultiThreaded
175 | $(ProjectDir)..\Image3dAPI
176 |
177 |
178 | true
179 | true
180 | true
181 | DummyLoader.def
182 |
183 |
184 | IF $(ConfigurationType) == DynamicLibrary (
185 | echo Registering DLL...
186 | regsvr32 /s "$(TargetPath)"
187 | ) ELSE (
188 | echo Registering EXE...
189 | "$(TargetPath)" /regserver
190 | )
191 |
192 | echo Registering component
193 |
194 |
195 | $(TargetName)$(TargetExt)
196 | Image3dFileLoader.rgs
197 |
198 |
199 | $(ProjectDir)..\Image3dAPI
200 | %(Filename).h
201 |
202 |
203 | $(IntDir)
204 |
205 |
206 | GenRgsFiles.py
207 | Generate RGS-files
208 |
209 |
210 |
211 |
212 | Level3
213 | MaxSpeed
214 | true
215 | true
216 | true
217 | MultiThreaded
218 | $(ProjectDir)..\Image3dAPI
219 |
220 |
221 | true
222 | true
223 | true
224 | DummyLoader.def
225 |
226 |
227 | IF $(ConfigurationType) == DynamicLibrary (
228 | echo Registering DLL...
229 | regsvr32 /s "$(TargetPath)"
230 | ) ELSE (
231 | echo Registering EXE...
232 | "$(TargetPath)" /regserver
233 | )
234 |
235 | echo Registering component
236 |
237 |
238 | $(TargetName)$(TargetExt)
239 | Image3dFileLoader.rgs
240 |
241 |
242 | $(ProjectDir)..\Image3dAPI
243 | %(Filename).h
244 |
245 |
246 | $(IntDir)
247 |
248 |
249 | GenRgsFiles.py
250 | Generate RGS-files
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
--------------------------------------------------------------------------------
/DummyLoader/DummyLoader.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/DummyLoader/GenRgsFiles.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | progid_template1 = """
4 | PROG-NAME.CLASS-NAME.1 = s 'CLASS-NAME Object'
5 | {
6 | CLSID = s '{CLASS-GUID}'
7 | }
8 | PROG-NAME.CLASS-NAME = s 'CLASS-NAME Object'
9 | {
10 | CLSID = s '{CLASS-GUID}'
11 | CurVer = s 'PROG-NAME.CLASS-NAME.1'
12 | }"""
13 |
14 | clsid_template1 = """
15 | NoRemove CLSID
16 | {
17 | ForceRemove {CLASS-GUID} = s 'CLASS-NAME Object'
18 | {"""
19 |
20 | progid_template2 = """
21 | ProgID = s 'PROG-NAME.CLASS-NAME.1'
22 | VersionIndependentProgID = s 'PROG-NAME.CLASS-NAME'"""
23 |
24 | clsid_template2 = """
25 | InprocServer32 = s '%MODULE%'
26 | {
27 | val ThreadingModel = s 'THREAD-MODEL'
28 | }
29 | TypeLib = s '{TYPE-LIB}'
30 | Version = s 'VERSION'
31 | ADDITIONAL-REGISTRY-ENTRIES
32 | }
33 | }"""
34 |
35 | class ComClass:
36 | def __init__(self, name, uuid, version, creatable):
37 | self.name = name
38 | self.uuid = uuid
39 | self.version = version
40 | self.creatable = creatable
41 | self.additional_entries = '' # default
42 |
43 |
44 | def GenRgsFiles (progname, typelib, classes, threadmodel, concat_filename=None):
45 | all_rgs_content = ''
46 | for cls in classes:
47 | content = "HKCR\n{"
48 | if cls.creatable:
49 | content += progid_template1
50 | content += clsid_template1
51 | if cls.creatable:
52 | content += progid_template2
53 | content += clsid_template2
54 | content += "\n}\n"
55 |
56 | content = content.replace('PROG-NAME', progname)
57 | content = content.replace('TYPE-LIB', typelib)
58 | content = content.replace('THREAD-MODEL', threadmodel)
59 | content = content.replace('CLASS-NAME', cls.name)
60 | content = content.replace('CLASS-GUID', cls.uuid)
61 | content = content.replace('VERSION', cls.version)
62 | content = content.replace('ADDITIONAL-REGISTRY-ENTRIES', cls.additional_entries)
63 |
64 | #print(content)
65 | filename = cls.name+'.rgs'
66 | with open(filename, 'w') as f:
67 | f.write(content)
68 | all_rgs_content += content
69 | print('Written '+filename)
70 |
71 | # write concatenation of all RGS files into a separate file (needed for reg-free COM)
72 | if concat_filename:
73 | with open(concat_filename, 'w') as f:
74 | f.write(all_rgs_content)
75 | print('Written '+concat_filename)
76 |
77 |
78 | def ParseUuidString (val):
79 | uuid = val[val.find('uuid(')+5:]
80 | return uuid[:uuid.find(')')]
81 |
82 | def ParseVersionString (val):
83 | ver = val[val.find('version(')+8:]
84 | return ver[:ver.find(')')]
85 |
86 |
87 | def ParseIdl (filename):
88 | '''Parse IDL file to determine library name, typelib GUID and classes with associated GUIDs'''
89 | with open(filename, 'r') as f:
90 | source = f.read()
91 |
92 | attribs = '\[[^[]+\]\s*' # detect [...] attribute blocks
93 | name = '\s+[a-zA-Z0-9_]+' # detect coclass/interface name
94 |
95 | # find "library" name and associated typelib uuid
96 | for lib_hit in re.findall(attribs+'library'+name, source):
97 | tokens = lib_hit.split()
98 | for token in tokens:
99 | if 'uuid(' in token:
100 | typelib = ParseUuidString(token)
101 | break
102 | libname = tokens[-1]
103 |
104 | # find "coclass" names with associated uuid and version
105 | classes = []
106 | for cls_hit in re.findall(attribs+'coclass'+name, source):
107 | tokens = cls_hit.split()
108 | for token in tokens:
109 | if 'uuid(' in token:
110 | uuid = ParseUuidString(token)
111 | elif 'version(' in token:
112 | version = ParseVersionString(token)
113 | classes.append(ComClass(tokens[-1], uuid, version, creatable=True))
114 |
115 | return libname, typelib, classes
116 |
117 | def ParseImage3dAPIVersion (filename):
118 | with open(filename, 'r') as f:
119 | for line in f:
120 | if "IMAGE3DAPI_VERSION_MAJOR =" in line:
121 | major = int(line.split()[-1][:-1])
122 | elif "IMAGE3DAPI_VERSION_MINOR =" in line:
123 | minor = int(line.split()[-1][:-1])
124 | return str(major)+"."+str(minor)
125 |
126 |
127 | if __name__ == "__main__":
128 | libname, typelib, classes = ParseIdl('DummyLoader.idl')
129 | version = ParseImage3dAPIVersion("../Image3dAPI/IImage3d.idl")
130 |
131 | # verify that COM class versions matches API version
132 | for cls in classes:
133 | if cls.version != version:
134 | raise Exception("Version mismatch detected for "+cls.name)
135 | if cls.name == "Image3dFileLoader":
136 | cls.additional_entries += '''
137 | val AppID = s '%APPID%'
138 | SupportedManufacturerModels
139 | {
140 | val 'Dummy medical systems' = s 'Super scanner *'
141 | val 'Dummy healthcare' = s 'Some scanner 1;Some scanner 2'
142 | }'''
143 | else:
144 | cls.creatable = False
145 |
146 | GenRgsFiles(libname, typelib, classes, 'Both')
147 |
--------------------------------------------------------------------------------
/DummyLoader/Image3dFileLoader.cpp:
--------------------------------------------------------------------------------
1 | #include "Image3dFileLoader.hpp"
2 |
3 |
4 | Image3dFileLoader::Image3dFileLoader() {
5 | }
6 |
7 | Image3dFileLoader::~Image3dFileLoader() {
8 | }
9 |
10 |
11 | HRESULT Image3dFileLoader::LoadFile(BSTR file_name, /*out*/Image3dError *err_type, /*out*/BSTR *err_msg) {
12 | if (!err_type || !err_msg)
13 | return E_INVALIDARG;
14 |
15 | *err_type = Image3d_SUCCESS;
16 | *err_msg = CComBSTR().Detach();
17 | return S_OK; // no operation
18 | }
19 |
20 | HRESULT Image3dFileLoader::GetImageSource(/*out*/IImage3dSource **img_src) {
21 | if (!img_src)
22 | return E_INVALIDARG;
23 |
24 | CComPtr obj = CreateLocalInstance();
25 | *img_src = obj.Detach();
26 | return S_OK;
27 | }
28 |
--------------------------------------------------------------------------------
/DummyLoader/Image3dFileLoader.hpp:
--------------------------------------------------------------------------------
1 | /* Dummy test loader for the "3D API".
2 | Designed by Fredrik Orderud .
3 | Copyright (c) 2016, GE Healthcare, Ultrasound. */
4 | #pragma once
5 |
6 | #include "../Image3dAPI/ComSupport.hpp"
7 | #include "../Image3dAPI/IImage3d.h"
8 | #include "Image3dSource.hpp"
9 |
10 | #include "DummyLoader.h"
11 | #include "Resource.h"
12 |
13 | class ATL_NO_VTABLE Image3dFileLoader :
14 | public CComObjectRootEx,
15 | public CComCoClass,
16 | public IImage3dFileLoader {
17 | public:
18 | Image3dFileLoader();
19 |
20 | /*NOT virtual*/ ~Image3dFileLoader();
21 |
22 | HRESULT STDMETHODCALLTYPE LoadFile(BSTR file_name, /*out*/Image3dError *err_type, /*out*/BSTR *err_msg) override;
23 |
24 | HRESULT STDMETHODCALLTYPE GetImageSource(/*out*/IImage3dSource **img_src) override;
25 |
26 | DECLARE_REGISTRY_RESOURCEID(IDR_Image3dFileLoader)
27 |
28 | BEGIN_COM_MAP(Image3dFileLoader)
29 | COM_INTERFACE_ENTRY(IImage3dFileLoader)
30 | END_COM_MAP()
31 | };
32 |
33 | OBJECT_ENTRY_AUTO(__uuidof(Image3dFileLoader), Image3dFileLoader)
34 |
--------------------------------------------------------------------------------
/DummyLoader/Image3dSource.cpp:
--------------------------------------------------------------------------------
1 | #include "Image3dSource.hpp"
2 | #include "LinAlg.hpp"
3 |
4 |
5 | static const uint8_t OUTSIDE_VAL = 0; // black outside image volume
6 | static const uint8_t PROBE_PLANE = 127; // gray value for plane closest to probe
7 |
8 |
9 | Image3dSource::Image3dSource() {
10 | m_probe.type = PROBE_External;
11 | m_probe.name = L"4V";
12 |
13 | // One second loop starting at t = 10
14 | const size_t numFrames = 25;
15 | const double duration = 1.0; // ECG duration in seconds (the sum of the duration of individual ECG samples)
16 | const double startTime = 10.0;
17 |
18 | {
19 | // simulate sine-wave ECG
20 | const int N = 128;
21 | CComSafeArray samples(N);
22 | for (int i = 0; i < N; ++i)
23 | samples[i] = static_cast(sin(4 * i*M_PI / N));
24 |
25 | CComSafeArray trig_times;
26 | trig_times.Add(startTime); // trig every 1/2 sec
27 | trig_times.Add(startTime + duration / 2);
28 | trig_times.Add(startTime + duration);
29 |
30 | EcgSeries ecg;
31 | ecg.start_time = startTime;
32 | ecg.delta_time = duration / N;
33 | ecg.samples = samples.Detach();
34 | ecg.trig_times = trig_times.Detach();
35 | m_ecg = EcgSeries(ecg);
36 | }
37 | {
38 | // flat gray tissue scale
39 | for (size_t i = 0; i < m_color_map_tissue.size(); ++i)
40 | m_color_map_tissue[i] = R8G8B8A8(static_cast(i), static_cast(i), static_cast(i), 0xFF);
41 | }
42 | {
43 | // image geometry X Y Z
44 | Cart3dGeom geom = { -0.1f, 0, -0.075f,// origin
45 | 0.20f,0, 0, // dir1 (width)
46 | 0, 0.10f, 0, // dir2 (depth)
47 | 0, 0, 0.15f};// dir3 (elevation)
48 | m_img_geom = geom;
49 | }
50 | {
51 | // checker board image data
52 | unsigned short dims[] = { 20, 15, 10 }; // matches length of dir1, dir2 & dir3, so that the image squares become quadratic
53 | std::vector img_buf(dims[0] * dims[1] * dims[2]);
54 | for (size_t frameNumber = 0; frameNumber < numFrames; ++frameNumber) {
55 | for (unsigned int z = 0; z < dims[2]; ++z) {
56 | for (unsigned int y = 0; y < dims[1]; ++y) {
57 | for (unsigned int x = 0; x < dims[0]; ++x) {
58 | bool even_f = (frameNumber / 2 % 2) == 0;
59 | bool even_x = (x / 2 % 2) == 0;
60 | bool even_y = (y / 2 % 2) == 0;
61 | bool even_z = (z / 2 % 2) == 0;
62 | byte & out_sample = img_buf[x + y*dims[0] + z*dims[0] * dims[1]];
63 | if (even_f ^ even_x ^ even_y ^ even_z)
64 | out_sample = 255;
65 | else
66 | out_sample = 0;
67 | }
68 | }
69 | }
70 |
71 | // special grayscale value for plane closest to probe
72 | for (unsigned int z = 0; z < dims[2]; ++z) {
73 | for (unsigned int x = 0; x < dims[0]; ++x) {
74 | unsigned int y = 0;
75 | byte & out_sample = img_buf[x + y*dims[0] + z*dims[0] * dims[1]];
76 | out_sample = PROBE_PLANE;
77 | }
78 | }
79 |
80 | m_frames.push_back(CreateImage3d(frameNumber*(duration/numFrames) + startTime, FORMAT_U8, dims, img_buf));
81 | }
82 | }
83 | }
84 |
85 | Image3dSource::~Image3dSource() {
86 | }
87 |
88 |
89 | HRESULT Image3dSource::GetFrameCount(/*out*/unsigned int *size) {
90 | if (!size)
91 | return E_INVALIDARG;
92 |
93 | *size = static_cast(m_frames.size());
94 | return S_OK;
95 | }
96 |
97 | HRESULT Image3dSource::GetFrameTimes(/*out*/SAFEARRAY * *frame_times) {
98 | if (!frame_times)
99 | return E_INVALIDARG;
100 |
101 | const unsigned int N = static_cast(m_frames.size());
102 | CComSafeArray result(N);
103 | if (N > 0) {
104 | double * time_arr = &result.GetAt(0);
105 | for (unsigned int i = 0; i < N; ++i)
106 | time_arr[i] = m_frames[i].time;
107 | }
108 |
109 | *frame_times = result.Detach();
110 | return S_OK;
111 | }
112 |
113 |
114 | HRESULT Image3dSource::GetFrame(unsigned int index, Cart3dGeom out_geom, unsigned short max_res[3], /*out*/Image3d *data) {
115 | if (!data)
116 | return E_INVALIDARG;
117 | if (index >= m_frames.size())
118 | return E_BOUNDS;
119 |
120 | ImageFormat format = m_frames[index].format;
121 | if (format == FORMAT_U8) {
122 | Image3d result = SampleFrame(m_frames[index], m_img_geom, out_geom, max_res);
123 | *data = std::move(result);
124 | return S_OK;
125 | }
126 |
127 | return E_NOTIMPL;
128 | }
129 |
130 | HRESULT Image3dSource::GetBoundingBox(/*out*/Cart3dGeom *geom) {
131 | if (!geom)
132 | return E_INVALIDARG;
133 |
134 | *geom = m_img_geom;
135 | return S_OK;
136 | }
137 |
138 | HRESULT Image3dSource::GetColorMap(/*out*/SAFEARRAY ** map) {
139 | if (!map)
140 | return E_INVALIDARG;
141 | if (*map)
142 | return E_INVALIDARG;
143 |
144 | // copy to new buffer
145 | CComSafeArray color_map(static_cast(m_color_map_tissue.size()));
146 | memcpy(&color_map.GetAt(0), m_color_map_tissue.data(), sizeof(m_color_map_tissue));
147 | *map = color_map.Detach(); // transfer ownership
148 | return S_OK;
149 | }
150 |
151 | HRESULT Image3dSource::GetECG(/*out*/EcgSeries *ecg) {
152 | if (!ecg)
153 | return E_INVALIDARG;
154 |
155 | // return a copy
156 | *ecg = EcgSeries(m_ecg);
157 | return S_OK;
158 | }
159 |
160 | HRESULT Image3dSource::GetProbeInfo(/*out*/ProbeInfo *probe) {
161 | if (!probe)
162 | return E_INVALIDARG;
163 |
164 | // return a copy
165 | *probe = m_probe;
166 | return S_OK;
167 | }
168 |
169 | HRESULT Image3dSource::GetSopInstanceUID(/*out*/BSTR *uid_str) {
170 | if (!uid_str)
171 | return E_INVALIDARG;
172 | if (*uid_str)
173 | return E_INVALIDARG; // input must be pointer to nullptr
174 |
175 | *uid_str = CComBSTR("DUMMY_UID").Detach();
176 | return S_OK;
177 | }
178 |
--------------------------------------------------------------------------------
/DummyLoader/Image3dSource.hpp:
--------------------------------------------------------------------------------
1 | /* Dummy test loader for the "3D API".
2 | Designed by Fredrik Orderud .
3 | Copyright (c) 2016, GE Healthcare, Ultrasound. */
4 | #pragma once
5 |
6 | #include "Image3dStream.hpp"
7 |
8 |
9 |
10 | class ATL_NO_VTABLE Image3dSource :
11 | public CComObjectRootEx,
12 | public CComCoClass,
13 | public IImage3dSource {
14 | public:
15 | Image3dSource();
16 |
17 | /*NOT virtual*/ ~Image3dSource();
18 |
19 | HRESULT STDMETHODCALLTYPE GetFrameCount(/*out*/unsigned int *size) override;
20 |
21 | HRESULT STDMETHODCALLTYPE GetFrameTimes(/*out*/SAFEARRAY * *frame_times) override;
22 |
23 | HRESULT STDMETHODCALLTYPE GetFrame(unsigned int index, Cart3dGeom out_geom, unsigned short max_res[3], /*out*/Image3d *data) override;
24 |
25 | HRESULT STDMETHODCALLTYPE GetBoundingBox(/*out*/Cart3dGeom *geom) override;
26 |
27 | HRESULT STDMETHODCALLTYPE GetColorMap(/*out*/SAFEARRAY ** map) override;
28 |
29 | HRESULT STDMETHODCALLTYPE GetECG(/*out*/EcgSeries *ecg) override;
30 |
31 | HRESULT STDMETHODCALLTYPE GetProbeInfo(/*out*/ProbeInfo *probe) override;
32 |
33 | HRESULT STDMETHODCALLTYPE GetSopInstanceUID(/*out*/BSTR *uid_str) override;
34 |
35 | DECLARE_REGISTRY_RESOURCEID(IDR_Image3dSource)
36 |
37 | BEGIN_COM_MAP(Image3dSource)
38 | COM_INTERFACE_ENTRY(IImage3dSource)
39 | END_COM_MAP()
40 |
41 | private:
42 | ProbeInfo m_probe;
43 | EcgSeries m_ecg;
44 | std::array m_color_map_tissue;
45 | Cart3dGeom m_img_geom = {};
46 | std::vector m_frames;
47 | };
48 |
49 | OBJECT_ENTRY_AUTO(__uuidof(Image3dSource), Image3dSource)
50 |
--------------------------------------------------------------------------------
/DummyLoader/Image3dStream.cpp:
--------------------------------------------------------------------------------
1 | #include "Image3dStream.hpp"
2 |
--------------------------------------------------------------------------------
/DummyLoader/Image3dStream.hpp:
--------------------------------------------------------------------------------
1 | /* Dummy test loader for the "3D API".
2 | Designed by Fredrik Orderud .
3 | Copyright (c) 2020, GE Healthcare, Ultrasound. */
4 | #pragma once
5 |
6 | #define _USE_MATH_DEFINES // for M_PI
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include "../Image3dAPI/ComSupport.hpp"
12 | #include "../Image3dAPI/IImage3d.h"
13 |
14 | #include "DummyLoader.h"
15 | #include "Resource.h"
16 |
17 |
18 | /** RGBA color struct that matches DXGI_FORMAT_R8G8B8A8_UNORM.
19 | Created due to the lack of such a class/struct in the Windows or Direct3D SDKs.
20 | Please remove this class if a more standardized alternative is available. */
21 | struct R8G8B8A8 {
22 | R8G8B8A8 () {
23 | }
24 |
25 | R8G8B8A8 (unsigned char _r, unsigned char _g, unsigned char _b, unsigned char _a) : r(_r), g(_g), b(_b), a(_a) {
26 | }
27 |
28 | operator unsigned int () const {
29 | return *reinterpret_cast(this);
30 | }
31 |
32 | uint8_t r = 0; ///< color channels
33 | uint8_t g = 0;
34 | uint8_t b = 0;
35 | uint8_t a = 0;
36 | };
37 |
38 | /** Determine the sample size [bytes] for a given image format. */
39 | static unsigned int ImageFormatSize(ImageFormat format) {
40 | switch (format) {
41 | case FORMAT_U8: return 1;
42 | }
43 |
44 | abort(); // should never be reached
45 | }
46 |
47 | /** Create a Image3d object from a std::vector buffer. */
48 | static Image3d CreateImage3d (double time, ImageFormat format, const unsigned short dims[3], const std::vector &img_buf) {
49 | assert(img_buf.size() == ImageFormatSize(format)*dims[0]*dims[1]*dims[2]);
50 |
51 | Image3d img;
52 | img.time = time;
53 | img.format = format;
54 | for (size_t i = 0; i < 3; ++i)
55 | img.dims[i] = dims[i];
56 |
57 | CComSafeArray data(static_cast(img_buf.size()));
58 | memcpy(data.m_psa->pvData, img_buf.data(), img_buf.size());
59 | img.data = data.Detach();
60 |
61 | // assume packed storage
62 | img.stride0 = dims[0] * ImageFormatSize(format);
63 | img.stride1 = dims[1] * img.stride0;
64 |
65 | return img;
66 | }
67 |
--------------------------------------------------------------------------------
/DummyLoader/LinAlg.hpp:
--------------------------------------------------------------------------------
1 | /* Basic Ultrasound Image library (UsImage).
2 | Designed by Fredrik Orderud
3 | Copyright (c) 2015, GE Vingmed Ultrasound */
4 | #pragma once
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 |
11 | /** 3D vector type. */
12 | struct vec3f {
13 | vec3f() : x(0), y(0), z(0) {
14 | }
15 | vec3f(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {
16 | }
17 |
18 | vec3f operator + (vec3f other) const {
19 | return vec3f(x + other.x, y + other.y, z + other.z);
20 | }
21 | vec3f operator - (vec3f other) const {
22 | return vec3f(x - other.x, y - other.y, z - other.z);
23 | }
24 |
25 | vec3f operator - () const {
26 | return vec3f(-x, -y, -z);
27 | }
28 |
29 | vec3f & operator *= (float val) {
30 | x *= val;
31 | y *= val;
32 | z *= val;
33 | return *this;
34 | }
35 | vec3f & operator /= (float val) {
36 | x /= val;
37 | y /= val;
38 | z /= val;
39 | return *this;
40 | }
41 |
42 | vec3f & operator += (vec3f val) {
43 | x += val.x;
44 | y += val.y;
45 | z += val.z;
46 | return *this;
47 | }
48 | vec3f & operator -= (vec3f val) {
49 | x -= val.x;
50 | y -= val.y;
51 | z -= val.z;
52 | return *this;
53 | }
54 |
55 | bool operator == (vec3f other) const {
56 | if ((x == other.x) && (y == other.y) && (z == other.z))
57 | return true;
58 | return false;
59 | }
60 | bool operator != (vec3f other) const {
61 | return !operator==(other);
62 | }
63 |
64 | float x, y, z;
65 | };
66 |
67 | static vec3f operator * (float val, vec3f vec) {
68 | return vec3f(val*vec.x, val*vec.y, val*vec.z);
69 | }
70 |
71 | /** Calculates the vector cross-product */
72 | static inline vec3f cross_prod(vec3f a, vec3f b) {
73 | return vec3f(a.y*b.z - a.z*b.y,
74 | a.z*b.x - a.x*b.z,
75 | a.x*b.y - a.y*b.x);
76 | }
77 |
78 |
79 | /** Calculates the vector dot-product */
80 | static inline float dot_prod(vec3f a, vec3f b) {
81 | return a.x*b.x + a.y*b.y + a.z*b.z;
82 | }
83 |
84 |
85 | /** Calculates the Eucledian length of a vector. */
86 | static inline float length(vec3f vec) {
87 | return sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);
88 | }
89 |
90 |
91 | /** Returns the input vector normalized to unit length. */
92 | static inline vec3f normalize(vec3f vec) {
93 | vec3f result = vec;
94 | float norm = length(result);
95 |
96 | if (norm == 0.0f)
97 | return result;
98 |
99 | result.x /= norm;
100 | result.y /= norm;
101 | result.z /= norm;
102 |
103 | return result;
104 | }
105 |
106 |
107 | /** 3x3 matrix type. */
108 | class mat33f {
109 | public:
110 | mat33f() {
111 | clear();
112 | }
113 |
114 | float & operator () (size_t i, size_t j) {
115 | assert(i < 3 && j < 3);
116 | return m_data[i + 3 * j];
117 | }
118 | const float & operator () (size_t i, size_t j) const {
119 | assert(i < 3 && j < 3);
120 | return m_data[i + 3 * j];
121 | }
122 |
123 | void clear() {
124 | m_data.fill(0);
125 | }
126 |
127 | const float * data() const {
128 | return m_data.data();
129 | }
130 |
131 | void transpose() {
132 | static_assert(3 == 3, "only transpose of square matrices is supported");
133 |
134 | // make temporary matrix
135 | mat33f t;
136 | for (unsigned int i = 0; i < 3; ++i)
137 | for (unsigned int j = 0; j < 3; ++j)
138 | t(j, i) = (*this)(i, j);
139 |
140 | // copy to "this"
141 | (*this) = t;
142 | }
143 |
144 | private:
145 | std::array m_data;
146 | };
147 |
148 | static inline void operator *= (mat33f & m, float val) {
149 | for (size_t j = 0; j < 3; j++)
150 | for (size_t i = 0; i < 3; i++)
151 | m(i, j) *= val;
152 | }
153 |
154 | /** Assign a row to a matrix. */
155 | static inline void row_assign(mat33f & m, size_t i, const vec3f & val) {
156 | m(i, 0) = val.x;
157 | m(i, 1) = val.y;
158 | m(i, 2) = val.z;
159 | }
160 |
161 | /** Assign a column to a matrix. */
162 | static inline void col_assign(mat33f & m, size_t i, const vec3f & val) {
163 | m(0, i) = val.x;
164 | m(1, i) = val.y;
165 | m(2, i) = val.z;
166 | }
167 |
168 |
169 | /** Determinant of a 3 x 3 matrix. */
170 | static inline float det(const mat33f & M) {
171 | float a = M(0, 0), b = M(0, 1), c = M(0, 2);
172 | float d = M(1, 0), e = M(1, 1), f = M(1, 2);
173 | float g = M(2, 0), h = M(2, 1), i = M(2, 2);
174 |
175 | float det = a*(e*i - f*h) - b*(d*i - f*g) + c*(d*h - e*g);
176 | return det;
177 | }
178 |
179 | /** Inverts a 3 x 3 matrix analytically. */
180 | static inline mat33f inv(const mat33f & M, bool normalize = true) {
181 | float a = M(0, 0), b = M(0, 1), c = M(0, 2);
182 | float d = M(1, 0), e = M(1, 1), f = M(1, 2);
183 | float g = M(2, 0), h = M(2, 1), i = M(2, 2);
184 |
185 | mat33f invM;
186 | invM(0, 0) = e*i - f*h;
187 | invM(0, 1) = c*h - b*i;
188 | invM(0, 2) = b*f - c*e;
189 | invM(1, 0) = f*g - d*i;
190 | invM(1, 1) = a*i - c*g;
191 | invM(1, 2) = c*d - a*f;
192 | invM(2, 0) = d*h - e*g;
193 | invM(2, 1) = b*g - a*h;
194 | invM(2, 2) = a*e - b*d;
195 |
196 | if (normalize) {
197 | invM *= 1.0f / det(M);
198 | }
199 |
200 | return invM;
201 | }
202 |
203 |
204 | /** Matrix vector product. */
205 | static inline vec3f prod(const mat33f & m, const vec3f & p) {
206 | vec3f sum(0, 0, 0);
207 | sum += p.x * vec3f(m(0, 0), m(1, 0), m(2, 0));
208 | sum += p.y * vec3f(m(0, 1), m(1, 1), m(2, 1));
209 | sum += p.z * vec3f(m(0, 2), m(1, 2), m(2, 2));
210 | return sum;
211 | }
212 |
213 |
214 | /** Convert from normalized voxel pos in [0,1) to (x,y,z) coordinate. */
215 | static vec3f PosToCoord (vec3f origin, vec3f dir1, vec3f dir2, vec3f dir3, const vec3f pos) {
216 | mat33f M;
217 | col_assign(M, 0, dir1);
218 | col_assign(M, 1, dir2);
219 | col_assign(M, 2, dir3);
220 |
221 | return prod(M, pos) + origin;
222 | }
223 |
224 |
225 | static std::tuple FromCart3dGeom (Cart3dGeom geom) {
226 | const vec3f origin(geom.origin_x, geom.origin_y, geom.origin_z);
227 | const vec3f dir1(geom.dir1_x, geom.dir1_y, geom.dir1_z);
228 | const vec3f dir2(geom.dir2_x, geom.dir2_y, geom.dir2_z);
229 | const vec3f dir3(geom.dir3_x, geom.dir3_y, geom.dir3_z);
230 |
231 | return std::make_tuple(origin, dir1, dir2, dir3);
232 | }
233 |
234 |
235 | static Cart3dGeom ToCart3dGeom (vec3f origin, vec3f dir1, vec3f dir2, vec3f dir3) {
236 | Cart3dGeom geom = {};
237 | {
238 | geom.origin_x = origin.x;
239 | geom.origin_y = origin.y;
240 | geom.origin_z = origin.z;
241 |
242 | geom.dir1_x = dir1.x;
243 | geom.dir1_y = dir1.y;
244 | geom.dir1_z = dir1.z;
245 |
246 | geom.dir2_x = dir2.x;
247 | geom.dir2_y = dir2.y;
248 | geom.dir2_z = dir2.z;
249 |
250 | geom.dir3_x = dir3.x;
251 | geom.dir3_y = dir3.y;
252 | geom.dir3_z = dir3.z;
253 | }
254 | return geom;
255 | }
256 |
257 |
258 | /** Convert from (x,y,z) coordinate to normalized voxel pos in [0,1). */
259 | static vec3f CoordToPos (Cart3dGeom geom, const vec3f xyz) {
260 | vec3f origin, dir1, dir2, dir3;
261 | std::tie(origin, dir1, dir2, dir3) = FromCart3dGeom(geom);
262 |
263 | mat33f M;
264 | col_assign(M, 0, dir1);
265 | col_assign(M, 1, dir2);
266 | col_assign(M, 2, dir3);
267 |
268 | return prod(inv(M), xyz - origin);
269 | }
270 |
271 |
272 | template
273 | static T SampleVoxel (const Image3d & frame, const vec3f pos) {
274 | assert(ImageFormatSize(frame.format) == sizeof(T));
275 |
276 | // out-of-bounds checking
277 | if ((pos.x < 0) || (pos.y < 0) || (pos.z < 0))
278 | return OUTSIDE_VAL;
279 |
280 | auto x = static_cast(frame.dims[0] * pos.x);
281 | auto y = static_cast(frame.dims[1] * pos.y);
282 | auto z = static_cast(frame.dims[2] * pos.z);
283 |
284 | // out-of-bounds checking
285 | if ((x >= frame.dims[0]) || (y >= frame.dims[1]) || (z >= frame.dims[2]))
286 | return OUTSIDE_VAL;
287 |
288 | return static_cast(frame.data->pvData)[x + y*frame.stride0/sizeof(T) + z*frame.stride1/sizeof(T)];
289 | }
290 |
291 |
292 | template
293 | static Image3d SampleFrame (const Image3d & frame, Cart3dGeom frame_geom, Cart3dGeom out_geom, unsigned short max_res[3]) {
294 | if (max_res[2] == 0)
295 | max_res[2] = 1; // require at least one plane to to retrieved
296 |
297 | vec3f out_origin, out_dir1, out_dir2, out_dir3;
298 | std::tie(out_origin, out_dir1, out_dir2, out_dir3) = FromCart3dGeom(out_geom);
299 |
300 | // allow 3rd axis to be empty if only retrieving a single slice
301 | if ((out_dir3 == vec3f(0, 0, 0)) && (max_res[2] < 2))
302 | out_dir3 = cross_prod(out_dir1, out_dir2);
303 |
304 | // sample image buffer
305 | std::vector img_buf(sizeof(T) * max_res[0] * max_res[1] * max_res[2], 127);
306 | for (unsigned short z = 0; z < max_res[2]; ++z) {
307 | for (unsigned short y = 0; y < max_res[1]; ++y) {
308 | for (unsigned short x = 0; x < max_res[0]; ++x) {
309 | // convert from input texture coordinate to output texture coordinate
310 | vec3f pos_in(x*1.0f/max_res[0], y*1.0f/max_res[1], z*1.0f/max_res[2]);
311 | vec3f xyz = PosToCoord(out_origin, out_dir1, out_dir2, out_dir3, pos_in);
312 | vec3f pos_out = CoordToPos(frame_geom, xyz);
313 |
314 | T val = SampleVoxel(frame, pos_out);
315 | reinterpret_cast(img_buf.data())[x + y*max_res[0] + z*max_res[0] * max_res[1]] = val;
316 | }
317 | }
318 | }
319 |
320 | return CreateImage3d(frame.time, frame.format, max_res, img_buf);
321 | }
322 |
--------------------------------------------------------------------------------
/DummyLoader/Main.cpp:
--------------------------------------------------------------------------------
1 | #include "Image3dSource.hpp"
2 | #include "Image3dFileLoader.hpp"
3 |
4 | #include "resource.h"
5 | #include "DummyLoader.h"
6 | #include "DummyLoader_i.c"
7 |
8 |
9 | class DummyLoaderModule :
10 | public ATL::CAtlDllModuleT
11 | {
12 | public:
13 | DECLARE_LIBID(LIBID_DummyLoader)
14 | DECLARE_REGISTRY_APPID_RESOURCEID(IDR_AppID, "{92280FDD-C149-44E3-BDEE-736F9F9EEA4E}")
15 | };
16 |
17 | DummyLoaderModule _AtlModule;
18 |
19 |
20 |
21 | // DLL Entry Point
22 | extern "C" BOOL WINAPI DllMain(HINSTANCE /*hInstance*/, DWORD dwReason, LPVOID lpReserved)
23 | {
24 | return _AtlModule.DllMain(dwReason, lpReserved);
25 | }
26 |
27 | // Used to determine whether the DLL can be unloaded by OLE.
28 | STDAPI DllCanUnloadNow()
29 | {
30 | return _AtlModule.DllCanUnloadNow();
31 | }
32 |
33 | // Returns a class factory to create an object of the requested type.
34 | _Check_return_
35 | STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID* ppv)
36 | {
37 | return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
38 | }
39 |
40 | // DllRegisterServer - Adds entries to the system registry.
41 | STDAPI DllRegisterServer()
42 | {
43 | // registers object, typelib and all interfaces in typelib
44 | return _AtlModule.DllRegisterServer();
45 | }
46 |
47 | // DllUnregisterServer - Removes entries from the system registry.
48 | STDAPI DllUnregisterServer()
49 | {
50 | return _AtlModule.DllUnregisterServer();
51 | }
52 |
53 | // DllInstall - Adds/Removes entries to the system registry per user per machine.
54 | STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine)
55 | {
56 | static const wchar_t szUserSwitch[] = L"user";
57 |
58 | if (pszCmdLine != NULL) {
59 | if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0)
60 | ATL::AtlSetPerUserRegistration(true);
61 | }
62 |
63 | HRESULT hr = E_FAIL;
64 | if (bInstall) {
65 | hr = DllRegisterServer();
66 | if (FAILED(hr))
67 | DllUnregisterServer();
68 | }
69 | else {
70 | hr = DllUnregisterServer();
71 | }
72 |
73 | return hr;
74 | }
75 |
--------------------------------------------------------------------------------
/DummyLoader/Resource.h:
--------------------------------------------------------------------------------
1 | #define IDS_PROJNAME 100
2 |
3 | #define IDR_AppID 105
4 | #define IDR_Image3dSource 106
5 | #define IDR_Image3dFileLoader 107
6 |
--------------------------------------------------------------------------------
/DummyLoader/UNREGISTER_DummyLoader.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | echo NOTICE: Script MUST be run as Administrator.
3 | :: Errors from "reg" tool are muted to avoid flooding the build log with errors from already deleted registry entries.
4 |
5 | :: Fix issue with "Run as Administrator" current dir
6 | setlocal enableextensions
7 | cd /d "%~dp0"
8 |
9 |
10 | :: Remove all traces of DummyLoader from registry
11 | for %%R in (HKEY_LOCAL_MACHINE HKEY_CURRENT_USER) do (
12 | :: TypeLib
13 | reg delete "%%R\SOFTWARE\Classes\TypeLib\{67E59584-3F6A-4852-8051-103A4583CA5E}" /f 2> NUL
14 |
15 | for %%P in (32 64) do (
16 | :: Image3dSource class
17 | reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dSource" /f 2> NUL
18 | reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dSource.1" /f 2> NUL
19 | reg delete "%%R\SOFTWARE\Classes\CLSID\{6FA82ED5-6332-4344-8417-DEA55E72098C}" /f /reg:%%P 2> NUL
20 |
21 | :: Image3dFileLoader class
22 | reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dFileLoader" /f 2> NUL
23 | reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dFileLoader.1" /f 2> NUL
24 | reg delete "%%R\SOFTWARE\Classes\CLSID\{8E754A72-0067-462B-9267-E84AF84828F1}" /f /reg:%%P 2> NUL
25 | )
26 | )
27 |
28 | ::pause
29 |
--------------------------------------------------------------------------------
/Guidelines.md:
--------------------------------------------------------------------------------
1 | ## Guidelines for implementing the interface
2 |
3 | ### Performance
4 |
5 | * Data loading of a typical image should take less than 3 seconds, including fetching all frames (from local HDD).
6 | * IImage3dFileLoader::LoadFile must be fast (1-10 ms). I.e. This is needed to permit scanning many DICOM files to see if they can be loaded. LoadFile should not do scan conversion (i.e., from ultrasound scan-lines to a 3D Cartesian volume) by itself. This conversion should be done only when the volume data is actually requested.
7 | * It is strongly recommended to avoid caching of frames inside the loader. It should be up to the host to perform necessary caching. This allows the host to limit memory usage depending on available resources. Hence, loading image frame data from file and scan conversion/image processing should be delayed until Iimage3dSource::GetFrame is called.
8 |
9 | ### Robustness
10 |
11 | * IImage3dFileLoader::LoadFile should return success only if the file can be read as 3D data.
12 | * If IImage3dFileLoader::LoadFile returns success, the file must be readable with the loader.
13 | * IImage3dFileLoader::LoadFile should be well behaved when and after provided with non-conforming DICOM files or non-DICOM files.
14 | * API methods shall not throw exceptions. If it is possible for exceptions to be thrown in the internal implementation, they should be caught within the component, and translated into a failure return value for the IImage3D API method call.
15 | * IImage3D objects should expect themselves to be launched as out-of-process COM servers, because the client software will want to prevent itself from failing if there is a failure in the IImage3D object.
16 |
17 | ### Environment
18 |
19 | * The loader should be compiled both in 32 and 64 bits.
20 | * A DirectX 11 compliant GPU is required for interactive review of 3D images. The loader should still be well behaved when running without a DirectX 11 compliant GPU, but without any performance guarantees.
21 | * The minimum requirement for CPU is 2GHz Intel Core i-series CPU or better, with 2 GB RAM or more.
22 | * The loader should support all resolutions (as specified by Image3dSource::GetFrame parameter max_resolution). It is up to the workstation integration team to assess memory consumption/performance and determine suitable limits for max_resolution upon integration. The loader should be well behaved even though it cannot support the max_resolution requested by the client.
23 | * IImage3dFileLoader::LoadFile should accept both paths on the form 'C:\path' as well as UNC paths on the form '\\server\path'
--------------------------------------------------------------------------------
/Image3dAPI.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Image3dAPI", "Image3dAPI\Image3dAPI.vcxproj", "{4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E504F5C4-A5F4-4D7B-B7DE-B74C0D6A220E}"
9 | ProjectSection(SolutionItems) = preProject
10 | README.md = README.md
11 | EndProjectSection
12 | EndProject
13 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DummyLoader", "DummyLoader\DummyLoader.vcxproj", "{277D8DBA-C8B3-423B-A707-B6435130BF4A}"
14 | ProjectSection(ProjectDependencies) = postProject
15 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3} = {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}
16 | EndProjectSection
17 | EndProject
18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RegFreeTest", "RegFreeTest\RegFreeTest.vcxproj", "{8ABF3DC3-6C48-439B-B3A1-DED84351B808}"
19 | ProjectSection(ProjectDependencies) = postProject
20 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3} = {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}
21 | {277D8DBA-C8B3-423B-A707-B6435130BF4A} = {277D8DBA-C8B3-423B-A707-B6435130BF4A}
22 | EndProjectSection
23 | EndProject
24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestViewer", "TestViewer\TestViewer.csproj", "{08D5EA11-0B4C-40FD-87EC-23D42C195D59}"
25 | ProjectSection(ProjectDependencies) = postProject
26 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3} = {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}
27 | EndProjectSection
28 | EndProject
29 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "TestPython", "TestPython\TestPython.pyproj", "{A594D690-99DB-499B-B3B0-0BF81364EBA2}"
30 | EndProject
31 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SandboxTest", "SandboxTest\SandboxTest.vcxproj", "{DFA5034E-BEA9-4299-A301-38A5DF7FD256}"
32 | ProjectSection(ProjectDependencies) = postProject
33 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3} = {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}
34 | {277D8DBA-C8B3-423B-A707-B6435130BF4A} = {277D8DBA-C8B3-423B-A707-B6435130BF4A}
35 | EndProjectSection
36 | EndProject
37 | Global
38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
39 | Debug|Win32 = Debug|Win32
40 | Debug|x64 = Debug|x64
41 | Release|Win32 = Release|Win32
42 | Release|x64 = Release|x64
43 | EndGlobalSection
44 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
45 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Debug|Win32.ActiveCfg = Debug|Win32
46 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Debug|Win32.Build.0 = Debug|Win32
47 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Debug|x64.ActiveCfg = Debug|x64
48 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Debug|x64.Build.0 = Debug|x64
49 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Release|Win32.ActiveCfg = Release|Win32
50 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Release|Win32.Build.0 = Release|Win32
51 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Release|x64.ActiveCfg = Release|x64
52 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}.Release|x64.Build.0 = Release|x64
53 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Debug|Win32.ActiveCfg = Debug|Win32
54 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Debug|Win32.Build.0 = Debug|Win32
55 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Debug|x64.ActiveCfg = Debug|x64
56 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Debug|x64.Build.0 = Debug|x64
57 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Release|Win32.ActiveCfg = Release|Win32
58 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Release|Win32.Build.0 = Release|Win32
59 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Release|x64.ActiveCfg = Release|x64
60 | {277D8DBA-C8B3-423B-A707-B6435130BF4A}.Release|x64.Build.0 = Release|x64
61 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Debug|Win32.ActiveCfg = Debug|Win32
62 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Debug|Win32.Build.0 = Debug|Win32
63 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Debug|x64.ActiveCfg = Debug|x64
64 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Debug|x64.Build.0 = Debug|x64
65 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Release|Win32.ActiveCfg = Release|Win32
66 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Release|Win32.Build.0 = Release|Win32
67 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Release|x64.ActiveCfg = Release|x64
68 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}.Release|x64.Build.0 = Release|x64
69 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Debug|Win32.ActiveCfg = Debug|x86
70 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Debug|Win32.Build.0 = Debug|x86
71 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Debug|x64.ActiveCfg = Debug|x64
72 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Debug|x64.Build.0 = Debug|x64
73 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Release|Win32.ActiveCfg = Release|x86
74 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Release|Win32.Build.0 = Release|x86
75 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Release|x64.ActiveCfg = Release|x64
76 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}.Release|x64.Build.0 = Release|x64
77 | {A594D690-99DB-499B-B3B0-0BF81364EBA2}.Debug|Win32.ActiveCfg = Debug|Any CPU
78 | {A594D690-99DB-499B-B3B0-0BF81364EBA2}.Debug|x64.ActiveCfg = Debug|Any CPU
79 | {A594D690-99DB-499B-B3B0-0BF81364EBA2}.Release|Win32.ActiveCfg = Release|Any CPU
80 | {A594D690-99DB-499B-B3B0-0BF81364EBA2}.Release|x64.ActiveCfg = Release|Any CPU
81 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Debug|Win32.ActiveCfg = Debug|Win32
82 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Debug|Win32.Build.0 = Debug|Win32
83 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Debug|x64.ActiveCfg = Debug|x64
84 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Debug|x64.Build.0 = Debug|x64
85 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Release|Win32.ActiveCfg = Release|Win32
86 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Release|Win32.Build.0 = Release|Win32
87 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Release|x64.ActiveCfg = Release|x64
88 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}.Release|x64.Build.0 = Release|x64
89 | EndGlobalSection
90 | GlobalSection(SolutionProperties) = preSolution
91 | HideSolutionNode = FALSE
92 | EndGlobalSection
93 | GlobalSection(ExtensibilityGlobals) = postSolution
94 | SolutionGuid = {9C3239D5-451E-429B-95DE-4B251511748E}
95 | EndGlobalSection
96 | EndGlobal
97 |
--------------------------------------------------------------------------------
/Image3dAPI/.gitignore:
--------------------------------------------------------------------------------
1 | /x64
2 | /Win32
3 | /IImage3d.h
4 | /IImage3d_i.c
5 | /IImage3d_p.c
6 | /dlldata.c
7 | /IImage3dTypeLibraryGenerator.h
8 | /IImage3dTypeLibraryGenerator_i.c
9 | /Image3dAPI.dll
10 | /Image3dAPI.vcxproj.user
11 |
--------------------------------------------------------------------------------
/Image3dAPI/ComSupport.hpp:
--------------------------------------------------------------------------------
1 | /* "Plugin API" convenience functions.
2 | Designed by Fredrik Orderud .
3 | Copyright (c) 2016, GE Healthcare, Ultrasound. */
4 | #pragma once
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | #include // for _com_error
11 | #include
12 | #include // for CComSafeArray
13 | #include // for CComObject
14 |
15 |
16 | /** Converts unicode string to ASCII */
17 | static inline std::string ToAscii (const std::wstring& w_str) {
18 | #pragma warning(push)
19 | #pragma warning(disable: 4996) // function or variable may be unsafe
20 | size_t N = w_str.size();
21 | std::string s_str;
22 | s_str.resize(N);
23 |
24 | wcstombs((char*)s_str.c_str(), w_str.c_str(), N);
25 |
26 | return s_str;
27 | #pragma warning(pop)
28 | }
29 |
30 |
31 | /** Translate COM HRESULT failure into exceptions. */
32 | static void CHECK (HRESULT hr) {
33 | if (FAILED(hr)) {
34 | _com_error err(hr);
35 | #ifdef _UNICODE
36 | const wchar_t * msg = err.ErrorMessage(); // weak ptr.
37 | throw std::runtime_error(ToAscii(msg));
38 | #else
39 | const char * msg = err.ErrorMessage(); // weak ptr.
40 | throw std::runtime_error(msg);
41 | #endif
42 | }
43 | }
44 |
45 |
46 | /* Disable BSTR caching to ease memory management debugging.
47 | REF: http://msdn.microsoft.com/en-us/library/windows/desktop/ms644360.aspx */
48 | extern "C" void SetOaNoCache();
49 |
50 |
51 |
52 | /** RAII class for COM initialization. */
53 | class ComInitialize {
54 | public:
55 | ComInitialize (COINIT apartment /*= COINIT_MULTITHREADED*/) : m_initialized(false) {
56 | // REF: https://msdn.microsoft.com/en-us/library/windows/desktop/ms695279.aspx
57 | HRESULT hr = CoInitializeEx(NULL, apartment);
58 | if (SUCCEEDED(hr))
59 | m_initialized = true;
60 |
61 | #ifdef _DEBUG
62 | SetOaNoCache();
63 | #endif
64 | }
65 |
66 | ~ComInitialize() {
67 | if (m_initialized)
68 | CoUninitialize();
69 | }
70 |
71 | private:
72 | bool m_initialized; ///< must uninitialize in dtor
73 | };
74 |
75 |
76 | /** Convenience function to create a locally implemented COM instance without the overhead of CoCreateInstance.
77 | The COM class does not need to be registred for construction to succeed. However, lack of registration can
78 | cause problems if transporting the class out-of-process. */
79 | template
80 | static CComPtr CreateLocalInstance() {
81 | // create an object (with ref. count zero)
82 | CComObject * tmp = nullptr;
83 | CHECK(CComObject::CreateInstance(&tmp));
84 |
85 | // move into smart-ptr (will incr. ref. count to one)
86 | return CComPtr(tmp);
87 | }
88 |
89 |
90 | /** Convert SafeArray to a std::vector. */
91 | template
92 | static std::vector ConvertToVector (const CComSafeArray & input) {
93 | std::vector result(input.GetCount());
94 | if (result.size() > 0) {
95 | const T * in_ptr = &input.GetAt(0); // will fail if empty
96 | for (size_t i = 0; i < result.size(); ++i)
97 | result[i] = in_ptr[i];
98 | }
99 | return result;
100 | }
101 |
102 | /** Convert raw array to SafeArray. */
103 | template
104 | static CComSafeArray ConvertToSafeArray (const T * input, size_t element_count) {
105 | CComSafeArray result(static_cast(element_count));
106 | if (element_count > 0) {
107 | T * out_ptr = &result.GetAt(0); // will fail if empty
108 | for (size_t i = 0; i < element_count; ++i)
109 | out_ptr[i] = input[i];
110 | }
111 | return result;
112 | }
113 |
--------------------------------------------------------------------------------
/Image3dAPI/IImage3d.idl:
--------------------------------------------------------------------------------
1 | /* "3D API" interface header.
2 | Designed by Fredrik Orderud .
3 | Copyright (c) 2016, GE Healthcare, Ultrasound.
4 |
5 | Design goals:
6 | * Stable compiler-independent ABI (binary API).
7 | * Support transfer of image data ownership across API (to avoid copying).
8 | * Support out-of-process loaders (for sandboxing).
9 | * Support multiple programming languages natively.
10 | */
11 |
12 | /* Interfaces and data structures for exchanging image data. */
13 | import "oaidl.idl";
14 |
15 |
16 | typedef [
17 | v1_enum, // 32bit enum size
18 | helpstring("Image3dAPI version.")]
19 | enum Image3dAPIVersion {
20 | IMAGE3DAPI_VERSION_MAJOR = 1,
21 | IMAGE3DAPI_VERSION_MINOR = 2,
22 | } Image3dAPIVersion;
23 |
24 |
25 | typedef [
26 | v1_enum, // 32bit enum size
27 | helpstring("Enum of supported image formats (extended upon demand).")]
28 | enum ImageFormat {
29 | FORMAT_INVALID = 0, ///< make sure that "cleared" state is invalid
30 | FORMAT_U8 = 1, ///< unsigned 8bit grayscale
31 | } ImageFormat;
32 |
33 |
34 | typedef [
35 | v1_enum, // 32bit enum size
36 | helpstring("Probe type enum."
37 | "Values taken from http://dicom.nema.org/medical/Dicom/2017b/output/chtml/part16/sect_CID_12035.html")]
38 | enum ProbeType {
39 | PROBE_UNKNOWN = 0,
40 | PROBE_External = 125261,
41 | PROBE_Transesophageal = 125262,
42 | PROBE_Endovaginal = 125263,
43 | PROBE_Endorectal = 125264,
44 | PROBE_Intravascular = 125265,
45 | } ProbeType;
46 |
47 | cpp_quote("")
48 | cpp_quote("#ifndef __cplusplus")
49 |
50 | typedef [
51 | helpstring("Probe information.")]
52 | struct ProbeInfo {
53 | [helpstring("Can be useful for initializing renderings and quantification tools.")]
54 | ProbeType type;
55 |
56 | [helpstring("short name describing probe")]
57 | BSTR name;
58 | } ProbeInfo;
59 |
60 | cpp_quote("")
61 | cpp_quote("#else // __cplusplus")
62 | cpp_quote("} // extern \"C\"")
63 | cpp_quote("")
64 | cpp_quote("struct ProbeInfo {")
65 | cpp_quote(" ProbeType type = PROBE_UNKNOWN;")
66 | cpp_quote(" CComBSTR name; ///< BSTR wrapper")
67 | cpp_quote("};")
68 | cpp_quote("")
69 | cpp_quote("extern \"C\"{")
70 | cpp_quote("#endif")
71 | cpp_quote("")
72 | cpp_quote("#if defined _WIN64 || defined __x86_64__")
73 | cpp_quote("static_assert(sizeof(ProbeInfo) == 4+4+8, \"ProbeInfo size mismatch\");")
74 | cpp_quote("#else")
75 | cpp_quote("static_assert(sizeof(ProbeInfo) == 4+4, \"ProbeInfo size mismatch\");")
76 | cpp_quote("#endif")
77 |
78 |
79 | cpp_quote("")
80 | cpp_quote("#ifndef __cplusplus")
81 |
82 | typedef [
83 | helpstring("3D image data struct. Stored in row-major order. The data buffer might be padded between rows due to alignment needs.")]
84 | struct Image3d {
85 | [helpstring("time [seconds]")] double time;
86 | [helpstring("")] ImageFormat format;
87 | [helpstring("resolution (width/columns, height/rows, planes)")] unsigned short dims[3];
88 | [helpstring("distance between each row [bytes] (>= width*element_size)")] unsigned int stride0;
89 | [helpstring("distance between each plane [bytes] (>= height*stride0)")] unsigned int stride1;
90 | [helpstring("underlying 1D image buffer (size >= planes*stride1)")] SAFEARRAY(byte) data;
91 | } Image3d;
92 |
93 | cpp_quote("")
94 | cpp_quote("#else // __cplusplus")
95 | cpp_quote("} // extern \"C\"")
96 | cpp_quote("")
97 | cpp_quote("struct Image3d {")
98 | cpp_quote(" double time = 0;")
99 | cpp_quote(" ImageFormat format = FORMAT_INVALID;")
100 | cpp_quote(" unsigned short dims[3] = {0,0,0};")
101 | cpp_quote(" unsigned int stride0 = 0;")
102 | cpp_quote(" unsigned int stride1 = 0;")
103 | cpp_quote(" SAFEARRAY * data = nullptr; ///< BYTE array")
104 | cpp_quote(" ")
105 | cpp_quote(" /* Primary ctor. */")
106 | cpp_quote(" Image3d() {")
107 | cpp_quote(" }")
108 | cpp_quote(" /** Copy ctor. Performs deep copy. */")
109 | cpp_quote(" Image3d(const Image3d& obj) {")
110 | cpp_quote(" time = obj.time;")
111 | cpp_quote(" format = obj.format;")
112 | cpp_quote(" for (unsigned int i = 0; i < 3; ++i)")
113 | cpp_quote(" dims[i] = obj.dims[i];")
114 | cpp_quote(" stride0 = obj.stride0;")
115 | cpp_quote(" stride1 = obj.stride1;")
116 | cpp_quote(" CComSafeArray tmp;")
117 | cpp_quote(" tmp.Attach(obj.data);")
118 | cpp_quote(" tmp.CopyTo(&data);")
119 | cpp_quote(" tmp.Detach();")
120 | cpp_quote(" }")
121 | cpp_quote(" ")
122 | cpp_quote(" ~Image3d() {")
123 | cpp_quote(" release(); // clear existing state")
124 | cpp_quote(" }")
125 | cpp_quote(" ")
126 | cpp_quote(" /** Move assignment.*/")
127 | cpp_quote(" Image3d& operator = (Image3d&& obj) {")
128 | cpp_quote(" release(); // clear existing state")
129 | cpp_quote(" ")
130 | cpp_quote(" time = obj.time;")
131 | cpp_quote(" format = obj.format;")
132 | cpp_quote(" for (unsigned int i = 0; i < 3; ++i)")
133 | cpp_quote(" dims[i] = obj.dims[i];")
134 | cpp_quote(" stride0 = obj.stride0;")
135 | cpp_quote(" stride1 = obj.stride1;")
136 | cpp_quote(" data = obj.data;")
137 | cpp_quote(" obj.data = nullptr;")
138 | cpp_quote(" return *this;")
139 | cpp_quote(" }")
140 | cpp_quote(" ")
141 | cpp_quote("private:")
142 | cpp_quote(" void release () {")
143 | cpp_quote(" if (data) {")
144 | cpp_quote(" CComSafeArray tmp;")
145 | cpp_quote(" tmp.Attach(data);")
146 | cpp_quote(" data = nullptr;")
147 | cpp_quote(" }")
148 | cpp_quote(" }")
149 | cpp_quote(" ")
150 | cpp_quote(" Image3d & operator = (const Image3d& obj) = delete; ///< disallow assignment operator")
151 | cpp_quote("};")
152 | cpp_quote("")
153 | cpp_quote("extern \"C\"{")
154 | cpp_quote("#endif")
155 | cpp_quote("")
156 | cpp_quote("#if defined _WIN64 || defined __x86_64__")
157 | cpp_quote("static_assert(sizeof(Image3d) == 40, \"Image3d size mismatch\");")
158 | cpp_quote("#else")
159 | cpp_quote("static_assert(sizeof(Image3d) == 32, \"Image3d size mismatch\");")
160 | cpp_quote("#endif")
161 |
162 |
163 | typedef [
164 | helpstring("3D image geometry description that matches C.8.X.2.1.2 'Transducer Frame of Reference' in DICOM Enhanced Ultrasound (sup 43)\n"
165 | "All units are in meter [m] with orthogonal axes forming a right-handed coordinate system.\n"
166 | "Coordinates are relative to the tip of the transducer, with origin at the center of the aperture. The X- & Z-axes follow the 1st and 2nd aperture axes, and the Y-axis point into the imaged body.")]
167 | struct Cart3dGeom {
168 | // coordinates are not arrays to avoid "warning MIDL2492: structure containing array of float/double might not be marshalled correctly when using type library marshalling"
169 | float origin_x; ///< coordinate of 1st sample
170 | float origin_y;
171 | float origin_z;
172 |
173 | float dir1_x; ///< 1st direction vector (from first to last column)
174 | float dir1_y;
175 | float dir1_z;
176 |
177 | float dir2_x; ///< 2nd direction vector (from first to last row)
178 | float dir2_y;
179 | float dir2_z;
180 |
181 | float dir3_x; ///< 3rd direction vector (from first to last plane)
182 | float dir3_y;
183 | float dir3_z;
184 | } Cart3dGeom;
185 |
186 | cpp_quote("")
187 | cpp_quote("#ifndef __cplusplus")
188 |
189 | typedef [
190 | helpstring("ECG time series.")]
191 | struct EcgSeries {
192 | [helpstring("time of 1st sample [seconds]")]
193 | double start_time;
194 |
195 | [helpstring("time difference between each sample [seconds]")]
196 | double delta_time;
197 |
198 | [helpstring("ECG sample waveform")]
199 | SAFEARRAY(float) samples;
200 |
201 | [helpstring("QRS/R-wave trig time(s) [seconds] within series [optional]")]
202 | SAFEARRAY(double) trig_times;
203 | } EcgSeries;
204 |
205 | cpp_quote("")
206 | cpp_quote("#else // __cplusplus")
207 | cpp_quote("} // extern \"C\"")
208 | cpp_quote("")
209 | cpp_quote("struct EcgSeries {")
210 | cpp_quote(" double start_time = 0;")
211 | cpp_quote(" double delta_time = 0;")
212 | cpp_quote(" SAFEARRAY * samples = nullptr; ///< float array")
213 | cpp_quote(" SAFEARRAY * trig_times = nullptr; ///< double array")
214 | cpp_quote(" ")
215 | cpp_quote(" /* Primary ctor initializes to empty EcgSeries. */")
216 | cpp_quote(" EcgSeries() {")
217 | cpp_quote(" }")
218 | cpp_quote(" /** Copy ctor. Performs deep copy. */")
219 | cpp_quote(" EcgSeries(const EcgSeries& obj) {")
220 | cpp_quote(" start_time = obj.start_time;")
221 | cpp_quote(" delta_time = obj.delta_time;")
222 | cpp_quote(" CComSafeArray samples_tmp;")
223 | cpp_quote(" samples_tmp.Attach(obj.samples);")
224 | cpp_quote(" samples_tmp.CopyTo(&samples);")
225 | cpp_quote(" samples_tmp.Detach();")
226 | cpp_quote(" CComSafeArray trig_tmp;")
227 | cpp_quote(" trig_tmp.Attach(obj.trig_times);")
228 | cpp_quote(" trig_tmp.CopyTo(&trig_times);")
229 | cpp_quote(" trig_tmp.Detach();")
230 | cpp_quote(" }")
231 | cpp_quote(" ")
232 | cpp_quote(" ~EcgSeries() {")
233 | cpp_quote(" release(); // clear existing state")
234 | cpp_quote(" }")
235 | cpp_quote(" ")
236 | cpp_quote(" /** Move assignment.*/")
237 | cpp_quote(" EcgSeries& operator = (EcgSeries&& obj) {")
238 | cpp_quote(" release(); // clear existing state")
239 | cpp_quote(" ")
240 | cpp_quote(" start_time = obj.start_time;")
241 | cpp_quote(" obj.start_time = 0;")
242 | cpp_quote(" delta_time = obj.delta_time;")
243 | cpp_quote(" obj.delta_time = 0;")
244 | cpp_quote(" samples = obj.samples;")
245 | cpp_quote(" obj.samples = nullptr;")
246 | cpp_quote(" trig_times = obj.trig_times;")
247 | cpp_quote(" obj.trig_times = nullptr;")
248 | cpp_quote(" return *this;")
249 | cpp_quote(" }")
250 | cpp_quote(" ")
251 | cpp_quote("private:")
252 | cpp_quote(" void release () {")
253 | cpp_quote(" if (samples) {")
254 | cpp_quote(" CComSafeArray samples_tmp;")
255 | cpp_quote(" samples_tmp.Attach(samples);")
256 | cpp_quote(" samples = nullptr;")
257 | cpp_quote(" }")
258 | cpp_quote(" if (trig_times) {")
259 | cpp_quote(" CComSafeArray trig_tmp;")
260 | cpp_quote(" trig_tmp.Attach(trig_times);")
261 | cpp_quote(" trig_times = nullptr;")
262 | cpp_quote(" }")
263 | cpp_quote(" }")
264 | cpp_quote(" ")
265 | cpp_quote(" EcgSeries & operator = (const EcgSeries& obj) = delete; ///< disallow assignment operator")
266 | cpp_quote("};")
267 | cpp_quote("")
268 | cpp_quote("extern \"C\"{")
269 | cpp_quote("#endif")
270 | cpp_quote("")
271 | cpp_quote("#if defined _WIN64 || defined __x86_64__")
272 | cpp_quote("static_assert(sizeof(EcgSeries) == 4*8, \"EcgSeries size mismatch\");")
273 | cpp_quote("#else")
274 | cpp_quote("static_assert(sizeof(EcgSeries) == 2*8+2*4, \"EcgSeries size mismatch\");")
275 | cpp_quote("#endif")
276 |
277 | [ object,
278 | oleautomation, // use "automation" marshaler (oleaut32.dll)
279 | uuid(D483D815-52DD-4750-8CA2-5C6C489588B6),
280 | helpstring("Interface for retrieving 3D image data.")]
281 | interface IImage3dSource : IUnknown {
282 | [helpstring("Get the number of frames available")]
283 | HRESULT GetFrameCount ([out,retval] unsigned int * size);
284 |
285 | [helpstring("Get the time of all frames (useful for matching frame indices to ECG before retrieving image data) ")]
286 | HRESULT GetFrameTimes ([out, retval] SAFEARRAY(double) * frame_times);
287 |
288 | [helpstring("Get image data (const) for a given frame within a specified geometry. The returned frame might have lower resolution than requested.")]
289 | HRESULT GetFrame ([in] unsigned int index, [in] Cart3dGeom geom, [in] unsigned short max_resolution[3], [out,retval] Image3d * data);
290 |
291 | [helpstring("Get a bounding box encapsulating all image data. Can be used as intput to GetFrame to avoid cropping.")]
292 | HRESULT GetBoundingBox ([out,retval] Cart3dGeom * geom);
293 |
294 | [helpstring("Retrieve color-map table for mapping image intensities to RGBx values. Length is 256.")]
295 | HRESULT GetColorMap ([out,retval] SAFEARRAY(unsigned int) * map);
296 |
297 | [helpstring("Get ECG data if available [optional]. Shall return S_OK with an empty EcgSeries (all struct members set to 0) if EGC is not available")]
298 | HRESULT GetECG ([out,retval] EcgSeries * ecg);
299 |
300 | [helpstring("")]
301 | HRESULT GetProbeInfo ([out,retval] ProbeInfo * probe);
302 |
303 | [helpstring("Get per-file DICOM UID string (to be matched against corresponding file)")]
304 | HRESULT GetSopInstanceUID ([out,string,retval] BSTR * uid_str);
305 | };
306 |
307 |
308 | typedef [
309 | v1_enum, // 32bit enum size
310 | helpstring("Image3dAPI error codes.")]
311 | enum Image3dError {
312 | Image3d_SUCCESS = 0,
313 | Image3d_ACCESS_FAILURE = 1, ///< file missing, inaccessible or locked
314 | Image3d_VALIDATION_FAILURE = 2, ///< file corrupt or does not contain required vendor tags
315 | Image3d_NOT_YET_SUPPORTED = 3, ///< the loader is too old to read the file and need to be updated
316 | Image3d_SUPPORT_DISCONTINUED = 4, ///< the loader no longer support the file version
317 | } Image3dError;
318 |
319 |
320 | [ object,
321 | oleautomation, // use "automation" marshaler (oleaut32.dll)
322 | uuid(CD30759B-EB38-4469-9CA5-4DF75737A31B),
323 | helpstring("Factory for loading 3D image data from a file.\n"
324 | "Implementors are responsible for also providing details on relevant DICOM tags that indicate that the loader might support the file.")]
325 | interface IImage3dFileLoader : IUnknown {
326 | [helpstring("Load proprietary image file.\n"
327 | "The file might already by opened elsewhere, so no exclusive locks can be taken.\n"
328 | "The function shall return quickly with error type and diagnostic message (in english) in case of failure.")]
329 | HRESULT LoadFile ([in,string] BSTR file_name, [out] Image3dError * error_type, [out,string] BSTR * error_msg);
330 |
331 | HRESULT GetImageSource ([out,retval] IImage3dSource ** img_src);
332 | };
333 |
--------------------------------------------------------------------------------
/Image3dAPI/IImage3dTypeLibraryGenerator.idl:
--------------------------------------------------------------------------------
1 | /* IImage3dTypeLibraryGenerator.idl
2 | * Generates a type library file for IImage3d.idl for use in interface implementation
3 | */
4 |
5 |
6 | import "oaidl.idl";
7 | import "IImage3d.idl";
8 |
9 |
10 | [
11 | uuid(3ff1aab8-f3d8-33d4-825d-00104b3646c0),
12 | helpstring("Interface to generate IImage3dAPI.tlb .")
13 | ]
14 | library Image3dlib
15 | {
16 | importlib("stdole32.tlb");
17 | importlib("stdole2.tlb");
18 |
19 | enum Image3dAPIVersion;
20 | interface IImage3dFileLoader;
21 | };
22 |
--------------------------------------------------------------------------------
/Image3dAPI/Image3dAPI.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Debug
10 | x64
11 |
12 |
13 | Release
14 | Win32
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | {4ADC48A2-1A9E-4F7B-A048-67FE02D31CB3}
23 | My3dAPI
24 | 10.0
25 |
26 |
27 |
28 | StaticLibrary
29 | true
30 | v142
31 | MultiByte
32 |
33 |
34 | StaticLibrary
35 | true
36 | v142
37 | MultiByte
38 |
39 |
40 | StaticLibrary
41 | false
42 | v142
43 | true
44 | MultiByte
45 |
46 |
47 | StaticLibrary
48 | false
49 | v142
50 | true
51 | MultiByte
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | $(SolutionDir)$(Platform)\$(Configuration)\
71 | $(Platform)\$(Configuration)\
72 |
73 |
74 | $(SolutionDir)$(Platform)\$(Configuration)\
75 | $(Platform)\$(Configuration)\
76 |
77 |
78 |
79 | Level3
80 | Disabled
81 | true
82 |
83 |
84 | true
85 |
86 |
87 | $(IntDir)
88 | %(Filename).h
89 | $(OutDir)$(ProjectName).tlb
90 |
91 |
92 | TlbImp.exe /machine:x86 "$(OutDir)$(TargetName).tlb" /out:"$(OutDir)$(TargetName).dll"
93 | :: Copy interface definitions to shared project dir
94 | xcopy /Y /D "$(IntDir)*.h" "$(ProjectDir)" || exit /b 1
95 | xcopy /Y /D "$(IntDir)*.c" "$(ProjectDir)" || exit /b 1
96 | :: Copy type libraries to shared platform folder
97 | xcopy /Y /D "$(OutDir)$(TargetName).tlb" "$(SolutionDir)$(Platform)" || exit /b 1
98 | xcopy /Y /D "$(OutDir)$(TargetName).dll" "$(SolutionDir)$(Platform)" || exit /b 1
99 |
100 |
101 |
102 |
103 | Level3
104 | Disabled
105 | true
106 |
107 |
108 | true
109 |
110 |
111 | $(IntDir)
112 | %(Filename).h
113 | $(OutDir)$(ProjectName).tlb
114 |
115 |
116 | TlbImp.exe /machine:x64 "$(OutDir)$(TargetName).tlb" /out:"$(OutDir)$(TargetName).dll"
117 | :: Copy interface definitions to shared project dir
118 | xcopy /Y /D "$(IntDir)*.h" "$(ProjectDir)" || exit /b 1
119 | xcopy /Y /D "$(IntDir)*.c" "$(ProjectDir)" || exit /b 1
120 | :: Copy type libraries to shared platform folder
121 | xcopy /Y /D "$(OutDir)$(TargetName).tlb" "$(SolutionDir)$(Platform)" || exit /b 1
122 | xcopy /Y /D "$(OutDir)$(TargetName).dll" "$(SolutionDir)$(Platform)" || exit /b 1
123 |
124 |
125 |
126 |
127 | Level3
128 | MaxSpeed
129 | true
130 | true
131 | true
132 |
133 |
134 | true
135 | true
136 | true
137 |
138 |
139 | $(IntDir)
140 | %(Filename).h
141 | $(OutDir)$(ProjectName).tlb
142 |
143 |
144 | TlbImp.exe /machine:x86 "$(OutDir)$(TargetName).tlb" /out:"$(OutDir)$(TargetName).dll"
145 | :: Copy interface definitions to shared project dir
146 | xcopy /Y /D "$(IntDir)*.h" "$(ProjectDir)" || exit /b 1
147 | xcopy /Y /D "$(IntDir)*.c" "$(ProjectDir)" || exit /b 1
148 | :: Copy type libraries to shared platform folder
149 | xcopy /Y /D "$(OutDir)$(TargetName).tlb" "$(SolutionDir)$(Platform)" || exit /b 1
150 | xcopy /Y /D "$(OutDir)$(TargetName).dll" "$(SolutionDir)$(Platform)" || exit /b 1
151 |
152 |
153 |
154 |
155 | Level3
156 | MaxSpeed
157 | true
158 | true
159 | true
160 |
161 |
162 | true
163 | true
164 | true
165 |
166 |
167 | $(IntDir)
168 | %(Filename).h
169 | $(OutDir)$(ProjectName).tlb
170 |
171 |
172 | TlbImp.exe /machine:x64 "$(OutDir)$(TargetName).tlb" /out:"$(OutDir)$(TargetName).dll"
173 | :: Copy interface definitions to shared project dir
174 | xcopy /Y /D "$(IntDir)*.h" "$(ProjectDir)" || exit /b 1
175 | xcopy /Y /D "$(IntDir)*.c" "$(ProjectDir)" || exit /b 1
176 | :: Copy type libraries to shared platform folder
177 | xcopy /Y /D "$(OutDir)$(TargetName).tlb" "$(SolutionDir)$(Platform)" || exit /b 1
178 | xcopy /Y /D "$(OutDir)$(TargetName).dll" "$(SolutionDir)$(Platform)" || exit /b 1
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/Image3dAPI/Image3dAPI.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Image3dAPI/RegistryCheck.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include "ComSupport.hpp"
4 |
5 |
6 | static const REGSAM WOW_SAME_AS_CLIENT = 0;
7 |
8 | /** Compare the version of a COM class against the current Image3dAPI version.
9 | NOTE: Not compatible with reg-free COM, since the COM class version is not included in the manifest.
10 | \param clsid - COM class ID (hexadecimal)
11 | \param bitness - Part of the registry to query. Alternatives:
12 | WOW_SAME_AS_CLIENT Query part of regitry that matches client bitnes
13 | KEY_WOW64_32KEY Query 32-bit part of registry, regardless of client bitness
14 | KEY_WOW64_64KEY Query 64-bit part of registry, regardless of client bitness */
15 | static HRESULT CheckImage3dAPIVersion (CLSID clsid, REGSAM bitness = WOW_SAME_AS_CLIENT) {
16 | // build registry path")
17 | CComBSTR reg_path(L"CLSID\\");
18 | reg_path.Append(clsid);
19 | reg_path.Append(L"\\Version");
20 |
21 | // extract COM class version
22 | CRegKey cls_reg;
23 | if (cls_reg.Open(HKEY_CLASSES_ROOT, reg_path, KEY_READ | bitness) != ERROR_SUCCESS)
24 | return E_NOT_SET;
25 | ULONG ver_str_len = 0;
26 | if (cls_reg.QueryStringValue(nullptr, nullptr, &ver_str_len) != ERROR_SUCCESS)
27 | return E_NOT_SET;
28 | std::wstring ver_str(ver_str_len, L'\0');
29 | if (cls_reg.QueryStringValue(nullptr, const_cast(ver_str.data()), &ver_str_len) != ERROR_SUCCESS)
30 | return E_NOT_SET;
31 | ver_str.resize(ver_str_len-1); // remove extra zero-termination
32 |
33 | // compare against current Image3dAPI version
34 | std::wstring cur_ver = std::to_wstring(Image3dAPIVersion::IMAGE3DAPI_VERSION_MAJOR)
35 | +L"."+std::to_wstring(Image3dAPIVersion::IMAGE3DAPI_VERSION_MINOR);
36 | if (ver_str == cur_ver)
37 | return S_OK;
38 | else
39 | return E_INVALIDARG; // version mismatch
40 | }
41 |
42 | struct SupportedManufacturerModels {
43 | /** Read list of suported (manufacturer,model)-pairs for a given plugin.
44 | \param clsid - COM class ID (hexadecimal)
45 | \param bitness - Part of the registry to query. Alternatives:
46 | WOW_SAME_AS_CLIENT Query part of regitry that matches client bitnes
47 | KEY_WOW64_32KEY Query 32-bit part of registry, regardless of client bitness
48 | KEY_WOW64_64KEY Query 64-bit part of registry, regardless of client bitness */
49 | static std::vector ReadList (CLSID clsid, REGSAM bitness = WOW_SAME_AS_CLIENT) {
50 | // build registry path")
51 | CComBSTR reg_path(L"CLSID\\");
52 | reg_path.Append(clsid);
53 | reg_path.Append(L"\\SupportedManufacturerModels");
54 |
55 | std::vector list;
56 |
57 | // extract COM class
58 | CRegKey cls_reg;
59 | if (cls_reg.Open(HKEY_CLASSES_ROOT, reg_path, KEY_READ | bitness) != ERROR_SUCCESS)
60 | return list;
61 |
62 |
63 | for (DWORD idx = 0;; ++idx) {
64 | // read key name and value length
65 | // registry key names are guaranteed to not exceed 16k characters (https://docs.microsoft.com/nb-no/windows/desktop/SysInfo/registry-element-size-limits)
66 | DWORD name_len = 16384; // key name length (excluding null-termination)
67 | std::wstring name(name_len, L'\0');
68 | DWORD type = 0;
69 | DWORD value_len = 0; // in bytes
70 | auto res = RegEnumValue(cls_reg, idx, /*out*/const_cast(name.data()), &name_len, NULL, &type, nullptr/*out*/, &value_len);
71 | if (res != ERROR_SUCCESS)
72 | break;
73 | name.resize(name_len); // shrink to fit
74 |
75 | if (type != REG_SZ)
76 | continue; // value not a string
77 |
78 | // read key value
79 | value_len /= 2; // convert lenght from bytes to #chars (including null-termination)
80 | std::wstring value(value_len, L'\0');
81 | res = cls_reg.QueryStringValue(name.c_str(), const_cast(value.data()), &value_len);
82 | if (res != ERROR_SUCCESS)
83 | break;
84 | value.resize(value_len-1); // remove null-termination
85 |
86 | // split key value on ';'
87 | std::vector models;
88 | for (size_t pos = 0;;) {
89 | auto prev = pos;
90 | pos = value.find(L';', pos);
91 |
92 | if (pos == std::wstring::npos) {
93 | // no more separators found
94 | models.push_back(value.substr(prev));
95 | break;
96 | } else {
97 | models.push_back(value.substr(prev, pos - prev));
98 | pos += 1; // skip separator
99 | }
100 | }
101 |
102 | list.push_back({name, models});
103 | }
104 |
105 | return list;
106 | }
107 |
108 | std::wstring manufacturer; ///< corresponds to Manufacturer tag, DICOM (0008,0070)
109 | std::vector models; ///< associated Manufacturer's Model Name tags, DICOM (0008,1090). The strings might contain '*' as wildcard
110 | };
111 |
--------------------------------------------------------------------------------
/Image3dAPI/UNREGISTER_Image3dAPI.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | echo NOTICE: Script MUST be run as Administrator.
3 | :: Errors from "reg" tool are muted to avoid flooding the build log with errors from already deleted registry entries.
4 |
5 | :: Fix issue with "Run as Administrator" current dir
6 | setlocal enableextensions
7 | cd /d "%~dp0"
8 |
9 |
10 | :: Remove all traces of Image3dAPI interfaces from registry
11 |
12 | :: IImage3dTypeLibraryGenerator.idl
13 | reg delete "HKCR\TypeLib\{3ff1aab8-f3d8-33d4-825d-00104b3646c0}" /f 2> NUL
14 |
15 | for %%P in (32 64) do (
16 | :: IImage3d.idl
17 | reg delete "HKCR\Interface\{D483D815-52DD-4750-8CA2-5C6C489588B6}" /f /reg:%%P 2> NUL
18 | reg delete "HKCR\Interface\{CD30759B-EB38-4469-9CA5-4DF75737A31B}" /f /reg:%%P 2> NUL
19 | )
20 |
21 | ::pause
22 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | API License Agreement for GE Healthcare’s Image3DAPI
2 |
3 | This API License Agreement (“Agreement”) contains the terms and conditions that govern your access to and use of GE Healthcare’s Image3DAPI (“Image3DAPI”), as further detailed below, and is an agreement between GE Healthcare (“GE”) and you or the entity on whose behalf you accept these terms. By clicking an “I Accept” button or check box presented with these terms, or by using the access credentials provided to download and/or use the Image3DAPI, you agree to the following terms and conditions.
4 |
5 | 1. Definition of Terms
6 | “Image3DAPI” defines a vendor neutral/agnostic Application Programming Interface (“API”) for loading 3D ultrasound image data stored in proprietary data formats. A separate loader library is required for each data format.
7 |
8 | 2. Copyright Notice
9 | Copyright (c) 2017, GE Healthcare
10 | All rights reserved.
11 |
12 | 3. User Terms and Conditions
13 | Redistribution and use of the Image3DAPI in source and/or binary forms, with or without modification, is permitted provided that the following conditions are met:
14 | • Redistributions of source code must retain the above Copyright Notice, this list of conditions and the following disclaimer.
15 | • Redistributions in binary form must reproduce the above Copyright Notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
16 | • Neither the name of the GE Healthcare nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
17 |
18 | 4. Disclaimer and Limitation of Liabilities
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GE HEALTHCARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
20 |
--------------------------------------------------------------------------------
/PackagingGE/BuildNuGet.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set NUGET_REPO=%1
3 |
4 | set PATH=%PATH%;C:\Python27
5 | set PATH=%PATH%;"C:\Program Files\Git\bin"
6 |
7 | :: Dependencies:
8 | :: * Python installed
9 | :: * Visual Studio command prompt (msbuild & C++ compiler in PATH)
10 | :: * NuGet.exe in PATH (https://www.nuget.org/downloads)
11 |
12 | pushd ..
13 |
14 | if DEFINED NUGET_REPO (
15 | PackagingGE\DetermineNextTag.py minor > NEW_TAG.txt
16 | ) else (
17 | :: Local nuget build. Not to be deployed.
18 | PackagingGE\DetermineNextTag.py patch > NEW_TAG.txt
19 | )
20 | IF %ERRORLEVEL% NEQ 0 exit /B 1
21 | set /p VERSION=< NEW_TAG.txt
22 |
23 |
24 | echo Building projects:
25 | msbuild /nologo /verbosity:minimal /target:Image3dAPI;DummyLoader /property:Configuration="Debug";Platform="Win32" Image3dAPI.sln
26 | IF %ERRORLEVEL% NEQ 0 exit /B 1
27 | msbuild /nologo /verbosity:minimal /target:Image3dAPI;DummyLoader /property:Configuration="Release";Platform="Win32" Image3dAPI.sln
28 | IF %ERRORLEVEL% NEQ 0 exit /B 1
29 | msbuild /nologo /verbosity:minimal /target:Image3dAPI;DummyLoader /property:Configuration="Debug";Platform="x64" Image3dAPI.sln
30 | IF %ERRORLEVEL% NEQ 0 exit /B 1
31 | msbuild /nologo /verbosity:minimal /target:Image3dAPI;DummyLoader /property:Configuration="Release";Platform="x64" Image3dAPI.sln
32 | IF %ERRORLEVEL% NEQ 0 exit /B 1
33 |
34 | echo Determine previous tag:
35 | git describe --abbrev=0 --tags > PREV_TAG.txt
36 | set /p PREV_TAG=< PREV_TAG.txt
37 |
38 |
39 | echo Creating new GIT tag:
40 | git tag %VERSION%
41 | if DEFINED NUGET_REPO (
42 | git push origin %VERSION%
43 | )
44 |
45 | echo Generating changelog (with tag decoration, graph and change stats):
46 | git log %PREV_TAG%..%VERSION% --decorate --graph --stat > changelog.txt
47 |
48 | pushd Image3dApi
49 | CALL ..\PackagingGE\PackagePublishNuget.bat ..\PackagingGE\Image3dAPI.nuspec %VERSION% %NUGET_REPO%
50 | IF %ERRORLEVEL% NEQ 0 exit /B 1
51 | popd
52 |
53 | pushd DummyLoader
54 | CALL ..\PackagingGE\PackagePublishNuget.bat ..\PackagingGE\DummyLoader.redist.nuspec %VERSION% %NUGET_REPO%
55 | IF %ERRORLEVEL% NEQ 0 exit /B 1
56 | popd
57 |
58 | popd
59 | echo [done]
60 |
--------------------------------------------------------------------------------
/PackagingGE/DetermineNextTag.py:
--------------------------------------------------------------------------------
1 | # Script to determine highest tag number and increment version
2 | from __future__ import print_function
3 | from distutils.version import StrictVersion
4 | import subprocess
5 | import sys
6 |
7 |
8 | def DetermineHighestTagVersion ():
9 | # retrive all tags
10 | cmd = 'git tag'
11 | p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
12 | tags = []
13 | for line in p.stdout.readlines():
14 | line = line.decode()
15 | line = line.strip(' /\r\n') # remove '/' & newline suffix
16 | tags.append(line)
17 |
18 | # identify newest tag
19 | newest_tag = tags[0]
20 | for tag in tags:
21 | try:
22 | if StrictVersion(tag) > StrictVersion(newest_tag):
23 | newest_tag = tag
24 | except:
25 | print('Ignoring unparsable tag '+tag, file=sys.stderr)
26 |
27 | return newest_tag
28 |
29 |
30 | def IncrementVersionNumber (version, incr_idx):
31 | # tokenize version
32 | tokens = version.split('.')
33 |
34 | # append zero tokens to avoid out-of-bounds access
35 | while incr_idx >= len(tokens):
36 | tokens.append('0')
37 |
38 | # increment token
39 | tokens[incr_idx] = str(int(tokens[incr_idx])+1)
40 |
41 | # set lower version indices to zero (eg. go from '0.9' to '1.0')
42 | for i in range(incr_idx+1, len(tokens)):
43 | tokens[i] = '0'
44 |
45 | # joint tokens into new version string
46 | return '.'.join(tokens)
47 |
48 |
49 | if __name__ == "__main__":
50 | incr_type = sys.argv[1] # version increment type (major, minor, patch)
51 |
52 | # determine which version index to increment
53 | if incr_type == 'major': incr_idx = 0
54 | elif incr_type == 'minor': incr_idx = 1
55 | elif incr_type == 'patch': incr_idx = 2
56 | else: raise Exception('unknown incr_type')
57 |
58 | prev_ver = DetermineHighestTagVersion()
59 | new_ver = IncrementVersionNumber(prev_ver, incr_idx)
60 |
61 | # output new version number to stdout, without newline suffix
62 | sys.stdout.write(new_ver)
63 |
--------------------------------------------------------------------------------
/PackagingGE/DummyLoader.redist.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DummyLoader.redist
5 | dummyloader, native
6 | GE Vingmed Ultrasound (GE Healthcare)
7 | <>
8 |
9 | http://PROPRIETARY_LICENSE
10 | http://www.ge.com/sites/all/themes/ge_2012/favicon.ico
11 | false
12 | Dummy implementation of COM APIs for cross-vendor exchange of 3D ultrasound image data.
13 |
14 |
15 | Fredrik Orderud
16 | <>
17 | changelog in docs/changelog.txt
18 | Copyright 2015-2018
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/PackagingGE/DummyLoader.redist.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/PackagingGE/Image3dAPI.net.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildThisFileDirectory)..\native\lib\Win32\Image3dAPI.dll
8 | true
9 |
10 |
11 |
12 |
13 | $(MSBuildThisFileDirectory)..\native\lib\x64\Image3dAPI.dll
14 | true
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/PackagingGE/Image3dAPI.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Image3dAPI
5 | image3dapi, native
6 | GE Ultrasound (GE Healthcare)
7 | <>
8 |
9 | http://PROPRIETARY_LICENSE
10 | http://www.ge.com/sites/all/themes/ge_2012/favicon.ico
11 | false
12 | COM APIs for cross-vendor exchange of 3D ultrasound image data
13 |
14 |
15 | Fredrik Orderud
16 | <>
17 |
18 | changelog in docs/changelog.txt
19 | Copyright 2015-2018
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/PackagingGE/Image3dAPI.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | $(MSBuildThisFileDirectory)include\;$(MSBuildThisFileDirectory)include\Image3dAPI;%(AdditionalIncludeDirectories)
6 |
7 |
8 | $(MSBuildThisFileDirectory)include\Image3dAPI;%(AdditionalIncludeDirectories)
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/PackagingGE/PackagePublishNuget.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set NUSPEC_FILE=%1
3 | set VERSION_NUMBER=%2
4 | set NUGET_REPO=%3
5 |
6 | :: Change NuGet packaging version and project URL
7 | ..\PackagingGE\SetNuspecVersion.py %NUSPEC_FILE% %VERSION_NUMBER% https://github.com/MedicalUltrasound/Image3dAPI/tree/%VERSION_NUMBER%
8 | IF %ERRORLEVEL% NEQ 0 exit /B 1
9 |
10 | :: Package artifacts
11 | nuget pack %NUSPEC_FILE%
12 | IF %ERRORLEVEL% NEQ 0 exit /B 1
13 |
14 | :: Publish artifact
15 | if NOT DEFINED NUGET_REPO exit /B 0
16 | :: Use timeout not dividable by 60 as work-around for https://nuget.codeplex.com/workitem/3917
17 | NuGet.exe push *.nupkg -Source %NUGET_REPO% -Timeout 1201
18 | IF %ERRORLEVEL% NEQ 0 exit /B 1
19 |
--------------------------------------------------------------------------------
/PackagingGE/SetNuspecVersion.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 | import fileinput
3 | import os
4 | import sys
5 | from xml.dom import minidom
6 |
7 |
8 | def IdentifyDependencies (filename):
9 | '''Parse NuGet dependencies from a 'packages.config'-style file'''
10 |
11 | # parse dependencies into a dict
12 | xml_doc = minidom.parse(filename)
13 | dep_dict = {}
14 | for package in xml_doc.childNodes[0].childNodes:
15 | if package.nodeName == 'package':
16 | id = package.attributes['id'].value
17 | version = package.attributes['version'].value
18 | dep_dict[id] = version
19 |
20 | return dep_dict
21 |
22 |
23 | def UpdateNuspec (filename, version, url, dependencies):
24 | '''Set the minor version in an nuspec file'''
25 |
26 | for line in fileinput.input(filename, inplace=True):
27 | if '<>' in line:
28 | # use current git version
29 | print(line.replace('<>', version), end='')
30 | elif '<>' in line:
31 | print(line.replace('<>', url), end='')
32 | elif '<>' in line:
33 | # line on "" format
34 | tokens = line.split()
35 | for token in tokens:
36 | if token[:4] == 'id="':
37 | package = token[4:-1]
38 | elif token[:6] == 'version="':
39 | pass # assume that tag contains "<>" string
40 | line = ' '
41 | print(line, end='\n')
42 | elif '<>' in line:
43 | # convert dependency dict to nuspec XML representation
44 | dep_str = ''
45 | for key in dependencies:
46 | dep_str += '\n '
47 | print(line.replace('<>', dep_str), end='')
48 | else:
49 | print(line, end='')
50 |
51 |
52 | if __name__ == "__main__":
53 | filename = sys.argv[1] # nuspec file
54 | version = sys.argv[2] # version number
55 | url = sys.argv[3] # release URL
56 |
57 | # parse dependencies from NuGet config file arguments
58 | deps = {}
59 | for i in range(4, len(sys.argv)):
60 | print('Parsing dependencies from '+sys.argv[i])
61 | tmp = IdentifyDependencies(sys.argv[i])
62 | for key in tmp:
63 | if key in deps:
64 | if deps[key] != tmp[key]:
65 | raise BaseException('Version mismatch in package '+key+': '+deps[key]+' vs. '+tmp[key])
66 | else:
67 | deps[key] = tmp[key]
68 |
69 | print('Setting version and URL of '+filename+' to '+version+' and '+url)
70 | UpdateNuspec(filename, version, url, deps)
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Image3dAPI
2 | Interfaces for inter-vendor exchange of 3D ultrasound data, together with test code.
3 |
4 | ## Description
5 | Application Programming Interface (API) to load any vendor’s proprietary ultrasound data, commonly referred to as “raw” data, using a well-defined data structures that can be used to render, display and manipulate 3D data within any vendor’s review and analysis tools.
6 |
7 | Each ultrasound vendor will need to provide an implementation of this API specific to their proprietary data format. Any software or analysis tool using the API to load data can be agnostic to the specifics of the data loader, as only the common API needs to be considered.
8 |
9 | ### Content
10 | * [DummyLoader](DummyLoader/) - Example loader library
11 | * [Image3dAPI](Image3dAPI/) - API definitions
12 | * [PackagingGE](PackagingGE/) - NuGet packaging configuration
13 | * [RegFreeTest](RegFreeTest/) - Example of how to leverage manifest files to avoid COM registration
14 | * [SandboxTest](SandboxTest/) - Example of how to sandbox a loader in a separate process
15 | * [TestPython](TestPython/) - Python-based sample code
16 | * [TestViewer](TestViewer/) - Simple .NET-based image viewer
17 |
18 | ## Benefits
19 | This will allow analysis tools to support data from multiple vendors, rather than having to use “plugins”, e.g. GE EchoPAC or Philips QLab. This approach also allows the vendors to simplify their commercial offerings to end-users since it will no longer be required to purchase multiple plugins from multiple vendors and coordinate multiple installs.
20 |
21 | Furthermore, companies outside the ultrasound system vendors (such as 3D printing, Virtual Reality, Surgical Planning, researchers, etc.) will be able to load 3D echo data seamlessly from all participating vendors.
22 |
23 | ## Relationship to DICOM
24 | The DICOM 3D standard is an alternative solution to sharing of 3D ultrasound data. However, DICOM 3D does not allow access to studies that are already stored in existing 3D proprietary formats. Furthermore, the limited vendor adoption of DICOM 3D means that it will take years for it to truly impact the market.
25 |
26 | This approach has advantages over the traditional DICOM data standard technique including complete seamless backward compatibility to existing stored studies, smaller files sizes, faster network transfers, and faster loading of studies (since no conversion from vendor raw data to DICOM format is required). This new approach also allows the vendors to continually innovate as well, instead of being locked into a frozen data specification.
27 |
28 | ## Getting started
29 | Install [Visual Studio](https://visualstudio.microsoft.com/), either professional or community edition. Any version >= 2015 is supported.
30 | Enable the following workloads during installation:
31 | * .Net desktop development,
32 | * Desktop development with C++, and
33 | * Python development.
34 |
35 | In addition, Python.exe need to be registered as default app for running .py scripts. This can be done by right-clicking on a .py file, then selecting "Open with", "Choose another app", locate Python.exe and enable "Always use this app to open .py files".
36 |
37 |
38 | Build instructions:
39 | * Open Visual Studio with administrative privileges.
40 | * Open `Image3dAPI.sln`.
41 | * Build the solution.
42 |
43 | ## Documentation
44 | * [API license](LICENSE.txt)
45 | * [Guidelines](Guidelines.md) for implementing the interface
46 |
--------------------------------------------------------------------------------
/RegFreeTest/.gitignore:
--------------------------------------------------------------------------------
1 | /Win32
2 | /x64
3 | /RegFreeTest.vcxproj.user
4 |
--------------------------------------------------------------------------------
/RegFreeTest/Main.cpp:
--------------------------------------------------------------------------------
1 | #include "../Image3dAPI/ComSupport.hpp"
2 | #include "../Image3dAPI/IImage3d.h"
3 | #include "../Image3dAPI/RegistryCheck.hpp"
4 | #include
5 |
6 |
7 | void ParseSource (IImage3dSource & source) {
8 | CComSafeArray color_map;
9 | {
10 | SAFEARRAY * tmp = nullptr;
11 | CHECK(source.GetColorMap(&tmp));
12 | color_map.Attach(tmp);
13 | tmp = nullptr;
14 | }
15 |
16 | Cart3dGeom bbox = {};
17 | CHECK(source.GetBoundingBox(&bbox));
18 |
19 | unsigned int frame_count = 0;
20 | CHECK(source.GetFrameCount(&frame_count));
21 | std::wcout << L"Frame count: " << frame_count << L"\n";
22 |
23 | for (unsigned int frame = 0; frame < frame_count; ++frame) {
24 | unsigned short max_res[] = {64, 64, 64};
25 |
26 | // retrieve frame data
27 | Image3d data;
28 | CHECK(source.GetFrame(frame, bbox, max_res, &data));
29 | }
30 | }
31 |
32 |
33 | int wmain (int argc, wchar_t *argv[]) {
34 | if (argc < 3) {
35 | std::wcout << L"Usage:\n";
36 | std::wcout << L"RegFreeTest.exe " << std::endl;
37 | return -1;
38 | }
39 |
40 | CComBSTR progid = argv[1]; // e.g. "DummyLoader.Image3dFileLoader"
41 | CComBSTR filename = argv[2];
42 |
43 | ComInitialize com(COINIT_MULTITHREADED);
44 |
45 | CLSID clsid = {};
46 | if (FAILED(CLSIDFromProgID(progid, &clsid))) {
47 | std::wcerr << L"ERRORR: Unknown progid " << progid.m_str << L"\n";
48 | return -1;
49 | }
50 |
51 | // verify that loader library is compatible
52 | //CHECK(CheckImage3dAPIVersion(clsid)); // disabled, since it's not compatible with reg-free COM
53 |
54 | // create loader without accessing the registry (possible since the DummyLoader manifest is linked in)
55 | std::wcout << L"Creating loader " << progid.m_str << L"...\n";
56 | CComPtr loader;
57 | CHECK(loader.CoCreateInstance(clsid));
58 |
59 | {
60 | // load file
61 | Image3dError err_type = {};
62 | CComBSTR err_msg;
63 | HRESULT hr = loader->LoadFile(filename, &err_type, &err_msg);
64 | if (FAILED(hr)) {
65 | std::wcerr << L"LoadFile failed: code=" << err_type << L", message=" << err_msg.m_str << std::endl;
66 | return -1;
67 | }
68 | }
69 |
70 | CComPtr source;
71 | CHECK(loader->GetImageSource(&source));
72 |
73 | ProbeInfo probe;
74 | CHECK(source->GetProbeInfo(&probe));
75 |
76 | EcgSeries ecg;
77 | CHECK(source->GetECG(&ecg));
78 |
79 | ParseSource(*source);
80 |
81 | return 0;
82 | }
83 |
--------------------------------------------------------------------------------
/RegFreeTest/RegFreeTest.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Debug
10 | x64
11 |
12 |
13 | Release
14 | Win32
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | {8ABF3DC3-6C48-439B-B3A1-DED84351B808}
23 | Win32Proj
24 | RegFreeTest
25 | 10.0
26 |
27 |
28 |
29 | Application
30 | true
31 | v142
32 | Unicode
33 |
34 |
35 | Application
36 | true
37 | v142
38 | Unicode
39 |
40 |
41 | Application
42 | false
43 | v142
44 | true
45 | Unicode
46 |
47 |
48 | Application
49 | false
50 | v142
51 | true
52 | Unicode
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | true
72 | $(SolutionDir)$(Platform)\$(Configuration)\
73 | $(Platform)\$(Configuration)\
74 |
75 |
76 | true
77 | $(SolutionDir)$(Platform)\$(Configuration)\
78 | $(Platform)\$(Configuration)\
79 |
80 |
81 | false
82 | $(SolutionDir)$(Platform)\$(Configuration)\
83 | $(Platform)\$(Configuration)\
84 |
85 |
86 | false
87 | $(SolutionDir)$(Platform)\$(Configuration)\
88 | $(Platform)\$(Configuration)\
89 |
90 |
91 |
92 | Level3
93 | Disabled
94 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
95 | true
96 |
97 |
98 | Console
99 | true
100 |
101 |
102 | $(OutDir)DummyLoaderD.dll.manifest
103 |
104 |
105 |
106 |
107 | Level3
108 | Disabled
109 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
110 | true
111 |
112 |
113 | Console
114 | true
115 |
116 |
117 | $(OutDir)DummyLoaderD.dll.manifest
118 |
119 |
120 |
121 |
122 | Level3
123 | MaxSpeed
124 | true
125 | true
126 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
127 | true
128 |
129 |
130 | Console
131 | true
132 | true
133 | true
134 |
135 |
136 | $(OutDir)DummyLoader.dll.manifest
137 |
138 |
139 |
140 |
141 | Level3
142 | MaxSpeed
143 | true
144 | true
145 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
146 | true
147 |
148 |
149 | Console
150 | true
151 | true
152 | true
153 |
154 |
155 | $(OutDir)DummyLoader.dll.manifest
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/RegFreeTest/RegFreeTest.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/SandboxTest/.gitignore:
--------------------------------------------------------------------------------
1 | /Win32
2 | /x64
3 | /SandboxTest.vcxproj.user
4 |
--------------------------------------------------------------------------------
/SandboxTest/LowIntegrity.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 |
5 |
6 | /** RAII class for temporarily impersonating low-integrity level for the current thread.
7 | Intended to be used together with CLSCTX_ENABLE_CLOAKING when creating COM objects.
8 | Based on "Designing Applications to Run at a Low Integrity Level" https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/bb625960(v=msdn.10) */
9 | struct LowIntegrity {
10 | LowIntegrity() {
11 | HANDLE cur_token = nullptr;
12 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, &cur_token))
13 | abort();
14 |
15 | if (!DuplicateTokenEx(cur_token, 0, NULL, SecurityImpersonation, TokenPrimary, &m_token))
16 | abort();
17 |
18 | CloseHandle(cur_token);
19 |
20 | PSID li_sid = nullptr;
21 | if (!ConvertStringSidToSid(L"S-1-16-4096", &li_sid)) // low integrity SID
22 | abort();
23 |
24 | // reduce process integrity level
25 | TOKEN_MANDATORY_LABEL TIL = {};
26 | TIL.Label.Attributes = SE_GROUP_INTEGRITY;
27 | TIL.Label.Sid = li_sid;
28 | if (!SetTokenInformation(m_token, TokenIntegrityLevel, &TIL, sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(li_sid)))
29 | abort();
30 |
31 | if (!ImpersonateLoggedOnUser(m_token)) // change current thread integrity
32 | abort();
33 |
34 | LocalFree(li_sid);
35 | li_sid = nullptr;
36 | }
37 |
38 | ~LowIntegrity() {
39 | if (!RevertToSelf())
40 | abort();
41 |
42 | CloseHandle(m_token);
43 | m_token = nullptr;
44 | }
45 |
46 | private:
47 | HANDLE m_token = nullptr;
48 | };
49 |
--------------------------------------------------------------------------------
/SandboxTest/Main.cpp:
--------------------------------------------------------------------------------
1 | #include "../Image3dAPI/ComSupport.hpp"
2 | #include "../Image3dAPI/IImage3d.h"
3 | #include "../Image3dAPI/RegistryCheck.hpp"
4 | #include "LowIntegrity.hpp"
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 |
12 | class PerfTimer {
13 | using clock = std::chrono::high_resolution_clock;
14 | public:
15 | PerfTimer(const char * prefix, bool enable) : m_prefix(prefix) {
16 | if (enable)
17 | m_start = clock::now();
18 |
19 | }
20 | ~PerfTimer() {
21 | if (m_start == clock::time_point())
22 | return; // profiling disabled
23 |
24 | auto stop = clock::now();
25 | auto duration = std::chrono::duration_cast(stop - m_start).count();
26 | std::cout << m_prefix << " took " << duration << "ms\n";
27 | }
28 | private:
29 | const char * m_prefix;
30 | clock::time_point m_start;
31 | };
32 |
33 |
34 | void ParseSource (IImage3dSource & source, bool verbose, bool profile) {
35 | CComSafeArray color_map;
36 | {
37 | SAFEARRAY * tmp = nullptr;
38 | CHECK(source.GetColorMap(&tmp));
39 | color_map.Attach(tmp);
40 | tmp = nullptr;
41 | }
42 |
43 | if (verbose) {
44 | std::cout << "Color-map:\n";
45 | for (unsigned int i = 0; i < color_map.GetCount(); i++) {
46 | unsigned int color = color_map[(int)i];
47 | uint8_t *rgbx = reinterpret_cast(&color);
48 | std::cout << " [" << (int)rgbx[0] << "," << (int)rgbx[1] << "," << (int)rgbx[2] << "," << (int)rgbx[3] << "]\n";
49 | }
50 | }
51 |
52 | Cart3dGeom bbox = {};
53 | CHECK(source.GetBoundingBox(&bbox));
54 |
55 | if (verbose) {
56 | std::cout << "Bounding box:\n";
57 | std::cout << " Origin: " << bbox.origin_x << ", " << bbox.origin_y << ", " << bbox.origin_z << "\n";
58 | std::cout << " Dir1: " << bbox.dir1_x << ", " << bbox.dir1_y << ", " << bbox.dir1_z << "\n";
59 | std::cout << " Dir2: " << bbox.dir2_x << ", " << bbox.dir2_y << ", " << bbox.dir2_z << "\n";
60 | std::cout << " Dir3: " << bbox.dir3_x << ", " << bbox.dir3_y << ", " << bbox.dir3_z << "\n";
61 | }
62 |
63 | unsigned int frame_count = 0;
64 | CHECK(source.GetFrameCount(&frame_count));
65 | std::wcout << L"Frame count: " << frame_count << L"\n";
66 |
67 | CComSafeArray frame_times;
68 | {
69 | SAFEARRAY * data = nullptr;
70 | CHECK(source.GetFrameTimes(&data));
71 | frame_times.Attach(data);
72 | data = nullptr;
73 | }
74 |
75 | if (verbose)
76 | {
77 | std::cout << "Frame times:\n";
78 | for (unsigned int i = 0; i < frame_times.GetCount(); i++) {
79 | double time = frame_times[(int)i];
80 | std::cout << " " << time << "\n";
81 | }
82 | }
83 |
84 | for (unsigned int frame = 0; frame < frame_count; ++frame) {
85 | unsigned short max_res[] = { 64, 64, 64 };
86 | if (profile) {
87 | max_res[0] = 128;
88 | max_res[1] = 128;
89 | max_res[2] = 128;
90 | }
91 |
92 | // retrieve frame data
93 | Image3d data;
94 | PerfTimer timer("GetFrame", profile);
95 | CHECK(source.GetFrame(frame, bbox, max_res, &data));
96 |
97 | if (frame == 0)
98 | std::cout << "First frame time: " << data.time << "\n";
99 | if (frame == frame_count-1)
100 | std::cout << "Last frame time: " << data.time << "\n";
101 | }
102 | }
103 |
104 | CComPtr CreateLoader(const CComBSTR &progid, const CLSID clsid, const bool profile)
105 | {
106 | CComPtr loader;
107 | std::wcout << L"Creating loader " << progid.m_str << L" in low-integrity mode...\n";
108 | LowIntegrity low_integrity;
109 | PerfTimer timer("CoCreateInstance", profile);
110 | HRESULT hr = loader.CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING);
111 | if (FAILED(hr)) {
112 | _com_error err(hr);
113 | std::wcerr << L"CoCreateInstance failed: code=" << hr << L", message=" << err.ErrorMessage() << std::endl;
114 | exit(-1);
115 | }
116 |
117 | return loader;
118 | }
119 |
120 | CComPtr LoadFileAndGetImageSource(const CComPtr& loader, const CComBSTR& filename, const bool profile)
121 | {
122 | {
123 | // load file
124 | Image3dError err_type = {};
125 | CComBSTR err_msg;
126 | PerfTimer timer("LoadFile", profile);
127 | HRESULT hr = loader->LoadFile(filename, &err_type, &err_msg);
128 | if (FAILED(hr)) {
129 | std::wcerr << L"LoadFile failed: code=" << err_type << L", message=" << err_msg.m_str << std::endl;
130 | exit(-1);
131 | }
132 | }
133 |
134 | CComPtr source;
135 | {
136 | PerfTimer timer("GetImageSource", profile);
137 | HRESULT hr = loader->GetImageSource(&source);
138 | if (FAILED(hr)) {
139 | _com_error err(hr);
140 | std::wcerr << L"GetImageSource failed: code=" << hr << L", message=" << err.ErrorMessage() << std::endl;
141 | exit(-1);
142 | }
143 | }
144 |
145 | return source;
146 | }
147 |
148 | int wmain(int argc, wchar_t *argv[]) {
149 | if (argc < 3) {
150 | std::wcout << L"Usage:\n";
151 | std::wcout << L"SandboxTest.exe [-verbose|-profile] [-threading]" << std::endl;
152 | return -1;
153 | }
154 |
155 | CComBSTR progid = argv[1]; // e.g. "DummyLoader.Image3dFileLoader"
156 | CComBSTR filename = argv[2];
157 |
158 | std::set options;
159 | for (int i = 3; i < argc; ++i) {
160 | options.insert(argv[i]);
161 | }
162 |
163 | bool verbose = options.find(L"-verbose") != options.end(); // more extensive logging
164 | bool profile = options.find(L"-profile") != options.end(); // profile loader performance
165 | bool test_threading = options.find(L"-threading") != options.end(); // instantiate loader, load file and get image source in a separate thread
166 |
167 | bool test_locked_input = true;
168 |
169 | std::ifstream locked_file;
170 | if (test_locked_input) {
171 | std::wcout << L"Open file: " << argv[2] << " to test read-locked input file in loader." << std::endl;
172 | locked_file.open(filename);
173 | }
174 |
175 | ComInitialize com(COINIT_APARTMENTTHREADED);
176 |
177 | CLSID clsid = {};
178 | if (FAILED(CLSIDFromProgID(progid, &clsid))) {
179 | std::wcerr << L"ERRORR: Unknown progid " << progid.m_str << L"\n";
180 | return -1;
181 | }
182 |
183 | auto list = SupportedManufacturerModels::ReadList(clsid);
184 |
185 | // verify that loader library is compatible
186 | // first check loader with matching bitness
187 | REGSAM bitness = WOW_SAME_AS_CLIENT;
188 | if (FAILED(CheckImage3dAPIVersion(clsid, bitness))) {
189 | // then check loader with non-matching bitness
190 | #ifdef _WIN64
191 | bitness = KEY_WOW64_32KEY;
192 | #else
193 | bitness = KEY_WOW64_64KEY;
194 | #endif
195 | if (FAILED(CheckImage3dAPIVersion(clsid, bitness))) {
196 | std::wcerr << L"ERRORR: Loader " << progid.m_str << L" not compatible with current API version.\n";
197 | return -1;
198 | }
199 | }
200 |
201 | // create loader in a separate "low integrity" dllhost.exe process
202 | CComPtr loader;
203 | CComPtr source;
204 |
205 | if (!test_threading) {
206 | loader = CreateLoader(progid, clsid, profile);
207 | source = LoadFileAndGetImageSource(loader, filename, profile);
208 | } else {
209 | std::wcout << L"Instantiate loader, load file and get image source in a separate thread" << std::endl;
210 | CComPtr stream;
211 |
212 | std::thread loader_thread([&]() {
213 | ComInitialize com_thread(COINIT_APARTMENTTHREADED);
214 | loader = CreateLoader(progid, clsid, profile);
215 |
216 | CComPtr internal_source = LoadFileAndGetImageSource(loader, filename, profile);
217 |
218 | CHECK(CoMarshalInterThreadInterfaceInStream(__uuidof(IImage3dSource), internal_source, &stream));
219 | });
220 |
221 | loader_thread.join();
222 |
223 | CHECK(CoGetInterfaceAndReleaseStream(stream.Detach(), __uuidof(IImage3dSource), (void**)&source));
224 | }
225 |
226 | {
227 | CComBSTR sopInstanceUID;
228 | CHECK(source->GetSopInstanceUID(&sopInstanceUID));
229 | std::wcout << L"SOP Instance UID: " << sopInstanceUID.m_str << L"\n";
230 | }
231 |
232 | ProbeInfo probe;
233 | CHECK(source->GetProbeInfo(&probe));
234 | std::wcout << L"Probe name: " << probe.name.m_str << L"\n";
235 |
236 | EcgSeries ecg;
237 | CHECK(source->GetECG(&ecg));
238 |
239 | if (verbose) {
240 | CComSafeArray samples;
241 | samples.Attach(ecg.samples); // transfer ownership
242 | ecg.samples = nullptr;
243 |
244 | std::wcout << L"ECG sample count: " << samples.GetCount() << L"\n";
245 | std::wcout << L"First ECG sample time: " << ecg.start_time << L"\n";
246 | std::wcout << L"Last ECG sample time: " << ecg.start_time + (samples.GetCount()-1)*ecg.delta_time << L"\n";
247 |
248 | CComSafeArray trig_times;
249 | trig_times.Attach(ecg.trig_times); // transfer ownership
250 | ecg.trig_times = nullptr;
251 |
252 | std::wcout << L"ECG QRS trig times:\n";
253 | for (unsigned int i = 0; i < trig_times.GetCount(); ++i)
254 | std::wcout << L" " << trig_times[(int)i] << "\n";
255 | }
256 |
257 | {
258 | PerfTimer timer("ParseSource", profile);
259 | ParseSource(*source, verbose, profile);
260 | }
261 |
262 | return 0;
263 | }
264 |
--------------------------------------------------------------------------------
/SandboxTest/SandboxTest.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {DFA5034E-BEA9-4299-A301-38A5DF7FD256}
29 | Win32Proj
30 | SandboxTest
31 | 10.0
32 |
33 |
34 |
35 | Application
36 | true
37 | v142
38 | Unicode
39 |
40 |
41 | Application
42 | false
43 | v142
44 | true
45 | Unicode
46 |
47 |
48 | Application
49 | true
50 | v142
51 | Unicode
52 |
53 |
54 | Application
55 | false
56 | v142
57 | true
58 | Unicode
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | true
78 | $(SolutionDir)$(Platform)\$(Configuration)\
79 | $(Platform)\$(Configuration)\
80 |
81 |
82 | true
83 | $(SolutionDir)$(Platform)\$(Configuration)\
84 | $(Platform)\$(Configuration)\
85 |
86 |
87 | false
88 | $(SolutionDir)$(Platform)\$(Configuration)\
89 | $(Platform)\$(Configuration)\
90 |
91 |
92 | false
93 | $(SolutionDir)$(Platform)\$(Configuration)\
94 | $(Platform)\$(Configuration)\
95 |
96 |
97 |
98 | Level3
99 | Disabled
100 | true
101 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
102 | true
103 |
104 |
105 | Console
106 | true
107 |
108 |
109 |
110 |
111 | Level3
112 | Disabled
113 | true
114 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
115 | true
116 |
117 |
118 | Console
119 | true
120 |
121 |
122 |
123 |
124 | Level3
125 | MaxSpeed
126 | true
127 | true
128 | true
129 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
130 | true
131 |
132 |
133 | Console
134 | true
135 | true
136 | true
137 |
138 |
139 |
140 |
141 | Level3
142 | MaxSpeed
143 | true
144 | true
145 | true
146 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
147 | true
148 |
149 |
150 | Console
151 | true
152 | true
153 | true
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/SandboxTest/SandboxTest.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TestPython/.gitignore:
--------------------------------------------------------------------------------
1 | /__pycache__
2 |
--------------------------------------------------------------------------------
/TestPython/ITKExport.py:
--------------------------------------------------------------------------------
1 | ## Sample code to demonstrate how to access Image3dAPI from a python script
2 | import platform
3 | import comtypes
4 | import comtypes.client
5 | import numpy as np
6 | import SimpleITK as sitk
7 | from utils import SafeArrayToNumpy
8 | from utils import FrameTo3dArray
9 |
10 | def SaveITKImage(imgFrame, bbox, outputFilename):
11 | array = FrameTo3dArray(imgFrame)
12 | itk_img = sitk.GetImageFromArray(array.astype("float64"))
13 |
14 | m2mm = 1000
15 |
16 | dir1 = [bbox.dir1_x, bbox.dir1_y, bbox.dir1_z]
17 | dir2 = [bbox.dir2_x, bbox.dir2_y, bbox.dir2_z]
18 | dir3 = [bbox.dir3_x, bbox.dir3_y, bbox.dir3_z]
19 |
20 | # all units from Image3dAPI are in meters according to https://github.com/MedicalUltrasound/Image3dAPI/wiki#image-geometry
21 | spacingX = np.linalg.norm(dir1) / imgFrame.dims[0] * m2mm # convert from meters to millimeters
22 | spacingY = np.linalg.norm(dir2) / imgFrame.dims[1] * m2mm
23 | spacingZ = np.linalg.norm(dir3) / imgFrame.dims[2] * m2mm
24 |
25 |
26 | dir1 = dir1 / np.linalg.norm(dir1)
27 | dir2 = dir2 / np.linalg.norm(dir2)
28 | dir3 = dir3 / np.linalg.norm(dir3)
29 |
30 | itk_img.SetSpacing((spacingX,spacingY,spacingZ))
31 | itk_img.SetOrigin((bbox.origin_x*m2mm, bbox.origin_y*m2mm, bbox.origin_z*m2mm))
32 | itk_img.SetDirection((dir1[0], dir2[0], dir3[0], dir1[1], dir2[1], dir3[1], dir1[2], dir2[2], dir3[2]))
33 |
34 | sitk.WriteImage(itk_img, outputFilename)
35 |
36 | if __name__=="__main__":
37 | # load type library
38 | if "32" in platform.architecture()[0]:
39 | Image3dAPI = comtypes.client.GetModule("../Win32/Image3dAPI.tlb")
40 | else:
41 | Image3dAPI = comtypes.client.GetModule("../x64/Image3dAPI.tlb")
42 |
43 | # create loader object
44 | loader = comtypes.client.CreateObject("DummyLoader.Image3dFileLoader")
45 | loader = loader.QueryInterface(Image3dAPI.IImage3dFileLoader)
46 |
47 | # load file
48 | err_type, err_msg = loader.LoadFile("dummy.dcm")
49 | source = loader.GetImageSource()
50 |
51 | # get bounding box
52 | bbox = source.GetBoundingBox()
53 | origin = [bbox.origin_x, bbox.origin_y, bbox.origin_z]
54 | dir1 = [bbox.dir1_x, bbox.dir1_y, bbox.dir1_z]
55 | dir2 = [bbox.dir2_x, bbox.dir2_y, bbox.dir2_z]
56 | dir3 = [bbox.dir3_x, bbox.dir3_y, bbox.dir3_z]
57 |
58 | color_map = source.GetColorMap()
59 | print("Color-map length: "+str(len(color_map)))
60 |
61 | frame_count = source.GetFrameCount()
62 | for i in range(frame_count):
63 | max_res = np.ctypeslib.as_ctypes(np.array([64, 64, 64], dtype=np.ushort))
64 | frame = source.GetFrame(i, bbox, max_res)
65 | data = FrameTo3dArray(frame)
66 | SaveITKImage(frame, bbox, "image3DAPIDummOutput" + str(i) + ".mhd")
67 |
--------------------------------------------------------------------------------
/TestPython/TestPython.py:
--------------------------------------------------------------------------------
1 | ## Sample code to demonstrate how to access Image3dAPI from a python script
2 | import platform
3 | import comtypes
4 | import comtypes.client
5 | import numpy as np
6 | from utils import SafeArrayToNumpy
7 | from utils import FrameTo3dArray
8 | from utils import TypeLibFromObject
9 |
10 |
11 | if __name__=="__main__":
12 | # create loader object
13 | loader = comtypes.client.CreateObject("DummyLoader.Image3dFileLoader")
14 | # cast to IImage3dFileLoader interface
15 | Image3dAPI = TypeLibFromObject(loader)
16 | loader = loader.QueryInterface(Image3dAPI.IImage3dFileLoader)
17 |
18 | # load file
19 | err_type, err_msg = loader.LoadFile("dummy.dcm")
20 | source = loader.GetImageSource()
21 |
22 | # retrieve probe info
23 | probe = source.GetProbeInfo()
24 | print("Probe name: "+probe.name)
25 | print("Probe type: "+str(probe.type))
26 |
27 | # retrieve ECG info
28 | ecg = source.GetECG()
29 | print("ECG start: "+str(ecg.start_time))
30 | print("ECG delta: "+str(ecg.delta_time))
31 | samples = SafeArrayToNumpy(ecg.samples)
32 | trig_times = SafeArrayToNumpy(ecg.trig_times)
33 |
34 | # get bounding box
35 | bbox = source.GetBoundingBox()
36 | origin = [bbox.origin_x, bbox.origin_y, bbox.origin_z]
37 | dir1 = [bbox.dir1_x, bbox.dir1_y, bbox.dir1_z]
38 | dir2 = [bbox.dir2_x, bbox.dir2_y, bbox.dir2_z]
39 | dir3 = [bbox.dir3_x, bbox.dir3_y, bbox.dir3_z]
40 |
41 | color_map = source.GetColorMap()
42 | print("Color-map length: "+str(len(color_map)))
43 |
44 | frame_count = source.GetFrameCount()
45 | for i in range(frame_count):
46 | max_res = np.ctypeslib.as_ctypes(np.array([64, 64, 64], dtype=np.ushort))
47 | frame = source.GetFrame(i, bbox, max_res)
48 |
49 | print("Frame metadata:")
50 | print(" time: "+str(frame.time))
51 | print(" format: "+str(frame.format))
52 | print(" dims: ("+str(frame.dims[0])+", "+str(frame.dims[1])+", "+str(frame.dims[2])+")")
53 |
54 | data = FrameTo3dArray(frame)
55 | print(" shape: "+str(data.shape))
56 |
57 |
--------------------------------------------------------------------------------
/TestPython/TestPython.pyproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | 2.0
6 | a594d690-99db-499b-b3b0-0bf81364eba2
7 | .
8 | TestPython.py
9 |
10 |
11 | .
12 | .
13 | TestPython
14 | TestPython
15 | Standard Python launcher
16 | False
17 |
18 |
19 | true
20 | false
21 |
22 |
23 | true
24 | false
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 10.0
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/TestPython/utils.py:
--------------------------------------------------------------------------------
1 | ## Sample code to demonstrate how to access Image3dAPI from a python script
2 | import platform
3 | import comtypes
4 | import comtypes.client
5 | import numpy as np
6 |
7 |
8 | def TypeLibFromObject (object):
9 | """Loads the type library associated with a COM class instance"""
10 | import winreg
11 |
12 | with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, "CLSID\\"+object.__clsid+"\\TypeLib", 0, winreg.KEY_READ) as key:
13 | typelib = winreg.EnumValue(key, 0)[1]
14 | with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, "CLSID\\"+object.__clsid+"\\Version", 0, winreg.KEY_READ) as key:
15 | version = winreg.EnumValue(key, 0)[1]
16 |
17 | try:
18 | major_ver, minor_ver = version.split(".")
19 | return comtypes.client.GetModule([typelib, int(major_ver), int(minor_ver)])
20 | except OSError as err:
21 | # API 1.2-only compatibility fallback to avoid breaking existing loaders
22 | if (version != "1.2") or (err.winerror != -2147319779): # Library not registered
23 | raise # rethrow
24 | # Fallback to TypeLib version 1.0
25 | return comtypes.client.GetModule([typelib, 1, 0])
26 |
27 |
28 | def SafeArrayToNumpy (safearr_ptr, copy=True):
29 | """Convert a SAFEARRAY buffer to its numpy equivalent"""
30 | import ctypes
31 |
32 | # only support 1D data for now
33 | assert(comtypes._safearray.SafeArrayGetDim(safearr_ptr) == 1)
34 |
35 | # access underlying pointer
36 | data_ptr = ctypes.POINTER(safearr_ptr._itemtype_)()
37 | comtypes._safearray.SafeArrayAccessData(safearr_ptr, ctypes.byref(data_ptr))
38 |
39 | upper_bound = comtypes._safearray.SafeArrayGetUBound(safearr_ptr, 1) + 1 # +1 to go from inclusive to exclusive bound
40 | lower_bound = comtypes._safearray.SafeArrayGetLBound(safearr_ptr, 1)
41 | array_size = upper_bound - lower_bound
42 |
43 | # wrap pointer in numpy array
44 | arr = np.ctypeslib.as_array(data_ptr,shape=(array_size,))
45 | return np.copy(arr) if copy else arr
46 |
47 |
48 | def FrameTo3dArray (frame):
49 | """Convert Image3d data into a numpy 3D array"""
50 | arr_1d = SafeArrayToNumpy(frame.data, copy=False)
51 | assert(arr_1d.dtype == np.uint8) # only tested with 1byte/elm
52 |
53 | arr_3d = np.lib.stride_tricks.as_strided(arr_1d, shape=frame.dims, strides=(1, frame.stride0, frame.stride1))
54 | return np.copy(arr_3d)
55 |
56 |
--------------------------------------------------------------------------------
/TestViewer/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /obj
3 |
--------------------------------------------------------------------------------
/TestViewer/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TestViewer/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/TestViewer/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace TestViewer
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TestViewer/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | DummyLoader.Image3dFileLoader
20 | GEHC_CARD_US.Image3dFileLoader
21 | CanonLoader.Image3dFileLoader
22 | HitCV3DLoader.Image3dFileLoader
23 | SiemensLoader.Image3dFileLoader
24 | PhilipsLoader.Image3dFileLoader
25 | KretzLoader.KretzImage3dFileLoader
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/TestViewer/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Media;
5 | using System.Windows.Media.Imaging;
6 | using System.Diagnostics;
7 | using System.Runtime.InteropServices;
8 | using Microsoft.Win32;
9 | using Image3dAPI;
10 |
11 |
12 | /** Alternative to System.Activator.CreateInstance that allows explicit control over the activation context.
13 | * Based on https://stackoverflow.com/questions/22901224/hosting-managed-code-and-garbage-collection */
14 | public static class ComExt {
15 | [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
16 | static extern void CoCreateInstance(
17 | [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
18 | [MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
19 | uint dwClsContext,
20 | [MarshalAs(UnmanagedType.LPStruct)] Guid riid,
21 | [MarshalAs(UnmanagedType.Interface)] out object rReturnedComObject);
22 |
23 | public static object CreateInstance(Guid clsid, bool force_out_of_process) {
24 | Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
25 | const uint CLSCTX_INPROC_SERVER = 0x1;
26 | const uint CLSCTX_LOCAL_SERVER = 0x4;
27 |
28 | uint class_context = CLSCTX_LOCAL_SERVER; // always allow out-of-process activation
29 | if (!force_out_of_process)
30 | class_context |= CLSCTX_INPROC_SERVER; // allow in-process activation
31 |
32 | object unk;
33 | CoCreateInstance(clsid, null, class_context, IID_IUnknown, out unk);
34 | return unk;
35 | }
36 | }
37 |
38 |
39 | namespace TestViewer
40 | {
41 | public partial class MainWindow : Window
42 | {
43 | IImage3dFileLoader m_loader;
44 | IImage3dSource m_source;
45 |
46 | Cart3dGeom m_bboxXY;
47 | Cart3dGeom m_bboxXZ;
48 | Cart3dGeom m_bboxZY;
49 |
50 | public MainWindow()
51 | {
52 | InitializeComponent();
53 | }
54 |
55 | void ClearUI()
56 | {
57 | FrameSelector.Minimum = 0;
58 | FrameSelector.Maximum = 0;
59 | FrameSelector.IsEnabled = false;
60 | FrameSelector.Value = 0;
61 |
62 | FrameCount.Text = "";
63 | ProbeInfo.Text = "";
64 | InstanceUID.Text = "";
65 |
66 |
67 | ImageXY.Source = null;
68 | ImageXZ.Source = null;
69 | ImageZY.Source = null;
70 |
71 | m_bboxXY = new Cart3dGeom();
72 | m_bboxXZ = new Cart3dGeom();
73 | m_bboxZY = new Cart3dGeom();
74 |
75 | ECG.Data = null;
76 |
77 | if (m_source != null) {
78 | Marshal.ReleaseComObject(m_source);
79 | m_source = null;
80 | }
81 | }
82 |
83 | private void LoadDefaultBtn_Click(object sender, RoutedEventArgs e)
84 | {
85 | LoadImpl(false);
86 | }
87 |
88 | private void LoadOutOfProcBtn_Click(object sender, RoutedEventArgs e)
89 | {
90 | LoadImpl(true);
91 | }
92 |
93 | private void LoadImpl(bool force_out_of_proc)
94 | {
95 | // try to parse string as ProgId first
96 | Type comType = Type.GetTypeFromProgID(LoaderName.Text);
97 | if (comType == null) {
98 | try {
99 | // fallback to parse string as CLSID hex value
100 | Guid guid = Guid.Parse(LoaderName.Text);
101 | comType = Type.GetTypeFromCLSID(guid);
102 | } catch (FormatException) {
103 | MessageBox.Show("Unknown ProgId of CLSID.");
104 | return;
105 | }
106 | }
107 |
108 | // API version compatibility check
109 | try {
110 | RegistryKey ver_key = Registry.ClassesRoot.OpenSubKey("CLSID\\{" + comType.GUID + "}\\Version");
111 | string ver_str = (string)ver_key.GetValue("");
112 | string cur_ver = string.Format("{0}.{1}", (int)Image3dAPIVersion.IMAGE3DAPI_VERSION_MAJOR, (int)Image3dAPIVersion.IMAGE3DAPI_VERSION_MINOR);
113 | if (ver_str != cur_ver) {
114 | MessageBox.Show(string.Format("Loader uses version {0}, while the current version is {1}.", ver_str, cur_ver), "Incompatible loader version");
115 | return;
116 | }
117 | } catch (Exception err) {
118 | MessageBox.Show(err.Message, "Version check error");
119 | // continue, since this error will also appear if the loader has non-matching bitness
120 | }
121 |
122 | // clear UI when switching to a new loader
123 | ClearUI();
124 |
125 | if (m_loader != null)
126 | Marshal.ReleaseComObject(m_loader);
127 | m_loader = (IImage3dFileLoader)ComExt.CreateInstance(comType.GUID, force_out_of_proc);
128 |
129 | this.FileOpenBtn.IsEnabled = true;
130 | }
131 |
132 | private void FileSelectBtn_Click(object sender, RoutedEventArgs e)
133 | {
134 | OpenFileDialog dialog = new OpenFileDialog();
135 | if (dialog.ShowDialog() != true)
136 | return; // user hit cancel
137 |
138 | // clear UI when opening a new file
139 | ClearUI();
140 |
141 | FileName.Text = dialog.FileName;
142 | }
143 |
144 | private void FileOpenBtn_Click(object sender, RoutedEventArgs e)
145 | {
146 | Debug.Assert(m_loader != null);
147 |
148 | Image3dError err_type = Image3dError.Image3d_SUCCESS;
149 | string err_msg = "";
150 | try {
151 | m_loader.LoadFile(FileName.Text, out err_type, out err_msg);
152 | } catch (Exception) {
153 | // NOTE: err_msg does not seem to be marshaled back on LoadFile failure in .Net.
154 | // NOTE: This problem is limited to .Net, and does not occur in C++
155 |
156 | string message = "Unknown error";
157 | if ((err_type != Image3dError.Image3d_SUCCESS)) {
158 | switch (err_type) {
159 | case Image3dError.Image3d_ACCESS_FAILURE:
160 | message = "Unable to open the file. The file might be missing or locked.";
161 | break;
162 | case Image3dError.Image3d_VALIDATION_FAILURE:
163 | message = "Unsupported file. Probably due to unsupported vendor or modality.";
164 | break;
165 | case Image3dError.Image3d_NOT_YET_SUPPORTED:
166 | message = "The loader is too old to parse the file.";
167 | break;
168 | case Image3dError.Image3d_SUPPORT_DISCONTINUED:
169 | message = "The the file version is no longer supported (pre-DICOM format?).";
170 | break;
171 | }
172 | }
173 | MessageBox.Show("LoadFile error: " + message + " (" + err_msg+")");
174 | return;
175 | }
176 |
177 | try {
178 | if (m_source != null)
179 | Marshal.ReleaseComObject(m_source);
180 | m_source = m_loader.GetImageSource();
181 | } catch (Exception err) {
182 | MessageBox.Show("ERROR: " + err.Message, "GetImageSource error");
183 | return;
184 | }
185 |
186 | FrameSelector.Minimum = 0;
187 | FrameSelector.Maximum = m_source.GetFrameCount()-1;
188 | FrameSelector.IsEnabled = true;
189 | FrameSelector.Value = 0;
190 |
191 | FrameCount.Text = "Frame count: " + m_source.GetFrameCount();
192 | ProbeInfo.Text = "Probe name: "+ m_source.GetProbeInfo().name;
193 | InstanceUID.Text = "UID: " + m_source.GetSopInstanceUID();
194 |
195 | InitializeSlices();
196 | DrawSlices(0);
197 | DrawEcg(m_source.GetFrameTimes()[0]);
198 | }
199 |
200 | private void DrawEcg (double cur_time)
201 | {
202 | EcgSeries ecg;
203 | try {
204 | ecg = m_source.GetECG();
205 |
206 | if (ecg.samples == null) {
207 | ECG.Data = null; // ECG not available
208 | return;
209 | }
210 | } catch (Exception) {
211 | ECG.Data = null; // ECG not available
212 | return;
213 | }
214 |
215 | // shrink width & height slightly, so that the "actual" width/height remain unchanged
216 | double W = (int)(ECG.ActualWidth - 1);
217 | double H = (int)(ECG.ActualHeight - 1);
218 |
219 | // horizontal scaling (index to X coord)
220 | double ecg_pitch = W/ecg.samples.Length;
221 |
222 | // vertical scaling (sample val to Y coord)
223 | double ecg_offset = H*ecg.samples.Max()/(ecg.samples.Max()-ecg.samples.Min());
224 | double ecg_scale = -H/(ecg.samples.Max()-ecg.samples.Min());
225 |
226 | // vertical scaling (time to Y coord conv)
227 | double time_offset = -W*ecg.start_time/(ecg.delta_time*ecg.samples.Length);
228 | double time_scale = W/(ecg.delta_time*ecg.samples.Length);
229 |
230 | PathGeometry pathGeom = new PathGeometry();
231 | {
232 | // draw ECG trace
233 | PathSegmentCollection pathSegmentCollection = new PathSegmentCollection();
234 | for (int i = 0; i < ecg.samples.Length; ++i) {
235 | LineSegment lineSegment = new LineSegment();
236 | lineSegment.Point = new Point(ecg_pitch*i, ecg_offset+ecg_scale*ecg.samples[i]);
237 | pathSegmentCollection.Add(lineSegment);
238 | }
239 |
240 | PathFigure pathFig = new PathFigure();
241 | pathFig.StartPoint = new Point(0, ecg_offset+ecg_scale*ecg.samples[0]);
242 | pathFig.Segments = pathSegmentCollection;
243 |
244 | PathFigureCollection pathFigCol = new PathFigureCollection();
245 | pathFigCol.Add(pathFig);
246 |
247 | pathGeom.Figures = pathFigCol;
248 | }
249 |
250 | {
251 | // draw current frame line
252 | double x_pos = time_offset + time_scale * cur_time;
253 |
254 | LineGeometry line = new LineGeometry();
255 | line.StartPoint = new Point(x_pos, 0);
256 | line.EndPoint = new Point(x_pos, H);
257 |
258 | pathGeom.AddGeometry(line);
259 | }
260 |
261 | ECG.Stroke = Brushes.Blue;
262 | ECG.StrokeThickness = 1.0;
263 | ECG.Data = pathGeom;
264 | }
265 |
266 | private void FrameSelector_ValueChanged(object sender, RoutedPropertyChangedEventArgs e)
267 | {
268 | var idx = (uint)FrameSelector.Value;
269 | DrawSlices(idx);
270 | DrawEcg(m_source.GetFrameTimes()[idx]);
271 | }
272 |
273 | private void InitializeSlices()
274 | {
275 | Debug.Assert(m_source != null);
276 |
277 | Cart3dGeom bbox = m_source.GetBoundingBox();
278 | if (Math.Abs(bbox.dir3_y) > Math.Abs(bbox.dir2_y)) {
279 | // swap 2nd & 3rd axis, so that the 2nd becomes predominately "Y"
280 | SwapVals(ref bbox.dir2_x, ref bbox.dir3_x);
281 | SwapVals(ref bbox.dir2_y, ref bbox.dir3_y);
282 | SwapVals(ref bbox.dir2_z, ref bbox.dir3_z);
283 | }
284 |
285 | // extend bounding-box axes, so that dir1, dir2 & dir3 have equal length
286 | ExtendBoundingBox(ref bbox);
287 |
288 | // get XY plane (assumes 1st axis is "X" and 2nd is "Y")
289 | m_bboxXY = bbox;
290 | m_bboxXY.origin_x = m_bboxXY.origin_x + m_bboxXY.dir3_x / 2;
291 | m_bboxXY.origin_y = m_bboxXY.origin_y + m_bboxXY.dir3_y / 2;
292 | m_bboxXY.origin_z = m_bboxXY.origin_z + m_bboxXY.dir3_z / 2;
293 | m_bboxXY.dir3_x = 0;
294 | m_bboxXY.dir3_y = 0;
295 | m_bboxXY.dir3_z = 0;
296 |
297 | // get XZ plane (assumes 1st axis is "X" and 3rd is "Z")
298 | m_bboxXZ = bbox;
299 | m_bboxXZ.origin_x = m_bboxXZ.origin_x + m_bboxXZ.dir2_x / 2;
300 | m_bboxXZ.origin_y = m_bboxXZ.origin_y + m_bboxXZ.dir2_y / 2;
301 | m_bboxXZ.origin_z = m_bboxXZ.origin_z + m_bboxXZ.dir2_z / 2;
302 | m_bboxXZ.dir2_x = m_bboxXZ.dir3_x;
303 | m_bboxXZ.dir2_y = m_bboxXZ.dir3_y;
304 | m_bboxXZ.dir2_z = m_bboxXZ.dir3_z;
305 | m_bboxXZ.dir3_x = 0;
306 | m_bboxXZ.dir3_y = 0;
307 | m_bboxXZ.dir3_z = 0;
308 |
309 | // get ZY plane (assumes 2nd axis is "Y" and 3rd is "Z")
310 | m_bboxZY = bbox;
311 | m_bboxZY.origin_x = bbox.origin_x + bbox.dir1_x / 2;
312 | m_bboxZY.origin_y = bbox.origin_y + bbox.dir1_y / 2;
313 | m_bboxZY.origin_z = bbox.origin_z + bbox.dir1_z / 2;
314 | m_bboxZY.dir1_x = bbox.dir3_x;
315 | m_bboxZY.dir1_y = bbox.dir3_y;
316 | m_bboxZY.dir1_z = bbox.dir3_z;
317 | m_bboxZY.dir2_x = bbox.dir2_x;
318 | m_bboxZY.dir2_y = bbox.dir2_y;
319 | m_bboxZY.dir2_z = bbox.dir2_z;
320 | m_bboxZY.dir3_x = 0;
321 | m_bboxZY.dir3_y = 0;
322 | m_bboxZY.dir3_z = 0;
323 | }
324 |
325 | private void DrawSlices (uint frame)
326 | {
327 | Debug.Assert(m_source != null);
328 |
329 | uint[] color_map = m_source.GetColorMap();
330 |
331 | // retrieve image slices
332 | const ushort HORIZONTAL_RES = 256;
333 | const ushort VERTICAL_RES = 256;
334 |
335 | // get XY plane (assumes 1st axis is "X" and 2nd is "Y")
336 | Image3d imageXY = m_source.GetFrame(frame, m_bboxXY, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 });
337 | ImageXY.Source = GenerateBitmap(imageXY, color_map);
338 |
339 | // get XZ plane (assumes 1st axis is "X" and 3rd is "Z")
340 | Image3d imageXZ = m_source.GetFrame(frame, m_bboxXZ, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 });
341 | ImageXZ.Source = GenerateBitmap(imageXZ, color_map);
342 |
343 | // get ZY plane (assumes 2nd axis is "Y" and 3rd is "Z")
344 | Image3d imageZY = m_source.GetFrame(frame, m_bboxZY, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 });
345 | ImageZY.Source = GenerateBitmap(imageZY, color_map);
346 |
347 | FrameTime.Text = "Frame time: " + imageXY.time;
348 | }
349 |
350 | private WriteableBitmap GenerateBitmap(Image3d t_img, uint[] t_map)
351 | {
352 | Debug.Assert(t_img.format == ImageFormat.FORMAT_U8);
353 |
354 | WriteableBitmap bitmap = new WriteableBitmap(t_img.dims[0], t_img.dims[1], 96.0, 96.0, PixelFormats.Rgb24, null);
355 | bitmap.Lock();
356 | unsafe {
357 | for (int y = 0; y < bitmap.Height; ++y) {
358 | for (int x = 0; x < bitmap.Width; ++x) {
359 | byte t_val = t_img.data[x + y * t_img.stride0];
360 |
361 | // lookup tissue color
362 | byte[] channels = BitConverter.GetBytes(t_map[t_val]);
363 |
364 | // assign red, green & blue
365 | byte* pixel = (byte*)bitmap.BackBuffer + x * (bitmap.Format.BitsPerPixel / 8) + y * bitmap.BackBufferStride;
366 | pixel[0] = channels[0]; // red
367 | pixel[1] = channels[1]; // green
368 | pixel[2] = channels[2]; // blue
369 | // discard alpha channel
370 | }
371 | }
372 | }
373 | bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
374 | bitmap.Unlock();
375 | return bitmap;
376 | }
377 |
378 | static void SwapVals(ref float v1, ref float v2)
379 | {
380 | float tmp = v1;
381 | v1 = v2;
382 | v2 = tmp;
383 | }
384 |
385 | static float VecLen(float x, float y, float z)
386 | {
387 | return (float)Math.Sqrt(x * x + y * y + z * z);
388 | }
389 |
390 | static float VecLen(Cart3dGeom g, int idx)
391 | {
392 | if (idx == 1)
393 | return VecLen(g.dir1_x, g.dir1_y, g.dir1_z);
394 | else if (idx == 2)
395 | return VecLen(g.dir2_x, g.dir2_y, g.dir2_z);
396 | else if (idx == 3)
397 | return VecLen(g.dir3_x, g.dir3_y, g.dir3_z);
398 |
399 | throw new Exception("unsupported direction index");
400 | }
401 |
402 | /** Scale bounding-box, so that all axes share the same length.
403 | * Also update the origin to keep the bounding-box centered. */
404 | static void ExtendBoundingBox(ref Cart3dGeom g)
405 | {
406 | float dir1_len = VecLen(g, 1);
407 | float dir2_len = VecLen(g, 2);
408 | float dir3_len = VecLen(g, 3);
409 |
410 | float max_len = Math.Max(dir1_len, Math.Max(dir2_len, dir3_len));
411 |
412 | if (dir1_len < max_len)
413 | {
414 | float delta = max_len - dir1_len;
415 | float dx, dy, dz;
416 | ScaleVector(g.dir1_x, g.dir1_y, g.dir1_z, delta, out dx, out dy, out dz);
417 | // scale up dir1 so that it becomes the same length as the other axes
418 | g.dir1_x += dx;
419 | g.dir1_y += dy;
420 | g.dir1_z += dz;
421 | // move origin to keep the bounding-box centered
422 | g.origin_x -= dx/2;
423 | g.origin_y -= dy/2;
424 | g.origin_z -= dz/2;
425 | }
426 |
427 | if (dir2_len < max_len)
428 | {
429 | float delta = max_len - dir2_len;
430 | float dx, dy, dz;
431 | ScaleVector(g.dir2_x, g.dir2_y, g.dir2_z, delta, out dx, out dy, out dz);
432 | // scale up dir2 so that it becomes the same length as the other axes
433 | g.dir2_x += dx;
434 | g.dir2_y += dy;
435 | g.dir2_z += dz;
436 | // move origin to keep the bounding-box centered
437 | g.origin_x -= dx / 2;
438 | g.origin_y -= dy / 2;
439 | g.origin_z -= dz / 2;
440 | }
441 |
442 | if (dir3_len < max_len)
443 | {
444 | float delta = max_len - dir3_len;
445 | float dx, dy, dz;
446 | ScaleVector(g.dir3_x, g.dir3_y, g.dir3_z, delta, out dx, out dy, out dz);
447 | // scale up dir3 so that it becomes the same length as the other axes
448 | float factor = max_len / dir3_len;
449 | g.dir3_x += dx;
450 | g.dir3_y += dy;
451 | g.dir3_z += dz;
452 | // move origin to keep the bounding-box centered
453 | g.origin_x -= dx / 2;
454 | g.origin_y -= dy / 2;
455 | g.origin_z -= dz / 2;
456 | }
457 | }
458 |
459 | static void ScaleVector(float in_x, float in_y, float in_z, float length, out float out_x, out float out_y, out float out_z)
460 | {
461 | float cur_len = VecLen(in_x, in_y, in_z);
462 | out_x = in_x * length / cur_len;
463 | out_y = in_y * length / cur_len;
464 | out_z = in_z * length / cur_len;
465 | }
466 | }
467 | }
468 |
--------------------------------------------------------------------------------
/TestViewer/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("TestViewer")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("GE Healthcare")]
14 | [assembly: AssemblyProduct("TestViewer")]
15 | [assembly: AssemblyCopyright("Copyright © GE Healthcare 2018")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/TestViewer/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TestViewer.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestViewer.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/TestViewer/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/TestViewer/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TestViewer.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/TestViewer/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TestViewer/TestViewer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {08D5EA11-0B4C-40FD-87EC-23D42C195D59}
8 | WinExe
9 | Properties
10 | TestViewer
11 | TestViewer
12 | v4.8
13 | 512
14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 4
16 | true
17 |
18 |
19 |
20 | true
21 | bin\x64\Debug\
22 | DEBUG;TRACE
23 | true
24 | full
25 | x64
26 | prompt
27 | MinimumRecommendedRules.ruleset
28 |
29 |
30 | bin\x64\Release\
31 | TRACE
32 | true
33 | true
34 | pdbonly
35 | x64
36 | prompt
37 | MinimumRecommendedRules.ruleset
38 |
39 |
40 | true
41 | bin\x86\Debug\
42 | DEBUG;TRACE
43 | true
44 | full
45 | x86
46 | prompt
47 | MinimumRecommendedRules.ruleset
48 |
49 |
50 | bin\x86\Release\
51 | TRACE
52 | true
53 | true
54 | pdbonly
55 | x86
56 | prompt
57 | MinimumRecommendedRules.ruleset
58 |
59 |
60 |
61 | ..\Win32\Image3dAPI.dll
62 | True
63 |
64 |
65 |
66 |
67 | ..\x64\Image3dAPI.dll
68 | True
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 4.0
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | MSBuild:Compile
90 | Designer
91 |
92 |
93 | MSBuild:Compile
94 | Designer
95 |
96 |
97 | App.xaml
98 | Code
99 |
100 |
101 | MainWindow.xaml
102 | Code
103 |
104 |
105 |
106 |
107 | Code
108 |
109 |
110 | True
111 | True
112 | Resources.resx
113 |
114 |
115 | True
116 | Settings.settings
117 | True
118 |
119 |
120 | ResXFileCodeGenerator
121 | Resources.Designer.cs
122 |
123 |
124 | SettingsSingleFileGenerator
125 | Settings.Designer.cs
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
140 |
--------------------------------------------------------------------------------