class_docstr: This class initialises an application by building a TF graph,
  and maintaining a session. It controls the
  starting/stopping of an application. Applications should be
  implemented by inheriting ``niftynet.application.base_application``
  to be compatible with this driver.
class_docstr_tok: ["This","class","initialises","an","application","by","building","a","TF","graph",",","and","maintaining","a","session",".","It","controls","the","starting\/stopping","of","an","application",".","Applications","should","be","implemented","by","inheriting","``","niftynet.application.base_application","``","to","be","compatible","with","this","driver","."]
docstr: Create a TF graph based on self.app properties
  and engine parameters.
  
  :return:
docstr_tok: ["Create","a","TF","graph","based","on","self.app","properties","and","engine","parameters",".",":","return",":"]
code: def create_graph(
      application, num_gpus=1, num_threads=1, is_training_action=False
  ):
      """
      Create a TF graph based on self.app properties
      and engine parameters.
  
      :return:
      """
      graph = tf.Graph()
      main_device = device_string(
          num_gpus, 0, False, is_training_action
      )
      outputs_collector = OutputsCollector(n_devices=max(num_gpus, 1))
      gradients_collector = GradientsCollector(
          n_devices=max(num_gpus, 1)
      )
      # start constructing the graph, handling training and inference cases
      with graph.as_default(), tf.device(main_device):
          # initialise sampler
          with tf.name_scope("Sampler"):
              application.initialise_sampler()
              for sampler in traverse_nested(application.get_sampler()):
                  sampler.set_num_threads(num_threads)
  
          # initialise network, these are connected in
          # the context of multiple gpus
          application.initialise_network()
          application.add_validation_flag()
  
          # for data parallelism --
          #     defining and collecting variables from multiple devices
          for gpu_id in range(0, max(num_gpus, 1)):
              worker_device = device_string(
                  num_gpus, gpu_id, True, is_training_action
              )
              scope_string = "worker_{}".format(gpu_id)
              with tf.name_scope(scope_string), tf.device(
                  worker_device
              ):
                  # setup network for each of the multiple devices
                  application.connect_data_and_network(
                      outputs_collector, gradients_collector
                  )
          with tf.name_scope("MergeOutputs"):
              outputs_collector.finalise_output_op()
          application.outputs_collector = outputs_collector
          application.gradients_collector = gradients_collector
          GRAPH_CREATED.send(application, iter_msg=None)
      return graph
  
imports: from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  import os
  import time
  import tensorflow
  from niftynet.engine.application_factory import ApplicationFactory, EventHandlerFactory, IteratorFactory
  from niftynet.engine.application_iteration import IterationMessage
  from niftynet.engine.application_variables import GradientsCollector, OutputsCollector
  from niftynet.engine.signal import TRAIN, ITER_STARTED, ITER_FINISHED, GRAPH_CREATED, SESS_FINISHED, SESS_STARTED
  from niftynet.io.image_sets_partitioner import ImageSetsPartitioner
  from niftynet.io.misc_io import infer_latest_model_file
  from niftynet.utilities.user_parameters_default import DEFAULT_EVENT_HANDLERS, DEFAULT_ITERATION_GENERATOR
  from niftynet.utilities.util_common import set_cuda_device, tf_config, device_string
  from niftynet.utilities.util_common import traverse_nested
label_desc: Use image node_id 4 for calling the ApplicationDriver obj's underlying member method code with example usage: obj.create_graph(application, num_gpus, num_threads, is_training_action) and returns: graph