前言
這篇文章其實早就想寫了,最近事情比較多,耽擱了,詳細的代碼可能不會提供多少,但我會盡可能把前端
開發的過程和思路寫清楚,其實總體邏輯很簡單,只是相關的資料和文檔不多,太難找,我也會盡可能java
在這篇文章中把經常使用的API標出。python
再囉嗦幾嘴linux
這是在上一家公司開發時遇到的需求,須要將"病理圖片"解析而且在瀏覽器上顯示,在我以前的技術git
沒能解決,而我以前也沒作過相似的功能,公司只給了我一個鏈接,是XX公司實現後的效果,要求github
作到這種,公司的老項目是個CS客戶端的,已經實現了相關的功能,可是是由python實現的,年代windows
比較久遠,甚至在職人員都不太清楚是怎麼實現的,並且有個比較大的問題就是,很卡,讀圖在頁面後端
上會比較卡,不流暢,BOSS不懂技術,覺得是語言的問題,因此特別要求此次要用java在瀏覽器上瀏覽器
實現,實際上這是一個前端優化跟緩存取用的問題,跟後臺用什麼語言實現沒什麼關係,不過既然緩存
被這麼要求了,就只能硬着頭皮作了。
正文
在鋪乾貨以前,我先寫寫當時的思路吧,其實在看了DEMO鏈接後,大概就清楚了是一個怎麼樣的實現
方式,在頁面上展現的實際上是無數256*256的小圖拼湊在一塊兒的,初步的思路大概就是這麼幾步
1.後端用了某種方式把一張圖片切成了無數256*256的小圖保存起來,多是在硬盤也多是在緩存內。
2.前端經過單純的url地址展現圖片,而且運用瀏覽器緩存進行必定範圍內的保存,當移動選定區域時再從新加載。
3.此方式彷佛沒法實現點開即讀,後續瞭解了一些類似的讀圖軟件,均不能作到點開即讀。
基本的思路有了,剩下就算是有跡可循了,後端的實現方式到這裏其實就有個大概的想法了,甚至代碼都已經隱約浮如今腦中了(其實徹底不是我想的那樣)
重點是前端,前端這個邏輯太複雜,想寫出這樣的效果怕是得來個資深大神。
因而在尋覓了好久後,終於發現了一些蛛絲馬跡。
一套來自微軟的方法,或者說組件?
叫作 DeepZoom
我理解的可能不是那麼到位,但要我總結的話
這大概是一種協議吧,或者說是一種方法,他描述的是」把一張大圖以何種方式切割「
這個方式就是每次縮放時,把邊長縮小一倍再切割,舉例的話
若是有一個邊長10000的正方形圖片切割,那麼第一次切割就是10000/256 這種思路
當切割完全部面積後,將原圖縮放成邊長5000的正方形,再次切割5000/256
以此類推
而後每個尺寸的圖都集中放進一個文件夾中,按照序號排序,若是說的通俗點,這個序號(文件夾)的個數
其實就是根據邊長能夠對摺多少次來排序的。最終邊長就是1(固然也能夠本身來定義這個最小邊長)
其實這套約定爲何要這麼定義,咱們不須要去關心,咱們只須要知道該怎麼作,該怎麼切就能夠了。
而後就引出了一套前端庫
OpenSeaGragon
這是一個由js實現的多層圖片處理庫
支持多種」協議「(好比DeepZoom)
用法很簡單,官網openseadragon
官網上有demo,有庫的源碼包,雖然全英文的
另外百度上也有相應的用法教程,還算比較全,這裏就不囉嗦了。
這是一套完美支持deepzoom方式的前端庫,全部上面提到的前端操做他都完成了,只要調用就行。
那麼剩下的問題就是後端的切圖了。
關於後端的實現,也正是本文的重點
在解決了前端問題後,我按照相應的思路試着寫了一版切圖,源碼不貼了(由於已經沒有了),說一下當時的實現思路。
無非就是利用BufferedImage來處理圖片。
循環切圖,記錄座標,縮放,生成新的圖片對象,再切圖,總體下來其實仍是挺好寫的。
整個util寫下來沒用上300行代碼。
測試-跑通,OK
大功告成。
當時是這麼覺得的。
但好景不長,爲了測試性能,換了幾張圖片,立馬就出了問題。
java.lang.NegativeArraySizeException
怎麼會出現這種東西。
隨後看了一下,過程不囉嗦了,緣由就是
圖片的邊長太長了,總像素超過了20e以上,BufferedImage源碼在構造時有個DataBufferIntd的轉換步驟
而其中一個參數size的來源就是邊長h*w,而且是用int來接收的,長度直接超出了int的上限值,因此報錯了。
這就頭疼了,由於想要在java中處理圖片,不管如何都要用到BufferedImage,也就是說無解了。
後來一拍腦門,想到公司客戶端也實現了相應的功能啊,能夠看看之前的代碼。
一看更鬧心了,是python實現的,而且項目是不知道經手了多少我的,找不到實現的相關代碼,也不知道用了什麼技術。
因而就用各類關鍵字在網上搜索相關的結果,大多都沒什麼用。
後來用.svs這個關鍵字搜索時,找到了這麼一篇文章。
醫學病理圖片(SVS格式)的格式轉換與顯示——python實現
因而瞭解到了這麼兩種技術
1.openSlide
2.libvips
隨後的開發過程當中,同事幫忙找到了原項目的實現代碼,證明後發現是使用了libvips
但本次我選用的是openSlide
其實兩種技術都是差很少的原理,但支持的格式不太相同,咱們的需求中有一種是.mrxs的格式,libvips並不支持。
瞭解到這些後其實就方便了,後續的就是如何使用了,後文的內容基本就是幫你們踩坑了。
OpenSlide的使用
OpenSlide提供多種語言的對接,其中對python的支持最爲良好,目前國內可查到的資料中,沒有關於java的詳細解讀
OpenSlide-java在其官網中能夠找到代碼支持,地址爲 openslide.org
1、準備工做
1.windows下的環境搭建
在官網下載對應版本的windows支持包,解壓到磁盤中後配置好環境變量(須要把bin和lib兩個文件夾都配置進去)
以後確保本機的VC環境,須要最新的版本,本次開發所用到的版本爲VC2019的版本
2.項目中的調用
下載好github上所提供的openSlide-java源碼包 地址爲 https://github.com/openslide/openslide-java
項目中能夠進行源碼的自定義修改和開發,但此代碼並不能直接複製到工程目錄內直接引用,須要額外打包,其已經給準備好了打包文件
在項目中找到 build.xml 直接以ant運行便可打包(這裏不作詳解,自行百度),打包完成後的openslide.jar默認存放於項目磁盤文件夾主目錄下
新生成的jar包經過本地包引入的方式放到目標工程中去便可使用openSlide
3.linux下的搭建
https://openslide.org/download/ 下載最新的包,目前版本爲3.4.1
同時須要下載OpenSlide Java interface(此處存在爭議,也許只須要下載這個java支持的包就能夠)
根據本身服務器的系統選擇安裝方式,官方有提供ubantu與centOS的安裝命令
以後把下載的兩個包上傳到服務器自定義的目錄解壓,解壓後運行configure構建新包
不管是以何種方式和系統,此處構建時都會丟失大量包與文件,須要逐個排查下載,而且須要使用阿里雲鏡像,原版鏡像中會找不到丟失的資源。
用以上方式按照循序分別先構建3.4.1再構建OpenSlide Java interface
構建成功後,將會生成兩個文件,分別是libopenslide-jni.so與openslide.jar
(這裏標註一下,後生成的openSlide.jar與條目2中構建的jar包是否相同暫未肯定,主要是爲了生成libopenslide-jni.so)
生成後,在項目啓動命令中以 -Djava.library.path= 命令引入文件所在的文件夾便可運行。
關於linux服務器上的環境部署還有不少須要待確認的步驟,以上方式能夠保證穩定運行,但未必是最精簡的方式。
4.API解讀
整個openslide的核心API其實只有OpenSlide對象的構建。
構建一個OpenSlide對象很簡單,只要傳入File便可
OpenSlide os = new OpenSlide(File);
構建出的os對象幾乎能夠支持全部的病理圖片格式,已知可穩定支持的有 .SVS .TIFF .TIFF .NDPI .MRXS
os.close();
關閉openslide對象的方法,構建完成os對象後如若再也不使用,要及時關閉,不然一直佔用內存。
os.createThumbnailImage(maxSize);
os.createThumbnailImage(x, y, w, h, maxSize);
os.createThumbnailImage(x, y, w, h, maxSize, bufferedImageType);
建立一張縮略圖的方法,返回對象爲bufferedImage 會有像素限制(等同bufferImage最大像素)
maxSize參數爲最大尺寸 這個尺寸能夠是圖片的寬也能夠是圖片的高。
x與y是縮略圖起始座標,若是想得到一張完整原圖的縮略圖,能夠輸入0,0
w,h則爲所但願截取原圖範圍的寬高,若是但願得到一張完整原圖的縮略圖能夠輸入原圖的寬高。
這裏再詳細解答一點。此w跟h是根據maxSize隨動的
舉個例子
好比原圖邊長爲1024的正方形圖片
咱們但願獲取邊長爲512的整圖縮略圖時,輸入的參數應該是 (0,0,1024,1024,512)
若咱們但願獲取一個寬度爲512可是高度爲256(原圖一半+一倍縮放)這種圖片時,那輸入的參數則變爲(0,0,1024,512,512)
是否看出不一樣了?
沒錯,就是這個h,這個h是根據maxSize來變化的,咱們能夠視爲 h=(但願所得邊長)*(原圖邊長/maxSize)
w也同理。
bufferedImageType即爲bufferedImage。getType獲取的值,通常傳入1便可,有興趣的能夠自行百度。
由於createThumbnailImage返回的是一個bufferedImageType對象,因此咱們能夠根據這個方法來制定如何切圖,好比先切一部分,再對這部分進行單獨切圖處理。本次功能的完成就重點運用了這個函數。
createThumbnailImage方法的底層一樣運用了ImageIO相關來實現,因此仍然會存在像素上限的問題,源碼中其實對於不一樣尺寸的縮放是採用了不一樣層級來獲取的,但若是目標圖片只有原尺寸1層,那麼此方法將沒法處理。
而且目前來看,這類圖片整個openSlide的相關支持也沒法處理(好比你但願從一個邊長102400*102400的原圖得到一張256*256的全圖鳥瞰,若是層級爲0,將沒法得到,緣由是輸入maxSize後,其實現方法是按照比例獲取縮放倍數,再得出縮放層級而後取出尺寸較小的圖片,當層級爲0時,只能取出原圖)
os.dispose();
關閉鏈接而且釋放資源。
os.getAssociatedImages();
獲取附加圖片,返回對象爲mMap<String, AssociatedImage>,AssociatedImage對象中有一個 .toBufferedImage()的方法用來獲得一個bufferedImage對象,對此圖片進行從新渲染可獲得條形碼照片(若不從新渲染則會失真)
此方法複用性較高,這裏給出一段獲取附帶圖片的代碼,能夠直接拿着用,若是返回是空,那就是沒有附帶圖片。
/** * 獲取圖像中的附帶圖像 * @param inFile 原始圖片 * @param outPut 輸出文件夾 * @return 圖片所在地址 */ public static String getOrderImg(File inFile,File outPut) { String fileName = inFile.getName(); String nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf('.')); OpenSlide os; try { os = new OpenSlide(inFile); Map<String, AssociatedImage> map = os.getAssociatedImages(); if(map.keySet().size()>0) { AssociatedImage aimg = map.get("label"); BufferedImage ar =aimg.toBufferedImage(); BufferedImage result = new BufferedImage(ar.getWidth(), ar.getHeight(), 1); Graphics2D g = result.createGraphics(); g.drawImage(ar, 0, 0, null); os.close(); ImageIO.write(result, "jpg", new File(outPut +File.separator+nameWithoutExtension+ "_others" + ".jpg")); return (nameWithoutExtension+ "_others" + ".jpg"); }else { return ""; } } catch (IOException e) { System.out.println("######建立OS對象失敗"); e.printStackTrace(); return ""; } }
os.getBestLevelForDownsample(downsample);
根據輸入的縮放倍數(double且不可爲0)獲取到最佳縮放層級,返回的是int
os.getLevel0Height();
os.getLevel0Width();
獲取圖片原尺寸的高與寬,也是層級=0時的尺寸
os.getLevelCount();
獲取圖片的層級,即一共有多少層,返回int
os.getLevelDownsample(level);
根據輸入的層級獲得相應的縮放倍數,返回double
os.getLevelHeight(level);
os.getLevelWidth(level);
根據輸入的層級獲得相應層級的高與寬
os.getProperties();
獲取圖片自帶的描述信息,返回map<String,String>
os.paintRegion(g, dx, dy, sx, sy, w, h, downsample);
os.paintRegionARGB(dest, x, y, level, w, h);
os.paintRegionOfLevel(g, dx, dy, sx, sy, w, h, level);
繪圖相關的三個方法。
其實源碼中最終都是由paintRegionARGB這個方法來實現的,只是輸入的參數不一樣用以達成不一樣的效果。
首先是g這個參數,其實就是Graphics2D
dx跟dy則是所須要切割的一個座標點,他根據sx,sy來確認一個方形區域用以切割,w跟h天然就是切割的目標寬高。
而downsample跟level不難理解了,前者是縮放倍數,後者是縮放層級。
咱們能夠經過這樣一段代碼來獲取到原圖中一塊區域
包括createThumbnailImage這個方法其實也是由這個來實現的。
其實我還想重點講一下paintRegionARGB這個方法的參數使用,比較繞,並非常規的理解。
簡單來講就是傳入怎樣的level,也同樣要傳入相對的xy和相對的wh,舉例就是若是我想在h尺度上以座標0,0 0,256兩點起始,橫着截取寬爲w等長的一條
在原圖次存下各個參數都很好理解,分別是
x=0;y=0;level=0;w=w(原始寬);h=256;
但當我想縮放一倍後,那麼傳入的參數
x=0;y=0;leve=1(假設原圖提供的第二層1正好就是縮放了一倍);w=w;h=512;
沒錯,同一條的話,邊長傳入的h放大了一倍,爲何是放大了一倍實話說我在源碼中沒太屢清楚,寫法問題吧,這個源碼實際上是能夠改的。
只是改過了以後須要本身再打包,我當時以爲麻煩就直接這麼用了。
若是不明白的話,其實本身多試驗幾回就行了。不是很難理解,只是稍微有點繞。
大部分的核心方法基本就是上面這些了。
這篇整個API其實都是我當初開完完畢就記錄下來的note
如今我換了東家,電腦上也沒再進行相關的環境配置,看不了源碼了,因此也懶得再寫下去了。
最後的最後補充一點應該寫在最前面的小知識,也是我作這個項目才瞭解到的
1.病理圖片又稱爲醫學病理圖片 目前主要應用於電子顯微鏡成像 其格式有不少 市面上常見的有.SVS .TIF .TIFF .NDPI .MRXS 等
2.上條所提到的病理圖片雖然格式不一樣 但構造基本相同 都是由多層級構成 以最大爲原尺寸以此縮放 根據圖片的大小不一樣層級也不一樣
3.原圖片文件中可會附帶附加圖片 一般爲切片上的條形碼照片 格式一般爲jpg或者png
4.經過掃描儀產出的源文件圖片再次轉碼轉換格式時 可能會丟失層級或者附帶圖片
5.此類圖片像素極高 一般爲10億像素到100億像素不等 多種語言自帶的圖片處理工具均沒法讀取 市面上經常使用的支持有VIPS和OpenSlide。
寫的可能不是很詳細,也沒怎麼貼代碼,主要是以爲這個技術的複用性不是很高,並且搞很差每一個人的需求都不太同樣,代碼很難複用。
實話說java來作這樣的處理仍是挺麻煩的,網上對於這類數據的處理大多采用python,教程也不少,java基本沒有。
若是還有什麼不明白的,能夠留言詢問,我要是看到了的話會第一時間回覆。