2

ボリュームCTデータからリアルタイムの模擬超音波画像を作成しようとしています。秘訣は、ユーザーがプローブの位置を制御することです。これにより、ユーザーが見ている平面が定義されます。

これまでに行ったことは、すべてのdicom画像からピクセルデータを単一の3D配列のピクセルに読み取ることです。次に、その3D配列をさまざまな角度で再スライスする必要があります。次の説明が少しずさんな場合は申し訳ありませんが、3Dの長方形のボックス(たとえば、幅と深さ[x、z]、長さ500 [y])と2Dの「表示面」(たとえば、50 x 50ピクセル)を想像してみてください。 )。表示平面の開始位置(平面の近端の中間点として定義される原点-[0,25])が[50,250,0](上面の死点、下を見下ろす)を原点としているとします。 、左から右に向けられ、長方形を真下に突き刺します。したがって、表示平面には、変更可能な3つのパラメータがあります。原点の位置と 垂直(原点から平面の反対側の端にある対応する点まで伸びる線)の周りの回転、および「傾斜」(ボックスと交差する線の周りの平面の回転)。したがって、ユーザーはこれら3つのパラメーターを変更でき、出力は、表示面が「触れた」ピクセルから作成された画像になります。

繰り返しになりますが、説明がずさんな場合はお詫び申し上げますが、私は数学の知識が豊富な医学生です。どんな助けでも大歓迎です。

4

2 に答える 2

1

面白い問題のように聞こえ、私はそれについて考え始めましたが、すぐにいくつかの問題に遭遇しました。それはあなたが最初に考えるかもしれないほど単純でも単純でもありません!最初に、2D配列を介して1Dスライスを取得する場合に簡略化しました。一部のスライスでは、どのピクセルがスライスの一部を形成するかがすべてのピクセルで明らかではないことがすぐに明らかになりました。私が何を意味するかを示すためにPDFを作成しました。これは、PDFドキュメントIssues2Dへのリンクです。私や他の人は、考えられる解決策を考え出す前に、もっと考える必要があります。申し訳ありませんが、現時点ではこれ以上お役に立てることはありません。

于 2013-01-23T13:05:34.710 に答える
1

直線の2D方程式を書き、xの値ごとに解き、結果のy変数を最も近い整数に丸めます–昨日Edje09

今のところ2Dの場合に固執することで、提案する方法には2つの主要な問題があります。

  1. 線が1のグラデーションよりも急な場合、一部のピクセルが失われる可能性があります。
  2. 丸めにより、選択したいピクセルより上のピクセルを選択できます。

このPDFは、2Dケースの問題と可能な解決策を示しており、3Dケースに基づいて構築できます。

編集さらに考えた後、私はアルゴリズムに、したがってコードに変換できる3DケースのPDFアウトラインソリューションを作成した可能性があります。これは、私がチェックを行っておらず、その正確性を保証することはできませんが、うまくいけば、あなたをさらに一歩進めることができる限りです。

追加された編集コード次のJavascriptコードは、必要なことを実行しているようです。かなり遅いので、SETをクリックしてから待つ必要があります。また、「ペイン」はビュー間でクリアされないため、「ペイン」が補充されるまで何が起こっているかを知ることはできません。私は、z方向に100ピクセルを表すために2つの画像を使用してテストしただけです。関数getPixelsの最初のコード行はこの制限を処理し、z方向の画像の完全なセットを削除します。私が行ったテストはかなり表面的なものですが、問題ないようです。画像のフルセットでより良い。

3D配列は、前面の画像(D-1)に向かってz方向に走る、背面の一連のD画像image(0)として想像しました。x方向に幅W、y方向に高さHの各画像。チャレンジしてくれてありがとう。

使用されている画像のzipフォルダーへのリンクは、コードの最後にあります。

<!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>

コードで使用される画像

于 2013-01-27T09:52:12.277 に答える