HTML5灰階影像處理練習2:窗寬窗位調節

來源:互聯網
上載者:User

大多數醫學映像的灰階層級都遠遠高於256,但一般的電腦螢幕只支援256級,加上人眼靈敏度的天然限制(大多隻能到16級),導致人類只能通過一個小小的視窗去觀察那個廣闊的灰階世界。因此,正如天文學家離不開望遠鏡,現代放射學家也離不開窗寬窗位。

一些富有想像力的讀者會問,未來的PACS影像工作站可以用HTML5來做嗎?至今為止,應該沒有人能夠回答。真正意義上的PACS影像工作站還有很多複雜的機制,比如大規模映像的載入、個人化的布局、便捷的瀏覽和導航工具,讓使用者在大序列中快速定位到想看的映像,或者在大幅映像中找到要仔細研究的部位,以及這一切背後複雜的記憶體管理和網路通訊。儘管利用HTML5可以在200行代碼之內搞定窗寬窗位,在大多數工作站上這也是最常用的功能,但這僅僅是邁向無需外掛程式的純粹Web平台工作站的很微小的一步,關於Canvas跨域訪問映像的安全問題,以及HTML5的File API, WebSocket API,WebStorage API的未來發展,還需要進一步關注。當然,另外的一種選擇,就是把所有這些哪怕是最簡單的空間和灰階變換之類的呈現邏輯都放在伺服器端。

至少,在幾年以前,用Javascript來調節窗寬窗位是難以想象的。首先,大家根本找不到Javascript能夠調用的像素處理API;其次,大多數瀏覽器的Javascript引擎都慢如蝸牛。HTML5的出現,也許提供了某種可能性。不過我也有點擔心,HTML5 API最後真的要把所有的案頭應用都替換掉嗎?如果是這樣,HTML5會不會變得過於複雜?是否有一個合適的技術架構和工作流程來控制這個複雜性?在這個狂熱的時代,HTML5的技術決策者們確實應該認真考慮清楚該做什麼和不做什麼,因為在IT界,遍地都是因為一個微小的功能差異,導致結果截然不同的案例。

下面是一個簡單的窗寬窗位調節案例,可以直接複製到本地一個html檔案中,然後用IE9/Opera10/etc.開啟。原理是先初始化一個512*512大小矩陣,裡面填充12位灰階值(即從0到4096取值)。為了便於觀察窗寬窗位調節的效果,這個矩陣中的灰階分布按照類似SMPTE的方式來設計。然後通過html中type為range的input控制項(即拖動條)來調節窗寬窗位,遺憾的是IE9不支援這個控制項,所以乾脆把滑鼠拖動的支援給加上:橫拖改變窗寬,縱拖改變窗位。在一步步編程實現的過程中,為了粗略驗證每一步的正確性,我還在頁面上增加了一個Canvas專門用來分別顯示未經處理資料和PV(Presentation Value)的長條圖,但在最後公布的代碼中我把它去掉了,對產生長條圖感興趣的讀者可以參考上一篇部落格。

做好了以後,發現IE9跟Opera10相比,確實還是有不小的差距。除了前面提到的對range類型的input控制項支援,以及Javascript指令碼引擎的效能以外,我還注意到了一個更加可怕的缺陷。我不記得用電腦圖形學的術語應該怎麼說了,如果用放射學的術語,可能可以用線對這種品質指標來衡量。當我在自己的test pattern中增加了橫縱兩組相隔1個像素的黑白線條之後,用相同的顯示器,發現IE9顯示出來的線條地區失真非常嚴重(人眼觀察有很明顯的疏密差異),但Opera10的顯示基本上沒有失真(而且奇怪的是,上傳到CSDN之後,被自動轉換成GIF,在Opera10上觀察這個圖片還是正常的,在IE9上觀察就失真了)。真希望有人告訴我Opera10是怎麼做到的。

關於用滑鼠調節窗寬窗位,我試過在Intel i3 M350機器上用Opera10運行,速度還是勉強可以接受的(跟案頭版本的工作站相比),但我用的演算法並沒有經過最佳化。感興趣的讀者可以根據“DICOM 醫學映像視窗變換的加速演算法”這篇論文做一些改進,相信改進過後,滑鼠拖動調節的效能跟案頭工作站相比應該沒有多大差別。當然,也還是需要在更大幅的普放映像上做進一步的實驗(儘管有人堅持認為普放映像調窗可能意義不大,或者希望用一些曲線來進行調節)。

