pyvips icon indicating copy to clipboard operation
pyvips copied to clipboard

Quit the saving process with pyvips.image.tiffsave

Open yzhang525 opened this issue 2 years ago • 14 comments

Hello,

I have been using Pyvips to process the whole slide images. I noticed that it usually took very long time to save pyramidal tiffs using pyvips.image.tiffsave (could be hours). My question is that, during this saving process, after some time if I decide not to keep waiting, is there a way that I can quit the tiffsave function rather than doing "Ctrl + C" in the terminal? I'd like to create a quit function.

Any help would be much appreciated!

yzhang525 avatar Jan 11 '23 08:01 yzhang525

Hello, there's some sample code here:

https://github.com/libvips/pyvips/blob/master/examples/progress.py

You can monitor save progress and kill computation.

jcupitt avatar Jan 11 '23 12:01 jcupitt

Thank you for your response, John!

I just did some quick test with my code as follows, but it seems the added code didn't kill the process. Do you think what I did wrong or do you have any suggestions? Thank you!

regComplete.set_progress(True)

regComplete.tiffsave(os.path.join(out_path, f"Output.ome.btf"),
                                                                            compression='lzw', bigtiff=True, 
                                                                            pyramid=True, subifd=True,
                                                                            tile=True, depth='onetile',
                                                                            tile_width=512, tile_height=512)

def progress_print(name, progress):
    print('{}:'.format(name))
    print('   run = {}'.format(progress.run))
    print('   eta = {}'.format(progress.eta))
    print('   tpels = {}'.format(progress.tpels))
    print('   npels = {}'.format(progress.npels))
    print('   percent = {}'.format(progress.percent))


def eval_cb(image, progress):
    progress_print('eval', progress)
    # you can kill computation if necessary
    if progress.percent > 50:
        image.set_kill(True)

regComplete.signal_connect('eval', eval_cb)

yzhang525 avatar Jan 17 '23 16:01 yzhang525

Sorry, I don't know, you'd need to post a complete program I can test.

Did the example work for you? I see:

$ ./progress.py 
preeval:
   run = 0
   eta = 0
   tpels = 500
   npels = 0
   percent = 0
eval:
   run = 0
   eta = 0
   tpels = 500
   npels = 320
   percent = 64
eval:
   run = 0
   eta = 0
   tpels = 500
   npels = 384
   percent = 76
eval:
   run = 0
   eta = 0
   tpels = 500
   npels = 384
   percent = 76
eval:
   run = 0
   eta = 0
   tpels = 500
   npels = 400
   percent = 80
eval:
   run = 0
   eta = 0
   tpels = 500
   npels = 400
   percent = 80
posteval:
   run = 0
   eta = 0
   tpels = 500
   npels = 500
   percent = 100
Traceback (most recent call last):
  File "/home/john/GIT/pyvips/examples/./progress.py", line 36, in <module>
    image.avg()
  File "/home/john/.local/lib/python3.10/site-packages/pyvips/vimage.py", line 1347, in call_function
    return pyvips.Operation.call(name, self, *args, **kwargs)
  File "/home/john/.local/lib/python3.10/site-packages/pyvips/voperation.py", line 305, in call
    raise Error('unable to call {0}'.format(operation_name))
pyvips.error.Error: unable to call avg
  VipsImage: killed for image "temp-0"

jcupitt avatar Jan 17 '23 18:01 jcupitt

Thank you, John! The entire code is pretty long. But the basic idea related to my question above is to read in a whole slide image and save it in a pyramidal structure using pyvips. In the above code, regComplete = pyvips.image.newfromfile[...] . All the information is restored in regComplete. Then my understanding is that once I call regComplete.tiffsave(...), then I don't have any way to kill the process. Is my understanding right?

yzhang525 avatar Jan 18 '23 06:01 yzhang525

You should be able to cancel. But you need to show me a complete program that fails to work before I can say what's wrong.

Here's another demo:

#!/usr/bin/python3

import sys
import pyvips


def progress_print(name, progress):
    print(f'signal {name}:'.format(name))
    print(f'   run = {progress.run} (seconds of run time)')
    print(f'   eta = {progress.eta} (estimated seconds left)')
    print(f'   tpels = {progress.tpels} (total number of pels)')
    print(f'   npels = {progress.npels} (number of pels computed so far)')
    print(f'   percent = {progress.percent} (percent complete)')


def preeval_cb(image, progress):
    progress_print('preeval', progress)


def eval_cb(image, progress):
    progress_print('eval', progress)

    # you can kill computation if necessary
    if progress.percent > 50:
        image.set_kill(True)


def posteval_cb(image, progress):
    progress_print('posteval', progress)


image = pyvips.Image.new_from_file(sys.argv[1], access="sequential")
image.set_progress(True)
image.signal_connect('preeval', preeval_cb)
image.signal_connect('eval', eval_cb)
image.signal_connect('posteval', posteval_cb)
image.write_to_file(sys.argv[2])

So it's just copying a file. I can run:

$ ./copy-with-cancel.py ~/pics/openslide/CMU-1.svs x.dz
signal preeval:
   run = 0 (seconds of run time)
   eta = 0 (estimated seconds left)
   tpels = 1514044000 (total number of pels)
   npels = 0 (number of pels computed so far)
   percent = 0 (percent complete)
