Example #1
0
static GwyContainer*
nanoscope_load(const gchar *filename,
               G_GNUC_UNUSED GwyRunType mode,
               GError **error)
{
    GwyContainer *meta, *container = NULL;
    GError *err = NULL;
    gchar *buffer = NULL;
    gchar *p;
    const gchar *self;
    gsize size = 0;
    NanoscopeFileType file_type;
    NanoscopeData *ndata;
    NanoscopeValue *val;
    GHashTable *hash, *scannerlist = NULL, *scanlist = NULL;
    GList *l, *list = NULL;
    gint i, xres = 0, yres = 0;
    gboolean ok, has_version = FALSE;

    if (!g_file_get_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }
    file_type = NANOSCOPE_FILE_TYPE_NONE;
    if (size > MAGIC_SIZE) {
        if (!memcmp(buffer, MAGIC_TXT, MAGIC_SIZE))
            file_type = NANOSCOPE_FILE_TYPE_TXT;
        else if (!memcmp(buffer, MAGIC_BIN, MAGIC_SIZE))
            file_type = NANOSCOPE_FILE_TYPE_BIN;
    }
    if (!file_type) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("File is not a Nanoscope file, "
                      "or it is a unknown subtype."));
        g_free(buffer);
        return NULL;
    }
    gwy_debug("File type: %d", file_type);
    /* as already know file_type, fix the first char for hash reading */
    *buffer = '\\';

    p = buffer;
    while ((hash = read_hash(&p, &err))) {
        ndata = g_new0(NanoscopeData, 1);
        ndata->hash = hash;
        list = g_list_append(list, ndata);
    }
    if (err) {
        g_propagate_error(error, err);
        ok = FALSE;
    }
    else
        ok = TRUE;

    for (l = list; ok && l; l = g_list_next(l)) {
        ndata = (NanoscopeData*)l->data;
        hash = ndata->hash;
        self = g_hash_table_lookup(hash, "#self");
        /* The alternate names were found in files written by some beast
         * called Nanoscope E software */
        if (gwy_strequal(self, "Scanner list")
            || gwy_strequal(self, "Microscope list")) {
            scannerlist = hash;
            continue;
        }
        if (gwy_strequal(self, "File list")) {
            has_version = !!g_hash_table_lookup(hash, "Version");
            gwy_debug("has Version: %d", has_version);
            continue;
        }
        if (gwy_strequal(self, "Ciao scan list")
            || gwy_strequal(self, "Afm list")
            || gwy_strequal(self, "NC Afm list")) {
            get_scan_list_res(hash, &xres, &yres);
            scanlist = hash;
        }
        if (!gwy_strequal(self, "Ciao image list")
            && !gwy_strequal(self, "AFM image list")
            && !gwy_strequal(self, "NCAFM image list"))
            continue;

        ndata->data_field = hash_to_data_field(hash, scannerlist, scanlist,
                                               file_type, has_version,
                                               size, buffer,
                                               xres, yres,
                                               &p, error);
        ok = ok && ndata->data_field;
    }

    if (ok) {
        gchar key[40];

        i = 0;
        container = gwy_container_new();
        for (l = list; l; l = g_list_next(l)) {
            ndata = (NanoscopeData*)l->data;
            if (ndata->data_field) {
                g_snprintf(key, sizeof(key), "/%d/data", i);
                gwy_container_set_object_by_name(container, key,
                                                 ndata->data_field);
                if ((val = g_hash_table_lookup(ndata->hash, "@2:Image Data"))
                    && val->soft_scale) {
                    g_snprintf(key, sizeof(key), "/%d/data/title", i);
                    gwy_container_set_string_by_name(container, key,
                                                     g_strdup(val->soft_scale));
                }

                meta = nanoscope_get_metadata(ndata->hash, list);
                g_snprintf(key, sizeof(key), "/%d/meta", i);
                gwy_container_set_object_by_name(container, key, meta);
                g_object_unref(meta);

                gwy_app_channel_check_nonsquare(container, i);
                i++;
            }
        }
        if (!i)
            gwy_object_unref(container);
    }

    for (l = list; l; l = g_list_next(l)) {
        ndata = (NanoscopeData*)l->data;
        gwy_object_unref(ndata->data_field);
        if (ndata->hash)
            g_hash_table_destroy(ndata->hash);
        g_free(ndata);
    }
    g_free(buffer);
    g_list_free(list);

    if (!container && ok)
        err_NO_DATA(error);

    return container;
}
Example #2
0
static GwySIUnit*
get_physical_scale(GHashTable *hash,
                   GHashTable *scannerlist,
                   GHashTable *scanlist,
                   gboolean has_version,
                   gdouble *scale,
                   GError **error)
{
    GwySIUnit *siunit, *siunit2;
    NanoscopeValue *val, *sval;
    gchar *key;
    gint q;

    /* Very old style scales (files with Version field) */
    if (!has_version) {
        if (!(val = g_hash_table_lookup(hash, "Z magnify image"))) {
            err_MISSING_FIELD(error, "Z magnify image");
            return NULL;
        }

        /* TODO: Luminescence */
        siunit = gwy_si_unit_new("m");
        /* According to Markus, the units are 1/100 nm, but his scale
         * calculation is raw/655.36 [nm].  We have the factor 1/65536 applied
         * automatically, that gives 1e-7 [m].  Whatever. */
        *scale = 1e-7 * val->hard_value;
        return siunit;
    }

    /* XXX: This is a damned heuristics.  For some value types we try to guess
     * a different quantity scale to look up. */
    if (!(val = g_hash_table_lookup(hash, "@2:Z scale"))) {
        if (!(val = g_hash_table_lookup(hash, "Z scale"))) {
            err_MISSING_FIELD(error, "Z scale");
            return NULL;
        }

        /* Old style scales */
        siunit = gwy_si_unit_new_parse(val->hard_value_units, &q);
        *scale = val->hard_value * pow10(q);
        if (val->hard_scale)
            *scale *= 65536.0/val->hard_scale;
        return siunit;
    }

    key = g_strdup_printf("@%s", val->soft_scale);

    if (!(sval = g_hash_table_lookup(scannerlist, key))
        && (!scanlist || !(sval = g_hash_table_lookup(scanlist, key)))) {
        g_warning("`%s' not found", key);
        g_free(key);
        /* XXX */
        *scale = val->hard_value;
        return gwy_si_unit_new("");
    }

    *scale = val->hard_value*sval->hard_value;

    if (!sval->hard_value_units || !*sval->hard_value_units) {
        if (gwy_strequal(val->soft_scale, "Sens. Phase"))
            siunit = gwy_si_unit_new("deg");
        else
            siunit = gwy_si_unit_new("V");
    }
    else {
        siunit = gwy_si_unit_new_parse(sval->hard_value_units, &q);
        siunit2 = gwy_si_unit_new("V");
        gwy_si_unit_multiply(siunit, siunit2, siunit);
        gwy_debug("Scale1 = %g V/LSB", val->hard_value);
        gwy_debug("Scale2 = %g %s", sval->hard_value, sval->hard_value_units);
        *scale *= pow10(q);
        gwy_debug("Total scale = %g %s/LSB",
                  *scale, gwy_si_unit_get_string(siunit,
                                                 GWY_SI_UNIT_FORMAT_PLAIN));
        g_object_unref(siunit2);
    }
    g_free(key);

    return siunit;
}
Example #3
0
static GwyContainer*
text_dump_import(gchar *buffer,
                 gsize size,
                 const gchar *filename,
                 GError **error)
{
    gchar *val, *key, *pos, *line, *title;
    GwyContainer *data;
    GwyDataField *dfield;
    gdouble xreal, yreal;
    gint xres, yres, id;
    GwySIUnit *uxy, *uz;
    const guchar *s;
    gdouble *d;
    gsize n;

    data = gwy_container_new();

    pos = buffer;
    while ((line = gwy_str_next_line(&pos)) && *line) {
        val = strchr(line, '=');
        if (!val || *line != '/') {
            g_warning("Garbage key: %s", line);
            continue;
        }
        if ((gsize)(val - buffer) + 1 > size) {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("End of file reached when value was expected."));
            goto fail;
        }
        *val = '\0';
        val++;
        if (!gwy_strequal(val, "[") || !pos || *pos != '[') {
            gwy_debug("<%s>=<%s>", line, val);
            if (*val)
                gwy_container_set_string_by_name(data, line, g_strdup(val));
            else
                gwy_container_remove_by_name(data, line);
            continue;
        }

        g_assert(pos && *pos == '[');
        pos++;
        dfield = NULL;
        gwy_container_gis_object_by_name(data, line, &dfield);

        id = 0;
        sscanf(line, "/%d", &id);

        /* get datafield parameters from already read values, failing back
         * to values of original data field */
        key = g_strconcat(line, "/xres", NULL);
        if (gwy_container_gis_string_by_name(data, key, &s))
            xres = atoi(s);
        else if (dfield)
            xres = gwy_data_field_get_xres(dfield);
        else {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("Missing data field width."));
            goto fail;
        }
        g_free(key);

        key = g_strconcat(line, "/yres", NULL);
        if (gwy_container_gis_string_by_name(data, key, &s))
            yres = atoi(s);
        else if (dfield)
            yres = gwy_data_field_get_yres(dfield);
        else {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("Missing data field height."));
            goto fail;
        }
        g_free(key);

        key = g_strconcat(line, "/xreal", NULL);
        if (gwy_container_gis_string_by_name(data, key, &s))
            xreal = g_ascii_strtod(s, NULL);
        else if (dfield)
            xreal = gwy_data_field_get_xreal(dfield);
        else {
            g_warning("Missing real data field width.");
            xreal = 1.0;   /* 0 could cause troubles */
        }
        g_free(key);

        key = g_strconcat(line, "/yreal", NULL);
        if (gwy_container_gis_string_by_name(data, key, &s))
            yreal = g_ascii_strtod(s, NULL);
        else if (dfield)
            yreal = gwy_data_field_get_yreal(dfield);
        else {
            g_warning("Missing real data field height.");
            yreal = 1.0;   /* 0 could cause troubles */
        }
        g_free(key);

        if (!(xres > 0 && yres > 0 && xreal > 0 && yreal > 0)) {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("Data field dimensions are not positive numbers."));
            goto fail;
        }

        key = g_strconcat(line, "/unit-xy", NULL);
        if (gwy_container_gis_string_by_name(data, key, &s))
            uxy = gwy_si_unit_new((const gchar*)s);
        else if (dfield) {
            uxy = gwy_data_field_get_si_unit_xy(dfield);
            uxy = gwy_si_unit_duplicate(uxy);
        }
        else {
            g_warning("Missing lateral units.");
            uxy = gwy_si_unit_new("m");
        }
        g_free(key);

        key = g_strconcat(line, "/unit-z", NULL);
        if (gwy_container_gis_string_by_name(data, key, &s))
            uz = gwy_si_unit_new((const gchar*)s);
        else if (dfield) {
            uz = gwy_data_field_get_si_unit_z(dfield);
            uz = gwy_si_unit_duplicate(uz);
        }
        else {
            g_warning("Missing value units.");
            uz = gwy_si_unit_new("m");
        }
        g_free(key);

        key = g_strconcat(line, "/title", NULL);
        title = NULL;
        gwy_container_gis_string_by_name(data, key, (const guchar**)&title);
        /* We got the contained string but that would disappear. */
        title = g_strdup(title);
        g_free(key);

        n = xres*yres*sizeof(gdouble);
        if ((gsize)(pos - buffer) + n + 3 > size) {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("End of file reached inside a data field."));
            goto fail;
        }
        dfield = GWY_DATA_FIELD(gwy_data_field_new(xres, yres, xreal, yreal,
                                                   FALSE));
        gwy_data_field_set_si_unit_xy(dfield, GWY_SI_UNIT(uxy));
        gwy_object_unref(uxy);
        gwy_data_field_set_si_unit_z(dfield, GWY_SI_UNIT(uz));
        gwy_object_unref(uz);
        d = gwy_data_field_get_data(dfield);
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
        memcpy(d, pos, n);
