2013-01-22 48 views
2

我試圖從體積CT數據製作實時模擬超聲圖像。訣竅是用戶控制探測器的位置,探測器定義他們所看到的平面。製作3D陣列的2D橫截面切片

我到目前爲止所做的是將所有dicom圖像的像素數據讀入一個單一的3D像素數組,現在我需要做的是以不同的角度重新制作3D數組。對不起,如果下面的描述出現了一點草率,但想象一個3D矩形框(例如寬100像素,深[x,z]和500長[y])和2D「觀看平面」(比如說50 x 50像素)。假設觀察平面的起始位置(原點定義爲平面近邊的中點 - [0,25])與原點[50,250,0](頂面的死點向下看) ,從左到右,並直接穿過矩形。因此,觀察平面具有三個可以改變的參數 - 原點的位置,圍繞垂直線的旋轉(從原點到平面相對邊緣上的對應點的線)以及「傾斜」(平面圍繞與盒相交的線旋轉)。因此,用戶可以改變這三個參數,輸出是由觀看平面「觸摸」的像素構建的圖像。

再一次,我很抱歉,如果描述是草率的,但我是一個沒有強大的數學背景的醫學生。任何幫助將不勝感激。

回答

1

我會寫爲訂單二維方程,解出x的每一個值, 和圓所得的變量y爲最接近的整型Edje09昨天

敷貼到2D情況下,用於那麼您建議的方法有兩個主要問題

  1. 如果行比梯度1陡峭,則可能會丟失一些像素。
  2. 舍入可以選擇一個你想要選擇的像素。

This pdf顯示了可以在3D案例中構建的2D案例的問題和可能的解決方案。

編輯經過進一步的思考,我可能已經產生了一個written pdf outline solution for the 3D case,可以變成一個算法,因此成爲代碼。就我所知,我沒有做過檢查,也不能保證它的正確性,但希望能夠讓你更進一步。

編輯添加代碼 以下JavaScript代碼似乎根據您的要求做。這很慢,所以你需要等到點擊SET後再等待。此外,「窗格」不會在視圖之間清除,因此在「窗格」重新填充之前您無法判斷髮生了什麼。 我只測試了2個圖像來表示z方向上的100個像素。函數getPixels中的第一條代碼行處理此限制,刪除z方向上的一組完整圖像。我進行的測試是相當膚淺的,但似乎通過了。使用全套圖像更好。

我已經將3D陣列想象成一系列D圖像圖像(0),在運行z方向的後部到前方的圖像(D-1)。每個圖像在x方向上具有寬度W並且在y方向上具有高度H.感謝我喜歡的挑戰。

鏈接到所用圖像的壓縮文件夾位於代碼結尾。

<!DOCTYPE HTML> 
<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
<!-- 
Copyright (c) 2013 John King 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
--> 
<title>3D Slicer</title> 
<style type="text/css"> 
    div, canvas, img { 
     position: absolute; 
    } 

    img { 
     top:0px; 
     left:0px; 
     visibility:hidden; 
    } 
    input { 
     text-align: right; 
    } 
    .seen { 
     visibility: visible; 
    } 
    #canvas3D { 
     left:10px; 
     top:10px; 
     visibility:hidden; 
    } 
    #canvas2D { 
     left:10px; 
     top:50px; 
     border:1px solid black; 
    } 
    #frame { 
     left:650px; 
     top:10px; 
     border:1px solid black; 
     background-color: #DDDDDD; 
     width:600px; 
     height:600px; 
    } 
    #framehead { 
     left:0px; 
     top:0px; 
     height:25px; 
     width:100%; 
     border-bottom: 1px solid black; 
     background-color: #999999; 
    } 
    #userdata { 
     top:10px; 
     left:10px; 
    } 
    #originins { 
     top:10px; 
     left:10px; 
     width:260px; 
    } 
    #origintext { 
     top:200px; 
     left:10px; 
     width:260px; 
    } 
    #origininput { 
     top:225px; 
     left:10px; 
     width:260px;   
    } 
    #originlimits { 
     top:250px; 
     left:10px; 
     width:260px; 
    } 
    #thetaimg { 
     top:10px; 
     left:225px; 
    } 
    #thetatext { 
     top:200px; 
     left:225px; 
     width:260px; 
    } 
    #thetainput { 
     top:225px; 
     left:225px; 
     width:260px; 
    } 
    #thetalimits { 
     top:250px; 
     left:225px; 
     width:260px; 
    } 
    #psiimg { 
     top:10px; 
     left:440px; 
    } 
    #psitext { 
     top:200px; 
     left:440px; 
     width:260px; 
    } 
    #psiinput { 
     top:220px; 
     left:440px; 
     width:260px; 
    } 
    #psilimits { 
     top:250px; 
     left:440px; 
     width:260px; 
    } 
    #setButton { 
     top:310px; 
     left:10px; 
     width:260px; 
    } 
    #axes { 
     top:350px; 
     left:10px; 
    } 
