Skip to content

Commit

Permalink
API: Introduce a new IImage3dStream interface to also support color-f…
Browse files Browse the repository at this point in the history
…low data

Extend the API to also support display of color-flow data, in addition to tissue. This is intended to be a simple representation that is compatible with the way color-flow data is processed by any vendor.

Please note that the color-flow appearance is unlikely to exactly match the appearance on the original system, since the "internal" vendor encoding and algorithms are likely to be more advanced. The goal is therefore to get visual appearance that is fairly close to the original.

Integration of this extension will probably require bidirectional code for converting between the "internal" vendor encoding and the Image3dAPI encoding of flow data.

Only an API update so far. Actual color-flow support will be added in a later PR.
  • Loading branch information
Fredrik Orderud committed May 25, 2020
1 parent d2edd53 commit 00c6d45
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 139 deletions.
10 changes: 10 additions & 0 deletions DummyLoader/DummyLoader.idl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ library DummyLoader
{
importlib("stdole2.tlb");

[
version(1.2),
uuid(78317A0E-56BF-4735-AB5B-FE0751219FE8),
helpstring("3D image stream")
]
coclass Image3dStream
{
[default] interface IImage3dStream;
};

[
version(1.2),
uuid(6FA82ED5-6332-4344-8417-DEA55E72098C),
Expand Down
Binary file modified DummyLoader/DummyLoader.rc
Binary file not shown.
81 changes: 18 additions & 63 deletions DummyLoader/Image3dSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
#include "LinAlg.hpp"


static const uint8_t OUTSIDE_VAL = 0; // black outside image volume
static const uint8_t PROBE_PLANE = 127; // gray value for plane closest to probe


Image3dSource::Image3dSource() {
Expand Down Expand Up @@ -45,93 +43,50 @@ Image3dSource::Image3dSource() {
0.20f,0, 0, // dir1 (width)
0, 0.10f, 0, // dir2 (depth)
0, 0, 0.15f};// dir3 (elevation)
m_img_geom = geom;
}
{
// checker board image data
unsigned short dims[] = { 20, 15, 10 }; // matches length of dir1, dir2 & dir3, so that the image squares become quadratic
std::vector<byte> img_buf(dims[0] * dims[1] * dims[2]);
for (size_t frameNumber = 0; frameNumber < numFrames; ++frameNumber) {
for (unsigned int z = 0; z < dims[2]; ++z) {
for (unsigned int y = 0; y < dims[1]; ++y) {
for (unsigned int x = 0; x < dims[0]; ++x) {
bool even_f = (frameNumber / 2 % 2) == 0;
bool even_x = (x / 2 % 2) == 0;
bool even_y = (y / 2 % 2) == 0;
bool even_z = (z / 2 % 2) == 0;
byte & out_sample = img_buf[x + y*dims[0] + z*dims[0] * dims[1]];
if (even_f ^ even_x ^ even_y ^ even_z)
out_sample = 255;
else
out_sample = 0;
}
}
}

// special grayscale value for plane closest to probe
for (unsigned int z = 0; z < dims[2]; ++z) {
for (unsigned int x = 0; x < dims[0]; ++x) {
unsigned int y = 0;
byte & out_sample = img_buf[x + y*dims[0] + z*dims[0] * dims[1]];
out_sample = PROBE_PLANE;
}
}

m_frames.push_back(CreateImage3d(frameNumber*(duration/numFrames) + startTime, IMAGE_FORMAT_U8, dims, img_buf));
}
m_bbox = geom;
}

// a single tissue stream
m_stream_types.push_back(IMAGE_TYPE_TISSUE);
}

Image3dSource::~Image3dSource() {
}


HRESULT Image3dSource::GetFrameCount(/*out*/unsigned int *size) {
HRESULT Image3dSource::GetStreamCount(/*out*/unsigned int *size) {
if (!size)
return E_INVALIDARG;

*size = static_cast<unsigned int>(m_frames.size());
*size = static_cast<unsigned int>(m_stream_types.size());
return S_OK;
}

HRESULT Image3dSource::GetFrameTimes(/*out*/SAFEARRAY * *frame_times) {
if (!frame_times)
return E_INVALIDARG;

const unsigned int N = static_cast<unsigned int>(m_frames.size());
CComSafeArray<double> result(N);
if (N > 0) {
double * time_arr = &result.GetAt(0);
for (unsigned int i = 0; i < N; ++i)
time_arr[i] = m_frames[i].time;
}

*frame_times = result.Detach();
return S_OK;
}


HRESULT Image3dSource::GetFrame(unsigned int index, Cart3dGeom out_geom, unsigned short max_res[3], /*out*/Image3d *data) {
if (!data)
HRESULT Image3dSource::GetStream(unsigned int index, Cart3dGeom out_geom, unsigned short max_resolution[3], /*out*/IImage3dStream ** stream) {
if (!stream)
return E_INVALIDARG;
if (index >= m_frames.size())
if (index >= m_stream_types.size())
return E_BOUNDS;

ImageFormat format = m_frames[index].format;
if (format == IMAGE_FORMAT_U8) {
Image3d result = SampleFrame<uint8_t>(m_frames[index], m_img_geom, out_geom, max_res);
*data = std::move(result);
return S_OK;
CComPtr<IImage3dStream> stream_obj;
{
// on-demand stream creation
auto stream_cls = CreateLocalInstance<Image3dStream>();
stream_cls->Initialize(m_stream_types[index], m_bbox, out_geom, max_resolution);
stream_obj = stream_cls; // cast class pointer to interface
}

return E_NOTIMPL;
*stream = stream_obj.Detach();
return S_OK;
}

HRESULT Image3dSource::GetBoundingBox(/*out*/Cart3dGeom *geom) {
if (!geom)
return E_INVALIDARG;

*geom = m_img_geom;
*geom = m_bbox;
return S_OK;
}

Expand Down
10 changes: 5 additions & 5 deletions DummyLoader/Image3dSource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ class ATL_NO_VTABLE Image3dSource :

/*NOT virtual*/ ~Image3dSource();

HRESULT STDMETHODCALLTYPE GetFrameCount(/*out*/unsigned int *size) override;

HRESULT STDMETHODCALLTYPE GetFrameTimes(/*out*/SAFEARRAY * *frame_times) override;
HRESULT STDMETHODCALLTYPE GetStreamCount (/*out*/unsigned int * size) override;

HRESULT STDMETHODCALLTYPE GetFrame(unsigned int index, Cart3dGeom out_geom, unsigned short max_res[3], /*out*/Image3d *data) override;
HRESULT STDMETHODCALLTYPE GetStream (unsigned int index, Cart3dGeom out_geom, unsigned short max_resolution[3], /*out*/IImage3dStream ** stream) override;

HRESULT STDMETHODCALLTYPE GetBoundingBox(/*out*/Cart3dGeom *geom) override;

Expand All @@ -42,8 +41,9 @@ class ATL_NO_VTABLE Image3dSource :
ProbeInfo m_probe;
EcgSeries m_ecg;
std::array<R8G8B8A8,256> m_color_map_tissue;
Cart3dGeom m_img_geom = {};
std::vector<Image3d> m_frames;

Cart3dGeom m_bbox = {};
std::vector<ImageType> m_stream_types;
};

OBJECT_ENTRY_AUTO(__uuidof(Image3dSource), Image3dSource)
109 changes: 109 additions & 0 deletions DummyLoader/Image3dStream.cpp
Original file line number Diff line number Diff line change
@@ -1 +1,110 @@
#include "Image3dStream.hpp"

#include "LinAlg.hpp"


static const uint8_t OUTSIDE_VAL = 0; // black outside image volume
static const uint8_t PROBE_PLANE = 127; // gray value for plane closest to probe


Image3dStream::Image3dStream() {
}

void Image3dStream::Initialize (ImageType type, Cart3dGeom img_geom, Cart3dGeom out_geom, unsigned short max_resolution[3]) {
m_type = type;
m_img_geom = img_geom;
m_out_geom = out_geom;

for (size_t i = 0; i < 3; ++i)
m_max_res[i] = max_resolution[i];

// One second loop starting at t = 10
const size_t numFrames = 25;
const double duration = 1.0; // Seconds
const double startTime = 10.0;

if (type == IMAGE_TYPE_TISSUE) {
// checker board image data
unsigned short dims[] = { 20, 15, 10 }; // matches length of dir1, dir2 & dir3, so that the image squares become quadratic
std::vector<byte> img_buf(dims[0] * dims[1] * dims[2]);
for (size_t frameNumber = 0; frameNumber < numFrames; ++frameNumber) {
for (unsigned int z = 0; z < dims[2]; ++z) {
for (unsigned int y = 0; y < dims[1]; ++y) {
for (unsigned int x = 0; x < dims[0]; ++x) {
bool even_f = (frameNumber / 2 % 2) == 0;
bool even_x = (x / 2 % 2) == 0;
bool even_y = (y / 2 % 2) == 0;
bool even_z = (z / 2 % 2) == 0;
byte & out_sample = img_buf[x + y*dims[0] + z*dims[0] * dims[1]];
if (even_f ^ even_x ^ even_y ^ even_z)
out_sample = 255;
else
out_sample = 0;
}
}
}

// special grayscale value for plane closest to probe
for (unsigned int z = 0; z < dims[2]; ++z) {
for (unsigned int x = 0; x < dims[0]; ++x) {
unsigned int y = 0;
byte & out_sample = img_buf[x + y*dims[0] + z*dims[0] * dims[1]];
out_sample = PROBE_PLANE;
}
}

m_frames.push_back(CreateImage3d(frameNumber*(duration/numFrames) + startTime, IMAGE_FORMAT_U8, dims, img_buf));
}
} else {
abort(); // should never be reached
}
}

Image3dStream::~Image3dStream() {
}


HRESULT Image3dStream::GetFrameCount(/*out*/unsigned int *size) {
if (!size)
return E_INVALIDARG;

*size = static_cast<unsigned int>(m_frames.size());
return S_OK;
}

HRESULT Image3dStream::GetFrameTimes(/*out*/SAFEARRAY * *frame_times) {
if (!frame_times)
return E_INVALIDARG;

const unsigned int N = static_cast<unsigned int>(m_frames.size());
CComSafeArray<double> result(N);
if (N > 0) {
double * time_arr = &result.GetAt(0);
for (unsigned int i = 0; i < N; ++i)
time_arr[i] = m_frames[i].time;
}

*frame_times = result.Detach();
return S_OK;
}


HRESULT Image3dStream::GetFrame(unsigned int index, /*out*/Image3d *data) {
if (!data)
return E_INVALIDARG;
if (index >= m_frames.size())
return E_BOUNDS;

ImageFormat format = m_frames[index].format;
if (format == IMAGE_FORMAT_U8) {
Image3d result = SampleFrame<uint8_t>(m_frames[index], m_img_geom, m_out_geom, m_max_res);
*data = std::move(result);
return S_OK;
} else if (format == IMAGE_FORMAT_FREQ8POW8) {
Image3d result = SampleFrame<uint16_t>(m_frames[index], m_img_geom, m_out_geom, m_max_res);
*data = std::move(result);
return S_OK;
}

return E_NOTIMPL;
}
47 changes: 46 additions & 1 deletion DummyLoader/Image3dStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ struct R8G8B8A8 {
/** Determine the sample size [bytes] for a given image format. */
static unsigned int ImageFormatSize(ImageFormat format) {
switch (format) {
case IMAGE_FORMAT_U8: return 1;
case IMAGE_FORMAT_U8: return 1;
case IMAGE_FORMAT_FREQ8POW8: return 2;
case IMAGE_FORMAT_R8G8B8A8: return 4;
}

abort(); // should never be reached
Expand All @@ -64,3 +66,46 @@ static Image3d CreateImage3d (double time, ImageFormat format, const unsigned sh

return img;
}


class ATL_NO_VTABLE Image3dStream :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<Image3dStream, &__uuidof(Image3dStream)>,
public IImage3dStream {
public:
Image3dStream();

/*NOT virtual*/ ~Image3dStream();

void Initialize (ImageType type, Cart3dGeom img_geom, Cart3dGeom out_geom, unsigned short max_resolution[3]);

HRESULT STDMETHODCALLTYPE GetType (/*out*/ImageType * type) override {
if (!type)
return E_INVALIDARG;

*type = m_type;
return S_OK;
}

HRESULT STDMETHODCALLTYPE GetFrameCount(/*out*/unsigned int *size) override;

HRESULT STDMETHODCALLTYPE GetFrameTimes(/*out*/SAFEARRAY * *frame_times) override;

HRESULT STDMETHODCALLTYPE GetFrame(unsigned int index, /*out*/Image3d *data) override;

DECLARE_REGISTRY_RESOURCEID(IDR_Image3dStream)

BEGIN_COM_MAP(Image3dStream)
COM_INTERFACE_ENTRY(IImage3dStream)
END_COM_MAP()

private:
ImageType m_type = IMAGE_TYPE_INVALID;
Cart3dGeom m_img_geom = {};
std::vector<Image3d> m_frames;

Cart3dGeom m_out_geom = {};
unsigned short m_max_res[3];
};

OBJECT_ENTRY_AUTO(__uuidof(Image3dStream), Image3dStream)
5 changes: 3 additions & 2 deletions DummyLoader/Resource.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#define IDS_PROJNAME 100

#define IDR_AppID 105
#define IDR_Image3dSource 106
#define IDR_Image3dFileLoader 107
#define IDR_Image3dStream 106
#define IDR_Image3dSource 107
#define IDR_Image3dFileLoader 108
5 changes: 5 additions & 0 deletions DummyLoader/UNREGISTER_DummyLoader.bat
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ for %%R in (HKEY_LOCAL_MACHINE HKEY_CURRENT_USER) do (
reg delete "%%R\SOFTWARE\Classes\TypeLib\{67E59584-3F6A-4852-8051-103A4583CA5E}" /f 2> NUL

for %%P in (32 64) do (
:: Image3dStream class
reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dStream" /f 2> NUL
reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dStream.1" /f 2> NUL
reg delete "%%R\SOFTWARE\Classes\CLSID\{78317A0E-56BF-4735-AB5B-FE0751219FE8}" /f /reg:%%P 2> NUL

:: Image3dSource class
reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dSource" /f 2> NUL
reg delete "%%R\SOFTWARE\Classes\DummyLoader.Image3dSource.1" /f 2> NUL
Expand Down
Loading

0 comments on commit 00c6d45

Please sign in to comment.