#else
        gwy_memcpy_byte_swap(pos, (guint8*)d,
                             sizeof(gdouble), xres*yres, sizeof(gdouble)-1);
#endif
        pos += n;
        val = gwy_str_next_line(&pos);
        if (!gwy_strequal(val, "]]")) {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("Missing end of data field marker."));
            gwy_object_unref(dfield);
            goto fail;
        }
        gwy_container_remove_by_prefix(data, line);
        gwy_container_set_object_by_name(data, line, dfield);
        g_object_unref(dfield);

        if (title) {
            key = g_strconcat(line, "/title", NULL);
            gwy_container_set_string_by_name(data, key, title);
            g_free(key);
        }

        gwy_file_channel_import_log_add(data, id, NULL, filename);
    }
    return data;

fail:
    gwy_container_remove_by_prefix(data, NULL);
    g_object_unref(data);
    return NULL;
}
Example #4
0
static GwyContainer*
gxsm_load(const gchar *filename,
          G_GNUC_UNUSED GwyRunType mode,
          GError **error)
{
    static const gchar *dimensions[] = { "time", "value", "dimy", "dimx" };
    GwyContainer *data = NULL;
    GwyDataField *dfield;
    GwySIUnit *siunit;
    NetCDF cdffile;
    const NetCDFDim *dim;
    const NetCDFVar *var;
    const NetCDFAttr *attr;
    gdouble real;
    gint i, power10;

    if (!cdffile_load(&cdffile, filename, error))
        return NULL;

    if (cdffile.nrecs) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("NetCDF records are not supported."));
        goto gxsm_load_fail;
    }

    /* Look for variable "H" or "FloatField".  This seems to be how GXSM calls
     * data. */
    if (!(var = cdffile_get_var(&cdffile, "H"))
        && !(var = cdffile_get_var(&cdffile, "FloatField"))) {
        err_NO_DATA(error);
        goto gxsm_load_fail;
    }

    /* Check the dimensions.  We only know how to handle time=1 and value=1. */
    for (i = 0; i < var->ndims; i++) {
        dim = cdffile.dims + var->dimids[i];
        if (!gwy_strequal(dim->name, dimensions[i])
            || (i < 2 && dim->length != 1)) {
            /* XXX */
            err_NO_DATA(error);
            goto gxsm_load_fail;
        }
    }

    if (err_DIMENSION(error, cdffile.dims[var->dimids[3]].length)
        || err_DIMENSION(error, cdffile.dims[var->dimids[2]].length))
        goto gxsm_load_fail;

    dfield = read_data_field((const guchar*)(cdffile.buffer + var->begin),
                             cdffile.dims[var->dimids[3]].length,
                             cdffile.dims[var->dimids[2]].length,
                             var->type);

    if ((siunit = read_real_size(&cdffile, "rangex", &real, &power10))) {
        /* Use negated positive conditions to catch NaNs */
        if (!((real = fabs(real)) > 0)) {
            g_warning("Real x size is 0.0, fixing to 1.0");
            real = 1.0;
        }
        gwy_data_field_set_xreal(dfield, real*pow10(power10));
        gwy_data_field_set_si_unit_xy(dfield, siunit);
        g_object_unref(siunit);
    }

    if ((siunit = read_real_size(&cdffile, "rangey", &real, &power10))) {
        /* Use negated positive conditions to catch NaNs */
        if (!((real = fabs(real)) > 0)) {
            g_warning("Real y size is 0.0, fixing to 1.0");
            real = 1.0;
        }
        gwy_data_field_set_yreal(dfield, real*pow10(power10));
        /* must be the same gwy_data_field_set_si_unit_xy(dfield, siunit); */
        g_object_unref(siunit);
    }

    if ((siunit = read_real_size(&cdffile, "rangez", &real, &power10))) {
        /* rangez seems to be some bogus value, take only units */
        gwy_data_field_set_si_unit_z(dfield, siunit);
        gwy_data_field_multiply(dfield, pow10(power10));
        g_object_unref(siunit);
    }
    if ((siunit = read_real_size(&cdffile, "dz", &real, &power10))) {
        /* on the other hand the units seem to be bogus here, take the range */
        gwy_data_field_multiply(dfield, real);
        g_object_unref(siunit);
    }

    data = gwy_container_new();
    gwy_container_set_object_by_name(data, "/0/data", dfield);
    g_object_unref(dfield);

    if ((attr = cdffile_get_attr(var->attrs, var->nattrs, "long_name"))
        && attr->type == NC_CHAR
        && attr->nelems) {
        gwy_container_set_string_by_name(data, "/0/data/title",
                                         g_strndup(attr->values, attr->nelems));
    }

