bool _openslide_jpeg_read_dimensions(const char *filename,
                                     int64_t offset,
                                     int32_t *w, int32_t *h,
                                     GError **err) {
  FILE *f = _openslide_fopen(filename, "rb", err);
  if (f == NULL) {
    return false;
  }
  if (offset && fseeko(f, offset, SEEK_SET) == -1) {
    _openslide_io_error(err, "Cannot seek to offset");
    fclose(f);
    return false;
  }

  bool success = jpeg_get_dimensions(f, NULL, 0, w, h, err);

  fclose(f);
  return success;
}
bool _openslide_jpeg_read(const char *filename,
                          int64_t offset,
                          uint32_t *dest,
                          int32_t w, int32_t h,
                          GError **err) {
  //g_debug("read JPEG: %s %" G_GINT64_FORMAT, filename, offset);

  FILE *f = _openslide_fopen(filename, "rb", err);
  if (f == NULL) {
    return false;
  }
  if (offset && fseeko(f, offset, SEEK_SET) == -1) {
    _openslide_io_error(err, "Cannot seek to offset");
    fclose(f);
    return false;
  }

  bool success = jpeg_decode(f, NULL, 0, dest, false, w, h, err);

  fclose(f);
  return success;
}
bool _openslide_gdkpixbuf_read(const char *format,
                               const char *filename,
                               int64_t offset,
                               int64_t length,
                               uint32_t *dest,
                               int32_t w, int32_t h,
                               GError **err) {
  GdkPixbufLoader *loader = NULL;
  uint8_t *buf = g_slice_alloc(BUFSIZE);
  bool success = false;
  struct load_state state = {
    .w = w,
    .h = h,
  };

  // open and seek
  FILE *f = _openslide_fopen(filename, "rb", err);
  if (!f) {
    goto DONE;
  }
  if (fseeko(f, offset, SEEK_SET)) {
    _openslide_io_error(err, "Couldn't fseek %s", filename);
    goto DONE;
  }

  // create loader
  loader = gdk_pixbuf_loader_new_with_type(format, err);
  if (!loader) {
    goto DONE;
  }
  g_signal_connect(loader, "area-prepared", G_CALLBACK(area_prepared), &state);

  // read data
  while (length) {
    size_t count = fread(buf, 1, MIN(length, BUFSIZE), f);
    if (!count) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FAILED,
                  "Short read loading pixbuf from %s", filename);
      goto DONE;
    }
    if (!gdk_pixbuf_loader_write(loader, buf, count, err)) {
      g_prefix_error(err, "gdk-pixbuf error: ");
      goto DONE;
    }
    if (state.err) {
      goto DONE;
    }
    length -= count;
  }

  // finish load
  if (!gdk_pixbuf_loader_close(loader, err)) {
    g_prefix_error(err, "gdk-pixbuf error: ");
    goto DONE;
  }
  if (state.err) {
    goto DONE;
  }
  g_assert(state.pixbuf);

  // copy pixels
  uint8_t *pixels = gdk_pixbuf_get_pixels(state.pixbuf);
  int rowstride = gdk_pixbuf_get_rowstride(state.pixbuf);
  for (int32_t y = 0; y < h; y++) {
    for (int32_t x = 0; x < w; x++) {
      dest[y * w + x] = 0xFF000000 |                              // A
                        pixels[y * rowstride + x * 3 + 0] << 16 | // R
                        pixels[y * rowstride + x * 3 + 1] << 8 |  // G
                        pixels[y * rowstride + x * 3 + 2];        // B
    }
  }

  success = true;

