예제 #1
0
static void
clone_meta(GwyContainer *container, GwyContainer *meta, guint nchannels)
{
    guint i;
    gchar s[32];

    if (!gwy_container_get_n_items(meta))
        return;

    /* Simply store identical metadata for each channel */
    for (i = 0; i < nchannels; i++) {
        GwyContainer *m = gwy_container_duplicate(meta);
        g_snprintf(s, sizeof(s), "/%u/meta", i);
        gwy_container_set_object_by_name(container, s, m);
        g_object_unref(m);
    }
}
예제 #2
0
static void
apedax_get_channels_data(unzFile uFile,
                         guchar *scanXmlContent,
                         gsize contentSize,
                         const gchar *filename,
                         GwyContainer *container,
                         GwyContainer *meta,
                         const APEScanSize *scanSize,
                         GError **error)
{
    xmlDocPtr doc = NULL;
    xmlNodePtr cur = NULL;
    xmlXPathContextPtr context;
    xmlXPathObjectPtr pathObj;
    xmlNodeSetPtr nodeset;
    xmlChar *buffer = NULL;
    gchar key[256];
    gint i;
    gint power10 = 0;
    gdouble scaleFactor = 1.0;
    GwySIUnit *zUnit;
    gchar *zUnitString = NULL;
    gchar *binFileName = NULL;
    GwyDataField *dfield;
    GwyContainer *tmp;

    if (scanXmlContent == NULL || contentSize == 0)
        return;

    gwy_clear(key, sizeof(key));

    doc = xmlReadMemory(scanXmlContent,
                        contentSize,
                        "scan.xml",
                        NULL,
                        0);

    if (doc == NULL)
        goto fail;

    context = xmlXPathNewContext(doc);

    if (context == NULL)
        goto fail;

    pathObj = xmlXPathEvalExpression("/Scan/Channels/Channel", context);

    if (pathObj == NULL) {
        xmlXPathFreeContext(context);
        goto fail;
    }

    /*There must be at least one channel*/
    if (xmlXPathNodeSetIsEmpty(pathObj->nodesetval)) {
        xmlXPathFreeObject(pathObj);
        xmlXPathFreeContext(context);
        err_NO_DATA(error);
        return;
    }

    nodeset = pathObj->nodesetval;

    if (nodeset->nodeNr <= 0)
        goto fail;

    for (i = 0; i < nodeset->nodeNr; i++) {

        cur = nodeset->nodeTab[i]->xmlChildrenNode;

        while (cur) {
            /*Label*/
            if (gwy_strequal((gchar*)cur->name, "Label")) {
                buffer = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                g_snprintf(key, sizeof(key), "/%d/data/title", i);
                gwy_container_set_string_by_name(container, key, g_strdup(buffer));
                xmlFree(buffer);
            }
            /*Factor*/
            if (gwy_strequal((gchar*)cur->name, "ConversionFactor")) {
                buffer = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                scaleFactor = g_ascii_strtod((gchar*)buffer, NULL);
                xmlFree(buffer);
            }
            /*Unit*/
            if (gwy_strequal((gchar*)cur->name, "DataUnit")) {
                buffer = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                zUnitString = g_strdup((gchar*)buffer);
                zUnit = gwy_si_unit_new_parse(zUnitString, &power10);
                xmlFree(buffer);
                g_object_unref(zUnit);
            }
            /*Binary file name*/
            if (gwy_strequal((gchar*)cur->name, "BINFile")) {
                buffer = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
                binFileName = g_strdup((gchar*)buffer);
                xmlFree(buffer);
            }
            cur = cur->next;
        }

        scaleFactor *= pow(10.0, power10);

        dfield = apedax_get_data_field(uFile,
                                       binFileName,
                                       scanSize,
                                       zUnitString,
                                       scaleFactor,
                                       error);
        if (dfield) {
            g_snprintf(key, sizeof(key), "/%d/data", i);
            gwy_container_set_object_by_name(container, key, dfield);
            g_object_unref(dfield);
            gwy_file_channel_import_log_add(container, i, NULL,
                                            filename);

            tmp = gwy_container_duplicate(meta);
            g_snprintf(key, sizeof(key), "/%d/meta", i);
            gwy_container_set_object_by_name(container, key, tmp);
            g_object_unref(tmp);
        }
    }

    xmlXPathFreeObject(pathObj);
    xmlXPathFreeContext(context);
    xmlFreeDoc(doc);

    return;

fail:
    err_FILE_TYPE(error, FILE_TYPE);
    if (doc)
        xmlFreeDoc(doc);
    return;
}
예제 #3
0
static GwyContainer*
apefile_load(const gchar *filename,
             G_GNUC_UNUSED GwyRunType mode,
             GError **error)
{
    APEFile apefile;
    GwyContainer *meta, *container = NULL;
    guchar *buffer = NULL;
    const guchar *p;
    gsize size = 0;
    GError *err = NULL;
    GwyDataField *dfield = NULL;
    guint b, i, n;

    if (!gwy_file_get_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }
    p = buffer;
    apefile.version = *(p++);
    if (size < 1294) {
        err_TOO_SHORT(error);
        gwy_file_abandon_contents(buffer, size, NULL);
        return NULL;
    }

    apefile.spm_mode = *(p++);
    p += 2;   /* Skip VisualBasic VARIANT type type */
    apefile.scan_date = gwy_get_gdouble_le(&p);
    apefile.maxr_x = gwy_get_gfloat_le(&p);
    apefile.maxr_y = gwy_get_gfloat_le(&p);
    apefile.x_offset = gwy_get_guint32_le(&p);
    apefile.y_offset = gwy_get_guint32_le(&p);
    apefile.size_flag = gwy_get_guint16_le(&p);
    apefile.res = 16 << apefile.size_flag;
    if (err_DIMENSION(error, apefile.res)) {
        gwy_file_abandon_contents(buffer, size, NULL);
        return NULL;
    }
    apefile.acquire_delay = gwy_get_gfloat_le(&p);
    apefile.raster_delay = gwy_get_gfloat_le(&p);
    apefile.tip_dist = gwy_get_gfloat_le(&p);
    apefile.v_ref = gwy_get_gfloat_le(&p);
    if (apefile.version == 1) {
        apefile.vpmt1 = gwy_get_guint16_le(&p);
        apefile.vpmt2 = gwy_get_guint16_le(&p);
    }
    else {
        apefile.vpmt1 = gwy_get_gfloat_le(&p);
        apefile.vpmt2 = gwy_get_gfloat_le(&p);
    }
    apefile.remark = g_strndup(p, 120);
    p += 120;
    apefile.x_piezo_factor = gwy_get_guint32_le(&p);
    apefile.y_piezo_factor = gwy_get_guint32_le(&p);
    apefile.z_piezo_factor = gwy_get_guint32_le(&p);
    apefile.hv_gain = gwy_get_gfloat_le(&p);
    apefile.freq_osc_tip = gwy_get_gdouble_le(&p);
    apefile.rotate = gwy_get_gfloat_le(&p);
    apefile.slope_x = gwy_get_gfloat_le(&p);
    apefile.slope_y = gwy_get_gfloat_le(&p);
    apefile.topo_means = gwy_get_guint16_le(&p);
    apefile.optical_means = gwy_get_guint16_le(&p);
    apefile.error_means = gwy_get_guint16_le(&p);
    /*
    g_printerr("%04x %04x %04x\n",
               apefile.topo_means, apefile.optical_means, apefile.error_means);
               */
    apefile.channels = gwy_get_guint32_le(&p);
    apefile.ndata = 0;
    for (b = apefile.channels; b; b = b >> 1)
        apefile.ndata += (b & 1);
    apefile.range_x = gwy_get_gfloat_le(&p);
    apefile.range_y = gwy_get_gfloat_le(&p);
    apefile.subversion = gwy_get_guint16_le(&p);
    /* Read everything since the header is long enough, check the version
     * later when we decide whether to use these values or not. */
    /* Since 2.1 */
    apefile.hv_gain_z = gwy_get_gfloat_le(&p);
    /* Since 2.2 */
    apefile.fast2_0 = gwy_get_gdouble_le(&p);
    apefile.fast2_1 = gwy_get_gdouble_le(&p);
    apefile.fast2_2 = gwy_get_gdouble_le(&p);
    apefile.fast2_3 = gwy_get_gdouble_le(&p);
    /* Since 2.3 */
    apefile.pg850_image = !!gwy_get_guint16_le(&p);
    /* Since 2.4 */
    apefile.xy_hv_status = gwy_get_gint16_le(&p);
    apefile.z_hv_status = gwy_get_gint16_le(&p);
    /* reserved */
    p += 2;

    apefile.xreal = apefile.maxr_x * apefile.x_piezo_factor * apefile.range_x
                    * apefile.hv_gain/65535.0 * 1e-9;
    apefile.yreal = apefile.maxr_y * apefile.y_piezo_factor * apefile.range_y
                    * apefile.hv_gain/65535.0 * 1e-9;
    /* Use negated positive conditions to catch NaNs */
    if (!((apefile.xreal = fabs(apefile.xreal)) > 0)) {
        g_warning("Real x size is 0.0, fixing to 1.0");
        apefile.xreal = 1.0;
    }
    if (!((apefile.yreal = fabs(apefile.yreal)) > 0)) {
        g_warning("Real y size is 0.0, fixing to 1.0");
        apefile.yreal = 1.0;
    }

    gwy_debug("version = %u.%u, spm_mode = %u",
              apefile.version, apefile.subversion, apefile.spm_mode);
    gwy_debug("scan_date = %f", apefile.scan_date);
    gwy_debug("maxr_x = %g, maxr_y = %g", apefile.maxr_x, apefile.maxr_y);
    gwy_debug("x_offset = %u, y_offset = %u",
              apefile.x_offset, apefile.y_offset);
    gwy_debug("size_flag = %u", apefile.size_flag);
    gwy_debug("acquire_delay = %g, raster_delay = %g, tip_dist = %g",
              apefile.acquire_delay, apefile.raster_delay, apefile.tip_dist);
    gwy_debug("v_ref = %g, vpmt1 = %g, vpmt2 = %g",
              apefile.v_ref, apefile.vpmt1, apefile.vpmt2);
    gwy_debug("x_piezo_factor = %u, y_piezo_factor = %u, z_piezo_factor = %u",
              apefile.x_piezo_factor, apefile.y_piezo_factor,
              apefile.z_piezo_factor);
    gwy_debug("hv_gain = %g, freq_osc_tip = %g, rotate = %g",
              apefile.hv_gain, apefile.freq_osc_tip, apefile.rotate);
    gwy_debug("slope_x = %g, slope_y = %g",
              apefile.slope_x, apefile.slope_y);
    gwy_debug("topo_means = %u, optical_means = %u, error_means = %u",
              apefile.topo_means, apefile.optical_means, apefile.error_means);
    gwy_debug("channel bitmask = %03x, ndata = %u",
              apefile.channels, apefile.ndata);
    gwy_debug("range_x = %g, range_y = %g",
              apefile.range_x, apefile.range_y);

    n = (apefile.res + 1)*(apefile.res + 1)*sizeof(float);
    if (size - (p - buffer) != n*apefile.ndata) {
        g_warning("Expected data size %u, but it's %u.",
                  n*apefile.ndata, (guint)(size - (p - buffer)));
        apefile.ndata = MIN(apefile.ndata, (size - (p - buffer))/n);
    }
    if (!apefile.ndata) {
        err_NO_DATA(error);
        gwy_file_abandon_contents(buffer, size, NULL);
        return NULL;
    }
    fill_data_fields(&apefile, p);
    gwy_file_abandon_contents(buffer, size, NULL);

    container = gwy_container_new();
    /* All metadata seems to be per-file (global) */
    meta = apefile_get_metadata(&apefile);
    for (b = apefile.channels, n = 0, i = 0; b; b = b >> 1, i++) {
        GwyContainer *tmp;
        gchar *title;
        gchar key[32];

        if (!(b & 1))
            continue;

        g_snprintf(key, sizeof(key), "/%d/data", n);
        dfield = apefile.data[n];
        gwy_container_set_object_by_name(container, key, dfield);
        g_object_unref(apefile.data[n]);

        g_snprintf(key, sizeof(key), "/%d/data/title", n);

        /*
         * Channel labelling based on SPM Mode
         */
        switch (apefile.spm_mode) {
            case SPM_MODE_SNOM:
            title = gwy_enuml_to_string(i,
                                        "Height",   APE_HEIGHT,
                                        "Height-R", APE_HEIGHT_R,
                                        "NSOM",     APE_NSOM,
                                        "NSOM-R",   APE_NSOM_R,
                                        "Error",    APE_ERROR,
                                        "Error-R",  APE_ERROR_R,
                                        "NSOM2",    APE_NSOM2,
                                        "NSOM2-R",  APE_NSOM2_R,
                                        "Lateral",     APE_AUX1,
                                        "Z-Z0",     APE_AUX2,
                                        "Lateral-R",   APE_AUX1_R,
                                        "Z-Z0-R",   APE_AUX2_R,
                                        NULL);
            break;

            case SPM_MODE_AFM_NONCONTACT:
            case SPM_MODE_AFM_CONTACT:
            case SPM_MODE_PHASE_DETECT_AFM:
            title = gwy_enuml_to_string(i,
                                        "Height",   APE_HEIGHT,
                                        "Height-R", APE_HEIGHT_R,
                                        "IN1",     APE_NSOM,
                                        "IN1-R",   APE_NSOM_R,
                                        "Error",    APE_ERROR,
                                        "Error-R",  APE_ERROR_R,
                                        "IN2",    APE_NSOM2,
                                        "IN2-R",  APE_NSOM2_R,
                                        "Lateral",     APE_AUX1,
                                        "Z-Z0",     APE_AUX2,
                                        "Lateral-R",   APE_AUX1_R,
                                        "Z-Z0-R",   APE_AUX2_R,
                                        NULL);
            break;

            default:
            title = gwy_enuml_to_string(i,
                                        "Height",   APE_HEIGHT,
                                        "Height-R", APE_HEIGHT_R,
                                        "IN1",     APE_NSOM,
                                        "IN1-R",   APE_NSOM_R,
                                        "Error",    APE_ERROR,
                                        "Error-R",  APE_ERROR_R,
                                        "IN2",    APE_NSOM2,
                                        "IN2-R",  APE_NSOM2_R,
                                        "Aux1",     APE_AUX1,
                                        "Z-Z0",     APE_AUX2,
                                        "Aux1-R",   APE_AUX1_R,
                                        "Z-Z0-R",   APE_AUX2_R,
                                        NULL);
            break;
        }
        if (title && *title)
            gwy_container_set_string_by_name(container, key, g_strdup(title));

        tmp = gwy_container_duplicate(meta);
        g_snprintf(key, sizeof(key), "/%d/meta", n);
        gwy_container_set_object_by_name(container, key, tmp);
        g_object_unref(tmp);

        gwy_file_channel_import_log_add(container, n, NULL, filename);

        n++;
    }

    g_object_unref(meta);
    g_free(apefile.remark);

    return container;
}
예제 #4
0
/* FIXME: this function could use some sort of failure indication, if the
 * file is damaged and no data field can be loaded, suspicionless caller can
 * return empty Container */
