Annotate graph with additional properties
authorAjay Jayaraj <ajayj@ti.com>
Wed, 9 May 2018 15:20:48 +0000 (10:20 -0500)
committerAjay Jayaraj <ajayj@ti.com>
Wed, 9 May 2018 17:30:23 +0000 (12:30 -0500)
* Additional layer specific annotations to nodes
* Generate SVG file if /usr/bin/dot is available
* Refactoring and cleanup

(MCT-975)

utils/Makefile
utils/main.cpp
utils/src/dot_graph.cpp [new file with mode: 0644]
utils/src/dot_graph.h [new file with mode: 0644]
utils/src/tinn_utils.cpp

index 369e7dcf7512464495bb7de480d5f0f8ca303195..f36f670cfee4ea40bed33be175d4b47548f139d4 100644 (file)
@@ -26,7 +26,7 @@
 
 TARGET ?= arm
 
-EXE = $(TARGET)/network_display
+EXE = $(TARGET)/netviz
 
 TINN_API_DIR=../tinn_api
 
@@ -34,15 +34,18 @@ include $(TINN_API_DIR)/make.inc
 
 UNAME_M :=$(shell uname -m)
 
+# -m32 required on x86 to ensure consistent layout of struct sTIDL_Network_t
 ifeq ($(TARGET),x86)
        CXX = g++
-       CXXFLAGS = -m32
+       CXXFLAGS = -m32 -static
        LDFLAGS =
 endif
 
-CXXFLAGS += -o3 --std=c++11 -Wall -Werror
+CXXFLAGS += -o3
+#CXXFLAGS += -g -ggdb
+CXXFLAGS += --std=c++11 -Wall -Werror
 CXXFLAGS += -Iinc -I$(TINN_API_DIR)/inc -I$(TINN_API_DIR)/src
-SOURCES = main.cpp src/tinn_utils.cpp $(TINN_API_DIR)/src/util.cpp
+SOURCES = main.cpp src/tinn_utils.cpp src/dot_graph.cpp $(TINN_API_DIR)/src/util.cpp
 
 $(EXE): $(TINN_LIB) $(HEADERS) $(SOURCES)
        mkdir -p $(TARGET)
index 13372798c2df053dcfb5ca920ee514c4fae093e6..06f556da4b3ac60b483fb14540b824d4d946fc8d 100644 (file)
 #include <fstream>
 #include <cassert>
 #include <string>
+#include <cstdlib>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
 
 #include "tinn_utils.h"
 
+using namespace tinn;
 
 static void ProcessArgs(int argc, char *argv[], std::string& network_file,
                         bool& do_print, std::string& dot_file);
 
 static void DisplayHelp();
