这个本来没啥 不是什么算法 绝技。 都不值得一提。
其实这个是医学影像胶片曝光时排版的一个逻辑。
dicom标准第三部分 主要是讲IOD定义 在第166页有这样的描述:
表C.13.5-1图象盒象素描述组件
属性名称 标记 说明
图象位置 (2020,0010) 基于图象显示格式(2010,0010)的胶片的图象位置。
参阅C.13.5.1的规范。
这个所谓的“基于图象显示格式(2010,0010)的胶片的图象位置”到底是啥意思呢 ?还是像往常一样拿个实例瞧瞧:
20 20 10 00 ............ ..
00000010 02 00 00 00 02 00
20 20 10 00 ............ ..
00000010 02 00 00 00 03 00
dicom数据元素的结构就不多说了哈 详情请查看dicom标准。只看最后两位,可见这个所谓的“基于图象显示格式(2010,0010)的胶片的图象位置” 就是一个个的从前到后的排序。从设备工作站发送胶片曝光作业的时候 根据dicom协议 图像传输 那么它就是以这种 “序列”的方式发送的。
再看dicom标准另外一段 第三部分167页 :
C.13.5.1图象位置
胶片中图象的位置;图象位置序列的编码是基于选择的图象显示格式
(2010,0010)。图象位置序列以值1递增。
图象位置(2020,0010)定义如下:
- 标准显示格式:图象盒序列是主要行顺序(从左到右或从上到下);左上图象位置等于1。
- 行显示格式:图象盒序列是主要行顺序(从左到右或从上到下);左上图象位置等于1。
- 列显示格式:图象盒序列是主要列顺序(从左到右或从上到下);左上图象位置等于1。
- SLIDE显示格式:图象盒序列是主要行顺序(从左到右或从上到下);左上图象位置等于1。
- SUPERSLIDE显示格式:图象盒序列是主要行顺序(从左到右或从上到下);左上图象位置等于1。
关于ImgBox 的几种方式的细节我们暂时不去管 只管第一种 ,因为就我遇到的 90%以上都是 “标准显示格式”在胶片曝光dimse的过程中 createFilmBox 的时候 会收到一个参数 command元素0x20100010, 它的值是类似这样STANDARD\1,1 这个standard 就是表示 这是一个“标准显示格式” 逗号分隔开的两个数字 第一个是列数 第二个是行数。标准显示格式就是: (在一个指定行数列数的网格里把图像按从左到右从上到下的方式排列)
如图:
说到底我们要实现的就是根据dicom标准 把这种依次编号的图像拼接成 一个整幅的网格图像 让它打印出来看上去跟工作站上阅片时的所见即所得,可以根据编号获取指定图像 可以根据几行几列或许指定图像 可以根据编号获知他是第几行第几列 可以根据第几行第几列获知他的编号。就这样简单。
来简单分析下,可以通过STANDARD\colNum,rowNum 来确定行数跟列数。这就容易了
他是一个“持续堆叠的过程”
第一行堆满了堆第二行 从左往右的不断堆。
以x表示列 y表示行 从1开始,num表示编号 从1开始 , 可以得出这样的结论:
for(1 to num)
{
if(num%colNum!=0)//不能除尽
{
y=num/colNun+1;
x=num%colNum;
else
{
y=num/colNun;
x=colNum;
}
}
你能理解这个过程么 ?什么, 能用么 你可以验证下:
num=7
7%3!=0
{
y=7/3+1=>3
x=7%3=>1
}
num=6
6%3==0
{
y=6/3=>2
x=3=>3
}
num=5
5%3!=0
{
y=5/3+1=>3
x=5%3=>2
}
为什么要确定x跟y呢,因为输出的时候要给单幅图像定位 就是左上角 有了x跟y 才能够实现。可以有一个image的数组 用来存储接收到的图像 它们按照编号从前到后排列。
解决一个问题了
还有另外一个问题 图像的缩放。
如果我假设输出区域是宽度=297 高度=420。胶片的分格是4行3列 。那么单幅图像的尺寸是 宽度=297/3 高度=420/4 但是每个分格的图像尺寸通常是不固定的,就像普通的看图软件都有个显示比例叫“缩放到显示区域” 我们要做的就是这个 这一过程可以用这个图说明:
预先就设定图像都有两种等比例缩放尺寸 高度对齐(让高度跟可显示区域相等) 宽度对齐(让宽度跟可显示区域相等)。
这里有一个简单的等比例公式 缩放前后
宽度对齐情况下:
显示区域宽(缩放后的宽度)/原图宽=缩放后的高度/原图高
高度对齐情况下:
显示区域高(缩放后的高度)/原图高=缩放后的宽度/原图宽
分别得出两种情况另一边的长度
显示区域宽(缩放后的宽度)*原图高/原图宽=缩放后的高度
显示区域高(缩放后的高度)*原图宽/原图高=缩放后的宽度
然后对两种情况进行判断,如果哪种情况缩放后的图像区域超出了显示范围 则被否决。
如下图 蓝色框代表图像 ,黑色框代表显示区域:
就这样就达到了适应显示范围的等比例缩放。简单吧。
怎么把上述思路整合 然后用代码实现,首先我们得定义一个类 这个类叫Paper 相当于一张胶片 他是待打印区域 。
然后相应的要有 行数列数 显示区域高度宽度 等变量, 还要有一个image数组用来存储顺序编号排列的图像。
1 public class Paper 2 { 3 int row, col;//行数 与 列数 4 int width, height;//胶片宽度 高度 5 IList<Image> images;//顺序编号的图像 6 7 public Paper(int _width, int _height, int _row, int _col) 8 { 9 width = _width; height = _height; row = _row; col = _col; 10 images = new List<Image>(row * height); 11 } 12 //初始化显示区域 13 public Paper(int _row, int _col) 14 { 15 //width = 345 * 9; height = 420 * 9; row = _row; col = _col;//14INx17IN 16 width = 297 * 9; height = 420 * 9; row = _row; col = _col;//A3 17 images = new List<Image>(row * height); 18 } 19 //新增图像 20 public void addImg(Image img) 21 { 22 if (images.Count >= row * col) 23 return; 24 else 25 { 26 images.Add(img); 27 } 28 } 29 }
这相当于又是定义了一个数据模型的框架 初始化一个实例代表输出一张新的胶片。面向对象的分析设计是多么的好 哇哈哈。
材料有了 。下面这个函数才是重头戏,用于实现第一部分 分析的所有逻辑[胶片排版 跟 图像缩放],请对照第一部分的说明来看:
1 //排版后的输出 2 //排版方式为从上到下从左到右 3 public Image layout() 4 { 5 Image layouted = new Bitmap(width, height); 6 7 Graphics g = Graphics.FromImage(layouted); 8 g.Clear(Color.Green); 9 for (int i = 0; i < images.Count; i++) 10 { 11 int _row, _col; 12 if ((i + 1) % col != 0) 13 { 14 _row = (i + 1) / col + 1 - 1; // _row = (i + 1) / col + 1 - 1; 15 _col = (i + 1) % col - 1; // _col = (i + 1) / col - 1; 16 } 17 else 18 { 19 _row = (i + 1) / col - 1; 20 _col = col - 1; 21 } 22 if (_col < 0) 23 { 24 _col = 0; 25 } 26 if (_row < 0) 27 { 28 _row = 0; 29 } 30 GraphicsUnit u = GraphicsUnit.Pixel; 31 RectangleF recSrc = images[i].GetBounds(ref u);//原图像大小 32 RectangleF recDst;//缩放后大小并调整偏移位置 33 34 //宽比宽 =长比长 超出的那一边需要固定长度 缩小以调整到视野内 35 float recH = ((width / col) * recSrc.Height) / recSrc.Width, 36 recW = ((height / row) * recSrc.Width) / recSrc.Height; 37 38 if (recW > (width / col))//宽度超出 固定宽度调整高度 39 recDst = new RectangleF((width / col) * _col + 0, 40 (height / row) * _row + ((height / row) - recH) / 2, 41 (width / col), recH); 42 else//高度超出 固定高度调整宽度 43 recDst = new RectangleF((width / col) * _col + ((width / col) - recW) / 2, 44 (height / row) * _row + 0, 45 recW, (height / row)); 46 47 g.DrawImage(images[i], recDst, images[i].GetBounds(ref u), u); 48 //if (i == 4) 49 images[i].Save(i + "out.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); 50 } 51 52 //layouted.Save("layout.jpg", System.Drawing.Imaging.ImageFormat.Jpeg); 53 g.Dispose(); 54 55 return layouted; 56 }
调用:
1 Paper film = new Paper(3, 2); 2 film.addImg(Image.FromFile("1.jpg")); 3 film.addImg(Image.FromFile("2.jpg")); 4 film.addImg(Image.FromFile("3.jpg")); 5 film.addImg(Image.FromFile("4.jpg")); 6 film.addImg(Image.FromFile("5.jpg")); 7 film.addImg(Image.FromFile("6.jpg")); 8 9 film.layout();
一直说想要讲下dicom协议的通讯 ,看来下次吧。
源码及测试文件下载猛击此处