Skip to content

Commit

Permalink
Implement rudimentary CF simulation and display.
Browse files Browse the repository at this point in the history
The important part here is the TestViewer GenerateBitmap function, which serves as a reference implementation of color-flow display.
  • Loading branch information
Fredrik Orderud committed Mar 23, 2020
1 parent 7a10d54 commit 2478c03
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 10 deletions.
67 changes: 65 additions & 2 deletions DummyLoader/Image3dSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ 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
Expand All @@ -46,8 +79,9 @@ Image3dSource::Image3dSource() {
m_bbox = geom;
}

// a single tissue stream
// simulate tissue + color-flow data
m_stream_types.push_back(IMAGE_TYPE_TISSUE);
m_stream_types.push_back(IMAGE_TYPE_BLOOD_VEL);
}

Image3dSource::~Image3dSource() {
Expand All @@ -73,8 +107,23 @@ HRESULT Image3dSource::GetStream(int index, Cart3dGeom out_geom, unsigned short
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], m_bbox, out_geom, max_resolution);
stream_cls->Initialize(m_stream_types[index], bbox, out_geom, max_resolution);
stream_obj = stream_cls; // cast class pointer to interface
}

Expand Down Expand Up @@ -103,6 +152,20 @@ HRESULT Image3dSource::GetColorMap(ColorMapType type, /*out*/ImageFormat * forma
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;
Expand Down
5 changes: 4 additions & 1 deletion DummyLoader/Image3dSource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ class ATL_NO_VTABLE Image3dSource :
private:
ProbeInfo m_probe;
EcgSeries m_ecg;
std::array<R8G8B8A8,256> m_color_map_tissue;

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;
Expand Down
18 changes: 18 additions & 0 deletions DummyLoader/Image3dStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ void Image3dStream::Initialize (ImageType type, Cart3dGeom img_geom, Cart3dGeom

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
}
Expand Down
101 changes: 94 additions & 7 deletions TestViewer/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ public partial class MainWindow : Window
IImage3dSource m_source;

IImage3dStream m_streamXY;
IImage3dStream m_streamXYcf;
IImage3dStream m_streamXZ;
IImage3dStream m_streamXZcf;
IImage3dStream m_streamZY;
IImage3dStream m_streamZYcf;

public MainWindow()
{
Expand All @@ -69,8 +72,11 @@ void ClearUI()
ImageZY.Source = null;

m_streamXY = null;
m_streamXYcf = null;
m_streamXZ = null;
m_streamXZcf = null;
m_streamZY = null;
m_streamZYcf = null;

ECG.Data = null;

Expand Down Expand Up @@ -302,6 +308,8 @@ private void InitializeSlices()
bboxXY.dir3_y = 0;
bboxXY.dir3_z = 0;
m_streamXY = m_source.GetStream(0, bboxXY, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 });
if (stream_count >= 2)
m_streamXYcf = m_source.GetStream(1, bboxXY, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 }); // assume 2nd stream is color-flow

// get XZ plane (assumes 1st axis is "X" and 3rd is "Z")
Cart3dGeom bboxXZ = bbox;
Expand All @@ -315,6 +323,8 @@ private void InitializeSlices()
bboxXZ.dir3_y = 0;
bboxXZ.dir3_z = 0;
m_streamXZ = m_source.GetStream(0, bboxXZ, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 });
if (stream_count >= 2)
m_streamXZcf = m_source.GetStream(1, bboxXZ, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 }); // assume 2nd stream is color-flow

// get ZY plane (assumes 2nd axis is "Y" and 3rd is "Z")
Cart3dGeom bboxZY = bbox;
Expand All @@ -331,30 +341,68 @@ private void InitializeSlices()
bboxZY.dir3_y = 0;
bboxZY.dir3_z = 0;
m_streamZY = m_source.GetStream(0, bboxZY, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 });
if (stream_count >= 2)
m_streamZYcf = m_source.GetStream(1, bboxZY, new ushort[] { HORIZONTAL_RES, VERTICAL_RES, 1 }); // assume 2nd stream is color-flow
}

