docs: public static void inverseN(WaveletDescription<WlCoef_I32> desc ,
  								GrayS32 input , GrayS32 output ,
  								GrayS32 storage,
  								int numLevels ,
  								int minValue , int maxValue)
  	{
  		if( numLevels == 1 ) {
  			inverse1(desc,input,output, storage,minValue,maxValue);
  			PixelMath.boundImage(output, minValue, maxValue);
  			return;
  		}
  
  		UtilWavelet.checkShape(desc.getForward(),output,input,numLevels);
  		storage = InputSanityCheck.checkDeclare(input, storage);
  		// modify the shape of a temporary image not the original
  		storage = storage.subimage(0,0,input.width,input.height, null);
  		storage.subImage = false;
  
  		int width,height;
  
  		int scale = UtilWavelet.computeScale(numLevels);
  		width = input.width/scale;
  		height = input.height/scale;
  		width += width%2;
  		height += height%2;
  
  		GrayS32 levelIn = input.subimage(0,0,width,height, null);
  		GrayS32 levelOut = output.subimage(0,0,width,height, null);
  		storage.reshape(width,height);
  		inverse1(desc,levelIn,levelOut, storage,Integer.MIN_VALUE,Integer.MAX_VALUE);
  
  		for( int i = numLevels-1; i >= 1; i-- ) {
  			// copy the decoded segment into the input
  			levelIn.setTo(levelOut);
  			if( i > 1 ) {
  				scale /= 2;
  				width = input.width/scale;
  				height = input.height/scale;
  				width += width%2;
  				height += height%2;
  
  				storage.reshape(width,height);
  				levelIn = input.subimage(0,0,width,height, null);
  				levelOut = output.subimage(0,0,width,height, null);
  			} else {
  				levelIn = input;
  				levelOut = output;
  			}
  
  			storage.reshape(levelIn.width,levelIn.height);
  			inverse1(desc,levelIn,levelOut, storage,Integer.MIN_VALUE,Integer.MAX_VALUE);
  		}
  
  		if( minValue != Integer.MIN_VALUE && maxValue != Integer.MAX_VALUE )
  			PixelMath.boundImage(output, minValue, maxValue);
  	}