diff --git a/DummyLoader/Image3dSource.cpp b/DummyLoader/Image3dSource.cpp index 37f1db6..2855d00 100644 --- a/DummyLoader/Image3dSource.cpp +++ b/DummyLoader/Image3dSource.cpp @@ -33,9 +33,37 @@ Image3dSource::Image3dSource() { m_ecg = EcgSeries(ecg); } { - // flat gray scale - for (size_t i = 0; i < m_color_map.size(); ++i) - m_color_map[i] = R8G8B8A8(static_cast(i), static_cast(i), static_cast(i), 0xFF); + // flat gray tissue scale + for (size_t i = 0; i < m_color_map_tissue.size(); ++i) + m_color_map_tissue[i] = R8G8B8A8(static_cast(i), static_cast(i), static_cast(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 >= 128) ? static_cast(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(freq) : 0; + // increasing blue for negative velocities + uint8_t blue = (freq >= 128) ? 128+static_cast(255-freq) : 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 freq = 0; freq < 256; ++freq) { // unsigned counter, so freq>127 corresponds to negative velocities + for (size_t bw = 0; bw < 256; ++bw) { + // show flow when |freq| >= 32 + bool show_flow = std::abs(static_cast(freq)) >= 32; + m_flow_arb[bw + freq*256] = show_flow ? 0xFF : 0x00; + } + } } { // image geometry X Y Z @@ -46,8 +74,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() { @@ -96,15 +125,30 @@ HRESULT Image3dSource::GetColorMap(ColorMapType type, /*out*/ImageFormat * forma if (*map) return E_INVALIDARG; - if (type != TYPE_TISSUE_COLOR) - return E_NOT_SET; + if (type == TYPE_TISSUE_COLOR) { + *format = IMAGE_FORMAT_R8G8B8A8; + // copy to new buffer + CComSafeArray color_map(4*static_cast(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 color_map(4*static_cast(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 color_map(static_cast(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; + } - *format = IMAGE_FORMAT_R8G8B8A8; - // copy to new buffer - CComSafeArray color_map(4*static_cast(m_color_map.size())); - memcpy(&color_map.GetAt(0), m_color_map.data(), sizeof(m_color_map)); - *map = color_map.Detach(); // transfer ownership - return S_OK; + return E_NOTIMPL; } HRESULT Image3dSource::GetECG(/*out*/EcgSeries *ecg) { diff --git a/DummyLoader/Image3dSource.hpp b/DummyLoader/Image3dSource.hpp index 5a3909c..e5091c4 100644 --- a/DummyLoader/Image3dSource.hpp +++ b/DummyLoader/Image3dSource.hpp @@ -40,7 +40,9 @@ class ATL_NO_VTABLE Image3dSource : private: ProbeInfo m_probe; EcgSeries m_ecg; - std::array m_color_map; + std::array m_color_map_tissue; + std::array m_color_map_flow; + std::array m_flow_arb; Cart3dGeom m_bbox = {}; std::vector m_stream_types; diff --git a/DummyLoader/Image3dStream.cpp b/DummyLoader/Image3dStream.cpp index 605bf60..3487ae4 100644 --- a/DummyLoader/Image3dStream.cpp +++ b/DummyLoader/Image3dStream.cpp @@ -23,7 +23,7 @@ void Image3dStream::Initialize (ImageType type, Cart3dGeom img_geom, Cart3dGeom 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 img_buf(dims[0] * dims[1] * dims[2]); @@ -55,6 +55,29 @@ 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) { + // sine wave velocity data + unsigned short dims[] = { 20, 15, 10 }; // matches length of dir1, dir2 & dir3, so that the image squares become quadratic + std::vector 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) { + double angle = x*2*M_PI/dims[0]; // in [0, 2*pi) + angle += frameNumber*2*M_PI/numFrames; // in [0, 4*pi) + + int8_t & freq = reinterpret_cast(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(255*(0.5f - y*1.0f/dims[1])); // [+127, -128] along Y axis + bw = static_cast(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 } } diff --git a/TestViewer/MainWindow.xaml.cs b/TestViewer/MainWindow.xaml.cs index df569db..2b483b7 100644 --- a/TestViewer/MainWindow.xaml.cs +++ b/TestViewer/MainWindow.xaml.cs @@ -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() { @@ -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; @@ -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; @@ -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; @@ -331,6 +341,8 @@ 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) @@ -338,30 +350,58 @@ private void DrawSlices (uint frame) Debug.Assert(m_source != null); // retrieve image slices + uint cf_frame = 0; + if (m_streamXYcf != null) { + // TODO: find closest corresponding CF frame + cf_frame = frame; + } // get XY plane (assumes 1st axis is "X" and 2nd is "Y") Image3d imageXY = m_streamXY.GetFrame(frame); + Image3d imageXYcf = new Image3d(); + if (m_streamXYcf != null) + imageXYcf = m_streamXYcf.GetFrame(cf_frame); // get XZ plane (assumes 1st axis is "X" and 3rd is "Z") Image3d imageXZ = m_streamXZ.GetFrame(frame); + Image3d imageXZcf = new Image3d(); + if (m_streamXZcf != null) + imageXZcf = m_streamXZcf.GetFrame(cf_frame); + // get ZY plane (assumes 2nd axis is "Y" and 3rd is "Z") Image3d imageZY = m_streamZY.GetFrame(frame); + Image3d imageZYcf = new Image3d(); + if (m_streamZYcf != null) + imageZYcf = m_streamZYcf.GetFrame(cf_frame); FrameTime.Text = "Frame time: " + imageXY.time; 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"); + + 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"); - ImageXY.Source = GenerateBitmap(imageXY, color_map); - ImageXZ.Source = GenerateBitmap(imageXZ, color_map); - ImageZY.Source = GenerateBitmap(imageZY, color_map); + 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"); + + + ImageXY.Source = GenerateBitmap(imageXY, imageXYcf, tissue_map, cf_map, arb_table); + ImageXZ.Source = GenerateBitmap(imageXZ, imageXZcf, tissue_map, cf_map, arb_table); + ImageZY.Source = GenerateBitmap(imageZY, imageZYcf, tissue_map, cf_map, arb_table); } - private WriteableBitmap GenerateBitmap(Image3d t_img, byte[] t_map) + 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 @@ -371,8 +411,10 @@ private WriteableBitmap GenerateBitmap(Image3d t_img, byte[] t_map) 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); + byte* pixel = (byte*)bitmap.BackBuffer + x * (bitmap.Format.BitsPerPixel / 8) + y * bitmap.BackBufferStride; - SetRGBVal(pixel, t_val, t_map); + SetRGBVal(pixel, t_val, cf_val, t_map, cf_map, arb_table); } } } @@ -381,10 +423,18 @@ private WriteableBitmap GenerateBitmap(Image3d t_img, byte[] t_map) return bitmap; } - unsafe static void SetRGBVal(byte* pixel, byte t_val, byte[] t_map) + unsafe static void SetRGBVal(byte* pixel, byte t_val, ushort cf_val, byte[] t_map, byte[] cf_map, byte[] arb_table) { - // split input rgba color into individual channels - byte[] channels = BitConverter.GetBytes(BitConverter.ToUInt32(t_map, 4*t_val)); + 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 pixel[0] = channels[0]; // red pixel[1] = channels[1]; // green