signal eval:
   run = 0 (seconds of run time)
   eta = 0 (estimated seconds left)
   tpels = 1514044000 (total number of pels)
   npels = 311296 (number of pels computed so far)
   percent = 0 (percent complete)
...
signal eval:
   run = 15 (seconds of run time)
   eta = 14 (estimated seconds left)
   tpels = 1514044000 (total number of pels)
   npels = 772212736 (number of pels computed so far)
   percent = 51 (percent complete)
signal posteval:
   run = 15 (seconds of run time)
   eta = 0 (estimated seconds left)
   tpels = 1514044000 (total number of pels)
   npels = 1514044000 (number of pels computed so far)
   percent = 100 (percent complete)
Traceback (most recent call last):
  File "/home/john/try/./copy-with-cancel.py", line 37, in <module>
    image.write_to_file(sys.argv[2])
  File "/home/john/.local/lib/python3.10/site-packages/pyvips/vimage.py", line 804, in write_to_file
    return pyvips.Operation.call(name, self, filename,
  File "/home/john/.local/lib/python3.10/site-packages/pyvips/voperation.py", line 305, in call
    raise Error('unable to call {0}'.format(operation_name))
pyvips.error.Error: unable to call VipsForeignSaveDzFile
  VipsImage: killed for image "/home/john/pics/openslide/CMU-1.svs"
VipsImage: killed for image "/home/john/pics/openslide/CMU-1.svs"

Now that's reading a large slide image and saving as deepzoom, then canceling computation at 50%.

jcupitt avatar Jan 18 '23 11:01 jcupitt

Thank you, John! I'll look into your example.

yzhang525 avatar Jan 18 '23 16:01 yzhang525

Hi John, I tested your code and found that it killed the process with "image = pyvips.Image.new_from_file(sys.argv[1], access="sequential")" as expected. However, when I did the way below, it didn't kill "image.write_to_file(sys.argv[2])". Any ideas? Thank you!

image = pyvips.Image.new_from_file(sys.argv[1], access="sequential")
image.write_to_file(sys.argv[2])
image.set_progress(True)
image.signal_connect('preeval', preeval_cb)
image.signal_connect('eval', eval_cb)
image.signal_connect('posteval', posteval_cb)

yzhang525 avatar Jan 26 '23 06:01 yzhang525

You need to attach the callbacks before calling write_to_file.

jcupitt avatar Jan 26 '23 09:01 jcupitt

Sorry I wasn't clear in my last post. Here is the code I was using. My question is that it didn't kill the write_to_file.

import sys
import pyvips


def progress_print(name, progress):
    print(f'signal {name}:'.format(name))
    print(f'   run = {progress.run} (seconds of run time)')
    print(f'   eta = {progress.eta} (estimated seconds left)')
    print(f'   tpels = {progress.tpels} (total number of pels)')
    print(f'   npels = {progress.npels} (number of pels computed so far)')
    print(f'   percent = {progress.percent} (percent complete)')

def preeval_cb(image, progress):
    progress_print('preeval', progress)


def eval_cb(image, progress):
    progress_print('eval', progress)
    if progress.percent > 20:
        image.set_kill(True)


def posteval_cb(image, progress):
    progress_print('posteval', progress)

image = pyvips.Image.new_from_file(sys.argv[1], access="sequential")

image.write_to_file(sys.argv[2])
image.set_progress(True)
image.signal_connect('preeval', preeval_cb)
image.signal_connect('eval', eval_cb)
image.signal_connect('posteval', posteval_cb)

yzhang525 avatar Feb 01 '23 06:02 yzhang525

Yes, you're attaching the callbacks after you've already written the image. Try:

image.set_progress(True)
image.signal_connect('preeval', preeval_cb)
image.signal_connect('eval', eval_cb)
image.signal_connect('posteval', posteval_cb)
image.write_to_file(sys.argv[2])

jcupitt avatar Feb 01 '23 06:02 jcupitt

By doing so, I will kill the "pyvips.Image.new_from_file", right? That's actually not what I want. I would specifically kill the process during the "writing". The reason is that in my application, the output writing (with pyramidal structure) takes extremely long time. I use "tiffsave" in my code as follows. regComplete.tiffsave(os.path.join(out_path, f"Output.ome.btf"), compression='lzw', bigtiff=True, pyramid=True, subifd=True, tile=True, depth='onetile', tile_width=512, tile_height=512)

yzhang525 avatar Feb 01 '23 06:02 yzhang525

No, it would stop the save at 20%. Try it!

jcupitt avatar Feb 01 '23 07:02 jcupitt

new_from_file is a pixel source, so it supplies pixels down the pipeline to operations that need them. It does not loop over the image, and does no processing itself.

The write_to_file contains the pixel loop. It scans over the image dimension in sections, pulling pixels though any operations you have connected, and writing the pixels to the output. After processing each section, it sends out the eval signal. Your eval code will set kill on the pipeline and break the pixel loop in write_to_file.

jcupitt avatar Feb 01 '23 07:02 jcupitt

I seem to understand what you mean. Let me try it out and also try with my code. Thank you, John!

yzhang525 avatar Feb 01 '23 07:02 yzhang525