private void DrawSlices (uint frame)
private void DrawSlices(uint frame)
{
Debug.Assert(m_source != null);

ImageFormat image_format;
byte[] color_map = m_source.GetColorMap(ColorMapType.TYPE_TISSUE_COLOR, out image_format);
byte[] tissue_map = m_source.GetColorMap(ColorMapType.TYPE_TISSUE_COLOR, out image_format);
if (image_format != ImageFormat.IMAGE_FORMAT_R8G8B8A8)
throw new Exception("Unexpected color-map format");

// retrieve image slices
byte[] cf_map = m_source.GetColorMap(ColorMapType.TYPE_FLOW_COLOR, out image_format);
if (image_format != ImageFormat.IMAGE_FORMAT_R8G8B8A8)
throw new Exception("Unexpected color-map format");

byte[] arb_table = m_source.GetColorMap(ColorMapType.TYPE_FLOW_ARB, out image_format);
if (image_format != ImageFormat.IMAGE_FORMAT_U8)
throw new Exception("Unexpected color-map format");

uint cf_frame = 0;
if (m_streamXYcf != null) {
// find closest corresponding CF frame
double t_time = m_streamXY.GetFrame(frame).time;
double[] cf_times = m_streamXYcf.GetFrameTimes();

int closest_idx = 0;
for (int i = 1; i < cf_times.Length; ++i) {
if (Math.Abs(cf_times[i] - t_time) < Math.Abs(cf_times[closest_idx] - t_time))
closest_idx = i;
}

cf_frame = (uint)closest_idx;
}

// get XY plane (assumes 1st axis is "X" and 2nd is "Y")
Image3d imageXY = m_streamXY.GetFrame(frame);
ImageXY.Source = GenerateBitmap(imageXY, color_map);

if (m_streamXYcf != null) {
Image3d imageXYcf = m_streamXYcf.GetFrame(cf_frame);
ImageXY.Source = GenerateBitmap(imageXY, imageXYcf, tissue_map, cf_map, arb_table);
} else {
ImageXY.Source = GenerateBitmap(imageXY, tissue_map);
}

// get XZ plane (assumes 1st axis is "X" and 3rd is "Z")
Image3d imageXZ = m_streamXZ.GetFrame(frame);
ImageXZ.Source = GenerateBitmap(imageXZ, color_map);
if (m_streamXZcf != null) {
Image3d imageXZcf = m_streamXZcf.GetFrame(cf_frame);
ImageXZ.Source = GenerateBitmap(imageXZ, imageXZcf, tissue_map, cf_map, arb_table);
} else {
ImageXZ.Source = GenerateBitmap(imageXZ, tissue_map);
}

// get ZY plane (assumes 2nd axis is "Y" and 3rd is "Z")
Image3d imageZY = m_streamZY.GetFrame(frame);
ImageZY.Source = GenerateBitmap(imageZY, color_map);
if (m_streamZYcf != null) {
Image3d imageZYcf = m_streamZYcf.GetFrame(cf_frame);
ImageZY.Source = GenerateBitmap(imageZY, imageZYcf, tissue_map, cf_map, arb_table);
} else {
ImageZY.Source = GenerateBitmap(imageZY, tissue_map);
}

FrameTime.Text = "Frame time: " + imageXY.time;
}
Expand Down Expand Up @@ -387,6 +435,45 @@ private WriteableBitmap GenerateBitmap(Image3d t_img, byte[] t_map)
return bitmap;
}

private WriteableBitmap GenerateBitmap(Image3d t_img, Image3d cf_img, byte[] t_map, byte[] cf_map, byte[] arb_table)
{
Debug.Assert(t_img.dims.SequenceEqual(cf_img.dims));
Debug.Assert(t_img.format == ImageFormat.IMAGE_FORMAT_U8);
Debug.Assert(cf_img.format == ImageFormat.IMAGE_FORMAT_FREQ8POW8);

WriteableBitmap bitmap = new WriteableBitmap(t_img.dims[0], t_img.dims[1], 96.0, 96.0, PixelFormats.Rgb24, null);
bitmap.Lock();
unsafe {
for (int y = 0; y < bitmap.Height; ++y) {
for (int x = 0; x < bitmap.Width; ++x) {
byte t_val = t_img.data[x + y * t_img.stride0];
ushort cf_val = BitConverter.ToUInt16(cf_img.data, 2*x + y*(int)cf_img.stride0);

uint rgba = 0;
if (arb_table[cf_val] > t_val) {
// display color-flow overlay
rgba = BitConverter.ToUInt32(cf_map, 4*cf_val);
} else {
// display tissue underlay
rgba = BitConverter.ToUInt32(t_map, 4*t_val);
}

byte[] channels = BitConverter.GetBytes(rgba);

// assign red, green & blue
byte* pixel = (byte*)bitmap.BackBuffer + x * (bitmap.Format.BitsPerPixel / 8) + y * bitmap.BackBufferStride;
pixel[0] = channels[0]; // red
pixel[1] = channels[1]; // green
pixel[2] = channels[2]; // blue
// discard alpha channel
}
}
}
bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
bitmap.Unlock();
return bitmap;
}

static void SwapVals(ref float v1, ref float v2)
{
float tmp = v1;
Expand Down

0 comments on commit 2478c03

Please sign in to comment.