static void
tiff_load_channel(TIFF *tiff,
                  GwyContainer *container, GwyContainer *meta,
                  gint idx, gint ilen, gint jlen, gdouble ulen, gdouble vlen)
{
    GwyDataField *dfield;
    GwySIUnit *siunit;
    GString *key;
    gdouble *data;
    guchar *buffer;
    gchar *channel;
    const gchar *name = NULL;
    const gchar *slot = NULL;
    const gchar *unit = NULL;
    gboolean retrace = FALSE;
    gboolean reflect = FALSE;
    gdouble mult = 0.0;
    gdouble offset = 0.0;
    gint num_slots = 0;
    gint i, j;

    tiff_get_custom_string(tiff, JPK_TIFFTAG_ChannelFancyName, &name);
    if (!name)
        tiff_get_custom_string(tiff, JPK_TIFFTAG_Channel, &name);
    g_return_if_fail(name != NULL);

    tiff_get_custom_boolean(tiff, JPK_TIFFTAG_Channel_retrace, &retrace);

    channel = g_strdup_printf("%s%s", name, retrace ? " (retrace)" : "");

    gwy_debug("channel: %s", channel);

    tiff_get_custom_integer(tiff, JPK_TIFFTAG_NrOfSlots, &num_slots);
    g_return_if_fail(num_slots > 0);

    gwy_debug("num_slots: %d", num_slots);

    /* Locate the default slot */

    tiff_get_custom_string(tiff, JPK_TIFFTAG_DefaultSlot, &slot);
    g_return_if_fail(slot != NULL);

    gwy_debug("num_slots: %d, default slot: %s", num_slots, slot);

    for (i = 0; i < num_slots; i++) {
        const gchar *string;

        if (tiff_get_custom_string(tiff, JPK_TIFFTAG_Slot_Name(i), &string)
            && string
            && gwy_strequal(string, slot)) {
            tiff_get_custom_string(tiff, JPK_TIFFTAG_Scaling_Type(i), &string);
            g_return_if_fail(gwy_strequal(string, "LinearScaling"));

            tiff_get_custom_double(tiff, JPK_TIFFTAG_Scaling_Multiply(i),
                                   &mult);
            tiff_get_custom_double(tiff, JPK_TIFFTAG_Scaling_Offset(i),
                                   &offset);

            gwy_debug("multipler: %g offset: %g", mult, offset);

            tiff_get_custom_string(tiff, JPK_TIFFTAG_Encoder_Unit(i), &unit);

            break;
        }
    }

    /* Create a new data field */

    dfield = gwy_data_field_new(ilen, jlen, ulen, vlen, FALSE);

    siunit = gwy_si_unit_new("m");
    gwy_data_field_set_si_unit_xy(dfield, siunit);
    g_object_unref(siunit);

    if (unit) {
        siunit = gwy_si_unit_new(unit);
        gwy_data_field_set_si_unit_z(dfield, siunit);
        g_object_unref(siunit);
    }

    /* Read the scan data */

    data = gwy_data_field_get_data(dfield);

    buffer = g_new(guchar, TIFFScanlineSize(tiff));

    tiff_get_custom_boolean(tiff, JPK_TIFFTAG_Grid_Reflect, &reflect);

    if (!reflect)
        data += (jlen - 1) * ilen;

    for (j = 0; j < jlen; j++) {
        const guint16 *src = (const guint16 *)buffer;
        gdouble *dest = data;

        TIFFReadScanline(tiff, buffer, j, 0);

        for (i = 0; i < ilen; i++) {
            guint16 s = *src++;

            *dest++ = offset + mult * (gdouble)s;
        }

        if (reflect)
            data += ilen;
        else
            data -= ilen;
    }

    /* Add the GwyDataField to the container */

    key = g_string_new("");
    g_string_printf(key, "/%d/data", idx);
    gwy_container_set_object_by_name(container, key->str, dfield);
    g_object_unref(dfield);

    g_string_append(key, "/title");
    gwy_container_set_string_by_name(container, key->str, channel);

    if (gwy_container_get_n_items(meta)) {
        GwyContainer *tmp;

        tmp = gwy_container_duplicate(meta);
        g_string_printf(key, "/%d/meta", idx);
        gwy_container_set_object_by_name(container, key->str, tmp);
        g_object_unref(tmp);
    }

    g_string_free(key, TRUE);
}
예제 #5
0
static GwyContainer*
igor_load(const gchar *filename,
          G_GNUC_UNUSED GwyRunType mode,
          GError **error)
{
    GwyContainer *meta = NULL, *container = NULL;
    GwyDataField *dfield = NULL, *maskfield = NULL;
    GwyTextHeaderParser parser;
    IgorFile igorfile;
    IgorWaveHeader5 *wave5;
    GError *err = NULL;
    guchar *p, *buffer = NULL;
    gint xres, yres;
    gsize expected_size, size = 0;
    gchar *note = NULL;
    const gchar *value;
    gchar key[64];
    guint i, chid;
    GQuark quark;
    guint nlabels;

    if (!gwy_file_get_contents(filename, &buffer, &size, &err)) {
        err_GET_FILE_CONTENTS(error, &err);
        return NULL;
    }

    gwy_clear(&igorfile, 1);
    if (!igor_read_headers(&igorfile, buffer, size, FALSE, error))
        goto fail;

    /* Only accept v5 files because older do not support 2D data */
    if (igorfile.header.version != 5) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Format version is %d.  Only version 5 is supported."),
                    igorfile.header.version);
        goto fail;
    }

    /* Detect Asylum research files, leave it at generic if not detected. */
    if (memcmp(buffer + size-5, "MFP3D", 5) == 0)
        igorfile.variant = IGOR_ASYLUM_MPF3D;
    else if (memcmp(buffer + size-5, "Force", 5) == 0)
        igorfile.variant = IGOR_ASYLUM_FORCE;
    gwy_debug("producer variant %u", igorfile.variant);

    /* Must have exactly 3 dims: xres, yres, nchannels */
    wave5 = &igorfile.wave5;
    xres = wave5->n_dim[0];
    yres = wave5->n_dim[1];
    igorfile.nchannels = wave5->n_dim[2];
    if (igorfile.nchannels==0) igorfile.nchannels=1;

    if (!xres || !yres || !igorfile.nchannels || wave5->n_dim[3]) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Only two-dimensional data are supported."));
        goto fail;
    }

    igorfile.type_size = igor_data_type_size(wave5->type);
    if (!igorfile.type_size) {
        err_DATA_TYPE(error, wave5->type);
        goto fail;
    }

    if (wave5->npts != xres*yres*igorfile.nchannels) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Number of data points %u does not match resolutions "
                      "%u×%u×%u."),
                    wave5->npts, xres, yres, igorfile.nchannels);
        goto fail;
    }

    if (igorfile.header.wfm_size <= igorfile.wave_header_size) {
        err_INVALID(error, "wfmSize");
        goto fail;
    }

    expected_size = igorfile.header.wfm_size - igorfile.wave_header_size;
    if (expected_size != wave5->npts*igorfile.type_size) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Data size %u does not match "
                      "the number of data points %u×%u."),
                    (guint)expected_size, wave5->npts, igorfile.type_size);
    }

    if (err_SIZE_MISMATCH(error, expected_size + igorfile.headers_size, size,
                          FALSE))
        goto fail;

    p = buffer + igorfile.headers_size + expected_size;
    gwy_debug("remaning data size: %lu", (gulong)(size - (p - buffer)));

    p += igorfile.header.formula_size;
    if ((igorfile.variant == IGOR_ASYLUM_FORCE
         || igorfile.variant == IGOR_ASYLUM_MPF3D)
        && igorfile.header.note_size
        && (p - buffer) + igorfile.header.note_size <= size) {
        note = g_strndup((const gchar*)p, size);
        gwy_clear(&parser, 1);
        parser.key_value_separator = ":";
        igorfile.meta = gwy_text_header_parse(note, &parser, NULL, NULL);
    }
    p += igorfile.header.note_size;

    /* FIXME: Support extended units for non-Asylum files! */
    p += igorfile.header.data_e_units_size;
    for (i = 0; i < MAXDIMS; i++)
        p += igorfile.header.dim_e_units_size[i];

    /* Skip labels of x and y dimension, we don't know what to do with them. */
    for (i = 0; i < 2; i++)
        p += igorfile.header.dim_labels_size[i];

    /* FIXME: The labels are mandatory only in Asylum Research files. */
    nlabels = igorfile.header.dim_labels_size[2]/(MAX_WAVE_NAME5+1);
    expected_size = (MAX_WAVE_NAME5 + 1)*(nlabels);
    if ((p - buffer) + expected_size > size ) {
        g_set_error(error, GWY_MODULE_FILE_ERROR, GWY_MODULE_FILE_ERROR_DATA,
                    _("Cannot read channel labels."));
        goto fail;
    }
    igorfile.titles = read_channel_labels(p, igorfile.nchannels+1, nlabels);
    p += igorfile.header.dim_labels_size[2];

    if (igorfile.meta) {
        igorfile.channel_info = g_new0(AsylumChannelInfo, igorfile.nchannels);
        for (i = 0; i < igorfile.nchannels; i++) {
            AsylumChannelInfo *chinfo = igorfile.channel_info + i;
            const gchar *title = g_ptr_array_index(igorfile.titles, i+1);

            if (title) {
                chinfo->name = canonicalize_title(title);
                g_snprintf(key, sizeof(key), "%sUnit", chinfo->name);
                value = g_hash_table_lookup(igorfile.meta, key);
                if (value)
                    chinfo->units = value;
                else
                    chinfo->units = channel_title_to_units(chinfo->name);
            }
        }
    }

    container = gwy_container_new();

    for (i = chid = 0; i < igorfile.nchannels; i++, chid++) {
        const gchar *title = g_ptr_array_index(igorfile.titles, i+1);
        const gchar *zunits = NULL;

        if (igorfile.channel_info) {
            AsylumChannelInfo *chinfo = igorfile.channel_info + i;
            zunits = chinfo->units;
            meta = igor_get_metadata(&igorfile, i + 1);
        }
        dfield = igor_read_data_field(&igorfile, buffer, i, zunits, FALSE);
        maskfield = gwy_app_channel_mask_of_nans(dfield, TRUE);
        quark = gwy_app_get_data_key_for_id(chid);
        gwy_container_set_object(container, quark, dfield);
        g_object_unref(dfield);
        if (maskfield) {
            g_snprintf(key, sizeof(key), "/%d/mask", chid);
            gwy_container_set_object_by_name(container, key, maskfield);
        }
        if (meta) {
            g_snprintf(key, sizeof(key), "/%d/meta", chid);
            gwy_container_set_object_by_name(container, key, meta);
        }

        if (title) {
            g_snprintf(key, sizeof(key), "/%d/data/title", chid);
            gwy_container_set_string_by_name(container, key, g_strdup(title));
        }
        gwy_app_channel_title_fall_back(container,chid);

        if (wave5->type & IGOR_COMPLEX) {
            chid++;
            dfield = igor_read_data_field(&igorfile, buffer, i, zunits, TRUE);
            quark = gwy_app_get_data_key_for_id(chid);
            gwy_container_set_object(container, quark, dfield);
            g_object_unref(dfield);
            if (meta) {
                g_snprintf(key, sizeof(key), "/%d/meta", chid);
                /* container still holds a reference */
                g_object_unref(meta);
                meta = gwy_container_duplicate(meta);
                gwy_container_set_object_by_name(container, key, meta);
            }
            if (maskfield) {
                g_snprintf(key, sizeof(key), "/%d/mask", chid);
                /* container still holds a reference */
                g_object_unref(maskfield);
                maskfield = gwy_data_field_duplicate(maskfield);
                gwy_container_set_object_by_name(container, key, maskfield);
            }

            if (title) {
                g_snprintf(key, sizeof(key), "/%d/data/title", chid);
                gwy_container_set_string_by_name(container, key, g_strdup(title));
            };
            gwy_app_channel_title_fall_back(container,chid);
        }
        gwy_object_unref(meta);
        gwy_object_unref(maskfield);

        gwy_file_channel_import_log_add(container, chid, NULL, filename);
    }

fail:
    gwy_file_abandon_contents(buffer, size, NULL);
    g_free(note);
    if (igorfile.channel_info) {
        for (i = 0; i < igorfile.nchannels; i++)
            g_free(igorfile.channel_info[i].name);
        g_free(igorfile.channel_info);
    }
    if (igorfile.meta)
        g_hash_table_destroy(igorfile.meta);
    if (igorfile.titles) {
        g_ptr_array_foreach(igorfile.titles, (GFunc)g_free, NULL);
        g_ptr_array_free(igorfile.titles, TRUE);
    }

    return container;
}