+static bool fs_exists(std::string path);
 
 int main(int argc, char *argv[])
 {
@@ -55,11 +63,23 @@ int main(int argc, char *argv[])
 
     bool status = true;
 
+    // Dump network to stdout if requested
     if (do_print)
-        status &= tinn::util::PrintNetwork(network_file);
+        status &= util::PrintNetwork(network_file);
+
+    // Generate a dot file
+    status &= util::GenerateDotGraphForNetwork(network_file, dot_file);
+
+    // If the dot utility exists, generate a SVG file
+    const std::string dot_bin("/usr/bin/dot");
+    if (fs_exists(dot_bin))
+    {
+        std::string command = dot_bin;
+        command += " -Tsvg " + dot_file + " -o " + dot_file + ".svg";
+        int x = std::system(command.c_str());
+        if (x != 0) status = false;
 
-    if (!dot_file.empty())
-        status &= tinn::util::GenerateDotGraphForNetwork(network_file, dot_file);
+    }
 
     if (!status)
         return EXIT_FAILURE;
@@ -71,7 +91,7 @@ int main(int argc, char *argv[])
 void ProcessArgs(int argc, char *argv[], std::string& network_file,
                  bool& do_print, std::string& dot_file)
 {
-    if (argc < 2)
+    if (argc < 4)
     {
         DisplayHelp();
         exit(EXIT_SUCCESS);
@@ -119,13 +139,28 @@ void ProcessArgs(int argc, char *argv[], std::string& network_file,
                       break;
         }
     }
+
+    if (dot_file.empty())
+    {
+        std::cerr << "ERROR: output dot file not specified." << std::endl;
+        DisplayHelp();
+        exit(EXIT_FAILURE);
+    }
 }
 
 void DisplayHelp()
 {
-    std::cout << "Usage: tinn_display [option] <Network binary file>\n"
+    std::cout << "Usage: netviz -d <dot file name> <network binary file>\n"
                  "Options:  \n"
                  " -p              Print network layer info\n"
-                 " -d <file name>  Generate network graph (dot file)\n"
                  " -h              Display this help message\n";
 }
+
+bool fs_exists(std::string path)
+{
+    struct stat statbuf;
+    if (stat(path.c_str(), &statbuf) == 0) return true;
+    else                                   return false;
+}
+
+
diff --git a/utils/src/dot_graph.cpp b/utils/src/dot_graph.cpp
new file mode 100644 (file)
index 0000000..25b6a97
--- /dev/null
@@ -0,0 +1,324 @@
+/******************************************************************************
+ * Copyright (c) 2017-18, Texas Instruments Incorporated - http://www.ti.com/
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above copyright
+ *        notice, this list of conditions and the following disclaimer in the
+ *        documentation and/or other materials provided with the distribution.
+ *      * Neither the name of Texas Instruments Incorporated nor the
+ *        names of its contributors may be used to endorse or promote products
+ *        derived from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ *  THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+#include "dot_graph.h"
+
+using Edge   =  boost::graph_traits<Graph>::edge_descriptor;
+
+template<typename T>
+using VertexPropertyMap =
+              typename boost::property_map<T, boost::vertex_attribute_t>::type;
+
+template<typename T>
+using EdgePropertyMap =
+              typename boost::property_map<T, boost::edge_attribute_t>::type;
+
+using LayerIdMap = std::map<uint32_t, Graph*>;
+
+static const char* FILLED = "filled";
+static const char* LABEL  = "label";
+static const char* COLOR  = "color";
+static const char* STYLE  = "style";
+static const char* SHAPE  = "shape";
+static const char* BOLD   = "bold";
+
+static const char* GetVertexColor(uint32_t layer_type);
+static std::string PoolingProperties(const sTIDL_PoolingParams_t& p);
+static std::string BiasProperties(const sTIDL_BiasParams_t& p);
+static std::string ReLUProperties(const sTIDL_ReLUParams_t& p);
+
+
+DotGraph::DotGraph(const sTIDL_Network_t& net):
+                    graph_m(net.numLayers), net_m(net)
+{
+    AddVertices();
+    AddEdges();
+    AddMetaData();
+}
+
+
+void DotGraph::AddVertices()
+{
+    // Add a vertex for each layer
+    LayerIdMap layerid_map;
+    for (int i = 0 ; i < net_m.numLayers; i++)
+    {
+        const sTIDL_Layer_t& layer = net_m.TIDLLayers[i];
+
+        // Special handling for input & output data layers:
+        // Do not put them in the same subgraph
+        if (layer.layerType == TIDL_DataLayer)
+        {
+            auto V = vertex(i, graph_m);
+            VertexPropertyMap<Graph> vpm = boost::get(vertex_attribute, graph_m);
+            vpm[V][LABEL] = TIDL_LayerString[layer.layerType];
+            vpm[V][COLOR] = GetVertexColor(layer.layerType);
+            vpm[V][STYLE] = FILLED;
+            vpm[V][SHAPE] = "ellipse";
+
+            continue;
+        }
+
+        // Use the layer group ID to create subgraphs
+        // Place all layers with the same ID into a single subgraph
+        uint32_t layerGroupId = layer.layersGroupId;
+        Graph* sub = nullptr;
+        if (layerid_map.find(layerGroupId) == layerid_map.end())
+        {
+            sub = &(graph_m.create_subgraph());
+            layerid_map[layerGroupId] = sub;
+            get_property(*sub, graph_name) = std::string("cluster") +
+                                             std::to_string(layerGroupId);
+            get_property(*sub, graph_graph_attribute)[LABEL] =
+                                    "Group " + std::to_string(layerGroupId);
+            get_property(*sub, graph_graph_attribute)[STYLE] = BOLD;
+        }
+        else
+            sub = layerid_map[layerGroupId];
+
+        auto V = add_vertex(i, *sub);
+
+        AddVertexProperties(V, sub, layer);
+    }
+
+}
+
+void DotGraph::AddVertexProperties(Vertex& V, Graph* g,
+                                   const sTIDL_Layer_t& layer)
+{
+    VertexPropertyMap<Graph> vpm = boost::get(vertex_attribute, *g);
+    vpm[V][COLOR] = GetVertexColor(layer.layerType);
+    vpm[V][STYLE] = BOLD;
+    vpm[V][LABEL] = "{";
+
+    switch (layer.layerType)
+    {
+        case TIDL_ConvolutionLayer:
+        {
+            vpm[V][LABEL] += TIDL_LayerString[layer.layerType];
+            const sTIDL_ConvParams_t& p = layer.layerParams.convParams;
+            vpm[V][LABEL] += "\\n" + std::to_string(p.kernelW) + "x" +
+                             std::to_string(p.kernelH);
+
+            if (p.enablePooling)
+                vpm[V][LABEL] += "|" + PoolingProperties(p.poolParams);
+
+            if (p.enableRelU)
+                vpm[V][LABEL] += "|" + ReLUProperties(p.reluParams);
+
+            if (p.enableBias)
+                vpm[V][LABEL] += "|" +
+                                 std::string(TIDL_LayerString[TIDL_BiasLayer]);
+
+            break;
+        }
+
+        case TIDL_PoolingLayer:
+        {
+            const sTIDL_PoolingParams_t& p = layer.layerParams.poolParams;
+            vpm[V][LABEL] += PoolingProperties(p);
+            break;
+        }
+
+        case TIDL_BiasLayer:
+        {
+            const sTIDL_BiasParams_t& p = layer.layerParams.biasParams;
+            vpm[V][LABEL] += BiasProperties(p);
+            break;
+        }
+
+        case TIDL_ReLULayer:
+        {
+            const sTIDL_ReLUParams_t& p = layer.layerParams.reluParams;
+            vpm[V][LABEL] += ReLUProperties(p);
+            break;
+        }
+
+        case TIDL_EltWiseLayer:
+        {
+
+            vpm[V][LABEL] += TIDL_LayerString[layer.layerType];
+            const sTIDL_EltWiseParams_t& p = layer.layerParams.eltWiseParams;
+            if (p.eltWiseType == TIDL_EltWiseProduct)
+                vpm[V][LABEL] += " Product";
+            else if (p.eltWiseType == TIDL_EltWiseSum)
+                vpm[V][LABEL] += " Sum";
+            else if (p.eltWiseType == TIDL_EltWiseMax)
+                vpm[V][LABEL] += " Max";
+
+            break;
+        }
+
+        default:
+            vpm[V][LABEL] += TIDL_LayerString[layer.layerType];
+            break;
+    }
+
+    vpm[V][LABEL] += "}";
+}
+
+
+void DotGraph::AddEdges()
+{
+    // Add edges based on dataId i.e. there is an edge from layer A -> B
+    // iff dataId is in outData for A and inData for B.
+    EdgePropertyMap<Graph> ep = boost::get(boost::edge_attribute, graph_m);
+    for (int i = 0 ; i < net_m.numLayers; i++)
+    {
+        const sTIDL_Layer_t& layer = net_m.TIDLLayers[i];
+
+        if (layer.numOutBufs < 0)
+            continue;
+
+        // Create a string to  annotate the edge - num_channels, height, width
+        const sTIDL_DataParams_t& outData = layer.outData[0];
+        int32_t out_id = outData.dataId;
+        std::string edge_info = std::to_string(outData.dimValues[1])+ "x" +
+                                std::to_string(outData.dimValues[2])+ "x" +
+                                std::to_string(outData.dimValues[3]);
+
+        for (int j = 0; j < net_m.numLayers; j++)
+        {
+            if (j == i) continue;
+
+            for (int x = 0; x < net_m.TIDLLayers[j].numInBufs; x++)
+                if (net_m.TIDLLayers[j].inData[x].dataId == out_id)
+                {
+                    Edge e = add_edge(i, j, graph_m).first;
+                    ep[e][LABEL] = edge_info;
+                }
+
+        }
+    }
+}
+
+
+void DotGraph::AddMetaData()
+{
+    get_property(graph_m, graph_name) = "TIDL Network";
+    get_property(graph_m, graph_vertex_attribute)[SHAPE] = "Mrecord";
+    get_property(graph_m, graph_graph_attribute)["fontname"] = "Arial";
+    get_property(graph_m, graph_vertex_attribute)["fontname"] = "Arial";
+    get_property(graph_m, graph_vertex_attribute)["fontsize"] = "12";
+    get_property(graph_m, graph_edge_attribute)["fontname"] = "Arial";
+    get_property(graph_m, graph_edge_attribute)["fontsize"] = "10";
+}
+
+
+// Generate dot file from boost graph
+void DotGraph::Write(const std::string& filename) const
+{
+    std::ofstream dot(filename);
+    boost::write_graphviz(dot, graph_m);
+}
+
+std::string PoolingProperties(const sTIDL_PoolingParams_t& p)
+{
+    std::string s = TIDL_LayerString[TIDL_PoolingLayer];
+
+    if (p.poolingType == TIDL_MaxPooling)
+        s += " Max";
+    else if (p.poolingType == TIDL_AveragePooling)
+        s += " Average";
+
+    s+= "\\n" + std::to_string(p.kernelW) + "x" + std::to_string(p.kernelH);
+
+    return s;
+}
+
+std::string ReLUProperties(const sTIDL_ReLUParams_t& p)
+{
+    std::string s;
+
+    if (p.reluType == TIDL_RelU)
+        s += "ReLU";
+    else if (p.reluType == TIDL_PRelU)
+        s += "PReLU";
+    else if (p.reluType == TIDL_RelU6)
+        s += "ReLU6";
+
+    return s;
+}
+
+std::string BiasProperties(const sTIDL_BiasParams_t& p)
+{
+    std::string s = TIDL_LayerString[TIDL_BiasLayer];
+    s += "\\n (BQ: " + std::to_string(p.biasQ) + ")";
+    return s;
+}
+
+
+
+const char* GetVertexColor(uint32_t layer_type)
+{
+    static const char* LayerColors[] =
+    {
+        "lightblue",
+        "red",
+        "darkorange",
+        "royalblue",
+        "darkgreen",
+        "yellow",
+        "magenta",
+        "darkviolet",
+        "brown",
+        "darksalmon",
+        "violet",
+        "darkturquoise",
+        "darkseagreen",
+        "limegreen"
+
+    };
+
+    return LayerColors[layer_type % (sizeof(LayerColors)/sizeof(char *))];
+}
+
+
+const char* TIDL_LayerString[] =
+{
+    "Data",
+    "Convolution",
+    "Pooling",
+    "ReLU",
+    "PReLU",
+    "EltWise",
+    "InnerProduct",
+    "SoftMax",
+    "BatchNorm",
+    "Bias",
+    "Scale",
+    "Deconv2D",
+    "Concat",
+    "Split",
+    "Slice",
+    "Crop",
+    "Flatten",
+    "DropOut",
+    "ArgMax",
+    "DetectionOutput",
+    "Reshape",
+};
diff --git a/utils/src/dot_graph.h b/utils/src/dot_graph.h
new file mode 100644 (file)
index 0000000..914fe48
--- /dev/null
@@ -0,0 +1,101 @@
+/******************************************************************************
+ * Copyright (c) 2017-18, Texas Instruments Incorporated - http://www.ti.com/
+ *  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *      * Redistributions of source code must retain the above copyright
+ *        notice, this list of conditions and the following disclaimer.
+ *      * Redistributions in binary form must reproduce the above copyright
+ *        notice, this list of conditions and the following disclaimer in the
+ *        documentation and/or other materials provided with the distribution.
+ *      * Neither the name of Texas Instruments Incorporated nor the
+ *        names of its contributors may be used to endorse or promote products
+ *        derived from this software without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ *  THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+#pragma once
+
+#include <cstdint>
+#include "tidl_create_params.h"
+
+#include <boost/graph/graphviz.hpp>
+#include <boost/graph/adjacency_list.hpp>
+#include <boost/graph/subgraph.hpp>
+
+using boost::adjacency_list;
+using boost::property;
+using boost::subgraph;
+using boost::get_property;
+using boost::add_vertex;
+using boost::add_edge;
+using boost::edge_index_t;
+using boost::edge_attribute_t;
+using boost::vertex_attribute_t;
+using boost::vertex_attribute;
+using boost::graph_name_t;
+using boost::graph_name;
+using boost::graph_graph_attribute_t;
+using boost::graph_graph_attribute;
+using boost::graph_vertex_attribute_t;
+using boost::graph_vertex_attribute;
+using boost::graph_edge_attribute_t;
+using boost::graph_edge_attribute;
+using boost::vecS;
+
+using GraphvizAttributes = std::map<std::string, std::string>;
+
+
+
+// Boost graph with notes and edges annotated with Dot properties.
+// These properties are used by the write_graphviz function.
+//
+// From https://www.boost.org/doc/libs/1_55_0/libs/graph/doc/subgraph.html
+// When creating a subgraph, the underlying graph type is required to have
+// vertex_index and edge_index internal properties. Add an edge index property
+// to the adjacency list. We do not need to add a vertex index property
+// because it is built in to the adjacency_list.
+using Graph =
+  subgraph<
+    adjacency_list<vecS, vecS, boost::directedS,
+      property<vertex_attribute_t, GraphvizAttributes>,
+      property<edge_index_t,int,property<edge_attribute_t, GraphvizAttributes>>,
+      property<graph_name_t, std::string,
+       property<graph_graph_attribute_t,  GraphvizAttributes,
+        property<graph_vertex_attribute_t, GraphvizAttributes,
+         property<graph_edge_attribute_t,   GraphvizAttributes>
+      >>>
+  >>;
+
+using Vertex =  boost::graph_traits<Graph>::vertex_descriptor;
+
+class DotGraph
+{
+  public:
+    DotGraph(const sTIDL_Network_t& net);
+    ~DotGraph() {}
+
+    void Write(const std::string& filename) const;
+
+  private:
+    void AddVertices();
+    void AddEdges();
+    void AddMetaData();
+    void AddVertexProperties(Vertex& V, Graph* g, const sTIDL_Layer_t& layer);
+
+    Graph graph_m;
+    const sTIDL_Network_t& net_m;
+};
+
+extern const char* TIDL_LayerString[];
index c6932c73b42af67c13cb7e4c3420cb806c71a3d3..ba5e3178e91c3a6ea47f2df7a1ef1a9a174b1600 100644 (file)
  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  *  THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
-#include <boost/graph/graphviz.hpp>
-#include <boost/graph/adjacency_list.hpp>
-#include <boost/graph/subgraph.hpp>
-#include <iostream>
-
 #include "util.h"
 #include "tinn_utils.h"
-#include "tidl_create_params.h"
+#include "dot_graph.h"
 
 using namespace tinn::util;
 
-const char* TIDL_LayerString[] =
-{
-    "Data",
-    "Convolution",
-    "Pooling",
-    "ReLU",
-    "PReLU",
-    "EltWise",
-    "InnerProduct",
-    "SoftMax",
-    "BatchNorm",
-    "Bias",
-    "Scale",
-    "Deconv2D",
-    "Concat",
-    "Split",
-    "Slice",
-    "Crop",
-    "Flatten",
-    "DropOut",
-    "ArgMax",
-    "DetectionOutput",
-    "Reshape",
-};
-
-
-static bool CreateGraph(const sTIDL_Network_t& net,
-                        const std::string& dot_file);
-
-static const char* GetVertexColor(uint32_t layer_type);
-
 bool tinn::util::PrintNetwork(const std::string& network_binary,
                               std::ostream& os)
 {
@@ -78,7 +42,11 @@ bool tinn::util::PrintNetwork(const std::string& network_binary,
                              reinterpret_cast<char *>(&net),
                              sizeof(sTIDL_Network_t));
     if (!status)
-       return false;
+    {
+        std::cerr << "ERROR: Invalid network binary: "
+                  << network_binary << std::endl;
+        exit(EXIT_FAILURE);
+    }
 
     printf("%3s  %-20s  %3s  %3s  %3s "
            " %3s  %3s  %3s  %3s  %3s  %3s  %3s  %3s %3s "
@@ -119,6 +87,7 @@ bool tinn::util::PrintNetwork(const std::string& network_binary,
     return true;
 }
 
+
 bool tinn::util::GenerateDotGraphForNetwork(const std::string& network_binary,
                                             const std::string& dot_file)
 {
@@ -130,185 +99,15 @@ bool tinn::util::GenerateDotGraphForNetwork(const std::string& network_binary,
                              reinterpret_cast<char *>(&net),
                              sizeof(sTIDL_Network_t));
     if (!status)
-       return false;
-
-    return CreateGraph(net, dot_file);
-}
-
-
-
-using boost::adjacency_list;
-using boost::property;
-using boost::subgraph;
-using boost::get_property;
-using boost::add_vertex;
-using boost::add_edge;
-using boost::edge_index_t;
-using boost::edge_attribute_t;
-using boost::vertex_attribute_t;
-using boost::vertex_attribute;
-using boost::graph_name_t;
-using boost::graph_name;
-using boost::graph_graph_attribute_t;
-using boost::graph_graph_attribute;
-using boost::graph_vertex_attribute_t;
-using boost::graph_vertex_attribute;
-using boost::graph_edge_attribute_t;
-using boost::graph_edge_attribute;
-using boost::vecS;
-
-using GraphvizAttributes = std::map<std::string, std::string>;
-
-
-// Boost graph with notes and edges annotated with Dot properties.
-// These properties are used by the write_graphviz function.
-//
-// From https://www.boost.org/doc/libs/1_55_0/libs/graph/doc/subgraph.html
-// When creating a subgraph, the underlying graph type is required to have
-// vertex_index and edge_index internal properties. Add an edge index property
-// to the adjacency list. We do not need to add a vertex index property
-// because it is built in to the adjacency_list.
-using Graph =
-  subgraph<
-    adjacency_list<vecS, vecS, boost::directedS,
-      property<vertex_attribute_t, GraphvizAttributes>,
-      property<edge_index_t,int,property<edge_attribute_t, GraphvizAttributes>>,
-      property<graph_name_t, std::string,
-       property<graph_graph_attribute_t,  GraphvizAttributes,
-        property<graph_vertex_attribute_t, GraphvizAttributes,
-         property<graph_edge_attribute_t,   GraphvizAttributes>
-      >>>
-  >>;
-
-using Edge =  boost::graph_traits<Graph>::edge_descriptor;
-
-template<typename T>
-using VertexPropertyMap = typename boost::property_map<T, boost::vertex_attribute_t>::type;
-
-template<typename T>
-using EdgePropertyMap = typename boost::property_map<T, boost::edge_attribute_t>::type;
-
-using LayerIdMap = std::map<uint32_t, Graph*>;
-
-
-bool CreateGraph(const sTIDL_Network_t& net, const std::string& dot_file)
-{
-    static const char* FILLED = "filled";
-    static const char* LABEL  = "label";
-    static const char* COLOR  = "color";
-    static const char* STYLE  = "style";
-    static const char* SHAPE  = "shape";
-
-    Graph tidl(net.numLayers);
-
-    // Add a vertex for each layer
-    LayerIdMap layerid_map;
-    for (int i = 0 ; i < net.numLayers; i++)
     {
-        uint32_t layer_type = net.TIDLLayers[i].layerType;
-
-        // Special handling for input & output data layers:
-        // Do not put them in the same subgraph
-        if (net.TIDLLayers[i].layerType == TIDL_DataLayer)
-        {
-            auto V = vertex(i, tidl);
-            VertexPropertyMap<Graph> vpm = boost::get(vertex_attribute, tidl);
-            vpm[V][LABEL] = TIDL_LayerString[layer_type];
-            vpm[V][COLOR] = GetVertexColor(layer_type);
-            vpm[V][STYLE] = FILLED;
-            vpm[V][SHAPE] = "ellipse";
-
-            continue;
-        }
-
-        // Use the layer group ID to create subgraphs
-        // Place all layers with the same ID into a single subgraph
-        uint32_t layerGroupId = net.TIDLLayers[i].layersGroupId;
-        Graph* sub = nullptr;
-        if (layerid_map.find(layerGroupId) == layerid_map.end())
-        {
-            sub = &(tidl.create_subgraph());
-            layerid_map[layerGroupId] = sub;
-            get_property(*sub, graph_name) = std::string("cluster") +
-                                             std::to_string(layerGroupId);
-            get_property(*sub, graph_graph_attribute)[LABEL] =
-                                    std::to_string(layerGroupId);
-            get_property(*sub, graph_graph_attribute)[STYLE] = FILLED;
-            get_property(*sub, graph_graph_attribute)["fillcolor"] = "lightgrey";
-        }
-        else
-            sub = layerid_map[layerGroupId];
-
-        auto V = add_vertex(i, *sub);
-        VertexPropertyMap<Graph> vpm = boost::get(vertex_attribute, *sub);
-        vpm[V][LABEL] = TIDL_LayerString[layer_type];
-        vpm[V][COLOR] = GetVertexColor(layer_type);
-        vpm[V][STYLE] = FILLED;
-
-    }
-
-    // Add edges based on dataId i.e. there is an edge from layer A -> B
-    // iff dataId is in outData for A and inData for B.
-    EdgePropertyMap<Graph> ep = boost::get(boost::edge_attribute, tidl);
-    for (int i = 0 ; i < net.numLayers; i++)
-    {
-        if (net.TIDLLayers[i].numOutBufs < 0)
-            continue;
-
-        // Create a string to  annotate the edge - num_channels, height, width
-        int32_t out_id = net.TIDLLayers[i].outData[0].dataId;
-        std::string edge_info =
-                    std::to_string(net.TIDLLayers[i].outData[0].dimValues[1])+
-                    "x" +
-                    std::to_string(net.TIDLLayers[i].outData[0].dimValues[2])+
-                    "x" +
-                    std::to_string(net.TIDLLayers[i].outData[0].dimValues[3]);
-
-        for (int j = 0; j < net.numLayers; j++)
-        {
-            if (j == i) continue;
-
-            for (int x = 0; x < net.TIDLLayers[j].numInBufs; x++)
-                if (net.TIDLLayers[j].inData[x].dataId == out_id)
-                {
-                    Edge e = add_edge(i, j, tidl).first;
-                    ep[e][LABEL] = edge_info;
-                }
-
-        }
+        std::cerr << "ERROR: Invalid network binary: "
+                  << network_binary << std::endl;
+        exit(EXIT_FAILURE);
     }
 
-    get_property(tidl, graph_name) = "TIDL Network";
-    get_property(tidl, graph_vertex_attribute)[SHAPE] = "Mrecord";
 
-    // Generate dot file from boost graph
-    std::ofstream dot(dot_file);
-    boost::write_graphviz(dot, tidl);
+    DotGraph g(net);
+    g.Write(dot_file);
 
     return true;
 }
-
-
-const char* GetVertexColor(uint32_t layer_type)
-{
-    static const char* LayerColors[] =
-    {
-        "lightblue",
-        "lightseagreen",
-        "lightpink",
-        "sienna",
-        "lightyellow",
-        "palegoldenrod",
-        "lightsalmon",
-        "lightcyan",
-        "wheat",
-        "olivedrab",
-        "lightskyblue",
-        "beige",
-        "lavender",
-        "linen"
-
-    };
-
-    return LayerColors[layer_type % (sizeof(LayerColors)/sizeof(char *))];
-}