Adding new band to product

Hello,

I am currently implementing a processor for SNAP in Python.
At the beginning of my computations I cut the source product (as not all data is relevant) and I also want to add a new band with additional data.
The additional data is loaded as tiff and converted to a numpy array. It has the same dimensions as the sub_product

Here the relevant code:

# get subset:
SubsetOp = snappy.jpy.get_type('org.esa.snap.core.gpf.common.SubsetOp')
op = SubsetOp()
op.setSourceProduct(source_product)
op.setRegion(snappy.Rectangle(range[0], range[2], range[1] - range[0], range[3] - range[2]))
sub_product = op.getTargetProduct()
writer = snappy.ProductIO.getProductWriter('BEAM-DIMAP')
sub_product.setProductWriter(writer)

# add band:
self.ref_band_org = sub_product.addBand('ref_image', snappy.ProductData.TYPE_UINT8)
self.ref_band_org.writePixels(0, 0, width, height, ref_image)

(ref_image is a numpy array of size width x height)

This is working correctly if I run this as a python script.
But when building and importing in SNAP (as processor) I get the following error:

java.lang.RuntimeException: Error in Python interpreter:
Type: <class ‘RuntimeError’>
Value: java.io.FileNotFoundException: ref_image.hdr (Access is denied)
Namespace: initialize

Is there another (better) way to add a band to a product?
Is there perhaps already a problem when creating the sub-product?

Thanks for any help!
Helga

Hi Helga,

Without knowing the complete code you have written, but you should not write product within an operator. This is something you should leave to the framework. Also I think you should not use the subset operator in your operator.
Just process what you get and let it up to the user to subset the region.
The user can use a graph to set up a processing chain.

I guess the ref_image is some ancillary data. How to deal with it depends where it comes from and how it should be used.
In general, I think, it would be good to read it as a second product. And then use ProductUtils.copyband(…)

Hi,

thank you for the tipps.
I will leave the subset to the user.

As for the second product:
Is it correct, that this is not possible via the GUI?

Thanks!
Helga

Yes, the Merge operator is missing in the GUI.
But it is possible to copy one band from one product to another by Band Maths.

1 Like

Hello,

I am trying to save a numpy array as a new band in a product, without having to write a temporary DIM file.

Indeed, my current workflow is: 1) export bands of a Sentinel-1 product as numpy arrays, 2) perform operations on these using numpy, 3) save the resulting array in a DIM file (following snappy_ndvi.py), 4) open this new DIM file with snappy + merge it with the original product (using the ‘Merge’ operator), and 5) geocode the product (using the ‘Back-Geocoding’ operator).

I have been trying to avoid having to write a temporary DIM file, but without any success. The following code results in an empty band (Error ‘java.lang.NullPointerException’). The variable D is a numpy array which I want to save as a new band.

from snappy import ProductData
from snappy import ProductIO

height, width = D.shape
targetBand = p.addBand('newBand', ProductData.TYPE_FLOAT32)

writer = ProductIO.getProductWriter('BEAM-DIMAP')
p.setProductWriter(writer)

for y in range(height):
    print("processing line {}/{}".format(y, height))
    d = D[y,:]
    #targetBand.writePixels(0, y, width, 1, d) #writePixels writes values to disk and by passes the band/product
    targetBand.setPixels(0, y, width, 1, d)

Could you please point at what I am doing wrong? Many thanks in advance!

1 Like

If you set the pixels you are not writing them out - they are only in memory. However, I guess you have already tried the writePixels line above that? Did you add the line p.closeIO() after writing to the band?
A NullPointerException is not the same as an empty band. Can you tell in which line the exception occurs and maybe even what object is null/None there?

And now, having said all this, I think you might also consider writing an operator, especially when you want to re-use it later (https://senbox.atlassian.net/wiki/spaces/SNAP/pages/42041346/How+to+write+a+processor+in+Python). You could then create a graph with the three operators (yours, merge, back-geocoding).

If you want to stick to the code you already have, you could call the operators from your code (see snappy_subset.py for an example where the Subset Op is used). You would then use your setPixels()-line from above to add the data to your bands and set your Product p as input to the next operator.
Finally, you don’t need the MergeOp then, as you can simply copy bands using ProductUtils.copyBand(sourceBandName, sourceProduct, targetProduct, True). And I recommend calling ProductUtils.copyProductNodes(sourceProduct, targetProduct) after that.

Thanks TonioF for your help, and sorry for getting back to you so late.

Regarding the first part of your comment:

  1. when using “setPixel”, the exception (java.lang.NullPointerException) occurs during the loop, at the first attempt to execute “targetBand.setPixels(0, y, width, 1, d)”
  2. when using “writePixels”, the loop executes entirely without any error, it is only when I try to read this new band that the exception occurs (java.lang.NullPointerException): b = p.getBand(‘newBand’); b.readPixels(0, 0, w, h, ‘newBand’)

I have never tried writing an operator, but I will try that as you suggest that it is a better strategy. I’ll post the result in case I succeed!

Again, many thanks for your time and help.