From c86b25bc73e389cd643993269ad0d96562e68086 Mon Sep 17 00:00:00 2001 From: "Akeret, Joel" Date: Wed, 3 Jan 2018 13:21:12 +0100 Subject: [PATCH 01/45] fixed url --- README.rst | 2 +- demo/README.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e9d90b3..b9145d7 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ Tensorflow Unet :target: http://ascl.net/1611.002 .. image:: https://mybinder.org/badge.svg - :target: https://mybinder.org/v2/gh/jakeret/tf_unet/binder?filepath=demo%2Fdemo_toy_problem.ipynb + :target: https://mybinder.org/v2/gh/jakeret/tf_unet/master?filepath=demo%2Fdemo_toy_problem.ipynb This is a generic **U-Net** implementation as proposed by `Ronneberger et al. `_ developed with **Tensorflow**. The code has been developed and used for `Radio Frequency Interference mitigation using deep convolutional neural networks `_ . diff --git a/demo/README.rst b/demo/README.rst index d91317c..8fb97f0 100644 --- a/demo/README.rst +++ b/demo/README.rst @@ -3,7 +3,7 @@ Tensorflow Unet demos ============================= .. image:: https://mybinder.org/badge.svg - :target: https://mybinder.org/v2/gh/jakeret/tf_unet/binder?filepath=demo%2Fdemo_toy_problem.ipynb + :target: https://mybinder.org/v2/gh/jakeret/tf_unet/master?filepath=demo%2Fdemo_toy_problem.ipynb From f12f57ad3694e5a214c05bc08e4284bb368452a9 Mon Sep 17 00:00:00 2001 From: "Akeret, Joel" Date: Tue, 20 Feb 2018 15:02:10 +0100 Subject: [PATCH 02/45] fixed url --- demo/demo_radio_data.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/demo_radio_data.ipynb b/demo/demo_radio_data.ipynb index efe19e5..b4c62df 100644 --- a/demo/demo_radio_data.ipynb +++ b/demo/demo_radio_data.ipynb @@ -44,7 +44,7 @@ }, "outputs": [], "source": [ - "!wget -q -r -nH -np --cut-dirs=2 http://people.phys.ethz.ch/~ast/cosmo/bgs_example_data/" + "!wget -q -r -nH -np --cut-dirs=2 https://people.phys.ethz.ch/~ipa/cosmo/bgs_example_data/" ] }, { From bef059733865b05749bf464c94c04fdd80b25c50 Mon Sep 17 00:00:00 2001 From: gwy15 Date: Fri, 6 Apr 2018 13:43:15 +0800 Subject: [PATCH 03/45] fix typo 'cpkt' --- demo/demo_radio_data.ipynb | 2 +- demo/demo_toy_problem.ipynb | 4 ++-- tf_unet/unet.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/demo_radio_data.ipynb b/demo/demo_radio_data.ipynb index b4c62df..c5658f9 100644 --- a/demo/demo_radio_data.ipynb +++ b/demo/demo_radio_data.ipynb @@ -241,7 +241,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "2017-03-27 22:11:29,783 Model restored from file: ./unet_trained_bgs_example_data/model.cpkt\n" + "2017-03-27 22:11:29,783 Model restored from file: ./unet_trained_bgs_example_data/model.ckpt\n" ] } ], diff --git a/demo/demo_toy_problem.ipynb b/demo/demo_toy_problem.ipynb index 7dc8d22..88ae8ec 100644 --- a/demo/demo_toy_problem.ipynb +++ b/demo/demo_toy_problem.ipynb @@ -203,14 +203,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2017-03-27 21:31:00,595 Model restored from file: ./unet_trained/model.cpkt\n" + "2017-03-27 21:31:00,595 Model restored from file: ./unet_trained/model.ckpt\n" ] } ], "source": [ "x_test, y_test = generator(1)\n", "\n", - "prediction = net.predict(\"./unet_trained/model.cpkt\", x_test)" + "prediction = net.predict(\"./unet_trained/model.ckpt\", x_test)" ] }, { diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 5eef78d..ec5b3a5 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -388,7 +388,7 @@ def train(self, data_provider, output_path, training_iters=10, epochs=100, dropo :param write_graph: Flag if the computation graph should be written as protobuf file to the output path :param prediction_path: path where to save predictions on each epoch """ - save_path = os.path.join(output_path, "model.cpkt") + save_path = os.path.join(output_path, "model.ckpt") if epochs == 0: return save_path From 82225578299bd8aa9d5572f5fb3c257c361d061a Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Thu, 31 May 2018 22:00:20 +0200 Subject: [PATCH 04/45] minor changes to be compatible with newer tensorflow version --- tf_unet/layers.py | 2 +- tf_unet/unet.py | 361 ++++++++++++++++++++++++---------------------- 2 files changed, 188 insertions(+), 175 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index f321511..b3f6bf8 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -60,7 +60,7 @@ def pixel_wise_softmax(output_map): def pixel_wise_softmax_2(output_map): exponential_map = tf.exp(output_map) - sum_exp = tf.reduce_sum(exponential_map, 3, keep_dims=True) + sum_exp = tf.reduce_sum(exponential_map, 3, keepdims=True) tensor_sum_exp = tf.tile(sum_exp, tf.stack([1, 1, 1, tf.shape(output_map)[3]])) return tf.div(exponential_map,tensor_sum_exp) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index ec5b3a5..0182597 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -28,16 +28,18 @@ import tensorflow as tf from tf_unet import util -from tf_unet.layers import (weight_variable, weight_variable_devonc, bias_variable, +from tf_unet.layers import (weight_variable, weight_variable_devonc, bias_variable, conv2d, deconv2d, max_pool, crop_and_concat, pixel_wise_softmax_2, cross_entropy) logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') -def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, filter_size=3, pool_size=2, summaries=True): + +def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, filter_size=3, pool_size=2, + summaries=True): """ Creates a new convolutional unet for the given parametrization. - + :param x: input tensor, shape [?,nx,ny,channels] :param keep_prob: dropout probability tensor :param channels: number of channels in the input image @@ -48,18 +50,20 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, :param pool_size: size of the max pooling operation :param summaries: Flag if summaries should be created """ - - logging.info("Layers {layers}, features {features}, filter size {filter_size}x{filter_size}, pool size: {pool_size}x{pool_size}".format(layers=layers, - features=features_root, - filter_size=filter_size, - pool_size=pool_size)) + + logging.info( + "Layers {layers}, features {features}, filter size {filter_size}x{filter_size}, pool size: {pool_size}x{pool_size}".format( + layers=layers, + features=features_root, + filter_size=filter_size, + pool_size=pool_size)) # Placeholder for the input image nx = tf.shape(x)[1] ny = tf.shape(x)[2] - x_image = tf.reshape(x, tf.stack([-1,nx,ny,channels])) + x_image = tf.reshape(x, tf.stack([-1, nx, ny, channels])) in_node = x_image batch_size = tf.shape(x_image)[0] - + weights = [] biases = [] convs = [] @@ -67,55 +71,55 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, deconv = OrderedDict() dw_h_convs = OrderedDict() up_h_convs = OrderedDict() - + in_size = 1000 size = in_size # down layers for layer in range(0, layers): - features = 2**layer*features_root - stddev = np.sqrt(2 / (filter_size**2 * features)) + features = 2 ** layer * features_root + stddev = np.sqrt(2 / (filter_size ** 2 * features)) if layer == 0: w1 = weight_variable([filter_size, filter_size, channels, features], stddev) else: - w1 = weight_variable([filter_size, filter_size, features//2, features], stddev) - + w1 = weight_variable([filter_size, filter_size, features // 2, features], stddev) + w2 = weight_variable([filter_size, filter_size, features, features], stddev) b1 = bias_variable([features]) b2 = bias_variable([features]) - + conv1 = conv2d(in_node, w1, keep_prob) tmp_h_conv = tf.nn.relu(conv1 + b1) conv2 = conv2d(tmp_h_conv, w2, keep_prob) dw_h_convs[layer] = tf.nn.relu(conv2 + b2) - + weights.append((w1, w2)) biases.append((b1, b2)) convs.append((conv1, conv2)) - + size -= 4 - if layer < layers-1: + if layer < layers - 1: pools[layer] = max_pool(dw_h_convs[layer], pool_size) in_node = pools[layer] size /= 2 - - in_node = dw_h_convs[layers-1] - + + in_node = dw_h_convs[layers - 1] + # up layers - for layer in range(layers-2, -1, -1): - features = 2**(layer+1)*features_root - stddev = np.sqrt(2 / (filter_size**2 * features)) - - wd = weight_variable_devonc([pool_size, pool_size, features//2, features], stddev) - bd = bias_variable([features//2]) + for layer in range(layers - 2, -1, -1): + features = 2 ** (layer + 1) * features_root + stddev = np.sqrt(2 / (filter_size ** 2 * features)) + + wd = weight_variable_devonc([pool_size, pool_size, features // 2, features], stddev) + bd = bias_variable([features // 2]) h_deconv = tf.nn.relu(deconv2d(in_node, wd, pool_size) + bd) h_deconv_concat = crop_and_concat(dw_h_convs[layer], h_deconv) deconv[layer] = h_deconv_concat - - w1 = weight_variable([filter_size, filter_size, features, features//2], stddev) - w2 = weight_variable([filter_size, filter_size, features//2, features//2], stddev) - b1 = bias_variable([features//2]) - b2 = bias_variable([features//2]) - + + w1 = weight_variable([filter_size, filter_size, features, features // 2], stddev) + w2 = weight_variable([filter_size, filter_size, features // 2, features // 2], stddev) + b1 = bias_variable([features // 2]) + b2 = bias_variable([features // 2]) + conv1 = conv2d(h_deconv_concat, w1, keep_prob) h_conv = tf.nn.relu(conv1 + b1) conv2 = conv2d(h_conv, w2, keep_prob) @@ -125,7 +129,7 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, weights.append((w1, w2)) biases.append((b1, b2)) convs.append((conv1, conv2)) - + size *= 2 size -= 4 @@ -135,212 +139,212 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, conv = conv2d(in_node, weight, tf.constant(1.0)) output_map = tf.nn.relu(conv + bias) up_h_convs["out"] = output_map - + if summaries: for i, (c1, c2) in enumerate(convs): - tf.summary.image('summary_conv_%02d_01'%i, get_image_summary(c1)) - tf.summary.image('summary_conv_%02d_02'%i, get_image_summary(c2)) - + tf.summary.image('summary_conv_%02d_01' % i, get_image_summary(c1)) + tf.summary.image('summary_conv_%02d_02' % i, get_image_summary(c2)) + for k in pools.keys(): - tf.summary.image('summary_pool_%02d'%k, get_image_summary(pools[k])) - + tf.summary.image('summary_pool_%02d' % k, get_image_summary(pools[k])) + for k in deconv.keys(): - tf.summary.image('summary_deconv_concat_%02d'%k, get_image_summary(deconv[k])) - + tf.summary.image('summary_deconv_concat_%02d' % k, get_image_summary(deconv[k])) + for k in dw_h_convs.keys(): - tf.summary.histogram("dw_convolution_%02d"%k + '/activations', dw_h_convs[k]) + tf.summary.histogram("dw_convolution_%02d" % k + '/activations', dw_h_convs[k]) for k in up_h_convs.keys(): - tf.summary.histogram("up_convolution_%s"%k + '/activations', up_h_convs[k]) - + tf.summary.histogram("up_convolution_%s" % k + '/activations', up_h_convs[k]) + variables = [] - for w1,w2 in weights: + for w1, w2 in weights: variables.append(w1) variables.append(w2) - - for b1,b2 in biases: + + for b1, b2 in biases: variables.append(b1) variables.append(b2) - return output_map, variables, int(in_size - size) class Unet(object): """ A unet implementation - + :param channels: (optional) number of channels in the input image :param n_class: (optional) number of output labels :param cost: (optional) name of the cost function. Default is 'cross_entropy' :param cost_kwargs: (optional) kwargs passed to the cost function. See Unet._get_cost for more options """ - + def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, **kwargs): tf.reset_default_graph() - + self.n_class = n_class self.summaries = kwargs.get("summaries", True) - + self.x = tf.placeholder("float", shape=[None, None, None, channels]) self.y = tf.placeholder("float", shape=[None, None, None, n_class]) - self.keep_prob = tf.placeholder(tf.float32) #dropout (keep probability) - + self.keep_prob = tf.placeholder(tf.float32) # dropout (keep probability) + logits, self.variables, self.offset = create_conv_net(self.x, self.keep_prob, channels, n_class, **kwargs) - + self.cost = self._get_cost(logits, cost, cost_kwargs) - + self.gradients_node = tf.gradients(self.cost, self.variables) - + self.cross_entropy = tf.reduce_mean(cross_entropy(tf.reshape(self.y, [-1, n_class]), tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class]))) - + self.predicter = pixel_wise_softmax_2(logits) self.correct_pred = tf.equal(tf.argmax(self.predicter, 3), tf.argmax(self.y, 3)) self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32)) - + def _get_cost(self, logits, cost_name, cost_kwargs): """ Constructs the cost function, either cross_entropy, weighted cross_entropy or dice_coefficient. - Optional arguments are: + Optional arguments are: class_weights: weights for the different classes in case of multi-class imbalance regularizer: power of the L2 regularizers added to the loss function """ - + flat_logits = tf.reshape(logits, [-1, self.n_class]) flat_labels = tf.reshape(self.y, [-1, self.n_class]) if cost_name == "cross_entropy": class_weights = cost_kwargs.pop("class_weights", None) - + if class_weights is not None: class_weights = tf.constant(np.array(class_weights, dtype=np.float32)) - + weight_map = tf.multiply(flat_labels, class_weights) weight_map = tf.reduce_sum(weight_map, axis=1) - - loss_map = tf.nn.softmax_cross_entropy_with_logits(logits=flat_logits, - labels=flat_labels) + + loss_map = tf.nn.softmax_cross_entropy_with_logits_v2(logits=flat_logits, + labels=flat_labels) weighted_loss = tf.multiply(loss_map, weight_map) - + loss = tf.reduce_mean(weighted_loss) - + else: - loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=flat_logits, - labels=flat_labels)) + loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=flat_logits, + labels=flat_labels)) elif cost_name == "dice_coefficient": eps = 1e-5 prediction = pixel_wise_softmax_2(logits) intersection = tf.reduce_sum(prediction * self.y) - union = eps + tf.reduce_sum(prediction) + tf.reduce_sum(self.y) - loss = -(2 * intersection/ (union)) - + union = eps + tf.reduce_sum(prediction) + tf.reduce_sum(self.y) + loss = -(2 * intersection / (union)) + else: - raise ValueError("Unknown cost function: "%cost_name) + raise ValueError("Unknown cost function: " % cost_name) regularizer = cost_kwargs.pop("regularizer", None) if regularizer is not None: regularizers = sum([tf.nn.l2_loss(variable) for variable in self.variables]) loss += (regularizer * regularizers) - + return loss def predict(self, model_path, x_test): """ Uses the model to create a prediction for the given data - + :param model_path: path to the model checkpoint to restore :param x_test: Data to predict on. Shape [n, nx, ny, channels] - :returns prediction: The unet prediction Shape [n, px, py, labels] (px=nx-self.offset/2) + :returns prediction: The unet prediction Shape [n, px, py, labels] (px=nx-self.offset/2) """ - + init = tf.global_variables_initializer() with tf.Session() as sess: # Initialize variables sess.run(init) - + # Restore model weights from previously saved model self.restore(sess, model_path) - + y_dummy = np.empty((x_test.shape[0], x_test.shape[1], x_test.shape[2], self.n_class)) prediction = sess.run(self.predicter, feed_dict={self.x: x_test, self.y: y_dummy, self.keep_prob: 1.}) - + return prediction - + def save(self, sess, model_path): """ Saves the current session to a checkpoint - + :param sess: current session :param model_path: path to file system location """ - + saver = tf.train.Saver() save_path = saver.save(sess, model_path) return save_path - + def restore(self, sess, model_path): """ Restores a session from a checkpoint - + :param sess: current session instance :param model_path: path to file system checkpoint location """ - + saver = tf.train.Saver() saver.restore(sess, model_path) logging.info("Model restored from file: %s" % model_path) + class Trainer(object): """ Trains a unet instance - + :param net: the unet instance to train :param batch_size: size of training batch :param norm_grads: (optional) true if normalized gradients should be added to the summaries :param optimizer: (optional) name of the optimizer to use (momentum or adam) :param opt_kwargs: (optional) kwargs passed to the learning rate (momentum opt) and to the optimizer - + """ - + verification_batch_size = 4 - + def __init__(self, net, batch_size=1, norm_grads=False, optimizer="momentum", opt_kwargs={}): self.net = net self.batch_size = batch_size self.norm_grads = norm_grads self.optimizer = optimizer self.opt_kwargs = opt_kwargs - + def _get_optimizer(self, training_iters, global_step): if self.optimizer == "momentum": learning_rate = self.opt_kwargs.pop("learning_rate", 0.2) decay_rate = self.opt_kwargs.pop("decay_rate", 0.95) momentum = self.opt_kwargs.pop("momentum", 0.2) - - self.learning_rate_node = tf.train.exponential_decay(learning_rate=learning_rate, - global_step=global_step, - decay_steps=training_iters, - decay_rate=decay_rate, - staircase=True) - + + self.learning_rate_node = tf.train.exponential_decay(learning_rate=learning_rate, + global_step=global_step, + decay_steps=training_iters, + decay_rate=decay_rate, + staircase=True) + optimizer = tf.train.MomentumOptimizer(learning_rate=self.learning_rate_node, momentum=momentum, - **self.opt_kwargs).minimize(self.net.cost, - global_step=global_step) + **self.opt_kwargs).minimize(self.net.cost, + global_step=global_step) elif self.optimizer == "adam": learning_rate = self.opt_kwargs.pop("learning_rate", 0.001) self.learning_rate_node = tf.Variable(learning_rate) - - optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate_node, + + optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate_node, **self.opt_kwargs).minimize(self.net.cost, - global_step=global_step) - + global_step=global_step) + return optimizer - + def _initialize(self, training_iters, output_path, restore, prediction_path): global_step = tf.Variable(0) - + self.norm_gradients_node = tf.Variable(tf.constant(0.0, shape=[len(self.net.gradients_node)])) - + if self.net.summaries and self.norm_grads: tf.summary.histogram('norm_grads', self.norm_gradients_node) @@ -351,164 +355,173 @@ def _initialize(self, training_iters, output_path, restore, prediction_path): self.optimizer = self._get_optimizer(training_iters, global_step) tf.summary.scalar('learning_rate', self.learning_rate_node) - self.summary_op = tf.summary.merge_all() + self.summary_op = tf.summary.merge_all() init = tf.global_variables_initializer() - + self.prediction_path = prediction_path abs_prediction_path = os.path.abspath(self.prediction_path) output_path = os.path.abspath(output_path) - + if not restore: logging.info("Removing '{:}'".format(abs_prediction_path)) shutil.rmtree(abs_prediction_path, ignore_errors=True) logging.info("Removing '{:}'".format(output_path)) shutil.rmtree(output_path, ignore_errors=True) - + if not os.path.exists(abs_prediction_path): logging.info("Allocating '{:}'".format(abs_prediction_path)) os.makedirs(abs_prediction_path) - + if not os.path.exists(output_path): logging.info("Allocating '{:}'".format(output_path)) os.makedirs(output_path) - + return init - def train(self, data_provider, output_path, training_iters=10, epochs=100, dropout=0.75, display_step=1, restore=False, write_graph=False, prediction_path = 'prediction'): + def train(self, data_provider, output_path, training_iters=10, epochs=100, dropout=0.75, display_step=1, + restore=False, write_graph=False, prediction_path='prediction'): """ Lauches the training process - + :param data_provider: callable returning training and verification data :param output_path: path where to store checkpoints :param training_iters: number of training mini batch iteration :param epochs: number of epochs :param dropout: dropout probability :param display_step: number of steps till outputting stats - :param restore: Flag if previous model should be restored + :param restore: Flag if previous model should be restored :param write_graph: Flag if the computation graph should be written as protobuf file to the output path :param prediction_path: path where to save predictions on each epoch """ save_path = os.path.join(output_path, "model.ckpt") if epochs == 0: return save_path - + init = self._initialize(training_iters, output_path, restore, prediction_path) - + with tf.Session() as sess: if write_graph: tf.train.write_graph(sess.graph_def, output_path, "graph.pb", False) - + sess.run(init) - + if restore: ckpt = tf.train.get_checkpoint_state(output_path) if ckpt and ckpt.model_checkpoint_path: self.net.restore(sess, ckpt.model_checkpoint_path) - + test_x, test_y = data_provider(self.verification_batch_size) pred_shape = self.store_prediction(sess, test_x, test_y, "_init") - + summary_writer = tf.summary.FileWriter(output_path, graph=sess.graph) logging.info("Start optimization") - + avg_gradients = None for epoch in range(epochs): total_loss = 0 - for step in range((epoch*training_iters), ((epoch+1)*training_iters)): + for step in range((epoch * training_iters), ((epoch + 1) * training_iters)): batch_x, batch_y = data_provider(self.batch_size) - + # Run optimization op (backprop) - _, loss, lr, gradients = sess.run((self.optimizer, self.net.cost, self.learning_rate_node, self.net.gradients_node), - feed_dict={self.net.x: batch_x, - self.net.y: util.crop_to_shape(batch_y, pred_shape), - self.net.keep_prob: dropout}) + _, loss, lr, gradients = sess.run( + (self.optimizer, self.net.cost, self.learning_rate_node, self.net.gradients_node), + feed_dict={self.net.x: batch_x, + self.net.y: util.crop_to_shape(batch_y, pred_shape), + self.net.keep_prob: dropout}) if self.net.summaries and self.norm_grads: avg_gradients = _update_avg_gradients(avg_gradients, gradients, step) norm_gradients = [np.linalg.norm(gradient) for gradient in avg_gradients] self.norm_gradients_node.assign(norm_gradients).eval() - + if step % display_step == 0: - self.output_minibatch_stats(sess, summary_writer, step, batch_x, util.crop_to_shape(batch_y, pred_shape)) - + self.output_minibatch_stats(sess, summary_writer, step, batch_x, + util.crop_to_shape(batch_y, pred_shape)) + total_loss += loss self.output_epoch_stats(epoch, total_loss, training_iters, lr) - self.store_prediction(sess, test_x, test_y, "epoch_%s"%epoch) - + self.store_prediction(sess, test_x, test_y, "epoch_%s" % epoch) + save_path = self.net.save(sess, save_path) logging.info("Optimization Finished!") - + return save_path - + def store_prediction(self, sess, batch_x, batch_y, name): - prediction = sess.run(self.net.predicter, feed_dict={self.net.x: batch_x, - self.net.y: batch_y, + prediction = sess.run(self.net.predicter, feed_dict={self.net.x: batch_x, + self.net.y: batch_y, self.net.keep_prob: 1.}) pred_shape = prediction.shape - - loss = sess.run(self.net.cost, feed_dict={self.net.x: batch_x, - self.net.y: util.crop_to_shape(batch_y, pred_shape), - self.net.keep_prob: 1.}) - + + loss = sess.run(self.net.cost, feed_dict={self.net.x: batch_x, + self.net.y: util.crop_to_shape(batch_y, pred_shape), + self.net.keep_prob: 1.}) + logging.info("Verification error= {:.1f}%, loss= {:.4f}".format(error_rate(prediction, - util.crop_to_shape(batch_y, - prediction.shape)), - loss)) - + util.crop_to_shape(batch_y, + prediction.shape)), + loss)) + img = util.combine_img_prediction(batch_x, batch_y, prediction) - util.save_image(img, "%s/%s.jpg"%(self.prediction_path, name)) - + util.save_image(img, "%s/%s.jpg" % (self.prediction_path, name)) + return pred_shape - + def output_epoch_stats(self, epoch, total_loss, training_iters, lr): - logging.info("Epoch {:}, Average loss: {:.4f}, learning rate: {:.4f}".format(epoch, (total_loss / training_iters), lr)) - + logging.info( + "Epoch {:}, Average loss: {:.4f}, learning rate: {:.4f}".format(epoch, (total_loss / training_iters), lr)) + def output_minibatch_stats(self, sess, summary_writer, step, batch_x, batch_y): # Calculate batch loss and accuracy - summary_str, loss, acc, predictions = sess.run([self.summary_op, - self.net.cost, - self.net.accuracy, - self.net.predicter], - feed_dict={self.net.x: batch_x, - self.net.y: batch_y, - self.net.keep_prob: 1.}) + summary_str, loss, acc, predictions = sess.run([self.summary_op, + self.net.cost, + self.net.accuracy, + self.net.predicter], + feed_dict={self.net.x: batch_x, + self.net.y: batch_y, + self.net.keep_prob: 1.}) summary_writer.add_summary(summary_str, step) summary_writer.flush() - logging.info("Iter {:}, Minibatch Loss= {:.4f}, Training Accuracy= {:.4f}, Minibatch error= {:.1f}%".format(step, - loss, - acc, - error_rate(predictions, batch_y))) + logging.info( + "Iter {:}, Minibatch Loss= {:.4f}, Training Accuracy= {:.4f}, Minibatch error= {:.1f}%".format(step, + loss, + acc, + error_rate( + predictions, + batch_y))) + def _update_avg_gradients(avg_gradients, gradients, step): if avg_gradients is None: avg_gradients = [np.zeros_like(gradient) for gradient in gradients] for i in range(len(gradients)): - avg_gradients[i] = (avg_gradients[i] * (1.0 - (1.0 / (step+1)))) + (gradients[i] / (step+1)) - + avg_gradients[i] = (avg_gradients[i] * (1.0 - (1.0 / (step + 1)))) + (gradients[i] / (step + 1)) + return avg_gradients + def error_rate(predictions, labels): """ Return the error rate based on dense predictions and 1-hot labels. """ - + return 100.0 - ( - 100.0 * - np.sum(np.argmax(predictions, 3) == np.argmax(labels, 3)) / - (predictions.shape[0]*predictions.shape[1]*predictions.shape[2])) + 100.0 * + np.sum(np.argmax(predictions, 3) == np.argmax(labels, 3)) / + (predictions.shape[0] * predictions.shape[1] * predictions.shape[2])) def get_image_summary(img, idx=0): """ Make an image summary for 4d tensor image with index idx """ - + V = tf.slice(img, (0, 0, 0, idx), (1, -1, -1, 1)) V -= tf.reduce_min(V) V /= tf.reduce_max(V) V *= 255 - + img_w = tf.shape(img)[1] img_h = tf.shape(img)[2] V = tf.reshape(V, tf.stack((img_w, img_h, 1))) From bfd9da2a6eb8acb286f78ff6a95ce5e5744f3662 Mon Sep 17 00:00:00 2001 From: "Wm. Keith van der Meulen" Date: Fri, 1 Jun 2018 15:46:21 -0600 Subject: [PATCH 05/45] Add names and namescopes to improve TensorBoard layout --- tf_unet/layers.py | 45 ++++----- tf_unet/unet.py | 244 ++++++++++++++++++++++++---------------------- 2 files changed, 149 insertions(+), 140 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index b3f6bf8..36f1eaa 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -21,16 +21,16 @@ import tensorflow as tf -def weight_variable(shape, stddev=0.1): +def weight_variable(shape, stddev=0.1, name="weight"): initial = tf.truncated_normal(shape, stddev=stddev) - return tf.Variable(initial) + return tf.Variable(initial, name=name) -def weight_variable_devonc(shape, stddev=0.1): - return tf.Variable(tf.truncated_normal(shape, stddev=stddev)) +def weight_variable_devonc(shape, stddev=0.1, name="weight_devonc"): + return tf.Variable(tf.truncated_normal(shape, stddev=stddev), name=name) -def bias_variable(shape): +def bias_variable(shape, name="bias"): initial = tf.constant(0.1, shape=shape) - return tf.Variable(initial) + return tf.Variable(initial, name=name) def conv2d(x, W,keep_prob_): conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') @@ -45,26 +45,27 @@ def max_pool(x,n): return tf.nn.max_pool(x, ksize=[1, n, n, 1], strides=[1, n, n, 1], padding='VALID') def crop_and_concat(x1,x2): - x1_shape = tf.shape(x1) - x2_shape = tf.shape(x2) - # offsets for the top left corner of the crop - offsets = [0, (x1_shape[1] - x2_shape[1]) // 2, (x1_shape[2] - x2_shape[2]) // 2, 0] - size = [-1, x2_shape[1], x2_shape[2], -1] - x1_crop = tf.slice(x1, offsets, size) - return tf.concat([x1_crop, x2], 3) + with tf.name_scope("crop_and_concat"): + x1_shape = tf.shape(x1) + x2_shape = tf.shape(x2) + # offsets for the top left corner of the crop + offsets = [0, (x1_shape[1] - x2_shape[1]) // 2, (x1_shape[2] - x2_shape[2]) // 2, 0] + size = [-1, x2_shape[1], x2_shape[2], -1] + x1_crop = tf.slice(x1, offsets, size) + return tf.concat([x1_crop, x2], 3) def pixel_wise_softmax(output_map): - exponential_map = tf.exp(output_map) - evidence = tf.add(exponential_map,tf.reverse(exponential_map,[False,False,False,True])) - return tf.div(exponential_map,evidence, name="pixel_wise_softmax") + with tf.name_scope("pixel_wise_softmax"): + exponential_map = tf.exp(output_map) + evidence = tf.add(exponential_map,tf.reverse(exponential_map,[False,False,False,True])) + return tf.div(exponential_map,evidence, name="pixel_wise_softmax") def pixel_wise_softmax_2(output_map): - exponential_map = tf.exp(output_map) - sum_exp = tf.reduce_sum(exponential_map, 3, keepdims=True) - tensor_sum_exp = tf.tile(sum_exp, tf.stack([1, 1, 1, tf.shape(output_map)[3]])) - return tf.div(exponential_map,tensor_sum_exp) - - + with tf.name_scope("pixel_wise_softmax_2"): + exponential_map = tf.exp(output_map) + sum_exp = tf.reduce_sum(exponential_map, 3, keepdims=True) + tensor_sum_exp = tf.tile(sum_exp, tf.stack([1, 1, 1, tf.shape(output_map)[3]])) + return tf.div(exponential_map,tensor_sum_exp) def cross_entropy(y_,output_map): return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 0182597..b3d4ba5 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -57,12 +57,14 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, features=features_root, filter_size=filter_size, pool_size=pool_size)) + # Placeholder for the input image - nx = tf.shape(x)[1] - ny = tf.shape(x)[2] - x_image = tf.reshape(x, tf.stack([-1, nx, ny, channels])) - in_node = x_image - batch_size = tf.shape(x_image)[0] + with tf.name_scope("preprocessing"): + nx = tf.shape(x)[1] + ny = tf.shape(x)[2] + x_image = tf.reshape(x, tf.stack([-1, nx, ny, channels])) + in_node = x_image + batch_size = tf.shape(x_image)[0] weights = [] biases = [] @@ -76,95 +78,98 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, size = in_size # down layers for layer in range(0, layers): - features = 2 ** layer * features_root - stddev = np.sqrt(2 / (filter_size ** 2 * features)) - if layer == 0: - w1 = weight_variable([filter_size, filter_size, channels, features], stddev) - else: - w1 = weight_variable([filter_size, filter_size, features // 2, features], stddev) - - w2 = weight_variable([filter_size, filter_size, features, features], stddev) - b1 = bias_variable([features]) - b2 = bias_variable([features]) - - conv1 = conv2d(in_node, w1, keep_prob) - tmp_h_conv = tf.nn.relu(conv1 + b1) - conv2 = conv2d(tmp_h_conv, w2, keep_prob) - dw_h_convs[layer] = tf.nn.relu(conv2 + b2) - - weights.append((w1, w2)) - biases.append((b1, b2)) - convs.append((conv1, conv2)) - - size -= 4 - if layer < layers - 1: - pools[layer] = max_pool(dw_h_convs[layer], pool_size) - in_node = pools[layer] - size /= 2 - - in_node = dw_h_convs[layers - 1] + with tf.name_scope("down_conv_{}".format(str(layer))): + features = 2 ** layer * features_root + stddev = np.sqrt(2 / (filter_size ** 2 * features)) + if layer == 0: + w1 = weight_variable([filter_size, filter_size, channels, features], stddev, name="w1") + else: + w1 = weight_variable([filter_size, filter_size, features // 2, features], stddev, name="w1") - # up layers - for layer in range(layers - 2, -1, -1): - features = 2 ** (layer + 1) * features_root - stddev = np.sqrt(2 / (filter_size ** 2 * features)) + w2 = weight_variable([filter_size, filter_size, features, features], stddev, name="w2") + b1 = bias_variable([features], name="b1") + b2 = bias_variable([features], name="b2") - wd = weight_variable_devonc([pool_size, pool_size, features // 2, features], stddev) - bd = bias_variable([features // 2]) - h_deconv = tf.nn.relu(deconv2d(in_node, wd, pool_size) + bd) - h_deconv_concat = crop_and_concat(dw_h_convs[layer], h_deconv) - deconv[layer] = h_deconv_concat + conv1 = conv2d(in_node, w1, keep_prob) + tmp_h_conv = tf.nn.relu(conv1 + b1) + conv2 = conv2d(tmp_h_conv, w2, keep_prob) + dw_h_convs[layer] = tf.nn.relu(conv2 + b2) - w1 = weight_variable([filter_size, filter_size, features, features // 2], stddev) - w2 = weight_variable([filter_size, filter_size, features // 2, features // 2], stddev) - b1 = bias_variable([features // 2]) - b2 = bias_variable([features // 2]) + weights.append((w1, w2)) + biases.append((b1, b2)) + convs.append((conv1, conv2)) - conv1 = conv2d(h_deconv_concat, w1, keep_prob) - h_conv = tf.nn.relu(conv1 + b1) - conv2 = conv2d(h_conv, w2, keep_prob) - in_node = tf.nn.relu(conv2 + b2) - up_h_convs[layer] = in_node + size -= 4 + if layer < layers - 1: + pools[layer] = max_pool(dw_h_convs[layer], pool_size) + in_node = pools[layer] + size /= 2 - weights.append((w1, w2)) - biases.append((b1, b2)) - convs.append((conv1, conv2)) + in_node = dw_h_convs[layers - 1] - size *= 2 - size -= 4 + # up layers + for layer in range(layers - 2, -1, -1): + with tf.name_scope("up_conv_{}".format(str(layer))): + features = 2 ** (layer + 1) * features_root + stddev = np.sqrt(2 / (filter_size ** 2 * features)) + + wd = weight_variable_devonc([pool_size, pool_size, features // 2, features], stddev, name="wd") + bd = bias_variable([features // 2], name="bd") + h_deconv = tf.nn.relu(deconv2d(in_node, wd, pool_size) + bd) + h_deconv_concat = crop_and_concat(dw_h_convs[layer], h_deconv) + deconv[layer] = h_deconv_concat + + w1 = weight_variable([filter_size, filter_size, features, features // 2], stddev, name="w1") + w2 = weight_variable([filter_size, filter_size, features // 2, features // 2], stddev, name="w2") + b1 = bias_variable([features // 2], name="b1") + b2 = bias_variable([features // 2], name="b2") + + conv1 = conv2d(h_deconv_concat, w1, keep_prob) + h_conv = tf.nn.relu(conv1 + b1) + conv2 = conv2d(h_conv, w2, keep_prob) + in_node = tf.nn.relu(conv2 + b2) + up_h_convs[layer] = in_node + + weights.append((w1, w2)) + biases.append((b1, b2)) + convs.append((conv1, conv2)) + + size *= 2 + size -= 4 # Output Map - weight = weight_variable([1, 1, features_root, n_class], stddev) - bias = bias_variable([n_class]) - conv = conv2d(in_node, weight, tf.constant(1.0)) - output_map = tf.nn.relu(conv + bias) - up_h_convs["out"] = output_map + with tf.name_scope("output_map"): + weight = weight_variable([1, 1, features_root, n_class], stddev, name="w") + bias = bias_variable([n_class], name="bias") + conv = conv2d(in_node, weight, tf.constant(1.0)) + output_map = tf.nn.relu(conv + bias) + up_h_convs["out"] = output_map - if summaries: - for i, (c1, c2) in enumerate(convs): - tf.summary.image('summary_conv_%02d_01' % i, get_image_summary(c1)) - tf.summary.image('summary_conv_%02d_02' % i, get_image_summary(c2)) + if summaries: + for i, (c1, c2) in enumerate(convs): + tf.summary.image('summary_conv_%02d_01' % i, get_image_summary(c1)) + tf.summary.image('summary_conv_%02d_02' % i, get_image_summary(c2)) - for k in pools.keys(): - tf.summary.image('summary_pool_%02d' % k, get_image_summary(pools[k])) + for k in pools.keys(): + tf.summary.image('summary_pool_%02d' % k, get_image_summary(pools[k])) - for k in deconv.keys(): - tf.summary.image('summary_deconv_concat_%02d' % k, get_image_summary(deconv[k])) + for k in deconv.keys(): + tf.summary.image('summary_deconv_concat_%02d' % k, get_image_summary(deconv[k])) - for k in dw_h_convs.keys(): - tf.summary.histogram("dw_convolution_%02d" % k + '/activations', dw_h_convs[k]) + for k in dw_h_convs.keys(): + tf.summary.histogram("dw_convolution_%02d" % k + '/activations', dw_h_convs[k]) - for k in up_h_convs.keys(): - tf.summary.histogram("up_convolution_%s" % k + '/activations', up_h_convs[k]) + for k in up_h_convs.keys(): + tf.summary.histogram("up_convolution_%s" % k + '/activations', up_h_convs[k]) - variables = [] - for w1, w2 in weights: - variables.append(w1) - variables.append(w2) + variables = [] + for w1, w2 in weights: + variables.append(w1) + variables.append(w2) - for b1, b2 in biases: - variables.append(b1) - variables.append(b2) + for b1, b2 in biases: + variables.append(b1) + variables.append(b2) return output_map, variables, int(in_size - size) @@ -185,9 +190,9 @@ def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, self.n_class = n_class self.summaries = kwargs.get("summaries", True) - self.x = tf.placeholder("float", shape=[None, None, None, channels]) - self.y = tf.placeholder("float", shape=[None, None, None, n_class]) - self.keep_prob = tf.placeholder(tf.float32) # dropout (keep probability) + self.x = tf.placeholder("float", shape=[None, None, None, channels], name="x") + self.y = tf.placeholder("float", shape=[None, None, None, n_class], name="y") + self.keep_prob = tf.placeholder(tf.float32, name="dropout_probability") # dropout (keep probability) logits, self.variables, self.offset = create_conv_net(self.x, self.keep_prob, channels, n_class, **kwargs) @@ -195,12 +200,14 @@ def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, self.gradients_node = tf.gradients(self.cost, self.variables) - self.cross_entropy = tf.reduce_mean(cross_entropy(tf.reshape(self.y, [-1, n_class]), - tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class]))) + with tf.name_scope("xent"): + self.cross_entropy = tf.reduce_mean(cross_entropy(tf.reshape(self.y, [-1, n_class]), + tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class]))) - self.predicter = pixel_wise_softmax_2(logits) - self.correct_pred = tf.equal(tf.argmax(self.predicter, 3), tf.argmax(self.y, 3)) - self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32)) + with tf.name_scope("results"): + self.predicter = pixel_wise_softmax_2(logits) + self.correct_pred = tf.equal(tf.argmax(self.predicter, 3), tf.argmax(self.y, 3)) + self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32)) def _get_cost(self, logits, cost_name, cost_kwargs): """ @@ -210,42 +217,43 @@ def _get_cost(self, logits, cost_name, cost_kwargs): regularizer: power of the L2 regularizers added to the loss function """ - flat_logits = tf.reshape(logits, [-1, self.n_class]) - flat_labels = tf.reshape(self.y, [-1, self.n_class]) - if cost_name == "cross_entropy": - class_weights = cost_kwargs.pop("class_weights", None) + with tf.name_scope("cost"): + flat_logits = tf.reshape(logits, [-1, self.n_class]) + flat_labels = tf.reshape(self.y, [-1, self.n_class]) + if cost_name == "cross_entropy": + class_weights = cost_kwargs.pop("class_weights", None) - if class_weights is not None: - class_weights = tf.constant(np.array(class_weights, dtype=np.float32)) + if class_weights is not None: + class_weights = tf.constant(np.array(class_weights, dtype=np.float32)) - weight_map = tf.multiply(flat_labels, class_weights) - weight_map = tf.reduce_sum(weight_map, axis=1) + weight_map = tf.multiply(flat_labels, class_weights) + weight_map = tf.reduce_sum(weight_map, axis=1) - loss_map = tf.nn.softmax_cross_entropy_with_logits_v2(logits=flat_logits, - labels=flat_labels) - weighted_loss = tf.multiply(loss_map, weight_map) + loss_map = tf.nn.softmax_cross_entropy_with_logits_v2(logits=flat_logits, + labels=flat_labels) + weighted_loss = tf.multiply(loss_map, weight_map) - loss = tf.reduce_mean(weighted_loss) + loss = tf.reduce_mean(weighted_loss) - else: - loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=flat_logits, - labels=flat_labels)) - elif cost_name == "dice_coefficient": - eps = 1e-5 - prediction = pixel_wise_softmax_2(logits) - intersection = tf.reduce_sum(prediction * self.y) - union = eps + tf.reduce_sum(prediction) + tf.reduce_sum(self.y) - loss = -(2 * intersection / (union)) + else: + loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=flat_logits, + labels=flat_labels)) + elif cost_name == "dice_coefficient": + eps = 1e-5 + prediction = pixel_wise_softmax_2(logits) + intersection = tf.reduce_sum(prediction * self.y) + union = eps + tf.reduce_sum(prediction) + tf.reduce_sum(self.y) + loss = -(2 * intersection / (union)) - else: - raise ValueError("Unknown cost function: " % cost_name) + else: + raise ValueError("Unknown cost function: " % cost_name) - regularizer = cost_kwargs.pop("regularizer", None) - if regularizer is not None: - regularizers = sum([tf.nn.l2_loss(variable) for variable in self.variables]) - loss += (regularizer * regularizers) + regularizer = cost_kwargs.pop("regularizer", None) + if regularizer is not None: + regularizers = sum([tf.nn.l2_loss(variable) for variable in self.variables]) + loss += (regularizer * regularizers) - return loss + return loss def predict(self, model_path, x_test): """ @@ -332,7 +340,7 @@ def _get_optimizer(self, training_iters, global_step): global_step=global_step) elif self.optimizer == "adam": learning_rate = self.opt_kwargs.pop("learning_rate", 0.001) - self.learning_rate_node = tf.Variable(learning_rate) + self.learning_rate_node = tf.Variable(learning_rate, name="learning_rate") optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate_node, **self.opt_kwargs).minimize(self.net.cost, @@ -341,9 +349,9 @@ def _get_optimizer(self, training_iters, global_step): return optimizer def _initialize(self, training_iters, output_path, restore, prediction_path): - global_step = tf.Variable(0) + global_step = tf.Variable(0, name="global_step") - self.norm_gradients_node = tf.Variable(tf.constant(0.0, shape=[len(self.net.gradients_node)])) + self.norm_gradients_node = tf.Variable(tf.constant(0.0, shape=[len(self.net.gradients_node)]), name="norm_gradients") if self.net.summaries and self.norm_grads: tf.summary.histogram('norm_grads', self.norm_gradients_node) From 4b75be685c46e918f59a62fa1a8a21fac12befcd Mon Sep 17 00:00:00 2001 From: James Guillochon Date: Thu, 7 Jun 2018 13:14:50 -0400 Subject: [PATCH 06/45] Fix broken links --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 46bcfc7..02858d3 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -31,5 +31,5 @@ Keep track of the learning progress using *Tensorboard*. **tf_unet** automatical :align: center -More examples can be found in the Jupyter notebooks for a `toy problem `_ or for a `RFI problem `_. +More examples can be found in the Jupyter notebooks for a `toy problem `_ or for a `RFI problem `_. Further code is stored in the `scripts `_ folder. From 9f0e79b6a38c0bbb26d674d83851e18ca0f379cc Mon Sep 17 00:00:00 2001 From: "Wm. Keith van der Meulen" Date: Sun, 10 Jun 2018 15:18:59 -0600 Subject: [PATCH 07/45] Update namescopes --- tf_unet/layers.py | 17 ++++++++++------- tf_unet/unet.py | 5 ++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index 36f1eaa..81c0588 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -33,13 +33,15 @@ def bias_variable(shape, name="bias"): return tf.Variable(initial, name=name) def conv2d(x, W,keep_prob_): - conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') - return tf.nn.dropout(conv_2d, keep_prob_) + with tf.name_scope("conv2d"): + conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID', name="conv2d") + return tf.nn.dropout(conv_2d, keep_prob_) def deconv2d(x, W,stride): - x_shape = tf.shape(x) - output_shape = tf.stack([x_shape[0], x_shape[1]*2, x_shape[2]*2, x_shape[3]//2]) - return tf.nn.conv2d_transpose(x, W, output_shape, strides=[1, stride, stride, 1], padding='VALID') + with tf.name_scope("deconv2d"): + x_shape = tf.shape(x) + output_shape = tf.stack([x_shape[0], x_shape[1]*2, x_shape[2]*2, x_shape[3]//2]) + return tf.nn.conv2d_transpose(x, W, output_shape, strides=[1, stride, stride, 1], padding='VALID', name="conv2d_transpose") def max_pool(x,n): return tf.nn.max_pool(x, ksize=[1, n, n, 1], strides=[1, n, n, 1], padding='VALID') @@ -68,5 +70,6 @@ def pixel_wise_softmax_2(output_map): return tf.div(exponential_map,tensor_sum_exp) def cross_entropy(y_,output_map): - return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") -# return tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(output_map), reduction_indices=[1])) + with tf.name_scope("xent"): + return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") + #return tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(output_map), reduction_indices=[1])) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index b3d4ba5..51c0236 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -200,9 +200,8 @@ def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, self.gradients_node = tf.gradients(self.cost, self.variables) - with tf.name_scope("xent"): - self.cross_entropy = tf.reduce_mean(cross_entropy(tf.reshape(self.y, [-1, n_class]), - tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class]))) + self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), + tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class])) with tf.name_scope("results"): self.predicter = pixel_wise_softmax_2(logits) From 0c2f31d705f156301db26846d77c9dd0f261ef28 Mon Sep 17 00:00:00 2001 From: "Wm. Keith van der Meulen" Date: Tue, 12 Jun 2018 09:24:05 -0600 Subject: [PATCH 08/45] Fix xent namescope for graph --- tf_unet/layers.py | 5 ++--- tf_unet/unet.py | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index 81c0588..cdbd82b 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -70,6 +70,5 @@ def pixel_wise_softmax_2(output_map): return tf.div(exponential_map,tensor_sum_exp) def cross_entropy(y_,output_map): - with tf.name_scope("xent"): - return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") - #return tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(output_map), reduction_indices=[1])) + return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") + #return tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(output_map), reduction_indices=[1])) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 51c0236..ddf71f8 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -200,8 +200,10 @@ def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, self.gradients_node = tf.gradients(self.cost, self.variables) - self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), - tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class])) + + with tf.name_scope("xent"): + self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), + tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class])) with tf.name_scope("results"): self.predicter = pixel_wise_softmax_2(logits) From f5febfdc5a587eaf6a79a953fa3a36cf5bea8612 Mon Sep 17 00:00:00 2001 From: "Wm. Keith van der Meulen" Date: Tue, 12 Jun 2018 09:38:45 -0600 Subject: [PATCH 09/45] Move bias addition before dropout --- tf_unet/layers.py | 4 ++-- tf_unet/unet.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index b3f6bf8..e751d40 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -32,8 +32,8 @@ def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) -def conv2d(x, W,keep_prob_): - conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') +def conv2d(x, W, b, keep_prob_): + conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') + b return tf.nn.dropout(conv_2d, keep_prob_) def deconv2d(x, W,stride): diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 0182597..94004dc 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -87,10 +87,10 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, b1 = bias_variable([features]) b2 = bias_variable([features]) - conv1 = conv2d(in_node, w1, keep_prob) - tmp_h_conv = tf.nn.relu(conv1 + b1) - conv2 = conv2d(tmp_h_conv, w2, keep_prob) - dw_h_convs[layer] = tf.nn.relu(conv2 + b2) + conv1 = conv2d(in_node, w1, b1, keep_prob) + tmp_h_conv = tf.nn.relu(conv1) + conv2 = conv2d(tmp_h_conv, w2, b2, keep_prob) + dw_h_convs[layer] = tf.nn.relu(conv2) weights.append((w1, w2)) biases.append((b1, b2)) @@ -120,10 +120,10 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, b1 = bias_variable([features // 2]) b2 = bias_variable([features // 2]) - conv1 = conv2d(h_deconv_concat, w1, keep_prob) - h_conv = tf.nn.relu(conv1 + b1) - conv2 = conv2d(h_conv, w2, keep_prob) - in_node = tf.nn.relu(conv2 + b2) + conv1 = conv2d(h_deconv_concat, w1, b1, keep_prob) + h_conv = tf.nn.relu(conv1) + conv2 = conv2d(h_conv, w2, b2, keep_prob) + in_node = tf.nn.relu(conv2) up_h_convs[layer] = in_node weights.append((w1, w2)) From 02727e233543e6b742dec0c1c9933007b68249dd Mon Sep 17 00:00:00 2001 From: "Wm. Keith van der Meulen" Date: Thu, 14 Jun 2018 08:57:46 -0600 Subject: [PATCH 10/45] Switch bias add to TensorFlow function --- tf_unet/layers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index e751d40..bee8d14 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -33,8 +33,9 @@ def bias_variable(shape): return tf.Variable(initial) def conv2d(x, W, b, keep_prob_): - conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') + b - return tf.nn.dropout(conv_2d, keep_prob_) + conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') + conv_2d_b = tf.nn.bias_add(conv2d, b) + return tf.nn.dropout(conv_2d_b, keep_prob_) def deconv2d(x, W,stride): x_shape = tf.shape(x) From 62f98209b94943eedb1e1195207c18062da4bed7 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Thu, 14 Jun 2018 22:05:28 +0200 Subject: [PATCH 11/45] fixing dropout issue --- tf_unet/layers.py | 2 +- tf_unet/unet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index bee8d14..fd178a8 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -34,7 +34,7 @@ def bias_variable(shape): def conv2d(x, W, b, keep_prob_): conv_2d = tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='VALID') - conv_2d_b = tf.nn.bias_add(conv2d, b) + conv_2d_b = tf.nn.bias_add(conv_2d, b) return tf.nn.dropout(conv_2d_b, keep_prob_) def deconv2d(x, W,stride): diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 94004dc..4ac84ce 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -136,7 +136,7 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, # Output Map weight = weight_variable([1, 1, features_root, n_class], stddev) bias = bias_variable([n_class]) - conv = conv2d(in_node, weight, tf.constant(1.0)) + conv = conv2d(in_node, weight, bias, tf.constant(1.0)) output_map = tf.nn.relu(conv + bias) up_h_convs["out"] = output_map From 24c99f79aee0530b084344169e7c216a007cae6a Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 23 Jun 2018 11:14:06 +0200 Subject: [PATCH 12/45] Merge branch 'tensorboard_cleanup' of https://github.com/wkeithvan/tf_unet into wkeithvan-tensorboard_cleanup # Conflicts: # tf_unet/layers.py # tf_unet/unet.py --- tf_unet/unet.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index e33121e..373d7ed 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -141,26 +141,26 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, with tf.name_scope("output_map"): weight = weight_variable([1, 1, features_root, n_class], stddev) bias = bias_variable([n_class], name="bias") - conv = conv2d(in_node, weight, tf.constant(1.0)) - output_map = tf.nn.relu(conv + bias) + conv = conv2d(in_node, weight, bias, tf.constant(1.0)) + output_map = tf.nn.relu(conv) up_h_convs["out"] = output_map - if summaries: - for i, (c1, c2) in enumerate(convs): - tf.summary.image('summary_conv_%02d_01' % i, get_image_summary(c1)) - tf.summary.image('summary_conv_%02d_02' % i, get_image_summary(c2)) + if summaries: + for i, (c1, c2) in enumerate(convs): + tf.summary.image('summary_conv_%02d_01' % i, get_image_summary(c1)) + tf.summary.image('summary_conv_%02d_02' % i, get_image_summary(c2)) - for k in pools.keys(): - tf.summary.image('summary_pool_%02d' % k, get_image_summary(pools[k])) + for k in pools.keys(): + tf.summary.image('summary_pool_%02d' % k, get_image_summary(pools[k])) - for k in deconv.keys(): - tf.summary.image('summary_deconv_concat_%02d' % k, get_image_summary(deconv[k])) + for k in deconv.keys(): + tf.summary.image('summary_deconv_concat_%02d' % k, get_image_summary(deconv[k])) - for k in dw_h_convs.keys(): - tf.summary.histogram("dw_convolution_%02d" % k + '/activations', dw_h_convs[k]) + for k in dw_h_convs.keys(): + tf.summary.histogram("dw_convolution_%02d" % k + '/activations', dw_h_convs[k]) - for k in up_h_convs.keys(): - tf.summary.histogram("up_convolution_%s" % k + '/activations', up_h_convs[k]) + for k in up_h_convs.keys(): + tf.summary.histogram("up_convolution_%s" % k + '/activations', up_h_convs[k]) variables = [] for w1, w2 in weights: From 1d4d552f51686bcd056b3c43acad53d9505b8c8f Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 23 Jun 2018 11:35:59 +0200 Subject: [PATCH 13/45] updated authr list --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 03d8087..465b8cf 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -16,6 +16,7 @@ Contributors * `@AlessioM `_ * `@FiLeonard `_ * `@nikkou `_ +* `@wkeithvan `_ Citations --------- From 523a8e7469c2515a37d1a046f683d509875fdbfb Mon Sep 17 00:00:00 2001 From: Sam Murphy Date: Mon, 25 Jun 2018 11:35:42 +0100 Subject: [PATCH 14/45] increase training_iters in demo toy problem A friend an I found that 20 iterations wasn't enough to converge on a solution. Increasing to 50 iterations successfully executed on my old laptop and resulted in decent prediction accuracy. I would suggest increasing the iterations in the toy problem because it is very important that this example works out of the box. --- demo/demo_toy_problem.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/demo_toy_problem.ipynb b/demo/demo_toy_problem.ipynb index 88ae8ec..6e7fa4d 100644 --- a/demo/demo_toy_problem.ipynb +++ b/demo/demo_toy_problem.ipynb @@ -189,7 +189,7 @@ } ], "source": [ - "path = trainer.train(generator, \"./unet_trained\", training_iters=20, epochs=10, display_step=2)" + "path = trainer.train(generator, \"./unet_trained\", training_iters=50, epochs=10, display_step=2)" ] }, { From 67bd0ba416366c2b31006a5bf951ae9586135f7c Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Mon, 25 Jun 2018 18:33:08 +0200 Subject: [PATCH 15/45] numerically stable cross entropy computation --- tf_unet/layers.py | 18 ++++++------------ tf_unet/unet.py | 12 +++++------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index 408bdca..99de400 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -59,17 +59,11 @@ def crop_and_concat(x1,x2): def pixel_wise_softmax(output_map): with tf.name_scope("pixel_wise_softmax"): - exponential_map = tf.exp(output_map) - evidence = tf.add(exponential_map,tf.reverse(exponential_map,[False,False,False,True])) - return tf.div(exponential_map,evidence, name="pixel_wise_softmax") - -def pixel_wise_softmax_2(output_map): - with tf.name_scope("pixel_wise_softmax_2"): - exponential_map = tf.exp(output_map) - sum_exp = tf.reduce_sum(exponential_map, 3, keepdims=True) - tensor_sum_exp = tf.tile(sum_exp, tf.stack([1, 1, 1, tf.shape(output_map)[3]])) - return tf.div(exponential_map,tensor_sum_exp) + max_axis = tf.reduce_max(output_map, axis=3, keepdims=True) + exponential_map = tf.exp(output_map - max_axis) + normalize = tf.reduce_sum(exponential_map, axis=3, keepdims=True) + return exponential_map / normalize def cross_entropy(y_,output_map): - return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") - #return tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(output_map), reduction_indices=[1])) + with tf.name_scope("xent"): + return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 373d7ed..9d80431 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -29,7 +29,7 @@ from tf_unet import util from tf_unet.layers import (weight_variable, weight_variable_devonc, bias_variable, - conv2d, deconv2d, max_pool, crop_and_concat, pixel_wise_softmax_2, + conv2d, deconv2d, max_pool, crop_and_concat, pixel_wise_softmax, cross_entropy) logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') @@ -200,13 +200,11 @@ def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, self.gradients_node = tf.gradients(self.cost, self.variables) - - with tf.name_scope("xent"): - self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), - tf.reshape(pixel_wise_softmax_2(logits), [-1, n_class])) + self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), + tf.reshape(pixel_wise_softmax(logits), [-1, n_class])) with tf.name_scope("results"): - self.predicter = pixel_wise_softmax_2(logits) + self.predicter = pixel_wise_softmax(logits) self.correct_pred = tf.equal(tf.argmax(self.predicter, 3), tf.argmax(self.y, 3)) self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32)) @@ -241,7 +239,7 @@ def _get_cost(self, logits, cost_name, cost_kwargs): labels=flat_labels)) elif cost_name == "dice_coefficient": eps = 1e-5 - prediction = pixel_wise_softmax_2(logits) + prediction = pixel_wise_softmax(logits) intersection = tf.reduce_sum(prediction * self.y) union = eps + tf.reduce_sum(prediction) + tf.reduce_sum(self.y) loss = -(2 * intersection / (union)) From ac3fb5f90314573c5adbffe7af94b61d402655ad Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Mon, 25 Jun 2018 18:47:13 +0200 Subject: [PATCH 16/45] parametrized verification batch size --- tf_unet/unet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 9d80431..5fa8ac2 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -307,17 +307,17 @@ class Trainer(object): :param net: the unet instance to train :param batch_size: size of training batch + :param verification_batch_size: size of verification batch :param norm_grads: (optional) true if normalized gradients should be added to the summaries :param optimizer: (optional) name of the optimizer to use (momentum or adam) :param opt_kwargs: (optional) kwargs passed to the learning rate (momentum opt) and to the optimizer """ - verification_batch_size = 4 - - def __init__(self, net, batch_size=1, norm_grads=False, optimizer="momentum", opt_kwargs={}): + def __init__(self, net, batch_size=1, verification_batch_size = 4, norm_grads=False, optimizer="momentum", opt_kwargs={}): self.net = net self.batch_size = batch_size + self.verification_batch_size = verification_batch_size self.norm_grads = norm_grads self.optimizer = optimizer self.opt_kwargs = opt_kwargs From 45f379e3d31e663ff5c8073b91344700f7ab6026 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Mon, 25 Jun 2018 18:47:29 +0200 Subject: [PATCH 17/45] simplified demo --- scripts/launcher.py | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/scripts/launcher.py b/scripts/launcher.py index 4a35fd7..93d6881 100644 --- a/scripts/launcher.py +++ b/scripts/launcher.py @@ -18,39 +18,31 @@ author: jakeret ''' from __future__ import print_function, division, absolute_import, unicode_literals +import numpy as np from tf_unet import image_gen from tf_unet import unet from tf_unet import util if __name__ == '__main__': - nx = 572 - ny = 572 - - training_iters = 20 - epochs = 100 - dropout = 0.75 # Dropout, probability to keep units - display_step = 2 - restore = False - - generator = image_gen.RgbDataProvider(nx, ny, cnt=20, rectangles=False) + np.random.seed(9876) + + generator = image_gen.GrayScaleDataProvider(nx=572, ny=572, cnt=20, rectangles=False) net = unet.Unet(channels=generator.channels, n_class=generator.n_class, - layers=3, - features_root=16, - cost="dice_coefficient") + layers=3, + features_root=16) trainer = unet.Trainer(net, optimizer="momentum", opt_kwargs=dict(momentum=0.2)) - path = trainer.train(generator, "./unet_trained", - training_iters=training_iters, - epochs=epochs, - dropout=dropout, - display_step=display_step, - restore=restore) + path = trainer.train(generator, "./unet_trained", + training_iters=32, + epochs=5, + dropout=0.75,# probability to keep units + display_step=2) x_test, y_test = generator(4) prediction = net.predict(path, x_test) - print("Testing error rate: {:.2f}%".format(unet.error_rate(prediction, util.crop_to_shape(y_test, prediction.shape)))) - \ No newline at end of file + print("Testing error rate: {:.2f}%".format(unet.error_rate(prediction, + util.crop_to_shape(y_test, prediction.shape)))) From 7279063ff61425abf90083d40ab3eda8ab6bd506 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Mon, 25 Jun 2018 19:02:32 +0200 Subject: [PATCH 18/45] simplified demo --- scripts/launcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/launcher.py b/scripts/launcher.py index 93d6881..c07e671 100644 --- a/scripts/launcher.py +++ b/scripts/launcher.py @@ -25,7 +25,7 @@ if __name__ == '__main__': - np.random.seed(9876) + np.random.seed(98765) generator = image_gen.GrayScaleDataProvider(nx=572, ny=572, cnt=20, rectangles=False) From 4dba4a42f44e10f98d10ae11223e37c4405777fd Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Mon, 25 Jun 2018 19:10:26 +0200 Subject: [PATCH 19/45] prettified graph --- tf_unet/unet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 5fa8ac2..09b52d0 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -145,7 +145,8 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, output_map = tf.nn.relu(conv) up_h_convs["out"] = output_map - if summaries: + if summaries: + with tf.name_scope("summaries"): for i, (c1, c2) in enumerate(convs): tf.summary.image('summary_conv_%02d_01' % i, get_image_summary(c1)) tf.summary.image('summary_conv_%02d_02' % i, get_image_summary(c2)) From 01e084ba247944ca753297b332bceaef4d7cafca Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Mon, 25 Jun 2018 19:13:39 +0200 Subject: [PATCH 20/45] prettified graph --- tf_unet/layers.py | 3 +-- tf_unet/unet.py | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tf_unet/layers.py b/tf_unet/layers.py index 99de400..810094d 100644 --- a/tf_unet/layers.py +++ b/tf_unet/layers.py @@ -65,5 +65,4 @@ def pixel_wise_softmax(output_map): return exponential_map / normalize def cross_entropy(y_,output_map): - with tf.name_scope("xent"): - return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") + return -tf.reduce_mean(y_*tf.log(tf.clip_by_value(output_map,1e-10,1.0)), name="cross_entropy") diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 09b52d0..339fb6f 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -201,8 +201,9 @@ def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, self.gradients_node = tf.gradients(self.cost, self.variables) - self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), - tf.reshape(pixel_wise_softmax(logits), [-1, n_class])) + with tf.name_scope("cross_entropy"): + self.cross_entropy = cross_entropy(tf.reshape(self.y, [-1, n_class]), + tf.reshape(pixel_wise_softmax(logits), [-1, n_class])) with tf.name_scope("results"): self.predicter = pixel_wise_softmax(logits) From 2fb257e24013582a6cea471bb2190d111882b1d3 Mon Sep 17 00:00:00 2001 From: Sam Murphy Date: Mon, 25 Jun 2018 21:36:17 +0100 Subject: [PATCH 21/45] training _iters = 32 --- demo/demo_toy_problem.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/demo_toy_problem.ipynb b/demo/demo_toy_problem.ipynb index 6e7fa4d..56a0a41 100644 --- a/demo/demo_toy_problem.ipynb +++ b/demo/demo_toy_problem.ipynb @@ -189,7 +189,7 @@ } ], "source": [ - "path = trainer.train(generator, \"./unet_trained\", training_iters=50, epochs=10, display_step=2)" + "path = trainer.train(generator, \"./unet_trained\", training_iters=32, epochs=10, display_step=2)" ] }, { From 57990285b49fb88186696442c240e533b058d9cb Mon Sep 17 00:00:00 2001 From: Sam Murphy Date: Mon, 25 Jun 2018 21:43:19 +0100 Subject: [PATCH 22/45] np.random.seed(98765) would this be a good place to set the seed for the random number generator? --- demo/demo_toy_problem.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/demo_toy_problem.ipynb b/demo/demo_toy_problem.ipynb index 56a0a41..252b93d 100644 --- a/demo/demo_toy_problem.ipynb +++ b/demo/demo_toy_problem.ipynb @@ -13,7 +13,8 @@ "import matplotlib.pyplot as plt\n", "import matplotlib\n", "import numpy as np\n", - "plt.rcParams['image.cmap'] = 'gist_earth'" + "plt.rcParams['image.cmap'] = 'gist_earth'\n", + "np.random.seed(98765)" ] }, { From b47695a5e163ba643c0ee272868e497d54ff755c Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Fri, 28 Dec 2018 11:56:29 +0100 Subject: [PATCH 23/45] avoiding bug if all pixel values are 0 --- tf_unet/image_util.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tf_unet/image_util.py b/tf_unet/image_util.py index a2deda6..92b5879 100644 --- a/tf_unet/image_util.py +++ b/tf_unet/image_util.py @@ -16,11 +16,11 @@ ''' from __future__ import print_function, division, absolute_import, unicode_literals -#import cv2 import glob import numpy as np from PIL import Image + class BaseDataProvider(object): """ Abstract base class for DataProvider implementation. Subclasses have to @@ -37,7 +37,6 @@ class BaseDataProvider(object): channels = 1 n_class = 2 - def __init__(self, a_min=None, a_max=None): self.a_min = a_min if a_min is not None else -np.inf @@ -71,7 +70,10 @@ def _process_data(self, data): # normalization data = np.clip(np.fabs(data), self.a_min, self.a_max) data -= np.amin(data) - data /= np.amax(data) + + if np.amax(data) != 0: + data /= np.amax(data) + return data def _post_process(self, data, labels): @@ -99,7 +101,8 @@ def __call__(self, n): Y[i] = labels return X, Y - + + class SimpleDataProvider(BaseDataProvider): """ A simple data provider for numpy arrays. @@ -135,6 +138,7 @@ class ImageDataProvider(BaseDataProvider): Assumes that the data images and label images are stored in the same folder and that the labels have a different file suffix e.g. 'train/fish_1.tif' and 'train/fish_1_mask.tif' + Number of pixels in x and y of the images and masks should be even. Usage: data_provider = ImageDataProvider("..fishes/train/*.tif") @@ -173,10 +177,8 @@ def _find_data_files(self, search_path): all_files = glob.glob(search_path) return [name for name in all_files if self.data_suffix in name and not self.mask_suffix in name] - def _load_file(self, path, dtype=np.float32): return np.array(Image.open(path), dtype) - # return np.squeeze(cv2.imread(image_name, cv2.IMREAD_GRAYSCALE)) def _cylce_file(self): self.file_idx += 1 From 22ec4185fc57c02cc817e2be224d99703e08e6d6 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Fri, 28 Dec 2018 11:57:11 +0100 Subject: [PATCH 24/45] making size computation more explicit --- tf_unet/unet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 339fb6f..61f11bb 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -99,11 +99,11 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, biases.append((b1, b2)) convs.append((conv1, conv2)) - size -= 4 + size -= 4 # valid conv if layer < layers - 1: pools[layer] = max_pool(dw_h_convs[layer], pool_size) in_node = pools[layer] - size /= 2 + size /= pool_size in_node = dw_h_convs[layers - 1] @@ -134,8 +134,8 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, biases.append((b1, b2)) convs.append((conv1, conv2)) - size *= 2 - size -= 4 + size *= pool_size + size -= 4 # valid conv # Output Map with tf.name_scope("output_map"): From 804158e1a4060d9fda9d3950f0b63b5a485d6047 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Fri, 28 Dec 2018 12:27:18 +0100 Subject: [PATCH 25/45] adding support to uneven img sizes --- tf_unet/util.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tf_unet/util.py b/tf_unet/util.py index a552dcc..8afb39d 100644 --- a/tf_unet/util.py +++ b/tf_unet/util.py @@ -82,9 +82,19 @@ def crop_to_shape(data, shape): :param data: the array to crop :param shape: the target shape """ - offset0 = (data.shape[1] - shape[1])//2 - offset1 = (data.shape[2] - shape[2])//2 - return data[:, offset0:(-offset0), offset1:(-offset1)] + diff_nx = (data.shape[1] - shape[1]) + diff_ny = (data.shape[2] - shape[2]) + + offset_nx_left = diff_nx // 2 + offset_nx_right = diff_nx - offset_nx_left + offset_ny_left = diff_ny // 2 + offset_ny_right = diff_ny - offset_ny_left + + cropped = data[:, offset_nx_left:(-offset_nx_right), offset_ny_left:(-offset_ny_right)] + + assert cropped.shape[1] == shape[1] + assert cropped.shape[2] == shape[2] + return cropped def combine_img_prediction(data, gt, pred): """ From 9b08e8224a3d4b5a972cd4581a2bc8b7981f70a3 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 29 Dec 2018 16:00:56 +0100 Subject: [PATCH 26/45] simplifing image provider usage --- scripts/ultrasound_launcher.py | 4 ++++ tf_unet/image_util.py | 32 +++++++++++++++++--------------- tf_unet/unet.py | 6 +++--- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/scripts/ultrasound_launcher.py b/scripts/ultrasound_launcher.py index 3b77f15..ba6ff50 100644 --- a/scripts/ultrasound_launcher.py +++ b/scripts/ultrasound_launcher.py @@ -43,6 +43,10 @@ def create_training_path(output_path): @click.option('--features_root', default=64) def launch(data_root, output_path, training_iters, epochs, restore, layers, features_root): print("Using data from: %s"%data_root) + + if not os.path.exists(data_root): + raise IOError("Kaggle Ultrasound Dataset not found") + data_provider = ultrasound_util.DataProvider(data_root + "/*.tif", a_min=0, a_max=210) diff --git a/tf_unet/image_util.py b/tf_unet/image_util.py index 92b5879..3fe901b 100644 --- a/tf_unet/image_util.py +++ b/tf_unet/image_util.py @@ -114,18 +114,16 @@ class SimpleDataProvider(BaseDataProvider): :param label: label numpy array. Shape=[n, X, Y, classes] :param a_min: (optional) min value used for clipping :param a_max: (optional) max value used for clipping - :param channels: (optional) number of channels, default=1 - :param n_class: (optional) number of classes, default=2 - + """ - def __init__(self, data, label, a_min=None, a_max=None, channels=1, n_class = 2): + def __init__(self, data, label, a_min=None, a_max=None): super(SimpleDataProvider, self).__init__(a_min, a_max) self.data = data self.label = label self.file_count = data.shape[0] - self.n_class = n_class - self.channels = channels + self.n_class = label.shape[-1] + self.channels = data.shape[-1] def _next_data(self): idx = np.random.choice(self.file_count) @@ -149,19 +147,16 @@ class ImageDataProvider(BaseDataProvider): :param data_suffix: suffix pattern for the data images. Default '.tif' :param mask_suffix: suffix pattern for the label images. Default '_mask.tif' :param shuffle_data: if the order of the loaded file path should be randomized. Default 'True' - :param channels: (optional) number of channels, default=1 - :param n_class: (optional) number of classes, default=2 - + """ - def __init__(self, search_path, a_min=None, a_max=None, data_suffix=".tif", mask_suffix='_mask.tif', shuffle_data=True, n_class = 2): + def __init__(self, search_path, a_min=None, a_max=None, data_suffix=".tif", mask_suffix='_mask.tif', shuffle_data=True): super(ImageDataProvider, self).__init__(a_min, a_max) self.data_suffix = data_suffix self.mask_suffix = mask_suffix self.file_idx = -1 self.shuffle_data = shuffle_data - self.n_class = n_class - + self.data_files = self._find_data_files(search_path) if self.shuffle_data: @@ -169,10 +164,17 @@ def __init__(self, search_path, a_min=None, a_max=None, data_suffix=".tif", mask assert len(self.data_files) > 0, "No training files" print("Number of files used: %s" % len(self.data_files)) - - img = self._load_file(self.data_files[0]) + + image_path = self.data_files[0] + label_path = image_path.replace(self.data_suffix, self.mask_suffix) + img = self._load_file(image_path) + mask = self._load_file(label_path) self.channels = 1 if len(img.shape) == 2 else img.shape[-1] - + self.n_class = 2 if len(mask.shape) == 2 else mask.shape[-1] + + print("Number of channels: %s"%self.channels) + print("Number of classes: %s"%self.n_class) + def _find_data_files(self, search_path): all_files = glob.glob(search_path) return [name for name in all_files if self.data_suffix in name and not self.mask_suffix in name] diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 61f11bb..5f1082b 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -179,13 +179,13 @@ class Unet(object): """ A unet implementation - :param channels: (optional) number of channels in the input image - :param n_class: (optional) number of output labels + :param channels: number of channels in the input image + :param n_class: number of output labels :param cost: (optional) name of the cost function. Default is 'cross_entropy' :param cost_kwargs: (optional) kwargs passed to the cost function. See Unet._get_cost for more options """ - def __init__(self, channels=3, n_class=2, cost="cross_entropy", cost_kwargs={}, **kwargs): + def __init__(self, channels, n_class, cost="cross_entropy", cost_kwargs={}, **kwargs): tf.reset_default_graph() self.n_class = n_class From b922a09d6abcce71f29ac381c2c36a9a7536fc82 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 29 Dec 2018 16:07:56 +0100 Subject: [PATCH 27/45] moving the creation of training path to utils --- scripts/ufig_launcher.py | 11 ++--------- scripts/ultrasound_launcher.py | 11 ++--------- tf_unet/util.py | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/scripts/ufig_launcher.py b/scripts/ufig_launcher.py index 1f74649..428efb0 100644 --- a/scripts/ufig_launcher.py +++ b/scripts/ufig_launcher.py @@ -18,7 +18,6 @@ author: jakeret ''' from __future__ import print_function, division, absolute_import, unicode_literals -import os import click from tf_unet import unet @@ -26,13 +25,6 @@ from scripts.ufig_util import DataProvider -def create_training_path(output_path): - idx = 0 - path = os.path.join(output_path, "run_{:03d}".format(idx)) - while os.path.exists(path): - idx += 1 - path = os.path.join(output_path, "run_{:03d}".format(idx)) - return path @click.command() @click.option('--data_root', default="./ufig_images/1.h5") @@ -56,7 +48,8 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat class_weights=weights), ) - path = output_path if restore else create_training_path(output_path) + path = output_path if restore else util.create_training_path(output_path) + trainer = unet.Trainer(net, optimizer="adam", opt_kwargs=dict(beta1=0.91)) path = trainer.train(data_provider, path, training_iters=training_iters, diff --git a/scripts/ultrasound_launcher.py b/scripts/ultrasound_launcher.py index ba6ff50..7b9e868 100644 --- a/scripts/ultrasound_launcher.py +++ b/scripts/ultrasound_launcher.py @@ -25,14 +25,6 @@ from tf_unet import util from scripts import ultrasound_util -def create_training_path(output_path): - idx = 0 - path = os.path.join(output_path, "run_{:03d}".format(idx)) - while os.path.exists(path): - idx += 1 - path = os.path.join(output_path, "run_{:03d}".format(idx)) - return path - @click.command() @click.option('--data_root', default="../../ultrasound/train") @click.option('--output_path', default="./unet_trained_ultrasound") @@ -57,7 +49,8 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat cost="dice_coefficient", ) - path = output_path if restore else create_training_path(output_path) + path = output_path if restore else util.create_training_path(output_path) + trainer = unet.Trainer(net, norm_grads=True, optimizer="adam") path = trainer.train(data_provider, path, training_iters=training_iters, diff --git a/tf_unet/util.py b/tf_unet/util.py index 8afb39d..4364bb4 100644 --- a/tf_unet/util.py +++ b/tf_unet/util.py @@ -18,6 +18,9 @@ author: jakeret ''' from __future__ import print_function, division, absolute_import, unicode_literals + +import os + import numpy as np from PIL import Image @@ -122,3 +125,17 @@ def save_image(img, path): """ Image.fromarray(img.round().astype(np.uint8)).save(path, 'JPEG', dpi=[300,300], quality=90) + +def create_training_path(output_path, prefix="run_"): + """ + Enumerates a new path using the prefix under the given output_path + :param output_path: the root path + :param prefix: (optional) defaults to `run_` + :return: the generated path as string in form `output_path`/`prefix_` + `` + """ + idx = 0 + path = os.path.join(output_path, "{:}{:03d}".format(prefix, idx)) + while os.path.exists(path): + idx += 1 + path = os.path.join(output_path, "{:}{:03d}".format(prefix, idx)) + return path \ No newline at end of file From 5de79886e99659ef1fec355856001861c6a28d90 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 29 Dec 2018 16:10:32 +0100 Subject: [PATCH 28/45] moving the creation of training path to utils --- scripts/rfi_launcher.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/scripts/rfi_launcher.py b/scripts/rfi_launcher.py index e8ae6d8..cae1dce 100644 --- a/scripts/rfi_launcher.py +++ b/scripts/rfi_launcher.py @@ -18,7 +18,6 @@ author: jakeret ''' from __future__ import print_function, division, absolute_import, unicode_literals -import os import glob import click @@ -27,13 +26,6 @@ from scripts.radio_util import DataProvider -def create_training_path(output_path): - idx = 0 - path = os.path.join(output_path, "run_{:03d}".format(idx)) - while os.path.exists(path): - idx += 1 - path = os.path.join(output_path, "run_{:03d}".format(idx)) - return path @click.command() @click.option('--data_root', default="./bleien_data") @@ -54,7 +46,7 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat cost_kwargs=dict(regularizer=0.001), ) - path = output_path if restore else create_training_path(output_path) + path = output_path if restore else util.create_training_path(output_path) trainer = unet.Trainer(net, optimizer="momentum", opt_kwargs=dict(momentum=0.2)) path = trainer.train(data_provider, path, training_iters=training_iters, From aefc65dcc0fa74c4133597aa24862eb682673cfd Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 29 Dec 2018 16:16:07 +0100 Subject: [PATCH 29/45] cleaning up the examples --- scripts/launcher.py | 3 ++ scripts/radio_util.py | 67 ---------------------------- scripts/rfi_launcher.py | 53 ++++++++++++++++++++-- scripts/ufig_launcher.py | 68 +++++++++++++++++++++++++++-- scripts/ufig_util.py | 80 ---------------------------------- scripts/ultrasound_launcher.py | 35 ++++++++++++--- scripts/ultrasound_util.py | 41 ----------------- 7 files changed, 148 insertions(+), 199 deletions(-) delete mode 100644 scripts/radio_util.py delete mode 100644 scripts/ufig_util.py delete mode 100644 scripts/ultrasound_util.py diff --git a/scripts/launcher.py b/scripts/launcher.py index c07e671..d983995 100644 --- a/scripts/launcher.py +++ b/scripts/launcher.py @@ -16,7 +16,10 @@ Created on Jul 28, 2016 author: jakeret + +Trains a tf_unet network to segment circles in noisy images. ''' + from __future__ import print_function, division, absolute_import, unicode_literals import numpy as np from tf_unet import image_gen diff --git a/scripts/radio_util.py b/scripts/radio_util.py deleted file mode 100644 index baa0e30..0000000 --- a/scripts/radio_util.py +++ /dev/null @@ -1,67 +0,0 @@ -# tf_unet is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# tf_unet is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with tf_unet. If not, see . - - -''' -Created on Aug 18, 2016 - -author: jakeret -''' -from __future__ import print_function, division, absolute_import, unicode_literals - -import h5py -import numpy as np -from tf_unet.image_util import BaseDataProvider - - -class DataProvider(BaseDataProvider): - """ - Extends the BaseDataProvider to randomly select the next - data chunk - """ - - channels = 1 - n_class = 2 - - def __init__(self, nx, files, a_min=30, a_max=210): - super(DataProvider, self).__init__(a_min, a_max) - self.nx = nx - self.files = files - - assert len(files) > 0, "No training files" - print("Number of files used: %s"%len(files)) - self._cylce_file() - - def _read_chunck(self): - with h5py.File(self.files[self.file_idx], "r") as fp: - nx = fp["data"].shape[1] - idx = np.random.randint(0, nx - self.nx) - - sl = slice(idx, (idx+self.nx)) - data = fp["data"][:, sl] - rfi = fp["mask"][:, sl] - return data, rfi - - def _next_data(self): - data, rfi = self._read_chunck() - nx = data.shape[1] - while nx < self.nx: - self._cylce_file() - data, rfi = self._read_chunck() - nx = data.shape[1] - - return data, rfi - - def _cylce_file(self): - self.file_idx = np.random.choice(len(self.files)) - diff --git a/scripts/rfi_launcher.py b/scripts/rfi_launcher.py index cae1dce..f0d924c 100644 --- a/scripts/rfi_launcher.py +++ b/scripts/rfi_launcher.py @@ -16,15 +16,20 @@ Created on Jul 28, 2016 author: jakeret + +Trains a tf_unet network to segment radio frequency interference pattern. +Requires data from the Bleien Observatory or a HIDE&SEEK simulation. ''' + from __future__ import print_function, division, absolute_import, unicode_literals import glob import click +import h5py +import numpy as np from tf_unet import unet from tf_unet import util - -from scripts.radio_util import DataProvider +from tf_unet.image_util import BaseDataProvider @click.command() @@ -62,4 +67,46 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat if __name__ == '__main__': - launch() \ No newline at end of file + launch() + + +class DataProvider(BaseDataProvider): + """ + Extends the BaseDataProvider to randomly select the next + data chunk + """ + + channels = 1 + n_class = 2 + + def __init__(self, nx, files, a_min=30, a_max=210): + super(DataProvider, self).__init__(a_min, a_max) + self.nx = nx + self.files = files + + assert len(files) > 0, "No training files" + print("Number of files used: %s"%len(files)) + self._cylce_file() + + def _read_chunck(self): + with h5py.File(self.files[self.file_idx], "r") as fp: + nx = fp["data"].shape[1] + idx = np.random.randint(0, nx - self.nx) + + sl = slice(idx, (idx+self.nx)) + data = fp["data"][:, sl] + rfi = fp["mask"][:, sl] + return data, rfi + + def _next_data(self): + data, rfi = self._read_chunck() + nx = data.shape[1] + while nx < self.nx: + self._cylce_file() + data, rfi = self._read_chunck() + nx = data.shape[1] + + return data, rfi + + def _cylce_file(self): + self.file_idx = np.random.choice(len(self.files)) \ No newline at end of file diff --git a/scripts/ufig_launcher.py b/scripts/ufig_launcher.py index 428efb0..cdf3219 100644 --- a/scripts/ufig_launcher.py +++ b/scripts/ufig_launcher.py @@ -16,14 +16,21 @@ Created on Jul 28, 2016 author: jakeret + +Trains a tf_unet network to segment stars and galaxies in a wide field image. +Requires data from a UFIG simulation. ''' + from __future__ import print_function, division, absolute_import, unicode_literals import click +import numpy as np + +from scipy.ndimage import gaussian_filter +import h5py from tf_unet import unet from tf_unet import util - -from scripts.ufig_util import DataProvider +from tf_unet.image_util import BaseDataProvider @click.command() @@ -64,4 +71,59 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat if __name__ == '__main__': - launch() \ No newline at end of file + launch() + + +class DataProvider(BaseDataProvider): + """ + Extends the BaseDataProvider to randomly select the next + chunk of the image and randomly applies transformations to the data + """ + + channels = 1 + n_class = 3 + + def __init__(self, nx, path, a_min=0, a_max=20, sigma=1): + super(DataProvider, self).__init__(a_min, a_max) + self.nx = nx + self.path = path + self.sigma = sigma + + self._load_data() + + def _load_data(self): + with h5py.File(self.path, "r") as fp: + self.image = gaussian_filter(fp["image"].value, self.sigma) + self.gal_map = fp["segmaps/galaxy"].value + self.star_map = fp["segmaps/star"].value + + def _transpose_3d(self, a): + return np.stack([a[..., i].T for i in range(a.shape[2])], axis=2) + + def _post_process(self, data, labels): + op = np.random.randint(0, 4) + if op == 0: + if np.random.randint(0, 2) == 0: + data, labels = self._transpose_3d(data[:,:,np.newaxis]), self._transpose_3d(labels) + else: + data, labels = np.rot90(data, op), np.rot90(labels, op) + + return data, labels + + def _next_data(self): + ix = np.random.randint(0, self.image.shape[0] - self.nx) + iy = np.random.randint(0, self.image.shape[1] - self.nx) + + slx = slice(ix, ix+self.nx) + sly = slice(iy, iy+self.nx) + + data = self.image[slx, sly] + gal_seg = self.gal_map[slx, sly] + star_seg = self.star_map[slx, sly] + + labels = np.zeros((self.nx, self.nx, self.n_class), dtype=np.float32) + labels[..., 1] = np.clip(gal_seg, 0, 1) + labels[..., 2] = np.clip(star_seg, 0, 1) + labels[..., 0] = (1+np.clip(labels[...,1] + labels[...,2], 0, 1))%2 + + return data, labels \ No newline at end of file diff --git a/scripts/ufig_util.py b/scripts/ufig_util.py deleted file mode 100644 index 22830eb..0000000 --- a/scripts/ufig_util.py +++ /dev/null @@ -1,80 +0,0 @@ -# tf_unet is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# tf_unet is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with tf_unet. If not, see . - - -''' -Created on Aug 30, 2016 - -author: jakeret -''' -from __future__ import print_function, division, absolute_import, unicode_literals - -import numpy as np -from scipy.ndimage import gaussian_filter -import h5py -from tf_unet.image_util import BaseDataProvider - -class DataProvider(BaseDataProvider): - """ - Extends the BaseDataProvider to randomly select the next - chunk of the image and randomly applies transformations to the data - """ - - channels = 1 - n_class = 3 - - def __init__(self, nx, path, a_min=0, a_max=20, sigma=1): - super(DataProvider, self).__init__(a_min, a_max) - self.nx = nx - self.path = path - self.sigma = sigma - - self._load_data() - - def _load_data(self): - with h5py.File(self.path, "r") as fp: - self.image = gaussian_filter(fp["image"].value, self.sigma) - self.gal_map = fp["segmaps/galaxy"].value - self.star_map = fp["segmaps/star"].value - - def _transpose_3d(self, a): - return np.stack([a[..., i].T for i in range(a.shape[2])], axis=2) - - def _post_process(self, data, labels): - op = np.random.randint(0, 4) - if op == 0: - if np.random.randint(0, 2) == 0: - data, labels = self._transpose_3d(data[:,:,np.newaxis]), self._transpose_3d(labels) - else: - data, labels = np.rot90(data, op), np.rot90(labels, op) - - return data, labels - - def _next_data(self): - ix = np.random.randint(0, self.image.shape[0] - self.nx) - iy = np.random.randint(0, self.image.shape[1] - self.nx) - - slx = slice(ix, ix+self.nx) - sly = slice(iy, iy+self.nx) - - data = self.image[slx, sly] - gal_seg = self.gal_map[slx, sly] - star_seg = self.star_map[slx, sly] - - labels = np.zeros((self.nx, self.nx, self.n_class), dtype=np.float32) - labels[..., 1] = np.clip(gal_seg, 0, 1) - labels[..., 2] = np.clip(star_seg, 0, 1) - labels[..., 0] = (1+np.clip(labels[...,1] + labels[...,2], 0, 1))%2 - - return data, labels - diff --git a/scripts/ultrasound_launcher.py b/scripts/ultrasound_launcher.py index 7b9e868..ea9d02d 100644 --- a/scripts/ultrasound_launcher.py +++ b/scripts/ultrasound_launcher.py @@ -16,14 +16,20 @@ Created on Jul 28, 2016 author: jakeret + +Trains a tf_unet network to segment nerves in the Ultrasound Kaggle Dataset. +Requires the Kaggle dataset. ''' + from __future__ import print_function, division, absolute_import, unicode_literals import os import click +import numpy as np from tf_unet import unet from tf_unet import util -from scripts import ultrasound_util +from tf_unet.image_util import ImageDataProvider + @click.command() @click.option('--data_root', default="../../ultrasound/train") @@ -39,9 +45,9 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat if not os.path.exists(data_root): raise IOError("Kaggle Ultrasound Dataset not found") - data_provider = ultrasound_util.DataProvider(data_root + "/*.tif", - a_min=0, - a_max=210) + data_provider = DataProvider(data_root + "/*.tif", + a_min=0, + a_max=210) net = unet.Unet(channels=data_provider.channels, n_class=data_provider.n_class, layers=layers, @@ -66,4 +72,23 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat if __name__ == '__main__': - launch() \ No newline at end of file + launch() + + +class DataProvider(ImageDataProvider): + """ + Extends the default ImageDataProvider to randomly select the next + image and ensures that only data sets are used where the mask is not empty + """ + + def _next_data(self): + data, mask = super(DataProvider, self)._next_data() + while mask.sum() == 0: + self._cylce_file() + data, mask = super(DataProvider, self)._next_data() + + return data, mask + + + def _cylce_file(self): + self.file_idx = np.random.choice(len(self.data_files)) \ No newline at end of file diff --git a/scripts/ultrasound_util.py b/scripts/ultrasound_util.py deleted file mode 100644 index e7b36d0..0000000 --- a/scripts/ultrasound_util.py +++ /dev/null @@ -1,41 +0,0 @@ -# tf_unet is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# tf_unet is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with tf_unet. If not, see . - -''' -Created on Aug 18, 2016 - -author: jakeret -''' -from __future__ import print_function, division, absolute_import, unicode_literals - -import numpy as np -from tf_unet.image_util import ImageDataProvider - -class DataProvider(ImageDataProvider): - """ - Extends the default ImageDataProvider to randomly select the next - image and ensures that only data sets are used where the mask is not empty - """ - - def _next_data(self): - data, mask = super(DataProvider, self)._next_data() - while mask.sum() == 0: - self._cylce_file() - data, mask = super(DataProvider, self)._next_data() - - return data, mask - - - def _cylce_file(self): - self.file_idx = np.random.choice(len(self.data_files)) - From 3cb4ac8d7859bd0323a09708ec4942ba504d0208 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 29 Dec 2018 16:19:40 +0100 Subject: [PATCH 30/45] cleaning up the examples --- scripts/rfi_launcher.py | 10 +++++----- scripts/ufig_launcher.py | 10 +++++----- scripts/ultrasound_launcher.py | 14 +++++++------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/scripts/rfi_launcher.py b/scripts/rfi_launcher.py index f0d924c..b23909b 100644 --- a/scripts/rfi_launcher.py +++ b/scripts/rfi_launcher.py @@ -66,10 +66,6 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat print("Testing error rate: {:.2f}%".format(unet.error_rate(prediction, util.crop_to_shape(y_test, prediction.shape)))) -if __name__ == '__main__': - launch() - - class DataProvider(BaseDataProvider): """ Extends the BaseDataProvider to randomly select the next @@ -109,4 +105,8 @@ def _next_data(self): return data, rfi def _cylce_file(self): - self.file_idx = np.random.choice(len(self.files)) \ No newline at end of file + self.file_idx = np.random.choice(len(self.files)) + + +if __name__ == '__main__': + launch() diff --git a/scripts/ufig_launcher.py b/scripts/ufig_launcher.py index cdf3219..7a5d9ed 100644 --- a/scripts/ufig_launcher.py +++ b/scripts/ufig_launcher.py @@ -70,10 +70,6 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat print("Testing error rate: {:.2f}%".format(unet.error_rate(prediction, util.crop_to_shape(label, prediction.shape)))) -if __name__ == '__main__': - launch() - - class DataProvider(BaseDataProvider): """ Extends the BaseDataProvider to randomly select the next @@ -126,4 +122,8 @@ def _next_data(self): labels[..., 2] = np.clip(star_seg, 0, 1) labels[..., 0] = (1+np.clip(labels[...,1] + labels[...,2], 0, 1))%2 - return data, labels \ No newline at end of file + return data, labels + + +if __name__ == '__main__': + launch() diff --git a/scripts/ultrasound_launcher.py b/scripts/ultrasound_launcher.py index ea9d02d..d30c9fa 100644 --- a/scripts/ultrasound_launcher.py +++ b/scripts/ultrasound_launcher.py @@ -37,7 +37,7 @@ @click.option('--training_iters', default=32) @click.option('--epochs', default=100) @click.option('--restore', default=False) -@click.option('--layers', default=5) +@click.option('--layers', default=4) @click.option('--features_root', default=64) def launch(data_root, output_path, training_iters, epochs, restore, layers, features_root): print("Using data from: %s"%data_root) @@ -57,7 +57,7 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat path = output_path if restore else util.create_training_path(output_path) - trainer = unet.Trainer(net, norm_grads=True, optimizer="adam") + trainer = unet.Trainer(net, batch_size=4, norm_grads=False, optimizer="adam") path = trainer.train(data_provider, path, training_iters=training_iters, epochs=epochs, @@ -71,10 +71,6 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat print("Testing error rate: {:.2f}%".format(unet.error_rate(prediction, util.crop_to_shape(y_test, prediction.shape)))) -if __name__ == '__main__': - launch() - - class DataProvider(ImageDataProvider): """ Extends the default ImageDataProvider to randomly select the next @@ -91,4 +87,8 @@ def _next_data(self): def _cylce_file(self): - self.file_idx = np.random.choice(len(self.data_files)) \ No newline at end of file + self.file_idx = np.random.choice(len(self.data_files)) + + +if __name__ == '__main__': + launch() From fc38c1da576c8247b773ced7127e3baac092cfcd Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Tue, 8 Jan 2019 21:50:12 +0100 Subject: [PATCH 31/45] making size computation more explicit --- tf_unet/unet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 5f1082b..35a894d 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -99,7 +99,7 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, biases.append((b1, b2)) convs.append((conv1, conv2)) - size -= 4 # valid conv + size -= 2 * 2 * filter_size // 2 # valid conv if layer < layers - 1: pools[layer] = max_pool(dw_h_convs[layer], pool_size) in_node = pools[layer] @@ -135,7 +135,7 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, convs.append((conv1, conv2)) size *= pool_size - size -= 4 # valid conv + size -= 2 * 2 * filter_size // 2 # valid conv # Output Map with tf.name_scope("output_map"): From e3141db34512203907addffae81409dbe0076485 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Tue, 8 Jan 2019 21:50:26 +0100 Subject: [PATCH 32/45] release 0.1.3 --- AUTHORS.rst | 1 + HISTORY.rst | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 465b8cf..e62aae3 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -17,6 +17,7 @@ Contributors * `@FiLeonard `_ * `@nikkou `_ * `@wkeithvan `_ +* `@samsammurphy `_ Citations --------- diff --git a/HISTORY.rst b/HISTORY.rst index 4944018..81aafac 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,16 @@ History ------- +0.1.2 (2018-01-08) +++++++++++++++++++ + +* Namescopes to improve TensorBoard layout +* Move bias addition before dropout +* numerically stable cross entropy computation +* parametrized verification batch size +* bugfix if all pixel values are 0 +* cleaned examples + 0.1.1 (2017-12-29) ++++++++++++++++++ From 07740676ec5ac5ae42e338aafdb3f4504f77e134 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Tue, 8 Jan 2019 21:50:43 +0100 Subject: [PATCH 33/45] bumping up the version --- setup.py | 2 +- tf_unet/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f133f78..5147e7a 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( name='tf_unet', - version='0.1.1', + version='0.1.2', description='Unet TensorFlow implementation', long_description=readme + '\n\n' + history, author='Joel Akeret', diff --git a/tf_unet/__init__.py b/tf_unet/__init__.py index 84b5898..7d6bb1c 100644 --- a/tf_unet/__init__.py +++ b/tf_unet/__init__.py @@ -1,3 +1,3 @@ __author__ = 'Joel Akeret' -__version__ = '0.1.1' +__version__ = '0.1.2' __credits__ = 'ETH Zurich, Institute for Astronomy' From bd2c940f2f73c1b57ca2397d6d200c72f0e4e4bc Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Tue, 8 Jan 2019 21:56:31 +0100 Subject: [PATCH 34/45] making size computation more explicit --- tf_unet/unet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf_unet/unet.py b/tf_unet/unet.py index 35a894d..04d4a51 100644 --- a/tf_unet/unet.py +++ b/tf_unet/unet.py @@ -99,7 +99,7 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, biases.append((b1, b2)) convs.append((conv1, conv2)) - size -= 2 * 2 * filter_size // 2 # valid conv + size -= 2 * 2 * (filter_size // 2) # valid conv if layer < layers - 1: pools[layer] = max_pool(dw_h_convs[layer], pool_size) in_node = pools[layer] @@ -135,7 +135,7 @@ def create_conv_net(x, keep_prob, channels, n_class, layers=3, features_root=16, convs.append((conv1, conv2)) size *= pool_size - size -= 2 * 2 * filter_size // 2 # valid conv + size -= 2 * 2 * (filter_size // 2) # valid conv # Output Map with tf.name_scope("output_map"): From 2c30e734a950226c1ca1012ccb1365de4e87fc6f Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 26 Jan 2019 16:40:16 +0100 Subject: [PATCH 35/45] improving ultrasound example --- scripts/ultrasound_launcher.py | 38 ++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/scripts/ultrasound_launcher.py b/scripts/ultrasound_launcher.py index d30c9fa..3017a3c 100644 --- a/scripts/ultrasound_launcher.py +++ b/scripts/ultrasound_launcher.py @@ -25,39 +25,44 @@ import os import click import numpy as np +from PIL import Image + from tf_unet import unet from tf_unet import util from tf_unet.image_util import ImageDataProvider +IMG_SIZE = (290, 210) + @click.command() @click.option('--data_root', default="../../ultrasound/train") @click.option('--output_path', default="./unet_trained_ultrasound") -@click.option('--training_iters', default=32) +@click.option('--training_iters', default=20) @click.option('--epochs', default=100) @click.option('--restore', default=False) -@click.option('--layers', default=4) -@click.option('--features_root', default=64) +@click.option('--layers', default=3) +@click.option('--features_root', default=32) def launch(data_root, output_path, training_iters, epochs, restore, layers, features_root): print("Using data from: %s"%data_root) if not os.path.exists(data_root): raise IOError("Kaggle Ultrasound Dataset not found") - data_provider = DataProvider(data_root + "/*.tif", - a_min=0, - a_max=210) + data_provider = DataProvider(search_path=data_root + "/*.tif", + mean=100, + std=56) + net = unet.Unet(channels=data_provider.channels, n_class=data_provider.n_class, layers=layers, features_root=features_root, - cost="dice_coefficient", + #cost="dice_coefficient", ) path = output_path if restore else util.create_training_path(output_path) - trainer = unet.Trainer(net, batch_size=4, norm_grads=False, optimizer="adam") + trainer = unet.Trainer(net, batch_size=1, norm_grads=False, optimizer="adam") path = trainer.train(data_provider, path, training_iters=training_iters, epochs=epochs, @@ -74,9 +79,15 @@ def launch(data_root, output_path, training_iters, epochs, restore, layers, feat class DataProvider(ImageDataProvider): """ Extends the default ImageDataProvider to randomly select the next - image and ensures that only data sets are used where the mask is not empty + image and ensures that only data sets are used where the mask is not empty. + The data then gets mean and std adjusted """ + def __init__(self, mean, std, *args, **kwargs): + super(DataProvider, self).__init__(*args, **kwargs) + self.mean = mean + self.std = std + def _next_data(self): data, mask = super(DataProvider, self)._next_data() while mask.sum() == 0: @@ -85,6 +96,15 @@ def _next_data(self): return data, mask + def _process_data(self, data): + data -= self.mean + data /= self.std + + return data + + def _load_file(self, path, dtype=np.float32): + image = Image.open(path) + return np.array(image.resize(IMG_SIZE), dtype) def _cylce_file(self): self.file_idx = np.random.choice(len(self.data_files)) From 2c16e2c53fd201e5fc2cc56a082e0889d38b213d Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 26 Jan 2019 16:40:39 +0100 Subject: [PATCH 36/45] avoid division by zero --- tf_unet/util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tf_unet/util.py b/tf_unet/util.py index 4364bb4..2273fb6 100644 --- a/tf_unet/util.py +++ b/tf_unet/util.py @@ -74,7 +74,9 @@ def to_rgb(img): img[np.isnan(img)] = 0 img -= np.amin(img) - img /= np.amax(img) + if np.argmax(img) != 0: + img /= np.amax(img) + img *= 255 return img From 41453f948bdf0b01ae1ca2c9e14be13806e984f6 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sun, 17 Feb 2019 17:52:36 +0100 Subject: [PATCH 37/45] avoid division by zero --- tf_unet/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tf_unet/util.py b/tf_unet/util.py index 2273fb6..1b8cba1 100644 --- a/tf_unet/util.py +++ b/tf_unet/util.py @@ -74,7 +74,7 @@ def to_rgb(img): img[np.isnan(img)] = 0 img -= np.amin(img) - if np.argmax(img) != 0: + if np.amax(img) != 0: img /= np.amax(img) img *= 255 From 2cc5e588a70932a9dcced60f21c88664dc92a112 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Wed, 6 Mar 2019 09:06:22 +0100 Subject: [PATCH 38/45] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e19faa0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. From 9f120eef2e9729d22b648c03584d52de009d9ef5 Mon Sep 17 00:00:00 2001 From: Siavash Khallaghi Date: Sat, 27 Apr 2019 18:53:31 -0700 Subject: [PATCH 39/45] Performing a check to make sure that the segmentation map from the child class is actually boolean array before performing bitwise logical not --- tf_unet/image_util.py | 60 ++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/tf_unet/image_util.py b/tf_unet/image_util.py index 3fe901b..8b3042b 100644 --- a/tf_unet/image_util.py +++ b/tf_unet/image_util.py @@ -2,12 +2,12 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # tf_unet is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with tf_unet. If not, see . @@ -34,7 +34,7 @@ class BaseDataProvider(object): :param a_max: (optional) max value used for clipping """ - + channels = 1 n_class = 2 @@ -44,28 +44,34 @@ def __init__(self, a_min=None, a_max=None): def _load_data_and_label(self): data, label = self._next_data() - + train_data = self._process_data(data) labels = self._process_labels(label) - + train_data, labels = self._post_process(train_data, labels) - + nx = train_data.shape[1] ny = train_data.shape[0] return train_data.reshape(1, ny, nx, self.channels), labels.reshape(1, ny, nx, self.n_class), - + def _process_labels(self, label): if self.n_class == 2: nx = label.shape[1] ny = label.shape[0] labels = np.zeros((ny, nx, self.n_class), dtype=np.float32) + + # It is the responsibility of the child class to make sure that the label + # is a boolean array, but we a chech here just in case. + if label.dtype != 'bool': + label = label.astype(np.bool) + labels[..., 1] = label labels[..., 0] = ~label return labels - + return label - + def _process_data(self, data): # normalization data = np.clip(np.fabs(data), self.a_min, self.a_max) @@ -75,37 +81,37 @@ def _process_data(self, data): data /= np.amax(data) return data - + def _post_process(self, data, labels): """ Post processing hook that can be used for data augmentation - + :param data: the data array :param labels: the label array """ return data, labels - + def __call__(self, n): train_data, labels = self._load_data_and_label() nx = train_data.shape[1] ny = train_data.shape[2] - + X = np.zeros((n, nx, ny, self.channels)) Y = np.zeros((n, nx, ny, self.n_class)) - + X[0] = train_data Y[0] = labels for i in range(1, n): train_data, labels = self._load_data_and_label() X[i] = train_data Y[i] = labels - + return X, Y class SimpleDataProvider(BaseDataProvider): """ - A simple data provider for numpy arrays. + A simple data provider for numpy arrays. Assumes that the data and label are numpy array with the dimensions data `[n, X, Y, channels]`, label `[n, X, Y, classes]`. Where `n` is the number of images, `X`, `Y` the size of the image. @@ -116,7 +122,7 @@ class SimpleDataProvider(BaseDataProvider): :param a_max: (optional) max value used for clipping """ - + def __init__(self, data, label, a_min=None, a_max=None): super(SimpleDataProvider, self).__init__(a_min, a_max) self.data = data @@ -134,13 +140,13 @@ class ImageDataProvider(BaseDataProvider): """ Generic data provider for images, supports gray scale and colored images. Assumes that the data images and label images are stored in the same folder - and that the labels have a different file suffix + and that the labels have a different file suffix e.g. 'train/fish_1.tif' and 'train/fish_1_mask.tif' Number of pixels in x and y of the images and masks should be even. Usage: data_provider = ImageDataProvider("..fishes/train/*.tif") - + :param search_path: a glob search pattern to find all data and label images :param a_min: (optional) min value used for clipping :param a_max: (optional) max value used for clipping @@ -149,7 +155,7 @@ class ImageDataProvider(BaseDataProvider): :param shuffle_data: if the order of the loaded file path should be randomized. Default 'True' """ - + def __init__(self, search_path, a_min=None, a_max=None, data_suffix=".tif", mask_suffix='_mask.tif', shuffle_data=True): super(ImageDataProvider, self).__init__(a_min, a_max) self.data_suffix = data_suffix @@ -158,10 +164,10 @@ def __init__(self, search_path, a_min=None, a_max=None, data_suffix=".tif", mask self.shuffle_data = shuffle_data self.data_files = self._find_data_files(search_path) - + if self.shuffle_data: np.random.shuffle(self.data_files) - + assert len(self.data_files) > 0, "No training files" print("Number of files used: %s" % len(self.data_files)) @@ -178,23 +184,23 @@ def __init__(self, search_path, a_min=None, a_max=None, data_suffix=".tif", mask def _find_data_files(self, search_path): all_files = glob.glob(search_path) return [name for name in all_files if self.data_suffix in name and not self.mask_suffix in name] - + def _load_file(self, path, dtype=np.float32): return np.array(Image.open(path), dtype) def _cylce_file(self): self.file_idx += 1 if self.file_idx >= len(self.data_files): - self.file_idx = 0 + self.file_idx = 0 if self.shuffle_data: np.random.shuffle(self.data_files) - + def _next_data(self): self._cylce_file() image_name = self.data_files[self.file_idx] label_name = image_name.replace(self.data_suffix, self.mask_suffix) - + img = self._load_file(image_name, np.float32) label = self._load_file(label_name, np.bool) - + return img,label From 24314c15fcce429136f68895bd90b4eb4f5b3a20 Mon Sep 17 00:00:00 2001 From: Siavash Khallaghi Date: Mon, 29 Apr 2019 15:01:24 -0700 Subject: [PATCH 40/45] The inverse of (crop_to_shape) by padding data with a scalar value around the borders. --- tf_unet/util.py | 56 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/tf_unet/util.py b/tf_unet/util.py index 1b8cba1..5fdc634 100644 --- a/tf_unet/util.py +++ b/tf_unet/util.py @@ -2,12 +2,12 @@ # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # tf_unet is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with tf_unet. If not, see . @@ -27,13 +27,13 @@ def plot_prediction(x_test, y_test, prediction, save=False): import matplotlib import matplotlib.pyplot as plt - + test_size = x_test.shape[0] fig, ax = plt.subplots(test_size, 3, figsize=(12,12), sharey=True, sharex=True) - + x_test = crop_to_shape(x_test, prediction.shape) y_test = crop_to_shape(y_test, prediction.shape) - + ax = np.atleast_2d(ax) for i in range(test_size): cax = ax[i, 0].imshow(x_test[i]) @@ -50,7 +50,7 @@ def plot_prediction(x_test, y_test, prediction, save=False): ax[i, 1].set_title("y") ax[i, 2].set_title("pred") fig.tight_layout() - + if save: fig.savefig(save) else: @@ -61,17 +61,17 @@ def to_rgb(img): """ Converts the given array into a RGB image. If the number of channels is not 3 the array is tiled such that it has 3 channels. Finally, the values are - rescaled to [0,255) - + rescaled to [0,255) + :param img: the array to convert [nx, ny, channels] - + :returns img: the rgb image [nx, ny, 3] """ img = np.atleast_3d(img) channels = img.shape[2] if channels < 3: img = np.tile(img, 3) - + img[np.isnan(img)] = 0 img -= np.amin(img) if np.amax(img) != 0: @@ -83,7 +83,7 @@ def to_rgb(img): def crop_to_shape(data, shape): """ Crops the array to the given image shape by removing the border (expects a tensor of shape [batches, nx, ny, channels]. - + :param data: the array to crop :param shape: the target shape """ @@ -101,27 +101,47 @@ def crop_to_shape(data, shape): assert cropped.shape[2] == shape[2] return cropped +def expand_to_shape(data, shape, border=0): + """ + Expands the array to the given image shape by padding it with a border (expects a tensor of shape [batches, nx, ny, channels]. + + :param data: the array to expand + :param shape: the target shape + """ + diff_nx = shape[1] - data.shape[1] + diff_ny = shape[2] - data.shape[2] + + offset_nx_left = diff_nx // 2 + offset_nx_right = diff_nx - offset_nx_left + offset_ny_left = diff_ny // 2 + offset_ny_right = diff_ny - offset_ny_left + + expanded = np.full(shape, border, dtype=np.float32) + expanded[:, offset_nx_left:(-offset_nx_right), offset_ny_left:(-offset_ny_right)] = data + + return expanded + def combine_img_prediction(data, gt, pred): """ Combines the data, grouth thruth and the prediction into one rgb image - + :param data: the data tensor :param gt: the ground thruth tensor :param pred: the prediction tensor - - :returns img: the concatenated rgb image + + :returns img: the concatenated rgb image """ ny = pred.shape[2] ch = data.shape[3] - img = np.concatenate((to_rgb(crop_to_shape(data, pred.shape).reshape(-1, ny, ch)), - to_rgb(crop_to_shape(gt[..., 1], pred.shape).reshape(-1, ny, 1)), + img = np.concatenate((to_rgb(crop_to_shape(data, pred.shape).reshape(-1, ny, ch)), + to_rgb(crop_to_shape(gt[..., 1], pred.shape).reshape(-1, ny, 1)), to_rgb(pred[..., 1].reshape(-1, ny, 1))), axis=1) return img def save_image(img, path): """ Writes the image to disk - + :param img: the rgb image to save :param path: the target path """ @@ -140,4 +160,4 @@ def create_training_path(output_path, prefix="run_"): while os.path.exists(path): idx += 1 path = os.path.join(output_path, "{:}{:03d}".format(prefix, idx)) - return path \ No newline at end of file + return path From 2f31d26cecc3ce8b86c681d1f82c408aba48fb00 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Tue, 7 May 2019 20:32:29 +0200 Subject: [PATCH 41/45] Adding siavashk to contributers list --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index e62aae3..7007c22 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -18,6 +18,7 @@ Contributors * `@nikkou `_ * `@wkeithvan `_ * `@samsammurphy `_ +* `@siavashk `_ Citations --------- From 2fe4d5f2a89eca1140cda663300fccf5136eb894 Mon Sep 17 00:00:00 2001 From: Joel Akeret Date: Sat, 17 Aug 2019 11:46:07 +0200 Subject: [PATCH 42/45] fixed import --- demo/demo_radio_data.ipynb | 100 +++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/demo/demo_radio_data.ipynb b/demo/demo_radio_data.ipynb index c5658f9..1feb5d4 100644 --- a/demo/demo_radio_data.ipynb +++ b/demo/demo_radio_data.ipynb @@ -14,12 +14,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "from __future__ import division, print_function\n", "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "import matplotlib\n", @@ -40,7 +37,9 @@ "cell_type": "code", "execution_count": 2, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -51,7 +50,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -62,30 +63,32 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "== Ivy run took: 57.897 s ===\r\n", - "Traversing file system : 0.006s\r\n", - "Generate gain files : 0.006s\r\n", - "Initialize : 0.000s\r\n", - "Load data : 14.805s\r\n", - "Convert TOD : 0.048s\r\n", - "Process coordinates : 0.412s\r\n", - "Masking objects : 0.671s\r\n", - "Masking artefacts : 0.002s\r\n", - "Remove RFI : 9.661s\r\n", - "postprocessing TOD : 0.045s\r\n", - "remove background baseline : 0.524s\r\n", - "Restructure TOD : 0.572s\r\n", - "ParallelPluginCollection : 26.743s\r\n", - "Create maps : 1.681s\r\n", - "ParallelPluginCollection : 1.760s\r\n", - "Write maps : 0.961s\r\n" + "== Ivy run took: 57.897 s ===\n", + "Traversing file system : 0.006s\n", + "Generate gain files : 0.006s\n", + "Initialize : 0.000s\n", + "Load data : 14.805s\n", + "Convert TOD : 0.048s\n", + "Process coordinates : 0.412s\n", + "Masking objects : 0.671s\n", + "Masking artefacts : 0.002s\n", + "Remove RFI : 9.661s\n", + "postprocessing TOD : 0.045s\n", + "remove background baseline : 0.524s\n", + "Restructure TOD : 0.572s\n", + "ParallelPluginCollection : 26.743s\n", + "Create maps : 1.681s\n", + "ParallelPluginCollection : 1.760s\n", + "Write maps : 0.961s\n" ] } ], @@ -95,22 +98,22 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": true - }, + "metadata": {}, "source": [ "## setting up the unet" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ - "from scripts.radio_util import DataProvider\n", + "from scripts.rfi_launcher import DataProvider\n", "from tf_unet import unet\n" ] }, @@ -118,7 +121,9 @@ "cell_type": "code", "execution_count": 3, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -129,7 +134,9 @@ "cell_type": "code", "execution_count": 5, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [ { @@ -170,7 +177,9 @@ "cell_type": "code", "execution_count": 6, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [ { @@ -227,7 +236,9 @@ "cell_type": "code", "execution_count": 10, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [ { @@ -255,7 +266,9 @@ "cell_type": "code", "execution_count": 11, "metadata": { - "collapsed": false + "jupyter": { + "outputs_hidden": false + } }, "outputs": [ { @@ -290,7 +303,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } }, "outputs": [], "source": [] @@ -298,23 +314,23 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 2", + "display_name": "Python 3", "language": "python", - "name": "python2" + "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.7.2" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } From 6e54df8c4d0af362d6f024c6eee8f96be74867f2 Mon Sep 17 00:00:00 2001 From: "Akeret, Joel" Date: Tue, 5 May 2020 11:27:28 +0200 Subject: [PATCH 43/45] reference to new project --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index b9145d7..45e80d4 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,8 @@ Tensorflow Unet .. image:: https://mybinder.org/badge.svg :target: https://mybinder.org/v2/gh/jakeret/tf_unet/master?filepath=demo%2Fdemo_toy_problem.ipynb +.. note:: + This project is discontinued in favour of a Tensorflow 2 compatible reimplementation of `tf_unet` found under https://github.com/jakeret/unet This is a generic **U-Net** implementation as proposed by `Ronneberger et al. `_ developed with **Tensorflow**. The code has been developed and used for `Radio Frequency Interference mitigation using deep convolutional neural networks `_ . From 3b1e473849d15a97e4e0bcb1603b3401d3e21279 Mon Sep 17 00:00:00 2001 From: "Akeret, Joel" Date: Tue, 5 May 2020 11:29:27 +0200 Subject: [PATCH 44/45] reference to new project --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 45e80d4..018b051 100644 --- a/README.rst +++ b/README.rst @@ -15,8 +15,10 @@ Tensorflow Unet .. image:: https://mybinder.org/badge.svg :target: https://mybinder.org/v2/gh/jakeret/tf_unet/master?filepath=demo%2Fdemo_toy_problem.ipynb -.. note:: - This project is discontinued in favour of a Tensorflow 2 compatible reimplementation of `tf_unet` found under https://github.com/jakeret/unet +.. warning:: + + This project is discontinued in favour of a Tensorflow 2 compatible reimplementation of this project found under https://github.com/jakeret/unet + This is a generic **U-Net** implementation as proposed by `Ronneberger et al. `_ developed with **Tensorflow**. The code has been developed and used for `Radio Frequency Interference mitigation using deep convolutional neural networks `_ . From 0dcdf2ff1ebcc2ee59997d127a0c0be847168884 Mon Sep 17 00:00:00 2001 From: "Akeret, Joel" Date: Tue, 5 May 2020 11:29:48 +0200 Subject: [PATCH 45/45] reference to new project --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 018b051..1249fdd 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,7 @@ Tensorflow Unet .. image:: https://mybinder.org/badge.svg :target: https://mybinder.org/v2/gh/jakeret/tf_unet/master?filepath=demo%2Fdemo_toy_problem.ipynb + .. warning:: This project is discontinued in favour of a Tensorflow 2 compatible reimplementation of this project found under https://github.com/jakeret/unet