</style> 
<script type="text/javascript"> 

    //add a trim function to string if not present - strips white space from start and end of string 
    if(typeof String.prototype.trim !== 'function') { 
     String.prototype.trim = function() { 
      return this.replace(/^\s+|\s+$/g, ''); 
     } 
    } 

    // abbreviation function for getElementById 
    function $(id) { 
     return document.getElementById(id); 
    } 

    //parameters for 3D array of pixels set in code 
    var W=100; //width of array in x direction, must be even 
    var D=100; //depth of array in z direction, must be even 
    var H=500; //height of array in y direction 

    //parameters for the rectangular plane PQRS that will select the pixels for a 2D array by slicing through the 3D array 
    //PQRS moves in such a way that PQ remains parallel to xz plane and PS remains parallel to yz plane 
    //these parameters set in code 
    var L=50; //length of rectangle PQ 
    var B=50; //breadth of rectangle PS 

    //Initialisation of parameters that can be changed by the user. 
    var O=new Point(W/2,0,D/2); //O is middle of PQ 
    var theta=0; //angle PQ is rotated after plane is rotated about a vertical axis through O, must be between -PI/2 and PI/2 
    var psi=0; //angle PS is rotated after plane is rotated about PQ as an axis, must be between -PI/2 and PI/2 

    //variable for canvases 
    var c3D, c2D; 


    /*getPixel gets an individual pixel from the 3D array of pixels formed by a stack of D (for depth) 2D images 
    * numbered from 0 to D-1, with 0 being the image at the back. 
    * Each image having width W and height H pixels. 
    * 0<= x <W, 0<= y <H, 0<= z <D 
    * each image is on the canvas canvas3D 
    * 
    * for this test img0.jpg will be used for img0.jpg to img49.jpg and img50.jpg will be used for img50 to img99 
    */ 
    function getPixel(x,y,z) { 
     // line below only required because just two images img0.jpg and img50.jpg are used for testing 
     z=Math.floor(z/50)*50;   
     //Remove above line if full series of images used in z direction 
     this.ctx.drawImage($("i"+z),0,0); 
     var imdata=this.ctx.getImageData(0,0,this.width,this.height); 
     var col=4*(y*this.width+x); 
     var pix=new Pixel(); 
     pix.red=imdata.data[col++]; 
     pix.green=imdata.data[col++]; 
     pix.blue=imdata.data[col++]; 
     pix.alpha=imdata.data[col]; 
     return pix; 
    } 

    //Pixel Object 
    function Pixel() { 
     this.red; 
     this.green; 
     this.blue; 
     this.alpha; 
    } 

    //Point Object 
    function Point(x,y,z) { 
     this.x=x; 
     this.y=y; 
     this.z=z; 
    } 

    function Point2D(a,d) { 
     this.a=a; 
     this.d=d; 
    } 

    function setValues() { 
     c2D.ctx.clearRect(0,0,c2D.width,c2D.height); 
     var Oobj=Ochecked($("Oin").value); 
     if(!Oobj.OK) { 
      $("Oin").style.backgroundColor="#F1B7B7"; 
      return 
     } 
     $("Oin").style.backgroundColor="#FFFFFF"; 
     O=Oobj.point; 
     var th=parseInt($("thetain").value.trim()); 
     if(isNaN(th)) { 
      $("thetain").style.backgroundColor="#F1B7B7"; 
      return 
     } 
     if(th<=-90 || th>90) { 
      $("thetain").style.backgroundColor="#F1B7B7"; 
      return 
     } 
     $("thetain").style.backgroundColor="#FFFFFF"; 
     theta=th*Math.PI/180; 
     var si=parseInt($("psiin").value.trim()); 
     if(isNaN(si)) { 
      $("psiin").style.backgroundColor="#F1B7B7"; 
      return 
     } 
     if(si<=-90 || si>90) { 
      $("psiin").style.backgroundColor="#F1B7B7"; 
      return 
     } 
     $("psiin").style.backgroundColor="#FFFFFF"; 
     psi=si*Math.PI/180; 
     printPane(); 
    } 

    function Ochecked(Ovalue) { 
     Ovalue=Ovalue.trim(); 
     var V=Ovalue.split(","); 
     if(V.length!=3) {return {OK:false}}; 
     var x=parseInt(V[0].trim()); 
     var y=parseInt(V[1].trim()); 
     var z=parseInt(V[2].trim()); 
     if(isNaN(x) || isNaN(y) || isNaN(z)) {return {OK:false}}; 
     if(x<0 || x>=W) {return {OK:false}}; 
     if(y<0 || y>=H) {return {OK:false}}; 
     if(z<0 || z>=D) {return {OK:false}}; 
     p=new Point(x,y,z); 
     return {OK:true,point:p}; 
    } 

    function printPane(){ 
     var p = new Point(O.x-Math.round((L/2)*Math.cos(theta)),O.y,O.z - Math.round((L/2)*Math.sin(theta))); 
     var q = new Point(O.x+Math.round((L/2)*Math.cos(theta)),O.y,O.z + Math.round((L/2)*Math.sin(theta))); 
     var s = new Point(p.x,p.y+Math.round((B)*Math.cos(psi)),p.z + Math.round((B)*Math.sin(psi))); 
     var n = new Point2D(q.x-p.x,q.z-p.z);  
     var PQincVec=getIncVec(n.a,n.d); 
     n = new Point2D(s.y-p.y,s.z-p.z);  
     var PSincVec=getIncVec(n.a,n.d); 
     var pixel,col; 
     var PSpoint =new Point(p.x,p.y,p.z); // points along PS initialised to start at P 
     var PQpoint; //variable for points along line parallel to PQ 
     var imdata=c2D.ctx.getImageData(0,0,c2D.width,c2D.height);   
     for(var ps=0;ps<PSincVec.length;ps++) { 
      //increment along line PS 
      PSpoint.y+=PSincVec[ps].a; 
      PSpoint.z+=PSincVec[ps].d; 
      PQpoint =new Point(PSpoint.x,PSpoint.y,PSpoint.z); // points along line parallel to PQ initialised to current point on PS 
      for(var pq=0;pq<PQincVec.length;pq++) { 
       //increment along line PQ 
       PQpoint.x+=PQincVec[pq].a; 
       PQpoint.z+=PQincVec[pq].d; 
       //check that PQpoint is inside 3D array 
       if(0<=PQpoint.x && PQpoint.x<W && 0<=PQpoint.y && PQpoint.y<H && 0<=PQpoint.z && PQpoint.z<D) { 
        pixel=c3D.getPixel(PQpoint.x,PQpoint.y,PQpoint.z); 
        //write pixel from point along line parallel to PQ onto plane 
        col=4*(ps*c2D.width+pq); 
        imdata.data[col++]=pixel.red; 
        imdata.data[col++]=pixel.green; 
        imdata.data[col++]=pixel.blue; 
        imdata.data[col]=pixel.alpha; 
       }    
      } 
     } 
     c2D.ctx.putImageData(imdata,0,0); 
    } 

    function getIncVec(a,d) { 
     var r,t; 
     if(a>Math.abs(d)) { 
      var incVec=getIncs(a,Math.abs(d)); 
     } 
     else { 
      var incVec=getIncs(Math.abs(d),a); 
      for(var i=0;i<incVec.length;i++) { 
       r=incVec[i]; 
       t=r.a; 
       r.a=r.d; 
       r.d=t; 
      } 
     } 
     if(d<0) { 
      for(var i=0;i<incVec.length;i++) { 
       incVec[i].d*=-1; 
      } 
     } 
     return incVec; 
    } 

    function getIncs(a,d) { 
     var p=new Point2D(0,0); 
     var vec=[]; 
     vec.push(p); 
     for(var i=0;i<a;i++) { 
      p=new Point2D(1,Math.floor((i+1)*d/a) - Math.floor(i*d/a)); 
      vec.push(p); 
     } 
     return vec; 
    } 

    function main() { 
     //set limits and values for user input. 
     $("Oin").value=O.x+","+O.y+","+O.z; 
     $("thetain").value=theta; 
     $("psiin").value=psi; 
     $("originlimits").innerHTML="0&lt;= x &lt;"+W+"<br>0&lt;= y &lt;"+H+"<br>0&lt;= z &lt;"+D; 
     //set canvas3D so that pixels are readable 
     c3D=$("canvas3D"); 
     c3D.width=W; 
     c3D.height=H; 
     c3D.ctx=c3D.getContext('2d'); 
     c3D.getPixel=getPixel; 

     //set canvas2D so that pixels are settable 
     c2D=$("canvas2D"); 
     c2D.width=L; 
     c2D.height=B; 
     c2D.ctx=c2D.getContext('2d'); 
     c2D.initialise=initialise; 

     $("hide").style.width=L+"px"; 
     $("hide").style.height=B+"px"; 
    } 
