| @@ -208,6 +208,9 @@ namespace Tensorflow | |||
| => image_ops_impl.non_max_suppression_padded(boxes, scores, max_output_size, iou_threshold, score_threshold, pad_to_max_output_size, | |||
| name, sorted_input, canonicalized_coordinates, tile_size); | |||
| public Tensor resize(Tensor image, TensorShape size) | |||
| => image_ops_impl.resize_images(image, tf.constant(size)); | |||
| public Tensor resize_bilinear(Tensor images, Tensor size, bool align_corners = false, bool half_pixel_centers = false, string name = null) | |||
| => gen_image_ops.resize_bilinear(images, size, align_corners: align_corners, half_pixel_centers: half_pixel_centers, name: name); | |||
| @@ -25,6 +25,9 @@ namespace Tensorflow | |||
| public IDatasetV2 from_tensor_slices(Tensor features, Tensor labels) | |||
| => new TensorSliceDataset(features, labels); | |||
| public IDatasetV2 from_tensor_slices(string[] array) | |||
| => new TensorSliceDataset(array); | |||
| public IDatasetV2 from_tensor_slices(NDArray array) | |||
| => new TensorSliceDataset(array); | |||
| @@ -11,6 +11,16 @@ namespace Tensorflow.Data | |||
| { | |||
| public class TensorSliceDataset : DatasetSource | |||
| { | |||
| public TensorSliceDataset(string[] array) | |||
| { | |||
| var element = tf.constant(array); | |||
| _tensors = new[] { element }; | |||
| var batched_spec = new[] { element.ToTensorSpec() }; | |||
| structure = batched_spec.Select(x => x._unbatch()).ToArray(); | |||
| variant_tensor = ops.tensor_slice_dataset(_tensors, output_shapes); | |||
| } | |||
| public TensorSliceDataset(NDArray array) | |||
| { | |||
| var element = tf.constant(array); | |||
| @@ -33,8 +33,6 @@ namespace Tensorflow.Functions | |||
| new Operation[] { input }, | |||
| new Operation[] { output }, | |||
| null); | |||
| c_api.TFE_ContextAddFunction(tf.Context.Handle, _handle, tf.Status.Handle); | |||
| } | |||
| tf.enable_eager_execution(); | |||
| @@ -54,6 +52,7 @@ namespace Tensorflow.Functions | |||
| public void Dispose() | |||
| { | |||
| c_api.TFE_ContextRemoveFunction(tf.Context.Handle, Name, tf.Status.Handle); | |||
| c_api.TF_DeleteFunction(_handle); | |||
| } | |||
| } | |||
| } | |||
| @@ -26,8 +26,6 @@ namespace Tensorflow.Graphs | |||
| new Operation[] { input1, input2 }, | |||
| new Operation[] { output }, | |||
| null); | |||
| c_api.TFE_ContextAddFunction(tf.Context.Handle, func_handle, tf.Status.Handle); | |||
| } | |||
| tf.enable_eager_execution(); | |||
| @@ -1,4 +1,4 @@ | |||
| /*using MethodBoundaryAspect.Fody.Attributes; | |||
| using MethodBoundaryAspect.Fody.Attributes; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| @@ -8,49 +8,73 @@ using static Tensorflow.Binding; | |||
| namespace Tensorflow.Graphs | |||
| { | |||
| public sealed class AutoGraphAspect : OnMethodBoundaryAspect | |||
| [AllowChangingInputArguments] | |||
| public sealed class AutoGraphAttribute : OnMethodBoundaryAspect | |||
| { | |||
| FuncGraph graph; | |||
| IntPtr func_handle; | |||
| Tensor[] originalInputs; | |||
| string func_name; | |||
| static Dictionary<string, Func<Tensor[], Tensor>> functions = new Dictionary<string, Func<Tensor[], Tensor>>(); | |||
| public override void OnEntry(MethodExecutionArgs args) | |||
| { | |||
| func_name = $"autograph_{args.Instance}.{args.Method.Name}"; | |||
| if (functions.ContainsKey(func_name)) | |||
| { | |||
| args.ReturnValue = functions[func_name](args.Arguments.Select(x => x as Tensor).ToArray()); | |||
| args.FlowBehavior = FlowBehavior.Return; | |||
| return; | |||
| } | |||
| tf.compat.v1.disable_eager_execution(); | |||
| // make function as an Operation by autograph | |||
| graph = new FuncGraph(func_name); | |||
| graph.as_default(); | |||
| originalInputs = new Tensor[args.Arguments.Length]; | |||
| // convert args to placeholder | |||
| for (var i = 0; i < args.Arguments.Length; i++) | |||
| { | |||
| if (args.Arguments[i] is EagerTensor tensor) | |||
| { | |||
| originalInputs[i] = tensor; | |||
| args.Arguments[i] = tf.placeholder(tensor.dtype, shape: tensor.TensorShape); | |||
| } | |||
| } | |||
| // make function as an Operation by autograph | |||
| graph = new FuncGraph("autograph_add"); | |||
| graph.as_default(); | |||
| } | |||
| public override void OnExit(MethodExecutionArgs args) | |||
| { | |||
| var output = (Tensor)args.Method.Invoke(args.Instance, args.Arguments); | |||
| var output = (Tensor)args.ReturnValue; | |||
| var inputs = args.Arguments.Select(x => x as Tensor).ToArray(); | |||
| var opers = graph._nodes_by_name.Values.Select(x => x as Operation).ToArray(); | |||
| func_handle = graph.ToGraph(opers, | |||
| new Operation[] { }, | |||
| new Operation[] { }, | |||
| graph.ToGraph(opers, | |||
| inputs.Select(x => x.op).ToArray(), | |||
| new Operation[] { output.op }, | |||
| null); | |||
| graph.Dispose(); | |||
| tf.enable_eager_execution(); | |||
| c_api.TFE_ContextAddFunction(tf.Context.Handle, func_handle, tf.Status.Handle); | |||
| Func<Tensor[], Tensor> function = (x) => | |||
| { | |||
| var result = tf.Runner.TFE_Execute(tf.Context, | |||
| tf.Context.DeviceName, | |||
| func_name, | |||
| x, | |||
| null, | |||
| 1); | |||
| var a1 = tf.constant(1); | |||
| var b1 = tf.constant(2); | |||
| return result[0]; | |||
| }; | |||
| // cache function. | |||
| functions[func_name] = function; | |||
| var result = tf.Runner.TFE_Execute(tf.Context, | |||
| tf.Context.DeviceName, | |||
| "autograph_add", | |||
| new[] { a1, b1 }, | |||
| null, | |||
| 1); | |||
| graph.Dispose(); | |||
| // run function | |||
| args.ReturnValue = function(originalInputs); | |||
| } | |||
| } | |||
| }*/ | |||
| } | |||
| @@ -47,8 +47,13 @@ namespace Tensorflow.Graphs | |||
| IntPtr.Zero, | |||
| null, | |||
| status.Handle); | |||
| status.Check(true); | |||
| c_api.TF_GraphCopyFunction(outer_graph, func_handle, IntPtr.Zero, status.Handle); | |||
| status.Check(true); | |||
| c_api.TFE_ContextAddFunction(tf.Context.Handle, func_handle, status.Handle); | |||
| status.Check(true); | |||
| return func_handle; | |||
| } | |||
| @@ -16,7 +16,10 @@ namespace Tensorflow.Keras | |||
| int num_classes, | |||
| string interpolation) | |||
| { | |||
| Shape shape = (image_paths.Length, image_size.dims[0], image_size.dims[1], num_channels); | |||
| var path_ds = tf.data.Dataset.from_tensor_slices(image_paths); | |||
| var img_ds = path_ds.map(x => path_to_image(x, image_size, num_channels, interpolation)); | |||
| /*Shape shape = (image_paths.Length, image_size.dims[0], image_size.dims[1], num_channels); | |||
| Console.WriteLine($"Allocating memory for shape{shape}, {NPTypeCode.Float}"); | |||
| var data = np.zeros(shape, NPTypeCode.Float); | |||
| @@ -35,13 +38,13 @@ namespace Tensorflow.Keras | |||
| var label_ds = tf.keras.preprocessing.dataset_utils.labels_to_dataset(labels, label_mode, num_classes); | |||
| img_ds = tf.data.Dataset.zip(img_ds, label_ds); | |||
| } | |||
| else | |||
| else*/ | |||
| throw new NotImplementedException(""); | |||
| return img_ds; | |||
| } | |||
| Tensor path_to_image(string path, TensorShape image_size, int num_channels, string interpolation) | |||
| Tensor path_to_image(Tensor path, TensorShape image_size, int num_channels, string interpolation) | |||
| { | |||
| var img = tf.io.read_file(path); | |||
| img = tf.image.decode_image( | |||
| @@ -1668,8 +1668,6 @@ new_height, new_width"); | |||
| public static Tensor decode_image(Tensor contents, int channels = 0, TF_DataType dtype = TF_DataType.TF_UINT8, | |||
| string name = null, bool expand_animations = true) | |||
| { | |||
| Tensor substr = null; | |||
| Func<ITensorOrOperation> _jpeg = () => | |||
| { | |||
| int jpeg_channels = channels; | |||
| @@ -1695,8 +1693,7 @@ new_height, new_width"); | |||
| { | |||
| var result = convert_image_dtype(gen_image_ops.decode_gif(contents), dtype); | |||
| if (!expand_animations) | |||
| // result = array_ops.gather(result, 0); | |||
| throw new NotImplementedException(""); | |||
| result = array_ops.gather(result, 0); | |||
| return result; | |||
| }); | |||
| }; | |||
| @@ -1728,18 +1725,16 @@ new_height, new_width"); | |||
| Func<ITensorOrOperation> check_gif = () => | |||
| { | |||
| var is_gif = math_ops.equal(substr, "\x47\x49\x46", name: "is_gif"); | |||
| return control_flow_ops.cond(is_gif, _gif, _bmp, name: "cond_gif"); | |||
| return control_flow_ops.cond(is_gif(contents), _gif, _bmp, name: "cond_gif"); | |||
| }; | |||
| Func<ITensorOrOperation> check_png = () => | |||
| { | |||
| return control_flow_ops.cond(_is_png(contents), _png, check_gif, name: "cond_png"); | |||
| return control_flow_ops.cond(is_png(contents), _png, check_gif, name: "cond_png"); | |||
| }; | |||
| return tf_with(ops.name_scope(name, "decode_image"), scope => | |||
| { | |||
| substr = tf.strings.substr(contents, 0, 3); | |||
| return control_flow_ops.cond(is_jpeg(contents), _jpeg, check_png, name: "cond_jpeg"); | |||
| }); | |||
| } | |||
| @@ -2089,7 +2084,7 @@ new_height, new_width"); | |||
| }); | |||
| } | |||
| public static Tensor _is_png(Tensor contents, string name = null) | |||
| static Tensor is_png(Tensor contents, string name = null) | |||
| { | |||
| return tf_with(ops.name_scope(name, "is_png"), scope => | |||
| { | |||
| @@ -2098,6 +2093,17 @@ new_height, new_width"); | |||
| }); | |||
| } | |||
| static Tensor is_gif(Tensor contents, string name = null) | |||
| { | |||
| return tf_with(ops.name_scope(name, "is_gif"), scope => | |||
| { | |||
| var substr = tf.strings.substr(contents, 0, 3); | |||
| var gif = tf.constant(new byte[] { 0x47, 0x49, 0x46 }, TF_DataType.TF_STRING); | |||
| var result = math_ops.equal(substr, gif, name: name); | |||
| return result; | |||
| }); | |||
| } | |||
| public static Tensor convert_image_dtype(Tensor image, TF_DataType dtype, bool saturate = false, | |||
| string name = null) | |||
| { | |||
| @@ -148,9 +148,18 @@ namespace Tensorflow | |||
| // If shape is not given, get the shape from the numpy array. | |||
| if (shape == null) | |||
| { | |||
| shape = nparray.shape; | |||
| is_same_size = true; | |||
| shape_size = nparray.size; | |||
| if(numpy_dtype == TF_DataType.TF_STRING) | |||
| { | |||
| // scalar string | |||
| shape = new int[0]; | |||
| shape_size = 0; | |||
| } | |||
| else | |||
| { | |||
| shape = nparray.shape; | |||
| is_same_size = true; | |||
| shape_size = nparray.size; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| @@ -470,6 +470,8 @@ namespace Tensorflow | |||
| return varVal._TensorConversionFunction(dtype: dtype, name: name, as_ref: as_ref); | |||
| case TensorShape ts: | |||
| return constant_op.constant(ts.dims, dtype: dtype, name: name); | |||
| case string str: | |||
| return constant_op.constant(value, dtype: tf.@string, name: name); | |||
| case int[] dims: | |||
| return constant_op.constant(dims, dtype: dtype, name: name); | |||
| case object[] objects: | |||
| @@ -1,4 +1,6 @@ | |||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
| using FluentAssertions; | |||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
| using NumSharp; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| @@ -26,12 +28,69 @@ namespace TensorFlowNET.UnitTest.Basics | |||
| contents = tf.io.read_file(imgPath); | |||
| } | |||
| [Ignore] | |||
| [TestMethod] | |||
| public void decode_image() | |||
| { | |||
| var img = tf.image.decode_image(contents); | |||
| Assert.AreEqual(img.name, "decode_image/cond_jpeg/Merge:0"); | |||
| } | |||
| [TestMethod, Ignore] | |||
| public void resize_image() | |||
| { | |||
| var image = tf.constant(new int[5, 5] | |||
| { | |||
| {1, 0, 0, 0, 0 }, | |||
| {0, 1, 0, 0, 0 }, | |||
| {0, 0, 1, 0, 0 }, | |||
| {0, 0, 0, 1, 0 }, | |||
| {0, 0, 0, 0, 1 } | |||
| }); | |||
| //image = image[tf.newaxis, ..., tf.newaxis]; | |||
| var img = tf.image.resize(contents, (3, 5)); | |||
| Assert.AreEqual(img.name, "decode_image/cond_jpeg/Merge:0"); | |||
| } | |||
| [TestMethod] | |||
| public void TestCropAndResize() | |||
| { | |||
| var graph = tf.Graph().as_default(); | |||
| // 3x3 'Image' with numbered coordinates | |||
| var input = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f); | |||
| var image = tf.reshape(input, new int[] { 1, 3, 3, 1 }); | |||
| // 4x4 'Image' with numbered coordinates | |||
| var input2 = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f); | |||
| var image2 = tf.reshape(input2, new int[] { 1, 4, 4, 1 }); | |||
| // create one box over the full image that flips it (y1 > y2) | |||
| var box = tf.reshape(np.array(1f, 0f, 0f, 1f), new int[] { 1, 4 }); | |||
| var boxInd = tf.Variable(np.array(0)); | |||
| // crop first 3x3 imageto size 1x1 | |||
| var cropSize1_1 = tf.Variable(np.array(1, 1)); | |||
| // don't crop second 4x4 image | |||
| var cropSize2_2 = tf.Variable(np.array(4, 4)); | |||
| var init = tf.global_variables_initializer(); | |||
| using (Session sess = tf.Session()) | |||
| { | |||
| sess.run(init); | |||
| var cropped = tf.image.crop_and_resize(image, box, boxInd, cropSize1_1); | |||
| var result = sess.run(cropped); | |||
| // check if cropped to 1x1 center was succesfull | |||
| result.size.Should().Be(1); | |||
| result[0, 0, 0, 0].Should().Be(4f); | |||
| cropped = tf.image.crop_and_resize(image2, box, boxInd, cropSize2_2); | |||
| result = sess.run(cropped); | |||
| // check if flipped and no cropping occured | |||
| result.size.Should().Be(16); | |||
| result[0, 0, 0, 0].Should().Be(12f); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,56 +0,0 @@ | |||
| using FluentAssertions; | |||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | |||
| using NumSharp; | |||
| using Tensorflow; | |||
| using Tensorflow.UnitTest; | |||
| using static Tensorflow.Binding; | |||
| namespace TensorFlowNET.UnitTest.img_test | |||
| { | |||
| [TestClass] | |||
| public class TestCrop : GraphModeTestBase | |||
| { | |||
| [TestMethod] | |||
| public void TestCropAndResize() | |||
| { | |||
| var graph = tf.Graph().as_default(); | |||
| // 3x3 'Image' with numbered coordinates | |||
| var input = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f); | |||
| var image = tf.reshape(input, new int[] { 1, 3, 3, 1 }); | |||
| // 4x4 'Image' with numbered coordinates | |||
| var input2 = np.array(0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f, 10f, 11f, 12f, 13f, 14f, 15f); | |||
| var image2 = tf.reshape(input2, new int[] { 1, 4, 4, 1 }); | |||
| // create one box over the full image that flips it (y1 > y2) | |||
| var box = tf.reshape(np.array(1f, 0f, 0f, 1f), new int[] {1, 4}); | |||
| var boxInd = tf.Variable(np.array(0)); | |||
| // crop first 3x3 imageto size 1x1 | |||
| var cropSize1_1 = tf.Variable(np.array(1, 1)); | |||
| // don't crop second 4x4 image | |||
| var cropSize2_2 = tf.Variable(np.array(4, 4)); | |||
| var init = tf.global_variables_initializer(); | |||
| using (Session sess = tf.Session()) | |||
| { | |||
| sess.run(init); | |||
| var cropped = tf.image.crop_and_resize(image, box, boxInd, cropSize1_1); | |||
| var result = sess.run(cropped); | |||
| // check if cropped to 1x1 center was succesfull | |||
| result.size.Should().Be(1); | |||
| result[0, 0, 0, 0].Should().Be(4f); | |||
| cropped = tf.image.crop_and_resize(image2, box, boxInd, cropSize2_2); | |||
| result = sess.run(cropped); | |||
| // check if flipped and no cropping occured | |||
| result.size.Should().Be(16); | |||
| result[0, 0, 0, 0].Should().Be(12f); | |||
| } | |||
| } | |||
| } | |||
| } | |||