예제 #1
0
    /** ======================================================
     *
     * Reads a Dicom RT Structure file and produces a 3D mesh
     * from the data within. Structure files contain several
     * different structures and each of these is turned into
     * a separate mesh.
     *
     * ======================================================= **/
    void LoadStructureSet(FileInfo file, string fname)
    {
        Stopwatch     sw          = new Stopwatch();
        Stopwatch     sw2         = new Stopwatch();
        Stopwatch     swCPU       = new Stopwatch();
        Stopwatch     swMarch     = new Stopwatch();
        FileStream    fs          = new FileStream(file.FullName, FileMode.Open, FileAccess.Read);
        List <string> objectNames = new List <string>();
        int           block       = 0;

        List <Color>         colours = new List <Color>();
        List <List <float> > ranges  = new List <List <float> >();

        List <MeshMaker.ModelData> modelData = new List <MeshMaker.ModelData>();

        List <List <List <Vector3> > > zData = new List <List <List <Vector3> > >();
        List <List <Vector3> >         sData = new List <List <Vector3> >();
        float   zCounter    = 0;
        float   zLast       = float.MinValue;
        bool    zNew        = true;
        bool    singlePoint = false;
        Vector3 point       = Vector3.zero;

        float xMin = float.MaxValue; float xMax = float.MinValue;
        float yMin = float.MaxValue; float yMax = float.MinValue;
        float zMin = float.MaxValue; float zMax = float.MinValue;
        int   models = 0;

        HashSet <string>         sequenceBlocks = FileReader.GetSequenceBlocks();
        Dictionary <int, string> nameList       = new Dictionary <int, string>();
        List <int> structNumbers = new List <int>();
        int        currentNumber = 0;

        bool  zDistFound = false;
        float zMod       = 1;
        float zH1        = 0;
        float zH2        = 0;
        float zState     = 0;


        sw.Start();
        sw2.Start();

        //General process of reading the DICOM files:
        //0. Some DICOM files seem to contain unknown header information in a non tag format
        //   To skip past this data, we search for a signpost to normality. In other words
        //   one of the following tags: "0008,0005" "0008,0016" "0008,0018"
        //   If none of these can be found we bail after some number of characters, as
        //   we have no hope of accomplishing anything in the unknown location we are in.

        //1. Skip all non relevant tags
        //   These are of the type - Tag 4 bytes, Length 4 bytes, Data Length bytes
        //
        //2. Some tags denote sequences or encapsulated data
        //   There are two main types - Tag 4 bytes, Length 4 bytes, <Data>
        //                            - Tag 4 bytes, Length 4 bytes, <Data>, Tag 4 bytes, Length 4 bytes
        //   These tags have to be checked carefully as <Data> represents a series of type 1. tags
        //   Method used here is to skip reading the data portion when a sequence tag is found
        //   Checked against FileReader.S_1, S_2, etc.
        //
        //3. The data we actually we want is found using the colour tag: "3006,002A"
        //   A colour tag is followed by a data tag: "3006,0050" and the pixel data it contains
        //
        //4. Once the first colour tag is found, each subsequent colour tag becomes a new structure object.
        //
        //5. Lastly, the file contains a list of names that match the structure objects previously found.
        //   The tag for these is "3006,0085". The list is in the same order as the structures.
        int  bailLimit = 500;
        bool started   = false;

        while (fs.Position < fs.Length && fs.Position >= 0)
        {
            string tag = FileReader.GetNextTag(fs);
            int    length;

            if (!started)
            {
                if (tag == FileReader.S_SP_1 || tag == FileReader.S_SP_2 || tag == FileReader.S_SP_3)
                {
                    started      = true;
                    length       = FileReader.GetNextLength(fs);
                    fs.Position += length;
                    continue;
                }
                else
                {
                    if (fs.Position >= bailLimit)
                    {
                        //Failed to load this file
                        Logger.Log("Failed to load model from file: " + file.FullName);
                        break;
                    }
                    if (tag != "0000,0000")
                    {
                        // string tag0 = FileReader.GetNextTag(fs);
                        // fs.Position -= 4;
                        // print("Tag: " + tag + " " + tag0);
                    }
                    fs.Position -= 3;
                    bailLimit   += 1;
                    continue;
                }
            }

            if (tag == FileReader.S_ID_0)
            {
                fs.Position  += 4;
                length        = 2;
                currentNumber = FileReader.GetDataIntString(fs, length);
                continue;
            }

            if (tag == FileReader.S_NAME_0)
            {
                length = FileReader.GetNextLength(fs);
                string s = FileReader.GetData(fs, length);
                nameList.Add(currentNumber, s);
                // print("Added Number/Name: " + currentNumber + ", " + s);
                continue;
            }

            if (tag == FileReader.S_DATA_ID)
            {
                fs.Position += 4;
                length       = 2;
                int n = FileReader.GetDataIntString(fs, 2);
                if (structNumbers.Count <= models)
                {
                    structNumbers.Add(n);
                    // print("Added Struct Number: " + structNumbers[structNumbers.Count-1]);
                }
                continue;
            }

            if (sequenceBlocks.Contains(tag))
            {
                fs.Position += 4;
                continue;
            }

            if (tag == "FFFF,FFFF")
            {
                //Blank tag
                continue;
            }

            //A new structure entry denoted by a colour tag (2nd object onwards)
            //Mesh for previous structure is created here, and related vars reset.
            if (block == 12 && tag == FileReader.S_COL)
            {
                block = 0;
                List <float> values = new List <float>();
                values.Add(xMin); values.Add(xMax);
                values.Add(yMin); values.Add(yMax);
                values.Add(zMin); values.Add(zMax);
                ranges.Add(values);
                xMin = float.MaxValue; xMax = float.MinValue;
                yMin = float.MaxValue; yMax = float.MinValue;
                zMin = float.MaxValue; zMax = float.MinValue;


                //Create mesh for current model and reset vars for the next
                if ((meshRangeOverride != -1 && models == meshRangeOverride) ||
                    (meshRangeOverride == -1 && models >= meshRangeMin && models <= meshRangeMax))
                {
                    if (drawMesh)
                    {
                        if (!singlePoint)
                        {
                            modelData.Add(MeshMaker.CreateModelData(zData, ranges[ranges.Count - 1], meshMarcher, models,
                                                                    colours[colours.Count - 1], swMarch, ""));
                        }
                        else
                        {
                            modelData.Add(MeshMaker.CreateModelData(point, ""));
                        }
                    }
                }
                zData       = new List <List <List <Vector3> > >();
                sData       = new List <List <Vector3> >();
                zNew        = true;
                zCounter    = 0;
                singlePoint = false;

                models++;
            }

            //Read colour of structure
            if ((block == 0 || block == 11) && tag == FileReader.S_COL)
            {
                length = FileReader.GetNextLength(fs);
                byte[] bytes = new byte[length];
                fs.Read(bytes, 0, length);
                string s = "";
                foreach (byte b in bytes)
                {
                    s += (char)b;
                }
                // print("Colour found: " + s + " | " + (fs.Position-(8+length)));
                string[] ss = s.Split('\\');
                Color    c  = new Color(float.Parse(ss[0]) / 255, float.Parse(ss[1]) / 255, float.Parse(ss[2]) / 255);
                if (block == 11)
                {
                    // colours[colours.Count-1] = c;
                }
                else
                {
                    block = 11;
                    colours.Add(c);
                    // print("Colour added: " + s);
                }
                continue;
            }


            //Read structure contour data
            if ((block == 11 || block == 12) && tag == FileReader.S_DATA)
            {
                if (block == 11)
                {
                    block = 12;
                }
                length = FileReader.GetNextLength(fs);
                // print("Data found: " + length + " | " + (fs.Position-(8)));
                byte[] bytes = new byte[length];

                //Read and add points to list
                fs.Read(bytes, 0, length);
                List <Vector3> points   = new List <Vector3>();
                float[]        p        = new float[3];
                int            pCounter = 0;
                string         current  = "";
                foreach (byte b in bytes)
                {
                    char c = (char)b;
                    if (c == '\\')
                    {
                        p[pCounter] = float.Parse(current);
                        pCounter++;
                        current = "";
                        if (pCounter == 3)
                        {
                            pCounter = 0;
                            points.Add(new Vector3(p[0], p[1], p[2]));
                            p = new float[3];
                        }
                    }
                    else
                    {
                        current += c;
                    }
                }
                //Add last point
                p[pCounter] = float.Parse(current);
                points.Add(new Vector3(p[0], p[1], p[2]));

                float vz = points[0].z * zMod;

                if (vz < zMin)
                {
                    zMin = vz;
                }
                if (vz > zMax)
                {
                    zMax = vz;
                }

                //Points are added to lists based on slice and structure data
                //There can be multiple structures per slice (i.e. neck and arms)
                if (!zNew && vz != zLast)
                {
                    zData.Add(sData);
                    sData = new List <List <Vector3> >();
                    sData.Add(points);

                    zCounter++;
                    zLast = vz;
                    if (zState == 0)
                    {
                        zH1    = vz;
                        zState = 1;
                    }
                    else if (zState == 1)
                    {
                        zH2    = vz;
                        zState = 2;
                    }
                }

                if (points.Count == 1)
                {
                    // print(models + " Single Point: " + points[0]);
                    singlePoint = true;
                    point       = points[0];
                }

                if (zNew)
                {
                    zNew = false;
                }

                for (int i = 0; i < points.Count; i++)
                {
                    Vector3 v = points[i];
                    if (v.x < xMin)
                    {
                        xMin = v.x;
                    }
                    if (v.x > xMax)
                    {
                        xMax = v.x;
                    }
                    if (v.y < yMin)
                    {
                        yMin = v.y;
                    }
                    if (v.y > yMax)
                    {
                        yMax = v.y;
                    }
                }

                continue;
            }


            //Normal Tag
            length       = FileReader.GetNextLength(fs);
            fs.Position += length;
        }



        for (int i = 0; i < objectNames.Count; i++)
        {
            print(objectNames[i]);
        }


        //Could redesign this to remove duplicate code, duplicate is under "if (block == 31 && tag == S_COL)"
        List <float> values2 = new List <float>();

        values2.Add(xMin); values2.Add(xMax);
        values2.Add(yMin); values2.Add(yMax);
        values2.Add(zMin); values2.Add(zMax);
        ranges.Add(values2);

        models++;
        print(file.Name + " read: " + colours.Count + " colours, " + models + " models");
        //Create mesh for final model
        if ((meshRangeOverride != -1 && models == meshRangeOverride) ||
            (meshRangeOverride == -1 && models >= meshRangeMin && models <= meshRangeMax))
        {
            if (drawMesh)
            {
                if (!singlePoint)
                {
                    modelData.Add(MeshMaker.CreateModelData(zData, ranges[ranges.Count - 1], meshMarcher, models,
                                                            colours[colours.Count - 1], swMarch, ""));
                }
                else
                {
                    modelData.Add(MeshMaker.CreateModelData(point, ""));
                }
            }
        }


        bool isoFound = false;
        int  isoIdx   = 0;

        for (int i = 0; i < modelData.Count; i++)
        {
            string s = "Unknown " + i;
            if (nameList.ContainsKey(structNumbers[i]))
            {
                s = nameList[structNumbers[i]];
            }
            modelData[i].name = s;
            string nameLower = s.ToLower();
            if (modelData[i].point && !isoFound)
            {
                if (nameLower.Contains("isocenter") || nameLower.Contains("izocenter") ||
                    nameLower.Contains("iso center") || nameLower.Contains("izo center") ||
                    nameLower.Contains("isocentre") || nameLower.Contains("izocentre") ||
                    nameLower.Contains("iso centre") || nameLower.Contains("izo centre"))
                {
                    isoIdx   = i;
                    isoFound = true;
                }
            }
        }

        //Apply z scaling
        int   zScale  = (int)Mathf.Round(Mathf.Abs(zH2 - zH1));
        float zScaleF = 1 / (float)zScale;

        MeshMaker.zScale = zScale;
        print("zScale: " + zScale + ", " + zScaleF + " zH1: " + zH1);


        //An alternative to using the expensive collider method of finding mesh surface points for markers
        //This method is much faster but needs more work to function correctly.
        //Currently the found points do not seem to match the raycast/collider version at all, nor do they seem
        //be within the mesh
        //Comparing to the rotated and scaled mesh might be an issue
        //#TODO
        //Issues:
        //The position of the cubes that are in line with the isocenter in the original data do not match
        //up with the points found by raycasting the final mesh.

        /* if (isoFound) {
         * Vector3 isoPoint = modelData[isoIdx].pointPosition;
         * print("IsoPoint: " + modelData[isoIdx].pointPosition + ", md0.z: " + modelData[0].data[40].Count);
         * int zCount = modelData[0].data.Count;
         * int isoZ = (int)(isoPoint.z);
         * float pxMin = isoPoint.x;
         * float pyMin = isoPoint.y;
         * bool zFound = false;
         * int zIdx = 0;
         * for (int i = 0; i < modelData[0].data.Count && !zFound; i++) {
         *  int sCount = modelData[0].data[i].Count;
         *  if (sCount > 0) {
         *    for (int j = 0; j < sCount && !zFound; j++) {
         *      if (modelData[0].data[i][j].Count > 0) {
         *        for (int k = 0; k < modelData[0].data[i][j].Count; k++) {
         *          if (Mathf.Round(modelData[0].data[i][j][k].z) == isoPoint.z) {
         *            zIdx = i;
         *            zFound = true;
         *            break;
         *          }
         *        }
         *      }
         *    }
         *  }
         * }
         *
         * GameObject ttp = new GameObject("Cubes");
         *
         * int zIdx2 = zIdx;
         * for (zIdx = 0; zIdx < modelData[0].data.Count; zIdx++) {
         *
         *  int zv = -9000;
         *  pxMin = isoPoint.x;
         *  pyMin = isoPoint.y;
         *  float pxMax = isoPoint.x;
         *  float pyMax = isoPoint.y;
         *  HashSet<Vector3> zPoints = new HashSet<Vector3>();
         *  for (int s = 0; s < modelData[0].data[zIdx].Count; s++) {
         *      for (int p = 0; p < modelData[0].data[zIdx][s].Count; p++) {
         *      Vector3 v1 = modelData[0].data[zIdx][s][p];
         *      if (zv == -9000) { zv = (int)Mathf.Round(v1.z); }
         *      Vector3 v2;
         *      if (p < modelData[0].data[zIdx][s].Count - 1) {
         *        v2 = modelData[0].data[zIdx][s][p+1];
         *      } else {
         *        v2 = modelData[0].data[zIdx][s][0];
         *      }
         *
         *      //Bresenham's Algorithm
         *      List<Vector3> vox = MeshMaker.VoxelLine(v1,v2,zv);
         *      foreach (Vector3 v in vox) {
         *        zPoints.Add(v);
         *      }
         *    }
         *  }
         *
         *  foreach (Vector3 pv in zPoints) {
         *
         *    //Decrease y from -40 to find bCube point
         *    //Decrease x from -100 to find cCube point
         *    //Iso: -100, -40, 40
         *    //b: -100, -70, 40
         *    //c: -138, -40, 40
         *
         *    if (zIdx == zIdx2) {
         *      GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
         *      cube.transform.position = pv;
         *      cube.name = pv.ToString();
         *      cube.transform.parent = ttp.transform;
         *    }
         *
         *    // if (modelData[0].data[i][j][k] == isoPoint) { bv = true; }
         *    // float px = Mathf.Round(pv.x);
         *    // float py = Mathf.Round(pv.y);
         *    // if (Mathf.Abs(px - isoPoint.x) < 2) {
         *    // if (px == isoPoint.x) {
         *      // print("x - ijk: " + i + ", " + j + ", " + k + " | " + pv);
         *    // if (zIdx == zIdx2) {
         *    if (pv.x <= isoPoint.x && pv.y == isoPoint.y) {
         *      pxMin = Mathf.Min(pxMin,pv.x);
         *    }
         *    if (pv.x >= isoPoint.x && pv.y == isoPoint.y) {
         *      pxMax = Mathf.Max(pxMax,pv.x);
         *    }
         *      // }
         *      // if (Mathf.Abs(py - isoPoint.y) < 2) {
         *      // if (py < isoPoint.y + 2 || py > isoPoint.y - 2) {
         *      // if (py == isoPoint.y || py == isoPoint.y + 1 ||) {
         *        // print("y - ijk: " + i + ", " + j + ", " + k + " | " + pv);
         *    if (pv.y <= isoPoint.y && pv.x == isoPoint.x) {
         *      pyMin = Mathf.Min(pyMin,pv.y);
         *    }
         *    if (pv.y >= isoPoint.y && pv.x == isoPoint.x) {
         *      pyMax = Mathf.Max(pyMax,pv.y);
         *    }
         *    // }
         *
         *  }
         *
         *
         *  // if (zIdx == zIdx2) {
         *    print("zIdx " + zIdx + ", z: " + zv + ", pxMin: " + pxMin + ", pyMin: " + pyMin + ", pxMax: " + pxMax + ", pyMax: " + pyMax + ", width: " + (pxMax - pxMin) + ", height: " + (pyMax - pyMin));
         *    // UnityEngine.Debug.DrawLine(new Vector3(pxMin, isoPoint.y, isoPoint.z), isoPoint, Color.cyan, 20);
         *    // UnityEngine.Debug.DrawLine(new Vector3(isoPoint.x, pyMin, isoPoint.z), isoPoint, Color.magenta, 20);
         *  // }
         * }
         * // UnityEngine.Debug.LogError("Iso Test");
         * // print("ModelData z: " + modelData[0].data.Count);
         * // for (int i = 0; i < modelData[0].data.Count; i++) {
         *  // print("z: " + modelData[0].data[i][0][0].z);
         * // }
         * // return;
         * } */



        sw.Stop();
        sw2.Start();
        for (int i = 0; i < modelData.Count; i++)
        {
            if (modelData[i].dimensions != null)
            {
                modelData[i].dimensions[4] *= zScaleF;
                modelData[i].dimensions[5] *= zScaleF;
            }
            if (i == 0)
            {
                modelData[i]         = MeshMaker.MakeMesh(modelData[i], pixels, slicePos);
                modelData[i].topName = fname;
                modelData[i].zMin    = zH1 * zScale;
            }
            else
            {
                modelData[i] = MeshMaker.MakeMesh(modelData[i], null, Vector3.zero);
            }
        }
        sw2.Stop();
        // FileReader.printStopwatch(sw,"Model Loader - Non Marching: ");
        // FileReader.printStopwatch(sw2,"Model Loader - Marching: ");

        //Close file
        fs.Close();

        sw.Reset();
        sw.Start();
        //Try to notify main thread
        while (true)
        {
            if (FileReader.modelData != null)
            {
                Thread.Sleep(100);
                continue;
            }
            FileReader.modelData = modelData;
            break;
        }
        sw.Stop();
        // FileReader.printStopwatch(sw,"Model Loader - Notify Wait: ");
    }