在实验室信息系统(LIS)中,除常见定量、定性、说明文本等结果外,图表、标本图片等也是常见的结果表现形式。
例如:血常规、电泳、病理、精子分析、阴道微生态、各种高倍镜镜检等。
常见的数据提供方式是 base64 编码的字节数组、图片路径、数据库二进制数据等,部分也需要根据仪器提供数据,LIS自行绘制折线图、柱状图。
这里以常见的图片提供方式,说明一些常见的图片处理。
解析 Base64 图片 常见于血常规图片的处理,部分仪器需要设置位图传输、指定图片类型为位图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);string base64Path = Path.Combine(desktop, "base64.txt" );string base64 = File.ReadAllText(base64Path);var binary = Convert.FromBase64String(base64);using (MemoryStream stream = new MemoryStream(binary))using (Image image = Image.FromStream(stream)){ string imagePath = Path.Combine(desktop, $"WBC{image.GetExtension()} " ); image.Save(imagePath); Console.WriteLine($"原始图片大小:{binary.Length} " ); Console.WriteLine($"保存到本地图片大小:{new FileInfo(imagePath).Length} " ); }
base64 文件内容链接: https://pan.baidu.com/s/1YPPJftih0YecJYys1Jenng 密码: dbfu
图片压缩 如上面演示的代码,如果我们直接使用 Image.Save()
方法,由于 GDI+
的处理,所保存图片大小与原始大小比较有大概一倍的增长。
不过这个流程还是要做,因为这样解析另外一个目的是为了获取图片的格式,以及确保二进制内容存储的是图片内容。
所以以上为错误演示,确认可以正确被解析为图片、解析到图片格式后,应该直接保存字节数组的内容到文件即可,无需使用 Image
对象保存。
1 2 File.WriteAllBytes(imagePath, binary);
当然,以上的做法可以避免 GDI 绘图导致的图片增长,但是无法避免另外一个问题:原始图片就比较大。
常见的血常规图片因为是软件绘制,并且文件大小都在 100KB 以内,大小可以接受。
但是部分镜检的仪器,因为输出的是”照片“,所以普遍文件大小都以 MB 计。
但是作为要体现在报告单上的图片,系统并不需要太多的图片细节。而且太大的图片,还会影响报告单文件的大小,所以还需要对图片进行压缩。
其实 GDI 已经提供了一个获取图片缩略图的方法,已经封装到工具类中,可以直接使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);string base64Path = Path.Combine(desktop, "base64.txt" );string base64 = File.ReadAllText(base64Path); var binary = Convert.FromBase64String(base64);using (MemoryStream stream = new MemoryStream(binary))using (Image image = Image.FromStream(stream))using (Image thumb = image.GetThumbnail()){ string imagePath = Path.Combine(desktop, $"WBC{image.GetExtension()} " ); string thumbPath = Path.Combine(desktop, $"WBC-EX{image.GetExtension()} " ); File.WriteAllBytes(imagePath, binary); thumb.Save(thumbPath); Console.WriteLine($"原始图片大小:{new FileInfo(imagePath).Length} " ); Console.WriteLine($"缩略图大小:{new FileInfo(thumbPath).Length} " ); }
how-to-process-images
可以看到,对于图片细节比较少的位图,图片可以从 88KB 缩小到不足 2K,而且由于没有设置缩略图尺寸,所以在软件中几乎看不到两张图的差异。
而对于大尺寸、高分辨率、细节比较多的图片,还可以进行如下处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);string imagePath = Path.Combine(desktop, "原始图.jpg" );string thumbPath = Path.Combine(desktop, "缩略图.jpg" ); using (Image image = Image.FromFile(imagePath))using (Image thumb = image.GetThumbnail(1000 )){ thumb.Save(thumbPath, ImageFormat.Jpeg); Console.WriteLine($"原始图片 尺寸:{image.Width} ×{image.Height} 大小:{new FileInfo(imagePath).Length} " ); Console.WriteLine($"缩略图 尺寸:{thumb.Width} ×{thumb.Height} 大小:{new FileInfo(thumbPath).Length} " ); }
当然,优秀的压缩效果,也损失了大量的图片细节,具体如何使用要根据实际情况进行调整。
反色处理 如缩略图演示的图片,部分图片为了方便在仪器软件展示,提供的图片为黑底,但是在报告单中不可能展示黑底的图片。
这时常规的做法为将图片进行反色处理(可以在图片编辑软件中使用 Ctrl+Shift+i 看到反色效果)。
同样反色也已经封装在工具类的扩展方法中,调用如下:
1 2 3 4 5 6 7 8 9 string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);string imagePath = Path.Combine(desktop, "WBC.bmp" );string invertPath = Path.Combine(desktop, "WBC-EX.bmp" ); using (Image image = Image.FromFile(imagePath))using (Image invertImage = image.InvertColors()){ invertImage.Save(invertPath); }
how-to-process-images-02
绘图 常见绘制的图片为柱状图、散点图,可以参考文章:贝克曼 DxH800 血球仪图片绘制问题
附常用图片相关扩展方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 using System;using System.Collections.Generic;using System.Drawing;using System.Drawing.Imaging;using System.IO;using System.Linq;using System.Text;namespace JohnSun.Util { public static class ImageExtensions { public static byte [] GetBytes (this Image image, ImageFormat format = default ) { using (var stream = new MemoryStream()) { image.Save(stream, new ImageFormat((format ?? image.RawFormat).Guid)); return stream.ToArray(); } } public static Image InvertColors (this Image image ) { Bitmap bitmap = new Bitmap(image); for (int x = 0 ; x < bitmap.Width; x++) { for (int y = 0 ; y < bitmap.Height; y++) { var pixel = bitmap.GetPixel(x, y); var invertPixel = Color.FromArgb(0xff - pixel.R, 0xff - pixel.G, 0xff - pixel.B); bitmap.SetPixel(x, y, invertPixel); } } return bitmap; } public static Image GetThumbnail (this Image image, int width = default , int height = default ) { if (width <= 0 ) width = height <= 0 ? image.Width : (int )Math.Ceiling(image.Width * (height / (decimal )image.Height)); if (height <= 0 ) height = (int )Math.Ceiling(image.Height * (width / (decimal )image.Width)); return image.GetThumbnailImage(width, height, new Image.GetThumbnailImageAbort(() => true ), IntPtr.Zero); } public static ImageFormat GetRawFormat (this Image image ) { return image.RawFormat.GetRawFormat(); } public static string GetExtension (this Image image ) { return image.RawFormat.GetExtension(); } public static ImageFormat GetRawFormat (this ImageFormat format ) { if (format.Equals(ImageFormat.MemoryBmp)) return ImageFormat.MemoryBmp; else if (format.Equals(ImageFormat.Bmp)) return ImageFormat.Bmp; else if (format.Equals(ImageFormat.Emf)) return ImageFormat.Emf; else if (format.Equals(ImageFormat.Wmf)) return ImageFormat.Wmf; else if (format.Equals(ImageFormat.Gif)) return ImageFormat.Gif; else if (format.Equals(ImageFormat.Jpeg)) return ImageFormat.Jpeg; else if (format.Equals(ImageFormat.Png)) return ImageFormat.Png; else if (format.Equals(ImageFormat.Tiff)) return ImageFormat.Tiff; else if (format.Equals(ImageFormat.Exif)) return ImageFormat.Exif; else if (format.Equals(ImageFormat.Icon)) return ImageFormat.Icon; else return format; } public static string GetExtension (this ImageFormat format ) { if (format.Equals(ImageFormat.MemoryBmp)) return ".bmp" ; else if (format.Equals(ImageFormat.Bmp)) return ".bmp" ; else if (format.Equals(ImageFormat.Emf)) return ".emf" ; else if (format.Equals(ImageFormat.Wmf)) return ".wmf" ; else if (format.Equals(ImageFormat.Gif)) return ".gif" ; else if (format.Equals(ImageFormat.Jpeg)) return ".jpg" ; else if (format.Equals(ImageFormat.Png)) return ".png" ; else if (format.Equals(ImageFormat.Tiff)) return ".tiff" ; else if (format.Equals(ImageFormat.Exif)) return ".exif" ; else if (format.Equals(ImageFormat.Icon)) return ".ico" ; else throw new Exception($"未知的图片格式:{format} " ); } } }