seikichi/tiff.js

firefox problem

Closed this issue · 14 comments

Hi everyone,

I am currently using tiff.js in a web application project and everything was fine until I tried to display in my browser Tiff pictures. I am getting tiff pictures from a websocket connection and I am trying to display each picture in the browser one by one after converting it to a blob and then as a arraybuffer.

The problem is that the memory increase steadily without stopping (more than 2GB of ram if i don't stop the app). I think that the garbage collector is not collecting the different arrayBuffer created by tiff.js but I don't understand why.
Changing the memory allocation with Tiff.initialize doesn't change anything.
here is my code:

  reader.onload = function () {
		Tiff.initialize({TOTAL_MEMORY:100100100)};
                let image = new Tiff({ buffer: reader.result });
                let canvas=image.toCanvas();
		canvas.setAttribute('id','canvasStream');		
  };

Edit: I added the manipulation of DOM element so you can see more precisely how I deal with it.

So my question is, is it possible to use tiff.js for converting tiff image to canvas every seconds ? Is there a way to dereference the tiff object, so that the garbage collector will sweep it.
I am using firefox.

Thanks in advance

Hi.
The line : let canvas=this.image.toCanvas()
create a new dom element.
See in tiff.js : var canvas = document.createElement('canvas');
that you should delete when you received the new image

Hi @ltrillaud, yes I delete the previous canvas element with $.remove() when creating a new canvas image.

The retaining path of some dominator node from Firefox console memory tool have some arraybuffer object with a shallow size of 500MB!!
how I could dereference this arrayBuffer that retain all the memory. Also maybe there is a method to close the FileReader like in Java ?

Hi
In my humble opinion, I guess that the leak is in the Canvas and the huge array buffer associated to it. Bad destroy? Hit on Google 'html5 canvas memory leak' to be sure to destroy it well

You can easily test 2 optimisations :

  • Avoid convert twice

    I can see that your Web Service give you an array buffer, because you begin to convert it into a blob, and then convert it again into an array buffer with a fileReader

    • first verify that your WebService is configured to send your an array buffer, not a blob
    • It will be compatible with the tiff library to be used directly
      let image = new Tiff({buffer: event.data});
  • Avoid recreate Canvas

    Don't use toCanvas() function. It very time consuming to manipulate the dom. You just want to update the image into an existing canvas

    • create a canvas with html <canvas id="canvasStream">
    • get the image raster with const raster = image.readRGBAImage()
    • inject it into the canvas
      var context = canvas.getContext('2d'); var imageData = context.createImageData(data.width, data.height); (imageData).data.set(new Uint8Array(data.image)); context.putImageData(imageData, 0, 0); };

You can see a working sample here : http://seikichi.github.io/tiff.js/worker.html

Laurent

@ltrillaud , thank you very much for your first optimisation, it works well!! for the second, I don't understand how to update the canvas without any DOM reference ? Because you need at least to get the DOM reference of the canvasElement to update it like a global variabe/jquery selector, or create a new one and append it to the DOM.
Also what is "data" ? in the demo it's event.data which has already a width and height property, but my "event.data" doesn't have, Do you mean respectively "image.width()", "image.height()" and "image" for "data.width", "data.height", and "data.image" ?
Thank you very much.

The stranger think is that I still get a memory leak without instantiating any canvas, like in this code:

    let image = new Tiff({ buffer: event.data });
    image.close();

Though the image.close() method reduces the memory leak, it's still pretty bad...
It seems that each Tiff instance is kept.
There is one dominator node which retains all the arrayBuffer and Tiff objects.

@francois3f Your last sample code is not a good way. You don't consume the data. Try this :
let image = new Tiff({ buffer: event.data }); let raster = image.readRGBAImage(); image.close();
Because inside the readRGBImage you have this line of code :
Tiff.Module.ccall('free', 'number', ['number'], [raster]);

@ltrillaud although it deosn't solve the memory leak, you are perfectly right, thank you to pointing it. Do you know how I could do to execute your second optimisation ? (without manipulating DOM element).
Thanks.

@francois3f Now you deal without FileReader and Blob, but I guess that you still have a memory leak.
That mean that the leak is not come from this AWESOME tiff library (Thanks Seiichi KONDO) but from the usage of the Canvas.

My advise is to use only one static canvas, and change the image of this canvas with the canvas api : createImageData(), and putImageData().

  • Create a blank canvas into your html page <canvas id="canvasStream"> and fix it size with css.
  • Keep a reference of your canvas const myCanvas = $('#canvasStream')
  • listen websocket incoming and inject raster into your canvas
webSocket.onmessage = function (event) { 
    const tiff = new Tiff({ buffer: event.data });
    const raster = tiff.readRGBAImage();
    const width = tiff.width();
    const height = tiff.height();
    const context = myCanvas.getContext('2d');
    myCanvas.width = width; // optional if never change
    myCanvas.height = height; // optional if never change
    const imageData = context.createImageData(width, height);
    imageData.data.set( new Uint8Array(raster), 0, 0);
    context.putImageData(imageData, 0, 0);
    tiff.close(); // not sure useful
}; 

It's just pseudo code, never run. If still leak, try to clean the canvas context before put another image

Hope that it will be useful

Laurent

Hi @ltrillaud , Thank you for your very detailed post. It works in the browser and the canvas are displayed very well but the memory leaks is still there... Now it's very clear, There is one node with a huge retaining size (1.4GB) who represent all the tiff images...
I really don't see which object is blocking the Garbage Collector :(

Hi
I'm already use this library to display multiple images into a canvas without memory leak.
Can you show me your final code ?
I'm still pretty sure that the leak come from the canvas, not the library.
Can you try with a latest version of Chrome?

It works in chrome! thank you very much.

@francois3f Cool. You can now open a ticket at Firefox!

My last advise in you want to avoid user interface freeze, with big images, slow computer or high frame rate : Use Web Worker to listen Web Socket, decode the tiff and transfert raster by reference to the main thread.

It's very easy to do. Follow this working sample from Seiichi KONDO : http://seikichi.github.io/tiff.js/worker.html

Laurent

I have the same memory issue while converting multipage tiff file

var Tiff = new Tiff({buffer: data});
for (var i = 0, len = Tiff.countDirectory(); i < len; ++i)
{
Tiff.setDirectory(i);
var Canvas = Tiff.toCanvas();
}
I tried creating a new canvas as specified in the comments above but the dataURL got from the Canvas.toDataURL() and by creating my own canvas is different. If I create my own canvas it gives me a blank canvas.

hi frnds .i want remove single element form multipage tiff file.can anyone share the code