Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: Add color-flow support (GE proposal) #140

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
159 changes: 91 additions & 68 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 @@ -39,113 +37,138 @@ Image3dSource::Image3dSource() {
for (size_t i = 0; i < m_color_map_tissue.size(); ++i)
m_color_map_tissue[i] = R8G8B8A8(static_cast<unsigned char>(i), static_cast<unsigned char>(i), static_cast<unsigned char>(i), 0xFF);
}
{
// red to blue flow scale with green at high BW
assert(m_color_map_flow.size() == 256*256);
for (size_t bw = 0; bw < 256; ++bw) {
// increasing green for high bandwidth
uint8_t green = (bw >= 192) ? static_cast<unsigned char>(bw) : 0;

for (size_t freq = 0; freq < 256; ++freq) { // unsigned counter, so freq>127 corresponds to negative velocities
// increasing red for positive velocities
uint8_t red = (freq < 128) ? 128+static_cast<unsigned char>(freq) : 0;
if (green)
red = 0;

// increasing blue for negative velocities
uint8_t blue = (freq >= 128) ? 128+static_cast<unsigned char>(255-freq) : 0;
if (green)
blue = 0;

m_color_map_flow[freq + bw*256] = R8G8B8A8(red, green, blue, 0xFF);
}
}
}
{
// flow arbitration scale
assert(m_flow_arb.size() == 256*256);
for (size_t bw = 0; bw < 256; ++bw) {
for (size_t freq = 0; freq < 256; ++freq) { // unsigned counter, so freq>127 corresponds to negative velocities
// show flow when |freq| >= 16
bool show_flow = std::abs(static_cast<int>(freq)) >= 16;
m_flow_arb[freq + bw*256] = show_flow ? 0xFF : 0x00;
}
}
}
{
// image geometry X Y Z
Cart3dGeom geom = { -0.1f, 0, -0.075f,// origin
0.20f,0, 0, // dir1 (width)
0, 0.10f, 0, // dir2 (depth)
0, 0, 0.15f};// dir3 (elevation)
m_img_geom = geom;
m_bbox = 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, FORMAT_U8, dims, img_buf));
}
}
// simulate tissue + color-flow data
m_stream_types.push_back(IMAGE_TYPE_TISSUE);
m_stream_types.push_back(IMAGE_TYPE_BLOOD_VEL);
}

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 == 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
Cart3dGeom bbox = m_bbox;
if (m_stream_types[index] == IMAGE_TYPE_BLOOD_VEL) {
// shrink color-flow sector to make it more realistic
vec3f origin, dir1, dir2, dir3;
std::tie(origin,dir1,dir2,dir3) = FromCart3dGeom(bbox);

float SCALE_FACTOR = 0.8f;
origin += 0.5f*(1-SCALE_FACTOR)*(dir1 + dir2 + dir3);
dir1 *= SCALE_FACTOR;
dir2 *= SCALE_FACTOR;
dir3 *= SCALE_FACTOR;

bbox = ToCart3dGeom(origin, dir1, dir2, dir3);
}

auto stream_cls = CreateLocalInstance<Image3dStream>();
stream_cls->Initialize(m_stream_types[index], 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;
}

HRESULT Image3dSource::GetColorMap(/*out*/SAFEARRAY ** map) {
HRESULT Image3dSource::GetColorMap(ColorMapType type, /*out*/ImageFormat * format, /*out*/SAFEARRAY ** map) {
if (!map)
return E_INVALIDARG;
if (*map)
return E_INVALIDARG;

// copy to new buffer
CComSafeArray<uint32_t> color_map(static_cast<unsigned int>(m_color_map_tissue.size()));
memcpy(&color_map.GetAt(0), m_color_map_tissue.data(), sizeof(m_color_map_tissue));
*map = color_map.Detach(); // transfer ownership
return S_OK;
if (type == TYPE_TISSUE_COLOR) {
*format = IMAGE_FORMAT_R8G8B8A8;
// copy to new buffer
CComSafeArray<uint8_t> color_map(4*static_cast<unsigned int>(m_color_map_tissue.size()));
memcpy(&color_map.GetAt(0), m_color_map_tissue.data(), sizeof(m_color_map_tissue));
*map = color_map.Detach(); // transfer ownership
return S_OK;
} else if (type == TYPE_FLOW_COLOR) {
*format = IMAGE_FORMAT_R8G8B8A8;
// copy to new buffer
CComSafeArray<uint8_t> color_map(4*static_cast<unsigned int>(m_color_map_flow.size()));
memcpy(&color_map.GetAt(0), m_color_map_flow.data(), sizeof(m_color_map_flow));
*map = color_map.Detach(); // transfer ownership
return S_OK;
} else if (type = TYPE_FLOW_ARB) {
*format = IMAGE_FORMAT_U8;
// copy to new buffer
CComSafeArray<uint8_t> color_map(static_cast<unsigned int>(m_flow_arb.size()));
memcpy(&color_map.GetAt(0), m_flow_arb.data(), sizeof(m_flow_arb));
*map = color_map.Detach(); // transfer ownership
return S_OK;
}

return E_NOTIMPL;
}

HRESULT Image3dSource::GetECG(/*out*/EcgSeries *ecg) {
Expand Down
17 changes: 10 additions & 7 deletions DummyLoader/Image3dSource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ 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;

HRESULT STDMETHODCALLTYPE GetColorMap(/*out*/SAFEARRAY ** map) override;
HRESULT STDMETHODCALLTYPE GetColorMap(ColorMapType type, /*out*/ImageFormat * format, /*out*/SAFEARRAY ** map) override;

HRESULT STDMETHODCALLTYPE GetECG(/*out*/EcgSeries *ecg) override;

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

std::array<R8G8B8A8,256> m_color_map_tissue;
std::array<R8G8B8A8,256*256> m_color_map_flow;
std::array<uint8_t,256*256> m_flow_arb;

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

OBJECT_ENTRY_AUTO(__uuidof(Image3dSource), Image3dSource)
127 changes: 127 additions & 0 deletions DummyLoader/Image3dStream.cpp
Original file line number Diff line number Diff line change
@@ -1 +1,128 @@
#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 if (type == IMAGE_TYPE_BLOOD_VEL) {
// velocity & bandwidth scale color-flow 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(2 * 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) {
int8_t & freq = reinterpret_cast<int8_t&>(img_buf[0 + 2*(x + y*dims[0] + z*dims[0] * dims[1])]);
byte & bw = img_buf[1 + 2*(x + y*dims[0] + z*dims[0] * dims[1])];

freq = static_cast<int8_t>(255*(0.5f - y*1.0f/dims[1])); // [+127, -128] along Y axis
bw = static_cast<uint8_t>(256*(x*1.0f/dims[0])); // [0,255] along X axis
}
}
}
m_frames.push_back(CreateImage3d(frameNumber*(duration/numFrames) + startTime, IMAGE_FORMAT_FREQ8POW8, 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;
}
Loading