Browse Source

Port 3D edf writer and add test

Matthias Vogelgesang 10 years ago
parent
commit
7d1a155b15

+ 1 - 0
src/CMakeLists.txt

@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 2.6)
 
 # --- Set sources -------------------------------------------------------------
 set(ufofilter_SRCS 
+    ufo-edf-3d-writer-task.c
     ufo-lamino-bp-task.c
     ufo-lamino-conv-task.c
     ufo-lamino-ramp-task.c

+ 286 - 0
src/ufo-edf-3d-writer-task.c

@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gmodule.h>
+#include <stdio.h>
+
+#include "ufo-edf-3d-writer-task.h"
+
+/**
+ * SECTION:ufo-edf-3d-writer-task
+ * @Short_description: Write 3D EDF files
+ * @Title: edf-3d-writer
+ */
+
+struct _UfoEdf3dWriterTaskPrivate {
+    gchar *filename;
+    gchar *template;
+    guint counter;
+};
+
+static void ufo_task_interface_init (UfoTaskIface *iface);
+static void ufo_cpu_task_interface_init (UfoCpuTaskIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (UfoEdf3dWriterTask, ufo_edf_3d_writer_task, UFO_TYPE_TASK_NODE,
+                         G_IMPLEMENT_INTERFACE (UFO_TYPE_TASK,
+                                                ufo_task_interface_init)
+                         G_IMPLEMENT_INTERFACE (UFO_TYPE_CPU_TASK,
+                                                ufo_cpu_task_interface_init))
+
+#define UFO_EDF_3D_WRITER_TASK_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_EDF_3D_WRITER_TASK, UfoEdf3dWriterTaskPrivate))
+
+enum {
+    PROP_0,
+    PROP_FILENAME,
+    N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+
+UfoNode *
+ufo_edf_3d_writer_task_new (void)
+{
+    return UFO_NODE (g_object_new (UFO_TYPE_EDF_3D_WRITER_TASK, NULL));
+}
+
+static gchar *
+build_template (const gchar *format)
+{
+    gchar *template;
+    gchar *percent;
+
+    template = g_strdup (format);
+    percent = g_strstr_len (template, -1, "%");
+
+    if (percent != NULL) {
+        percent++;
+
+        while (*percent) {
+            if (*percent == '%')
+                *percent = '_';
+            percent++;
+        }
+    }
+    else {
+        g_warning ("Specifier %%i not found. Appending it.");
+        g_free (template);
+        template = g_strconcat (format, "%i", NULL);
+    }
+
+    return template;
+}
+
+static void
+ufo_edf_3d_writer_task_setup (UfoTask *task,
+                       UfoResources *resources,
+                       GError **error)
+{
+    UfoEdf3dWriterTaskPrivate *priv;
+
+    priv = UFO_EDF_3D_WRITER_TASK_GET_PRIVATE (task);
+    priv->template = build_template (priv->filename);
+}
+
+static void
+ufo_edf_3d_writer_task_get_requisition (UfoTask *task,
+                                        UfoBuffer **inputs,
+                                        UfoRequisition *requisition)
+{
+    requisition->n_dims = 0;
+}
+
+static void
+ufo_edf_3d_writer_task_get_structure (UfoTask *task,
+                                      guint *n_inputs,
+                                      UfoInputParam **in_params,
+                                      UfoTaskMode *mode)
+{
+    *mode = UFO_TASK_MODE_PROCESSOR;
+    *n_inputs = 1;
+    *in_params = g_new0 (UfoInputParam, 1);
+    (*in_params)[0].n_dims = 3;
+}
+
+static gboolean
+write_header (FILE *fp, UfoBuffer *buffer)
+{
+    UfoRequisition req;
+    gchar *header;
+    const gsize length = 512;
+    gboolean result = TRUE;
+
+    ufo_buffer_get_requisition (buffer, &req);
+    header = g_strnfill (length, ' ');
+    
+    g_snprintf (header, length,
+                "{\nByteOrder = LowByteFirst;\nDataType = FloatValue;\nDim_1 = %zu;\nDim_2 = %zu;\nDim_3 = %zu;\nSize = %zu;\n",
+                req.dims[0], req.dims[1], req.dims[2],
+                req.dims[0] * req.dims[1] * req.dims[2] * sizeof(gfloat));
+
+    header[510] = '}';
+    header[511] = '\x0A';
+
+    result = fwrite (header, sizeof(gchar), length, fp) == length;
+    g_free (header);
+    return result;
+}
+
+static gboolean
+write_body (FILE *fp, UfoBuffer *buffer)
+{
+    gfloat *data;
+    gsize size;
+
+    data = ufo_buffer_get_host_array (buffer, NULL);
+    size = ufo_buffer_get_size (buffer);
+    return fwrite (data, 1, size, fp) == size;
+}
+
+static gboolean
+ufo_edf_3d_writer_task_process (UfoCpuTask *task,
+                         UfoBuffer **inputs,
+                         UfoBuffer *output,
+                         UfoRequisition *requisition)
+{
+    UfoEdf3dWriterTaskPrivate *priv;
+    UfoProfiler *profiler;
+    gchar *filename;
+    FILE *fp;
+
+    priv = UFO_EDF_3D_WRITER_TASK_GET_PRIVATE (UFO_EDF_3D_WRITER_TASK (task));
+    profiler = ufo_task_node_get_profiler (UFO_TASK_NODE (task));
+    filename = g_strdup_printf (priv->template, priv->counter);
+
+    ufo_profiler_start (profiler, UFO_PROFILER_TIMER_IO);
+    fp = fopen (filename, "wb");
+
+    if (!write_header (fp, inputs[0]))
+        goto error_cleanup;
+
+    if (!write_body (fp, inputs[0]))
+        goto error_cleanup;
+
+    fclose (fp);
+    ufo_profiler_stop (profiler, UFO_PROFILER_TIMER_IO);
+
+    g_free (filename);
+    priv->counter++;
+    return TRUE;
+
+error_cleanup:
+    g_warning ("Could not write EDF data");
+    fclose (fp);
+    g_free (filename);
+    return FALSE;
+}
+
+static void
+ufo_edf_3d_writer_task_set_property (GObject *object,
+                              guint property_id,
+                              const GValue *value,
+                              GParamSpec *pspec)
+{
+    UfoEdf3dWriterTaskPrivate *priv = UFO_EDF_3D_WRITER_TASK_GET_PRIVATE (object);
+
+    switch (property_id) {
+        case PROP_FILENAME:
+            g_free (priv->filename);
+            priv->filename = g_value_dup_string (value);
+            break;
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+            break;
+    }
+}
+
+static void
+ufo_edf_3d_writer_task_get_property (GObject *object,
+                              guint property_id,
+                              GValue *value,
+                              GParamSpec *pspec)
+{
+    UfoEdf3dWriterTaskPrivate *priv = UFO_EDF_3D_WRITER_TASK_GET_PRIVATE (object);
+
+    switch (property_id) {
+        case PROP_FILENAME:
+            g_value_set_string (value, priv->filename);
+            break;
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+            break;
+    }
+}
+
+static void
+ufo_edf_3d_writer_task_finalize (GObject *object)
+{
+    UfoEdf3dWriterTaskPrivate *priv;
+
+    priv = UFO_EDF_3D_WRITER_TASK_GET_PRIVATE (object);
+
+    g_free (priv->filename);
+    g_free (priv->template);
+
+    G_OBJECT_CLASS (ufo_edf_3d_writer_task_parent_class)->finalize (object);
+}
+
+static void
+ufo_task_interface_init (UfoTaskIface *iface)
+{
+    iface->setup = ufo_edf_3d_writer_task_setup;
+    iface->get_structure = ufo_edf_3d_writer_task_get_structure;
+    iface->get_requisition = ufo_edf_3d_writer_task_get_requisition;
+}
+
+static void
+ufo_cpu_task_interface_init (UfoCpuTaskIface *iface)
+{
+    iface->process = ufo_edf_3d_writer_task_process;
+}
+
+static void
+ufo_edf_3d_writer_task_class_init (UfoEdf3dWriterTaskClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->set_property = ufo_edf_3d_writer_task_set_property;
+    gobject_class->get_property = ufo_edf_3d_writer_task_get_property;
+    gobject_class->finalize = ufo_edf_3d_writer_task_finalize;
+
+    properties[PROP_FILENAME] =
+        g_param_spec_string ("filename",
+            "Filename format string",
+            "Format string of the path and filename. If multiple files are written it must contain a '%i' specifier denoting the current count",
+            "./output-%05i.edf",
+            G_PARAM_READWRITE);
+
+    for (guint i = PROP_0 + 1; i < N_PROPERTIES; i++)
+        g_object_class_install_property (gobject_class, i, properties[i]);
+
+    g_type_class_add_private (gobject_class, sizeof(UfoEdf3dWriterTaskPrivate));
+}
+
+static void
+ufo_edf_3d_writer_task_init(UfoEdf3dWriterTask *self)
+{
+    self->priv = UFO_EDF_3D_WRITER_TASK_GET_PRIVATE(self);
+    self->priv->filename = g_strdup ("./output-%05i.edf");
+    self->priv->template = NULL;
+    self->priv->counter = 0;
+}

+ 66 - 0
src/ufo-edf-3d-writer-task.h

@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011-2013 Karlsruhe Institute of Technology
+ *
+ * This file is part of Ufo.
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UFO_EDF_3D_edf_3d_writer_TASK_H
+#define __UFO_EDF_3D_edf_3d_writer_TASK_H
+
+#include <ufo/ufo.h>
+
+G_BEGIN_DECLS
+
+#define UFO_TYPE_EDF_3D_WRITER_TASK             (ufo_edf_3d_writer_task_get_type())
+#define UFO_EDF_3D_WRITER_TASK(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UFO_TYPE_EDF_3D_WRITER_TASK, UfoEdf3dWriterTask))
+#define UFO_IS_EDF_3D_WRITER_TASK(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UFO_TYPE_EDF_3D_WRITER_TASK))
+#define UFO_EDF_3D_WRITER_TASK_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UFO_TYPE_EDF_3D_WRITER_TASK, UfoEdf3dWriterTaskClass))
+#define UFO_IS_EDF_3D_WRITER_TASK_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_EDF_3D_edf_3d_writer_TASK))
+#define UFO_EDF_3D_WRITER_TASK_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UFO_TYPE_EDF_3D_WRITER_TASK, UfoEdf3dWriterTaskClass))
+
+typedef struct _UfoEdf3dWriterTask           UfoEdf3dWriterTask;
+typedef struct _UfoEdf3dWriterTaskClass      UfoEdf3dWriterTaskClass;
+typedef struct _UfoEdf3dWriterTaskPrivate    UfoEdf3dWriterTaskPrivate;
+
+/**
+ * UfoEdf3dWriterTask:
+ *
+ * Main object for organizing filters. The contents of the #UfoEdf3dWriterTask structure
+ * are private and should only be accessed via the provided API.
+ */
+struct _UfoEdf3dWriterTask {
+    /*< private >*/
+    UfoTaskNode parent_instance;
+
+    UfoEdf3dWriterTaskPrivate *priv;
+};
+
+/**
+ * UfoEdf3dWriterTaskClass:
+ *
+ * #UfoEdf3dWriterTask class
+ */
+struct _UfoEdf3dWriterTaskClass {
+    /*< private >*/
+    UfoTaskNodeClass parent_class;
+};
+
+UfoNode  *ufo_edf_3d_writer_task_new       (void);
+GType     ufo_edf_3d_writer_task_get_type  (void);
+
+G_END_DECLS
+
+#endif

+ 0 - 202
src/ufo-filter-3d-edf-writer.c

@@ -1,202 +0,0 @@
-#ifdef __APPLE__
-#include <OpenCL/cl.h>
-#else
-#include <CL/cl.h>
-#endif
-#include <stdio.h>
-
-#include "ufo-filter-3d-edf-writer.h"
-
-
-/**
- * SECTION:ufo-filter-3d-edf-writer
- * @Short_description: Stores 3d buffer as an EDF file
- * @Title: 3d-edf-writer
- *
- * The writer node writes each incoming buffer as an EDF file to disk.
- * Each file is prefixed with #UfoFilter3DEdfWriter:prefix and written into
- * #UfoFilter3DEdfWriter:path.
- */
-
-struct _UfoFilter3DEdfWriterPrivate {
-    gchar *path;
-    gchar *prefix;
-};
-
-GType ufo_filter_3d_edf_writer_get_type(void) G_GNUC_CONST;
-
-/* Inherit from UFO_TYPE_FILTER */
-G_DEFINE_TYPE(UfoFilter3DEdfWriter, ufo_filter_3d_edf_writer, UFO_TYPE_FILTER_SINK);
-
-#define UFO_FILTER_3D_EDF_WRITER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UFO_TYPE_FILTER_3D_EDF_WRITER, UfoFilter3DEdfWriterPrivate))
-
-enum {
-    PROP_0,
-    PROP_PATH,
-    PROP_PREFIX,
-    N_PROPERTIES
-};
-
-static GParamSpec * edfwriter3d_properties[N_PROPERTIES] = { NULL, };
-
-static gboolean
-filter_3d_edf_write_header(FILE * file, const guint32 xs, const guint32 ys, const guint32 zs)
-{
-    /*
-       {
-       ByteOrder = LowByteFirst;
-       DataType = FloatValue;
-       Dim_1 = 1024;
-       Dim_2 = 1024;
-       Dim_3 = 256;
-       Size = 268435456;
-       */
-
-    const guint32 headsz = 512;
-    gchar  * header  =  g_strnfill (headsz, ' ');
-
-    sprintf(header, "{\nByteOrder = LowByteFirst;\nDataType = FloatValue;\nDim_1 = %i;\nDim_2 = %i;\nDim_3 = %i;\nSize = %li;\n",
-            xs, ys, zs, xs*ys*zs*sizeof(float));
-
-    header[510] = '}';
-    header[511] = '\x0A';
-
-    fwrite(header, sizeof(char), headsz, file);
-    return TRUE;
-}
-
-static gboolean
-filter_3d_edf_write_body(FILE * file, float * data, const guint32 sz)
-{
-    fwrite(data, sizeof(float), sz, file);
-    return TRUE;
-}
-
-static void
-ufo_filter_3d_edf_writer_consume (UfoFilterSink *self, UfoBuffer *input[], GError **error)
-{
-    g_return_if_fail(UFO_IS_FILTER(self));
-    UfoFilter3DEdfWriterPrivate *priv = UFO_FILTER_3D_EDF_WRITER_GET_PRIVATE(self);
-    g_message("ufo_filter_3d_edf_writer_process");
-
-    cl_command_queue cmd_queue = ufo_filter_get_command_queue (UFO_FILTER (self));
-    guint num_dims = 0;
-    guint *dim_size = NULL;
-    guint counter = 0;
-    GString *filename = g_string_new("");
-
-    ufo_buffer_get_dimensions(input[0], &num_dims, &dim_size);
-    g_assert(num_dims == 3);
-
-    const guint xs = dim_size[0];
-    const guint ys = dim_size[1];
-    const guint zs = dim_size[2];
-
-    float *data = ufo_buffer_get_host_array(input[0], (cl_command_queue) cmd_queue);
-
-    g_string_printf(filename, "%s/%s%05i.edf", priv->path, priv->prefix, counter++);
-    //if (!filter_write_tiff(data, filename->str, width, height))
-    //				            g_message("something went wrong")
-    // write EDF header
-    // write raw data
-    // temporal saving unless 3d writer is not ready
-    FILE * idfile = fopen(filename->str,"wb");
-
-    if (!filter_3d_edf_write_header(idfile, xs, ys, zs))
-        g_message("cannot write edf header");
-
-    if (!filter_3d_edf_write_body(idfile, data, xs*ys*zs))
-        g_message("cannot write data");
-
-    fclose(idfile);
-
-    g_string_free(filename, TRUE);
-    g_free(dim_size);
-}
-
-static void 
-ufo_filter_3d_edf_writer_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
-{
-    UfoFilter3DEdfWriter *filter = UFO_FILTER_3D_EDF_WRITER(object);
-
-    switch (property_id) {
-        case PROP_PATH:
-            g_free(filter->priv->path);
-            filter->priv->path = g_strdup(g_value_get_string(value));
-            break;
-        case PROP_PREFIX:
-            g_free(filter->priv->prefix);
-            filter->priv->prefix = g_strdup(g_value_get_string(value));
-            break;
-        default:
-            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
-            break;
-    }
-}
-
-
-static void
-ufo_filter_3d_edf_writer_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
-{
-    UfoFilter3DEdfWriter *filter = UFO_FILTER_3D_EDF_WRITER(object);
-
-    switch (property_id) {
-        case PROP_PATH:
-            g_value_set_string(value, filter->priv->path);
-            break;
-        case PROP_PREFIX:
-            g_value_set_string(value, filter->priv->prefix);
-            break;
-        default:
-            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
-            break;
-    }
-}
-
-static void
-ufo_filter_3d_edf_writer_class_init(UfoFilter3DEdfWriterClass *klass)
-{
-    UfoFilterSinkClass *filter_class = UFO_FILTER_SINK_CLASS(klass);
-    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
-
-    gobject_class->set_property = ufo_filter_3d_edf_writer_set_property;
-    gobject_class->get_property = ufo_filter_3d_edf_writer_get_property;
-    filter_class->consume = ufo_filter_3d_edf_writer_consume;
-
-    edfwriter3d_properties[PROP_PREFIX] =
-        g_param_spec_string("prefix",
-                "Filename prefix",
-                "Prefix of output filename.",
-                "",
-                G_PARAM_READWRITE);
-
-    edfwriter3d_properties[PROP_PATH] =
-        g_param_spec_string("path",
-                "File path",
-                "Path where to store files.",
-                ".",
-                G_PARAM_READWRITE);
-
-    g_object_class_install_property(gobject_class, PROP_PATH, edfwriter3d_properties[PROP_PATH]);
-    g_object_class_install_property(gobject_class, PROP_PREFIX, edfwriter3d_properties[PROP_PREFIX]);
-
-    g_type_class_add_private(gobject_class, sizeof(UfoFilter3DEdfWriterPrivate));
-}
-
-static void
-ufo_filter_3d_edf_writer_init (UfoFilter3DEdfWriter *self)
-{
-    UfoInputParameter input_params[] = {{3, UFO_FILTER_INFINITE_INPUT}};
-
-    self->priv = UFO_FILTER_3D_EDF_WRITER_GET_PRIVATE(self);
-    self->priv->path = g_strdup(".");
-    self->priv->prefix = NULL;
-
-    ufo_filter_register_inputs (UFO_FILTER (self), 1, input_params);
-}
-
-G_MODULE_EXPORT
-UfoFilter *ufo_filter_plugin_new(void)
-{
-    return g_object_new(UFO_TYPE_FILTER_3D_EDF_WRITER, NULL);
-}

+ 0 - 36
src/ufo-filter-3d-edf-writer.h

@@ -1,36 +0,0 @@
-#ifndef __UFO_FILTER_3D_EDF_WRITER_H__
-#define __UFO_FILTER_3D_EDF_WRITER_H__ 
-
-#include <glib.h>
-#include <ufo/ufo.h>
-
-#define UFO_TYPE_FILTER_3D_EDF_WRITER             (ufo_filter_3d_edf_writer_get_type())
-#define UFO_FILTER_3D_EDF_WRITER(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), UFO_TYPE_FILTER_3D_EDF_WRITER, UfoFilter3DEdfWriter))
-#define UFO_IS_FILTER_3D_EDF_WRITER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), UFO_TYPE_FILTER_3D_EDF_WRITER))
-#define UFO_FILTER_3D_EDF_WRITER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), UFO_TYPE_FILTER_3D_EDF_WRITER, UfoFilter3DEdfWriterClass))
-#define UFO_IS_FILTER_3D_EDF_WRITER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), UFO_TYPE_FILTER_3D_EDF_WRITER))
-#define UFO_FILTER_3D_EDF_WRITER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS((obj), UFO_TYPE_FILTER_3D_EDF_WRITER, UfoFilter3DEdfWriterClass))
-
-typedef struct _UfoFilter3DEdfWriter           UfoFilter3DEdfWriter;
-typedef struct _UfoFilter3DEdfWriterClass      UfoFilter3DEdfWriterClass;
-typedef struct _UfoFilter3DEdfWriterPrivate    UfoFilter3DEdfWriterPrivate;
-
-struct _UfoFilter3DEdfWriter {
-    /*< private >*/
-    UfoFilterSink parent_instance;
-    UfoFilter3DEdfWriterPrivate *priv;
-};
-
-/**
-  * UfoFilter3DEdfWriterClass:
-  *
- * #UfoFilter3DEdfWriter class
-*/
-struct _UfoFilter3DEdfWriterClass {
-    /*< private >*/
-    UfoFilterSinkClass parent_class;
-};
-
-GType ufo_filter_3d_edf_writer_get_type(void);
-
-#endif

+ 23 - 1
tests/tests.py

@@ -53,7 +53,7 @@ class BasicTests(unittest.TestCase):
 
     def test_bug_lamino_reco(self):
         reader = self.get_task('reader', path=data_path('bug/projections/*.tif'))
-        writer = self.get_task('writer', filename=self.tmp_path('vol-%05i.tif'))
+        writer = self.get_task('edf-3d-writer', filename=self.tmp_path('vol-%05i.tif'))
         reco = self.get_task('lamino-bp')
         padding = self.get_task('padding-2d')
         fft_input = self.get_task('fft', dimensions=2)
@@ -82,3 +82,25 @@ class BasicTests(unittest.TestCase):
         self.graph.connect_nodes(ifft, reco)
         self.graph.connect_nodes(reco, writer)
         self.sched.run(self.graph)
+
+    def test_3d_edf(self):
+        reader = self.get_task('reader', path=data_path('bug/projections'), count=1)
+        writer = self.get_task('edf-3d-writer', filename=self.tmp_path('vol-%05i.edf'))
+        reco = self.get_task('lamino-bp')
+
+        vx, vy, vz = 256, 256, 128
+        reco.set_properties(vol_sx=vx, vol_sy=vy, vol_sz=vz,
+		                    vol_ox=0, vol_oy=0, vol_oz=0,
+		                    proj_ox=592, proj_oy=205)
+
+        self.graph.connect_nodes(reader, reco)
+        self.graph.connect_nodes(reco, writer)
+        self.sched.run(self.graph)
+
+        files = os.listdir(self.tmpdir)
+        self.assertIn('vol-00000.edf', files)
+        self.assertEqual(len(files), 1)
+
+        filesize = os.path.getsize(self.tmp_path('vol-00000.edf'))
+        expected = vx * vy * vz * 4 + 512   # floats + header
+        self.assertEqual(filesize, expected)