</script> 
</head> 
<body onload="main()"> 
    <!-- list of images for 3D array --> 
    <img id="i0" src="images/img0.jpg"> 
    <img id="i50" src="images/img50.jpg"> 
    <!-- end of list of images for 3D array --> 

    <canvas id="canvas3D"></canvas> 
    <div id="frame"> 
     <div id="framehead">&nbsp;&nbsp;&nbsp;View of Slicing Pane</div> 
     <canvas id="canvas2D"></canvas> 
    </div> 
    <div id="userdata"> 
     <div id="originins">Enter in form x,y,z </br> eg 40,27,83</div> 
     <div id="origintext">Position for Origin O</div> 
     <div id="origininput"><input id="Oin"></div> 
     <div id="originlimits">limits</div> 
     <img class="seen" id="thetaimg" src="images/theta.png"> 
     <div id="thetatext">Theta in degrees</div> 
     <div id="thetainput"><input id="thetain"></div> 
     <div id="thetalimits">-90 &lt; theta &lt;=90</div> 
     <img class="seen" id="psiimg" src="images/psi.jpg"> 
     <div id="psitext">Psi in degrees</div> 
     <div id="psiinput"><input id="psiin"></div> 
     <div id="psilimits">-90 &lt; psi &lt;=90</div> 
     <div id="setButton"><input type="button" value="SET" onclick="setValues()"></div> 
     <img class="seen" id="axes" src="images/axes.jpg"> 
    </div> 