最後,還是希望IE在對HTML5的支援方面能夠進步得快一些,畢竟在微軟平台上IE普及率還是很高的。IE10已經提出了HTML5本地化的概念,看看最後他們能貫徹到什麼程度。微軟雖然很大,但大多數還是很靈活的,他不僅不會像之前的柯達那樣抱著老古董不放而錯失良機,有時還會活生生地宰殺已經擁有多年的技術,比如90年代的VB/COM,和00年代的Winform,儘管他們曾經而且依舊十分的優秀。真不知道微軟擁抱HTML5之後,Sliverlight會不會被宰殺。

 

 

<!DOCTYPE html><br /><html><br /><head><br /><meta charset="utf-8"/><br /><title>Grayscale Windowing Test</title><br /><style type="text/css"><br /> table {margin:auto;}<br /> </style><br /></head><br /><body><br /><table><br /><tr align="center"><td><h3>Grayscale Image Test</h3><hr height="1px"/></td><br /> </tr><br /><tr><br /><td align="right"><br />WW(<span id="spanWW"></span>): <input id="rangeWW" type="range" min="0" max="4096" onchange="setWindow();"/><br />WC(<span id="spanWC"></span>): <input id="rangeWC" type="range" min="0" max="4096" onchange="setWindow();"/><br /><button id="resetImage" onclick="loadImage();return true;">Reset</button><br /></td><br /></tr><br /><tr align="center"><br /><td><canvas id="imageviewer" width="512px" height="512px" style="border: 1px solid;" mce_style="border: 1px solid;" /></td><br /></tr><br /><tr align="center"><td> May, 2011, lifegame@263.net</td><br /></tr><br /></table><br /></body><br /><script><br />var patternData = [];<br />var patternWidth = 512;<br />var patternHeight = 512;<br />var patternMaxGray = 0x0FFF;</p><p>function getPatternData(x,y){<br />return patternData[y*patternWidth+x];<br />}</p><p>function setPatternData(x,y,v){<br />patternData[y*patternWidth+x]=v;<br />}</p><p>function initPatternData(){<br />function fillRect(x,y,w,h,v){<br />var maxX = x+w;<br />var maxY = y+h;<br />for(i=x;i<maxX;i++){<br />for(j=y;j<maxY;j++){<br />setPatternData(i,j,v);<br />}<br />}<br />}<br />function fillGradientRect(x,y,w,h,f,t){<br />var maxX = x+w;<br />var maxY = y+h;<br />for(i=x;i<maxX;i++){<br />var v = f+Math.round((t-f)*(i-x)/w);<br />for(j=y;j<maxY;j++){<br />setPatternData(i,j,v);<br />}<br />}<br />}<br />function fillGrid(c,r,v){<br />var x = c*128;<br />var y = r*128;<br />fillRect(x,y,128,128,v);<br />}<br />function fillGradientGrid(c,r,f,t){<br />var x = c*128;<br />var y = r*128;<br />fillGradientRect(x,y,128,128,f,t);<br />}<br />function drawLines(x,y,w,h,d){<br />var v;<br />var maxX = x+w;<br />var maxY = y+h;<br />if(d==1){<br />for(i=x;i<maxX;i++){<br />v = ((i%2)==0)? 0 : patternMaxGray;<br />for(j=y;j<maxY;j++){<br />setPatternData(i,j,v);<br />}<br />}<br />}<br />else{<br />for(i=y;i<maxY;i++){<br />v = ((i%2)==0)? 0 : patternMaxGray;<br />for(j=x;j<maxX;j++){<br />setPatternData(j,i,v);<br />}<br />}<br />}<br />}<br />function fillLineGrid(c,r){<br />var x = c*128;<br />var y = r*128;<br />drawLines(x,y,64,128,1);<br />drawLines(x+64,y,64,128,0);<br />}<br />fillGrid(0,0,0);<br />fillRect(32,32,64,64,patternMaxGray*0.05);<br />fillGrid(1,0,patternMaxGray*0.1);<br />fillGrid(2,0,patternMaxGray*0.2);<br />fillGrid(3,0,patternMaxGray*0.3);<br />fillGrid(0,1,patternMaxGray*0.4);<br />fillGrid(1,1,patternMaxGray*0.5);<br />fillGrid(2,1,patternMaxGray*0.6);<br />fillGrid(3,1,patternMaxGray*0.7);<br />fillGrid(0,2,patternMaxGray*0.8);<br />fillGrid(1,2,patternMaxGray*0.9);<br />fillGrid(2,2,patternMaxGray);<br />fillRect(256+32,256+32,64,64,patternMaxGray*0.95);<br />fillLineGrid(3,2);<br /> fillGradientGrid(0,3,0,patternMaxGray/4);<br />fillGradientGrid(1,3,patternMaxGray/4,patternMaxGray*2/4);<br />fillGradientGrid(2,3,patternMaxGray*2/4,patternMaxGray*3/4);<br />fillGradientGrid(3,3,patternMaxGray*3/4,patternMaxGray);<br />}<br /></script><br /><script><br />var icanvas = document.getElementById('imageviewer');<br />var icanvasWidth = parseInt(icanvas.getAttribute("width"));<br />var icanvasHeight = parseInt(icanvas.getAttribute("height"));<br />var icontext = icanvas.getContext('2d');<br />var isWindowing = 0;<br />var beginPointX = 0;<br />var beginPointY = 0;<br />var wwStep = 5;<br />var wcStep = 5;</p><p>function loadImage(){<br />icanvas.onmousedown = function(e){<br />isWindowing = 1;<br />if(e.offsetX) {<br />beginPointX = e.offsetX;<br />beginPointY = e.offsetY;<br /> }<br /> else if(e.layerX) {<br /> beginPointX = e.layerX;<br /> beginPointY = e.layerY;<br /> }<br />}<br />icanvas.onmouseup = function(e){<br />isWindowing = 0;<br />if(e.offsetX) {<br />x = e.offsetX;<br />y = e.offsetY;<br /> }<br /> else if(e.layerX) {<br /> x = e.layerX;<br /> y = e.layerY;<br /> }<br /> var ww = parseInt(rangeWW.value);<br />var wc = parseInt(rangeWC.value);<br />ww = ww+(x-beginPointX)*wwStep;<br />wc = wc+(y-beginPointY)*wcStep;<br />updateWindow(ww,wc);<br /> }<br /> function updateWindow(ww,wc){<br />loadPattern(ww,wc);<br />rangeWW.value = ww;<br />rangeWC.value = wc;<br />spanWW.innerHTML = ww;<br />spanWC.innerHTML = wc;<br /> }<br />initPatternData();<br />updateWindow(4096,2048);<br />}</p><p>function loadPattern(ww,wc){<br />function setPoint(img,x,y,pv){<br />var i = (y*img.width+x)*4;<br />img.data[i]=img.data[i+1]=img.data[i+2]=pv;<br />img.data[i+3]=255;<br />}<br />function getPresentationValue(v,w,c){<br />var minv = c-Math.round(w/2);<br />if(v<minv) return 0;<br />var maxv = c+Math.round(w/2);<br />if(v>maxv) return 255;<br />var pv = Math.round(255*(v-minv)/w);<br />if(pv<0) return 0;<br />if(pv>255) return 255;<br />return pv;<br />}<br />var imgData = icontext.getImageData(0,0,icanvasWidth,icanvasHeight);<br />for(y=0;y<patternHeight;y++){<br />if(y>=imgData.height) break;<br />for(x=0;x<patternWidth;x++){<br />if(x>=imgData.width) break;<br />var v = getPatternData(x,y);<br />var pv = getPresentationValue(v,ww,wc);<br />setPoint(imgData,x,y,pv);<br />}<br />}<br />icontext.putImageData(imgData,0,0);<br />}</p><p>function setWindow(){<br />var ww = parseInt(rangeWW.value);<br />var wc = parseInt(rangeWC.value);<br />loadPattern(ww,wc);<br />spanWW.innerHTML = ww;<br />spanWC.innerHTML = wc;<br />}</p><p>window.addEventListener("load", loadImage(), true);<br /></script><br /><html>

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.