DONE:
  // clean up
  if (loader) {
    gdk_pixbuf_loader_close(loader, NULL);
    g_object_unref(loader);
  }
  if (f) {
    fclose(f);
  }
  g_slice_free1(BUFSIZE, buf);

  // now that the loader is closed, we know state.err won't be set
  // behind our back
  if (state.err) {
    // signal handler validation errors override GdkPixbuf errors
    g_clear_error(err);
    g_propagate_error(err, state.err);
    // signal handler errors should have been noticed before falling through
    g_assert(!success);
  }
  return success;
}
static bool hamamatsu_ndpi_part2(openslide_t *osr,
				int num_jpegs, char *image_filenames,
				int num_jpeg_cols,
				GKeyFile *key_file,
				GError **err) {
  bool success = false;

  // initialize individual jpeg structs
  struct _openslide_jpeg_file **jpegs = g_new0(struct _openslide_jpeg_file *,
					       num_jpegs);
  for (int i = 0; i < num_jpegs; i++) {
    jpegs[i] = g_slice_new0(struct _openslide_jpeg_file);
  }

  // init levels: base image + map
  int32_t level_count = 2;
  struct _openslide_jpeg_level **levels =
    g_new0(struct _openslide_jpeg_level *, level_count);
  for (int32_t i = 0; i < level_count; i++) {
    levels[i] = g_slice_new0(struct _openslide_jpeg_level);
    levels[i]->tiles = _openslide_jpeg_create_tiles_table();
  }

  // process jpegs
  int32_t jpeg0_tw = 0;
  int32_t jpeg0_th = 0;
  int32_t jpeg0_ta = 0;
  int32_t jpeg0_td = 0;

  for (int i = 0; i < num_jpegs; i++) {
    struct _openslide_jpeg_file *jp = jpegs[i];

    //jp->start_in_file = 0;
    jp->filename = g_strdup(image_filenames);

    FILE *f;
    if ((f = _openslide_fopen(jp->filename, "rb", err)) == NULL) {
      g_prefix_error(err, "Can't open JPEG %d: ", i);
      goto DONE;
    }

    // set start and end of jpeg file
    // from key_file
    jp->start_in_file = g_key_file_get_integer(key_file, GROUP_VMS,
                        g_strdup_printf("hamamatsu.JpegStartOffset[%i]",i), err);
    int jpeg_size = g_key_file_get_integer(key_file, GROUP_VMS,
                        g_strdup_printf("hamamatsu.JpegSize[%i]",i), err);

    if (fseeko(f, jp->start_in_file, SEEK_SET) != 0) {
      _openslide_io_error(err, "Cannot seek to offset");
      goto DONE;
    }

    // comment?
    char *comment = NULL;
    char **comment_ptr = NULL;
    if (i == 0 && osr) {
      comment_ptr = &comment;
    }

    if (!verify_jpeg(f, &jp->w, &jp->h, &jp->tw, &jp->th, comment_ptr, err)) {
      g_prefix_error(err, "Can't verify JPEG %d: ", i);
      fclose(f);
      goto DONE;
    }

    if (comment) {
      g_hash_table_insert(osr->properties,
			  g_strdup(OPENSLIDE_PROPERTY_NAME_COMMENT),
			  comment);
    }

    //seek to end of jpeg offset
    fseeko(f, jpeg_size, SEEK_CUR);
    jp->end_in_file = ftello(f);
    if (jp->end_in_file == -1) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Can't read file size for JPEG %d", i);
      fclose(f);
      goto DONE;
    }

    // file is done now
    fclose(f);

    int32_t num_tiles_across = jp->w / jp->tw;
    int32_t num_tiles_down = jp->h / jp->th;

    //my sample NDPI file dont have map file.
    //if I dont modified this code, I get error
    //The overwriting error message was: Tile size not consistent
    //so, I am remove "else if" code

    // because map file is last, ensure that all tw and th are the
    // same for 0 through num_jpegs-2
    //    g_debug("tile size: %d %d", tw, th);
    //if (i == 0) {
      jpeg0_tw = jp->tw;
      jpeg0_th = jp->th;
      jpeg0_ta = num_tiles_across;
      jpeg0_td = num_tiles_down;
    /*} else if (i != (num_jpegs - 1)) {
      // not map file (still within level 0)
      g_assert(jpeg0_tw != 0 && jpeg0_th != 0);
      if (jpeg0_tw != jp->tw || jpeg0_th != jp->th) {
        g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                    "Tile size not consistent");
        goto DONE;
      }
    }*/

    //NDPI file dont have optimisation file input, so remove this code
    /*
    // use the optimisation file, if present
    int32_t mcu_starts_count = (jp->w / jp->tw) * (jp->h / jp->th); // number of tiles
    int64_t *mcu_starts = NULL;
    if (optimisation_file) {
      mcu_starts = extract_one_optimisation(optimisation_file,
					    num_tiles_down,
					    num_tiles_across,
					    mcu_starts_count);
    }
    if (mcu_starts) {
      jp->mcu_starts = mcu_starts;
    } else if (optimisation_file != NULL) {
      // the optimisation file is useless, ignore it
      optimisation_file = NULL;
    }
    */

    // accumulate into some of the fields of the levels
    int32_t level;
    if (i != num_jpegs - 1) {
      // base (level 0)
      level = 0;
    } else {
      // map (level 1)
      level = 1;
    }

    struct _openslide_jpeg_level *l = levels[level];
    int32_t file_x = 0;
    int32_t file_y = 0;
    if (level == 0) {
      file_x = i % num_jpeg_cols;
      file_y = i / num_jpeg_cols;
    }
    if (file_y == 0) {
      l->level_w += jp->w;
      l->tiles_across += num_tiles_across;
    }
    if (file_x == 0) {
      l->level_h += jp->h;
      l->tiles_down += num_tiles_down;
    }

    // set some values (don't accumulate)
    l->raw_tile_width = jp->tw;
    l->raw_tile_height = jp->th;
    l->tile_advance_x = jp->tw;   // no overlaps or funny business
    l->tile_advance_y = jp->th;
  }

  // at this point, jpeg0_ta and jpeg0_td are set to values from 0,0 in level 0

  for (int i = 0; i < num_jpegs; i++) {
    struct _openslide_jpeg_file *jp = jpegs[i];

    int32_t level;
    int32_t file_x;
    int32_t file_y;
    if (i != num_jpegs - 1) {
      // base (level 0)
      level = 0;
      file_x = i % num_jpeg_cols;
      file_y = i / num_jpeg_cols;
    } else {
      // map (level 1)
      level = 1;
      file_x = 0;
      file_y = 0;
    }

    //g_debug("processing file %d %d %d", file_x, file_y, level);

    int32_t num_tiles_across = jp->w / jp->tw;

    struct _openslide_jpeg_level *l = levels[level];

    int32_t tile_count = (jp->w / jp->tw) * (jp->h / jp->th); // number of tiles

    // add all the tiles
    for (int local_tileno = 0; local_tileno < tile_count; local_tileno++) {
      struct _openslide_jpeg_tile *t = g_slice_new0(struct _openslide_jpeg_tile);

      int32_t local_tile_x = local_tileno % num_tiles_across;
      int32_t local_tile_y = local_tileno / num_tiles_across;

      t->fileno = i;
      t->tileno = local_tileno;
      t->w = jp->tw;
      t->h = jp->th;
      // no dest or src offsets

      // compute key for hashtable (y * w + x)
      int64_t x = file_x * jpeg0_ta + local_tile_x;
      int64_t y = file_y * jpeg0_td + local_tile_y;

      int64_t *key = g_slice_new(int64_t);
      *key = (y * l->tiles_across) + x;

      //g_debug("inserting tile: fileno %d tileno %d, %gx%g, file: %d %d, local: %d %d, global: %" G_GINT64_FORMAT " %" G_GINT64_FORMAT ", l->tiles_across: %d, key: %" G_GINT64_FORMAT, t->fileno, t->tileno, t->w, t->h, file_x, file_y, local_tile_x, local_tile_y, x, y, l->tiles_across, *key);
      g_assert(!g_hash_table_lookup(l->tiles, key));
      g_hash_table_insert(l->tiles, key, t);
    }
  }

  success = true;

 DONE:
  if (success) {
    _openslide_add_jpeg_ops(osr, num_jpegs, jpegs, level_count, levels);
  } else {
    // destroy
    for (int i = 0; i < num_jpegs; i++) {
      g_free(jpegs[i]->filename);
      g_free(jpegs[i]->mcu_starts);
      g_slice_free(struct _openslide_jpeg_file, jpegs[i]);
    }
    g_free(jpegs);

    for (int32_t i = 0; i < level_count; i++) {
      g_hash_table_unref(levels[i]->tiles);
      g_slice_free(struct _openslide_jpeg_level, levels[i]);
    }
    g_free(levels);
  }

  return success;
}
bool _openslide_try_hamamatsu(openslide_t *osr, const char *filename,
			      struct _openslide_hash *quickhash1,
			      GError **err) {
  // initialize any variables destroyed/used in DONE
  bool success = false;

  char *dirname = g_path_get_dirname(filename);

  int num_images = 0;
  char **image_filenames = NULL;

  int num_cols = -1;
  int num_rows = -1;

  char **all_keys = NULL;

  int num_layers = -1;

  // first, see if it's a VMS/VMU file
  GKeyFile *key_file = g_key_file_new();
  if (!_openslide_read_key_file(key_file, filename, G_KEY_FILE_NONE, NULL)) {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FORMAT_NOT_SUPPORTED,
                "Can't load key file");
    goto DONE;
  }

  // select group or fail, then read dimensions
  const char *groupname;
  if (g_key_file_has_group(key_file, GROUP_VMS)) {
    groupname = GROUP_VMS;

    num_cols = g_key_file_get_integer(key_file, groupname,
				      KEY_NUM_JPEG_COLS,
				      NULL);
    num_rows = g_key_file_get_integer(key_file,
				      groupname,
				      KEY_NUM_JPEG_ROWS,
				      NULL);
  } else if (g_key_file_has_group(key_file, GROUP_VMU)) {
    groupname = GROUP_VMU;

    num_cols = 1;  // not specified in file for VMU
    num_rows = 1;
  } else {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FORMAT_NOT_SUPPORTED,
                "Not VMS or VMU file");
    goto DONE;
  }

  // validate cols/rows
  if (num_cols < 1) {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FORMAT_NOT_SUPPORTED,
                "File has no columns");
    goto DONE;
  }
  if (num_rows < 1) {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FORMAT_NOT_SUPPORTED,
                "File has no rows");
    goto DONE;
  }

  // init the image filenames
  // this format has cols*rows image files, plus the map
  num_images = (num_cols * num_rows) + 1;
  image_filenames = g_new0(char *, num_images);

  // hash in the key file
  if (!_openslide_hash_file(quickhash1, filename, err)) {
    goto DONE;
  }

  // make sure values are within known bounds
  num_layers = g_key_file_get_integer(key_file, groupname, KEY_NUM_LAYERS,
				      NULL);
  if (num_layers < 1) {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                "Cannot handle Hamamatsu files with NoLayers < 1");
    goto DONE;
  }

  // add properties
  if (osr) {
    add_properties(osr->properties, key_file, groupname);
  }

  // extract MapFile
  char *tmp;
  tmp = g_key_file_get_string(key_file,
			      groupname,
			      KEY_MAP_FILE,
			      NULL);
  if (tmp) {
    char *map_filename = g_build_filename(dirname, tmp, NULL);
    g_free(tmp);

    image_filenames[num_images - 1] = map_filename;

    // hash in the map file
    if (!_openslide_hash_file(quickhash1, map_filename, err)) {
      goto DONE;
    }
  } else {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                "Can't read map file");
    goto DONE;
  }

  // now each ImageFile
  all_keys = g_key_file_get_keys(key_file, groupname, NULL, NULL);
  for (char **tmp = all_keys; *tmp != NULL; tmp++) {
    char *key = *tmp;
    char *value = g_key_file_get_string(key_file, groupname, key, NULL);

    //    g_debug("%s", key);

    if (strncmp(KEY_IMAGE_FILE, key, strlen(KEY_IMAGE_FILE)) == 0) {
      // starts with ImageFile
      char *suffix = key + strlen(KEY_IMAGE_FILE);

      int layer;
      int col;
      int row;

      char **split = g_strsplit(suffix, ",", 0);
      switch (g_strv_length(split)) {
      case 0:
	// all zero
	layer = 0;
	col = 0;
	row = 0;
	break;

      case 1:
	// (z)
	// first item, skip '('
	layer = g_ascii_strtoll(split[0] + 1, NULL, 10);
	col = 0;
	row = 0;
	break;

      case 2:
	// (x,y)
	layer = 0;
	// first item, skip '('
	col = g_ascii_strtoll(split[0] + 1, NULL, 10);
	row = g_ascii_strtoll(split[1], NULL, 10);
	break;

      case 3:
        // (z,x,y)
        // first item, skip '('
        layer = g_ascii_strtoll(split[0] + 1, NULL, 10);
        col = g_ascii_strtoll(split[1], NULL, 10);
        row = g_ascii_strtoll(split[2], NULL, 10);
        break;

      default:
        // we just don't know
        g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                    "Unknown number of image dimensions: %d",
                    g_strv_length(split));
        g_free(value);
        g_strfreev(split);
        g_strfreev(all_keys);
        goto DONE;
      }
      g_strfreev(split);

      //g_debug("layer: %d, col: %d, row: %d", layer, col, row);

      if (layer != 0) {
        // skip non-zero layers for now
        g_free(value);
        continue;
      }

      if (col >= num_cols || row >= num_rows || col < 0 || row < 0) {
        g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                    "Invalid row or column in Hamamatsu file (%d,%d)",
                    col, row);
        g_free(value);
	g_strfreev(all_keys);
        goto DONE;
      }

      // compute index from x,y
      int i = row * num_cols + col;

      // init the file
      if (image_filenames[i]) {
        g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                    "Duplicate image for (%d,%d)", col, row);
        g_free(value);
        g_strfreev(all_keys);
        goto DONE;
      }
      image_filenames[i] = g_build_filename(dirname, value, NULL);
    }
    g_free(value);
  }
  g_strfreev(all_keys);

  // ensure all image filenames are filled
  for (int i = 0; i < num_images; i++) {
    if (!image_filenames[i]) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Can't read image filename %d", i);
      goto DONE;
    }
  }

  // add macro image
  tmp = g_key_file_get_string(key_file,
			      groupname,
			      KEY_MACRO_IMAGE,
			      NULL);
  if (tmp) {
    char *macro_filename = g_build_filename(dirname, tmp, NULL);
    bool result = _openslide_add_jpeg_associated_image(osr ? osr->associated_images : NULL,
                                                       "macro",
                                                       macro_filename, 0, err);
    g_free(macro_filename);
    g_free(tmp);

    if (!result) {
      g_prefix_error(err, "Could not read macro image: ");
      goto DONE;
    }
  }

  // finalize depending on what format
  if (groupname == GROUP_VMS) {
    // open OptimisationFile
    FILE *optimisation_file = NULL;
    char *tmp = g_key_file_get_string(key_file,
				      GROUP_VMS,
				      KEY_OPTIMISATION_FILE,
				      NULL);
    if (tmp) {
      char *optimisation_filename = g_build_filename(dirname, tmp, NULL);
      g_free(tmp);

      optimisation_file = _openslide_fopen(optimisation_filename, "rb", NULL);

      if (optimisation_file == NULL) {
	// g_debug("Can't open optimisation file");
      }
      g_free(optimisation_filename);
    } else {
      // g_debug("Optimisation file key not present");
    }

    // do all the jpeg stuff
    success = hamamatsu_vms_part2(osr,
				  num_images, image_filenames,
				  num_cols,
				  optimisation_file,
				  err);

    // clean up
    if (optimisation_file) {
      fclose(optimisation_file);
    }
  } else if (groupname == GROUP_VMU) {
    // verify a few assumptions for VMU
    int bits_per_pixel = g_key_file_get_integer(key_file,
						GROUP_VMU,
						KEY_BITS_PER_PIXEL,
						NULL);
    char *pixel_order = g_key_file_get_string(key_file,
					      GROUP_VMU,
					      KEY_PIXEL_ORDER,
					      NULL);

    if (bits_per_pixel != 36) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "%s must be 36", KEY_BITS_PER_PIXEL);
    } else if (!pixel_order || (strcmp(pixel_order, "RGB") != 0)) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "%s must be RGB", KEY_PIXEL_ORDER);
    } else {
      // assumptions verified
      success = hamamatsu_vmu_part2(osr,
				    num_images, image_filenames,
				    err);
    }
    g_free(pixel_order);
  } else {
    g_assert_not_reached();
  }

 DONE:
  g_free(dirname);

  if (image_filenames) {
    for (int i = 0; i < num_images; i++) {
      g_free(image_filenames[i]);
    }
    g_free(image_filenames);
  }
  g_key_file_free(key_file);

  return success;
}
static bool hamamatsu_vmu_part2(openslide_t *osr,
				int num_files, char **image_filenames,
				GError **err) {
  bool success = false;

  // initialize individual ngr structs
  struct _openslide_ngr **files = g_new0(struct _openslide_ngr *,
					 num_files);
  for (int i = 0; i < num_files; i++) {
    files[i] = g_slice_new0(struct _openslide_ngr);
  }

  // open files
  for (int i = 0; i < num_files; i++) {
    struct _openslide_ngr *ngr = files[i];

    ngr->filename = g_strdup(image_filenames[i]);

    FILE *f;
    if ((f = _openslide_fopen(ngr->filename, "rb", err)) == NULL) {
      goto DONE;
    }

    // validate magic
    if ((fgetc(f) != 'G') || (fgetc(f) != 'N')) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Bad magic on NGR file");
      fclose(f);
      goto DONE;
    }

    // read w, h, column width, headersize
    fseeko(f, 4, SEEK_SET);
    ngr->w = read_le_int32_from_file(f);
    ngr->h = read_le_int32_from_file(f);
    ngr->column_width = read_le_int32_from_file(f);

    fseeko(f, 24, SEEK_SET);
    ngr->start_in_file = read_le_int32_from_file(f);

    // validate
    if ((ngr->w <= 0) || (ngr->h <= 0) ||
	(ngr->column_width <= 0) || (ngr->start_in_file <= 0)) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Error processing header");
      fclose(f);
      goto DONE;
    }

    // ensure no remainder on columns
    if ((ngr->w % ngr->column_width) != 0) {
      g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA,
                  "Width not multiple of column width");
      fclose(f);
      goto DONE;
    }

    fclose(f);
  }

  success = true;

 DONE:
  if (success) {
    _openslide_add_ngr_ops(osr, num_files, files);
  } else {
    // destroy
    for (int i = 0; i < num_files; i++) {
      g_slice_free(struct _openslide_ngr, files[i]);
      g_free(files[i]->filename);
    }
    g_free(files);
  }

  return success;
}
bool _openslide_try_hamamatsu_ndpi(openslide_t *osr, const char *filename,
				   struct _openslide_hash *quickhash1,
				   GError **err) {
  bool success = false;

  char *image_filenames = NULL;
  char *dirname = g_path_get_dirname(filename);
  image_filenames = g_new0(char, 1);
  image_filenames = g_build_filename(dirname, filename, NULL);

  FILE *f = _openslide_fopen(image_filenames, "rb", NULL);
  if (!f) {
    g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FORMAT_NOT_SUPPORTED,
                "Can't open file");
    return false;
  }

  //dump data from NDPI file. from this function, we get all information about
  //NDPI file (number of image, size of image, offset, etc)
  GSList *dump = _openslide_tiffdump_create(f, err);
  if (!dump) {
    fclose(f);
    return false;
  }
  fclose(f);

  const char *groupname = GROUP_VMS;
  GKeyFile *key_file = g_key_file_new();

  //NDPI file dont have key file. So, I am create the key using this method
  //but, the problem is I can not show the property using :
  //openslide_get_property_names(osr)
  //are you know why?
  g_key_file_set_string(key_file, groupname, "ImageFile", image_filenames);

  //I dont get information about number of layers from NDPI file.
  //So, I set this num_layers to 1
  int num_layers = 1;
  g_key_file_set_integer(key_file, groupname, "NoLayers", num_layers);

  //create NDPI key from from dump variable
  //create key_file from this GSList.
  //We will get some of image from NDPI file with jpeg format
  //I save the count number of all jpeg image in "num_images" variable
  //some of the jpeg file dont have complete header (dont have restart marker offset)
  //"num_jpegs" is total number of jpeg with complete header
  int num_images; //the total number of jpegs images
  int num_jpegs;  //the total number of jpegs with complete header

  //I am still confused how to create index name from extracted
  //NDPI file. So, I am create a file with symbols [%].
  //please check this function
  ndpi_create_key_file(dump, key_file,
                       groupname, &num_images, &num_jpegs);

  //I dont get information about num_cols and num_rows
  //So, I set this "num_cols" variable like this
  int num_cols;
  if(num_jpegs>1)
    num_cols = 2;
  else
    num_cols = 1;

  // add properties
  if (osr) {
    add_properties(osr->properties, key_file, groupname);
  }

  //I am not yet add the macros file
  //we can get the macros file from key_file

  success = hamamatsu_ndpi_part2(osr,
            num_jpegs, image_filenames,
            num_cols, key_file,
            err);

  _openslide_tiffdump_destroy(dump);
  g_key_file_free(key_file);
  return success;
}