gxsm_load_fail:
    gwy_file_abandon_contents(cdffile.buffer, cdffile.size, NULL);
    cdffile_free(&cdffile);

    return data;
}
Example #5
0
static gboolean
read_file_info(const guchar *buffer, gsize size,
               FileInfo *info,
               GError **error)
{
    guint expected_size, i;
    const guchar *p;

    gwy_clear(info, 1);
    p = buffer + MAGIC_SIZE;
    /* read structure variables from buffer */
    for (i = 0; i < (HEADER_SIZE - MAGIC_SIZE)/8; i++) {
        gchar key[5];
        guint32 value;

        key[4] = '\0';
        memcpy(key, p, 4);
        p += 4;
        value = gwy_get_guint32_le(&p);
        if (!key[0])
            continue;

        gwy_debug("%s: 0x%04x", key, value);

        /* Do not take values past the end of file and zeros into account at
         * all.   The software seems to sometimes emit silly extra fields. */
        if (!value || value >= size)
            continue;

        else if (gwy_strequal(key, "DESC"))
            info->desc_offset = value;
        else if (gwy_strequal(key, "DATE"))
            info->date_offset = value;
        else if (gwy_strequal(key, "PLET"))
            info->palette_offset = value;
        else if (gwy_strequal(key, "IMAG"))
            info->image_offset = value;
        else if (gwy_strequal(key, "HARD"))
            info->hard_offset = value;
        else if (gwy_strequal(key, "IMGP"))
            info->img_p_offset = value;
        else if (gwy_strequal(key, "SDES"))
            info->short_desc_offset = value;
        else if (gwy_strequal(key, "KEYS"))
            info->keys_offset = value;
        else {
            gwy_debug("Unknown field %s", key);
        }
    }

    /* Pixel image size */
    if (!(p = get_param_pointer(buffer, size,
                                info->image_offset, sizeof(guint16),
                                "IMAG", error)))
        return FALSE;
    info->img_res = gwy_get_guint16_le(&p);
    if (err_DIMENSION(error, info->img_res))
        return FALSE;

    /* Image data.  It is the *same* pointer, just after the pixel size.  */
    info->image_data = (const guint16*)p;
    expected_size = (p - buffer) + info->img_res*info->img_res*sizeof(guint16);
    if (err_SIZE_MISMATCH(error, expected_size, size, FALSE))
        return FALSE;

    /* Real image size */
    if (!(p = get_param_pointer(buffer, size,
                                info->hard_offset, sizeof(gfloat),
                                "HARD", error)))
        return FALSE;
    info->real_size = gwy_get_gfloat_le(&p);
    if (!((info->real_size = fabs(info->real_size)) > 0)) {
        g_warning("Real size is 0.0, fixing to 1.0");
        info->real_size = 1.0;
    }

    /* Value scale factor */
    if (!(p = get_param_pointer(buffer, size,
                                info->img_p_offset + 8, sizeof(gfloat),
                                "IMGP", error)))
        return FALSE;
    info->z_scale = gwy_get_gfloat_le(&p);

    return TRUE;
}
Example #6
0
static guint
find_data_offsets(const gchar *buffer,
                  gsize size,
                  GPtrArray *ezdfile,
                  GError **error)
{
    EZDSection *dataset, *section;
    GString *grkey;
    guint required_size = 0;
    gint ngroups, nchannels, i, j, k;
    guint ndata = 0;
    gchar *p;

    /* Sanity check */
    if (!ezdfile->len) {
        err_NO_DATA(error);
        return 0;
    }
    dataset = (EZDSection*)g_ptr_array_index(ezdfile, 0);
    if (strcmp(dataset->name, "DataSet")) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("First section isn't DataSet"));
        return 0;
    }

    if (!(p = g_hash_table_lookup(dataset->meta, "GroupCount"))
        || (ngroups = atol(p)) <= 0) {
        err_INVALID(error, _("GroupCount in [DataSet]"));
        return 0;
    }

    /* Scan groups */
    grkey = g_string_new(NULL);
    for (i = 0; i < ngroups; i++) {
        g_string_printf(grkey, "Gr%d-Count", i);
        if (!(p = g_hash_table_lookup(dataset->meta, grkey->str))) {
            g_warning("No count for group %u", i);
            continue;
        }

        if ((nchannels = atol(p)) <= 0)
            continue;

        /* Scan channels inside a group, note it's OK there's less channels
         * than specified */
        for (j = 0; j < nchannels; j++) {
            g_string_printf(grkey, "Gr%d-Ch%d", i, j);
            if (!(p = g_hash_table_lookup(dataset->meta, grkey->str)))
                continue;

            section = NULL;
            for (k = 1; k < ezdfile->len; k++) {
                section = (EZDSection*)g_ptr_array_index(ezdfile, k);
                if (gwy_strequal(section->name, p))
                    break;
            }
            if (!section) {
                g_warning("Cannot find section for %s", p);
                continue;
            }

            /* Compute data position */
            gwy_debug("Data %s at offset %u from data start",
                      grkey->str, required_size);
            gwy_debug("xres = %d, yres = %d, bpp = %d, z-name = %s",
                      section->xres, section->yres, section->bitdepth,
                      section->zrange.name);
            if (section->yres < 2) {
                gwy_debug("Skipping 1D data Gr%d-Ch%d. FIXME.", i, j);
                continue;
            }
            ndata++;
            section->data = buffer + required_size;
            required_size += section->xres * section->yres
                             * (section->bitdepth/8);
            if (required_size > size) {
                g_warning("Truncated file, %s doesn't fit", grkey->str);
                g_string_free(grkey, TRUE);
                section->data = NULL;

                return 0;
            }
            section->group = i;
            section->channel = j;
        }
    }
    g_string_free(grkey, TRUE);

    if (!ndata)
        err_NO_DATA(error);

    return ndata;
}
Example #7
0
static gboolean
file_read_header(GPtrArray *ezdfile,
                 gchar *buffer,
                 GError **error)
{
    EZDSection *section = NULL;
    gchar *p, *line;
    guint len;

    while ((line = gwy_str_next_line(&buffer))) {
        line = g_strstrip(line);
        if (!(len = strlen(line)))
            continue;
        if (line[0] == '[' && line[len-1] == ']') {
            section = g_new0(EZDSection, 1);
            g_ptr_array_add(ezdfile, section);
            line[len-1] = '\0';
            section->name = g_strdup(line + 1);
            section->meta = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                  g_free, g_free);
            gwy_debug("Section <%s>", section->name);
            continue;
        }
        if (!section) {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("Garbage before first header section."));
            return FALSE;
        }
        /* Skip comments */
        if (g_str_has_prefix(line, "--"))
            continue;

        p = strchr(line, '=');
        if (!p) {
            g_set_error(error, GWY_MODULE_FILE_ERROR,
                        GWY_MODULE_FILE_ERROR_DATA,
                        _("Malformed header line (missing =)."));
            return FALSE;
        }
        *p = '\0';
        p++;

        if (gwy_strequal(line, "SaveMode")) {
            if (strcmp(p, "Binary"))
                g_warning("SaveMode is not Binary, this is not supported");
        }
        else if (gwy_strequal(line, "SaveBits"))
            section->bitdepth = atol(p);
        else if (gwy_strequal(line, "SaveSign")) {
            section->sign = gwy_strequal(p, "Signed");
            if (!section->sign)
                g_warning("SaveSign is not Signed, this is not supported");
        }
        else if (gwy_strequal(line, "SaveOrder")) {
            if (gwy_strequal(p, "Intel"))
                section->byteorder = G_LITTLE_ENDIAN;
            else
                g_warning("SaveOrder is not Intel, this is not supported");
        }
        else if (gwy_strequal(line, "Frame")) {
            if (gwy_strequal(p, "Scan forward"))
                section->direction = SCAN_FORWARD;
            else if (gwy_strequal(p, "Scan backward"))
                section->direction = SCAN_BACKWARD;
        }
        else if (gwy_strequal(line, "Points"))
            section->xres = atol(p);
        else if (gwy_strequal(line, "Lines"))
            section->yres = atol(p);
        /* FIXME: this is ugly, and incorrect for non-2D data */
        else if (gwy_strequal(line, "Dim0Name"))
            section->xrange.name = g_strdup(p);
        else if (gwy_strequal(line, "Dim1Name"))
            section->yrange.name = g_strdup(p);
        else if (gwy_strequal(line, "Dim2Name"))
            section->zrange.name = g_strdup(p);
        else if (gwy_strequal(line, "Dim0Unit"))
            section->xrange.unit = g_strdup(p);
        else if (gwy_strequal(line, "Dim1Unit"))
            section->yrange.unit = g_strdup(p);
        else if (gwy_strequal(line, "Dim2Unit"))
            section->zrange.unit = g_strdup(p);
        else if (gwy_strequal(line, "Dim0Min"))
            section->xrange.min = g_ascii_strtod(p, NULL);
        else if (gwy_strequal(line, "Dim1Min"))
            section->yrange.min = g_ascii_strtod(p, NULL);
        else if (gwy_strequal(line, "Dim2Min"))
            section->zrange.min = g_ascii_strtod(p, NULL);
        else if (gwy_strequal(line, "Dim0Range"))
            section->xrange.range = g_ascii_strtod(p, NULL);
        else if (gwy_strequal(line, "Dim1Range"))
            section->yrange.range = g_ascii_strtod(p, NULL);
        else if (gwy_strequal(line, "Dim2Range"))
            section->zrange.range = g_ascii_strtod(p, NULL);
        else
            g_hash_table_replace(section->meta, g_strdup(line), g_strdup(p));
    }
    return TRUE;
}