<div id="msg"></div>  
</body> 
</html> 

images used in code

+0

經過相當膚淺的掠過之後,這真是太棒了!非常感謝你抽出時間寫出所有這些(並說明!)。我會讓你知道結果如何! – Edje09

+0

計算解決方案中的P和Q值用於糾正3D案例,重新檢查pdf – jing3142

+0

計算解決3D中糾正的S案件,重新檢查pdf – jing3142

1

聽起來像一個有趣的問題,我開始思考它,但很快遇到了一些問題。它不像你先想的那麼簡單或直接!作爲開始,我將其簡化爲通過2D陣列拍攝一維切片的情況。很快就清楚了,對於某些切片來說,對於所有像素來說,哪些切片會成爲切片的一部分並不明顯。我已經制作了一份pdf來展示我的意思。這是pdf文檔Issues 2D的鏈接。在提出可能的解決方案之前,我或其他人需要更多的思考。對不起,我目前無法提供更多幫助。

+0

感謝這麼多的響應!那正是我期待的問題。我最初的(相當模糊的)計劃是使用3D平面方程將查看窗格「放置」在陣列的3D空間中,然後循環並查看查看平面上每個點對應的位置。對於你提出的問題,我正在考慮將哪一個像素最接近由方程確定的「確切」位置。類似於你的PDF,在2D中,我會爲線條寫出二維方程,求解x的每個值,並將生成的y變量四捨五入爲最接近的整數。 – Edje09

+0

現在想一想,我可能會這樣做,只是遍歷所有的x和y值,然後圍繞結果z,但正如我所提到的,我的數學背景不是很強,我還沒有想出如何將這些方程轉換爲代碼。思考? – Edje09