jj2000.j2k.roi.encoder.ROIScaler Maven / Gradle / Ivy
Show all versions of jai-imageio-jpeg2000 Show documentation
/*
* $RCSfile: ROIScaler.java,v $
* $Revision: 1.1 $
* $Date: 2005/02/11 05:02:23 $
* $State: Exp $
*
* Class: ROIScaler
*
* Description: This class takes care of the scaling of the
* samples
*
*
*
* COPYRIGHT:
*
* This software module was originally developed by Raphaël Grosbois and
* Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
* Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
* Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
* Centre France S.A) in the course of development of the JPEG2000
* standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
* software module is an implementation of a part of the JPEG 2000
* Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
* Systems AB and Canon Research Centre France S.A (collectively JJ2000
* Partners) agree not to assert against ISO/IEC and users of the JPEG
* 2000 Standard (Users) any of their rights under the copyright, not
* including other intellectual property rights, for this software module
* with respect to the usage by ISO/IEC and Users of this software module
* or modifications thereof for use in hardware or software products
* claiming conformance to the JPEG 2000 Standard. Those intending to use
* this software module in hardware or software products are advised that
* their use may infringe existing patents. The original developers of
* this software module, JJ2000 Partners and ISO/IEC assume no liability
* for use of this software module or modifications thereof. No license
* or right to this software module is granted for non JPEG 2000 Standard
* conforming products. JJ2000 Partners have full right to use this
* software module for his/her own purpose, assign or donate this
* software module to any third party and to inhibit third parties from
* using this software module for non JPEG 2000 Standard conforming
* products. This copyright notice must be included in all copies or
* derivative works of this software module.
*
* Copyright (c) 1999/2000 JJ2000 Partners.
* */
package jj2000.j2k.roi.encoder;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.Vector;
import jj2000.j2k.ModuleSpec;
import jj2000.j2k.image.DataBlkInt;
import jj2000.j2k.image.ImgDataAdapter;
import jj2000.j2k.image.input.ImgReaderPGM;
import jj2000.j2k.quantization.quantizer.CBlkQuantDataSrcEnc;
import jj2000.j2k.quantization.quantizer.Quantizer;
import jj2000.j2k.roi.MaxShiftSpec;
import jj2000.j2k.wavelet.Subband;
import jj2000.j2k.wavelet.analysis.CBlkWTData;
import jj2000.j2k.wavelet.analysis.SubbandAn;
import com.github.jaiimageio.jpeg2000.impl.J2KImageWriteParamJava;
/**
* This class deals with the ROI functionality.
*
* The ROI method is the Maxshift method. The ROIScaler works by scaling
* the quantized wavelet coefficients that do not affect the ROI (i.e
* background coefficients) so that these samples get a lower significance
* than the ROI ones. By scaling the coefficients sufficiently, the ROI
* coefficients can be recognized by their amplitude alone and no ROI mask
* needs to be generated at the decoder side.
*
*
The source module must be a quantizer and code-block's data is exchange
* with thanks to CBlkWTData instances.
*
* @see Quantizer
* @see CBlkWTData
* */
public class ROIScaler extends ImgDataAdapter implements CBlkQuantDataSrcEnc {
/** The prefix for ROI Scaler options: 'R' */
public final static char OPT_PREFIX = 'R';
/** The list of parameters that are accepted for ROI coding. Options
* for ROI Scaler start with 'R'. */
private final static String [][] pinfo = {
{ "Rroi","[] R "+
" or [] C "+
" or [] A ",
"Specifies ROIs shape and location. The shape can be either "+
"rectangular 'R', or circular 'C' or arbitrary 'A'. "+
"Each new occurrence of an 'R', a 'C' or an 'A' is a new ROI. "+
"For circular and rectangular ROIs, all values are "+
"given as their pixel values relative to the canvas origin. "+
"Arbitrary shapes must be included in a PGM file where non 0 "+
"values correspond to ROI coefficients. The PGM file must have "+
"the size as the image. "+
"The component idx specifies which components "+
"contain the ROI. The component index is specified as described "+
"by points 3 and 4 in the general comment on tile-component idx. "+
"If this option is used, the codestream is layer progressive by "+
"default unless it is overridden by the 'Aptype' option.",
null},
{ "Ralign","[true|false]",
"By specifying this argument, the ROI mask will be "+
"limited to covering only entire code-blocks. The ROI coding can "+
"then be performed without any actual scaling of the coefficients "+
"but by instead scaling the distortion estimates.","false"},
{ "Rstart_level","",
"This argument forces the lowest resolution levels to "+
"belong to the ROI. By doing this, it is possible to avoid only "+
"getting information for the ROI at an early stage of "+
"transmission. = 0 means the lowest resolution level "+
"belongs to the ROI, 1 means the two lowest etc. (-1 deactivates"+
" the option)","-1"},
{ "Rno_rect","[true|false]",
"This argument makes sure that the ROI mask generation is not done "+
"using the fast ROI mask generation for rectangular ROIs "+
"regardless of whether the specified ROIs are rectangular or not",
"false"},
};
/** The maximum number of magnitude bit-planes in any subband. One value
* for each tile-component */
private int maxMagBits[][];
/** Flag indicating the presence of ROIs */
private boolean roi;
/** Flag indicating if block aligned ROIs are used */
private boolean blockAligned;
/** Number of resolution levels to include in ROI mask */
private int useStartLevel;
/** The class generating the ROI mask */
private ROIMaskGenerator mg;
/** The ROI mask */
private DataBlkInt roiMask;
/** The source of quantized wavelet transform coefficients */
private Quantizer src;
/**
* Constructor of the ROI scaler, takes a Quantizer as source of data to
* scale.
*
* @param src The quantizer that is the source of data.
*
* @param mg The mask generator that will be used for all components
*
* @param roi Flag indicating whether there are rois specified.
*
* @param sLev The resolution levels that belong entirely to ROI
*
* @param uba Flag indicating whether block aligning is used.
*
* @param encSpec The encoder specifications for addition of roi specs
* */
public ROIScaler(Quantizer src,
ROIMaskGenerator mg,
boolean roi,
int sLev,
boolean uba,
J2KImageWriteParamJava wp){
super(src);
this.src = src;
this.roi = roi;
this.useStartLevel = sLev;
if(roi){
// If there is no ROI, no need to do this
this.mg = mg;
roiMask = new DataBlkInt();
calcMaxMagBits(wp);
blockAligned = uba;
}
}
/**
* Since ROI scaling is always a reversible operation, it calls
* isReversible() method of it source (the quantizer module).
*
* @param t The tile to test for reversibility
*
* @param c The component to test for reversibility
*
* @return True if the quantized data is reversible, false if not.
* */
public boolean isReversible(int t,int c){
return src.isReversible(t,c);
}
/**
* Returns a reference to the subband tree structure representing the
* subband decomposition for the specified tile-component.
*
* @param t The index of the tile.
*
* @param c The index of the component.
*
* @return The subband tree structure, see SubbandAn.
*
* @see SubbandAn
*
* @see Subband
* */
public SubbandAn getAnSubbandTree(int t,int c) {
return src.getAnSubbandTree(t,c);
}
/**
* Returns the horizontal offset of the code-block partition. Allowable
* values are 0 and 1, nothing else.
* */
public int getCbULX() {
return src.getCbULX();
}
/**
* Returns the vertical offset of the code-block partition. Allowable
* values are 0 and 1, nothing else.
* */
public int getCbULY() {
return src.getCbULY();
}
/**
* Creates a ROIScaler object. The Quantizer is the source of data to
* scale.
*
* The ROI Scaler creates a ROIMaskGenerator depending on what ROI
* information is in the J2KImageWriteParamJava. If only rectangular ROI are used,
* the fast mask generator for rectangular ROI can be used.
*
* @param src The source of data to scale
*
* @param pl The parameter list (or options).
*
* @param encSpec The encoder specifications for addition of roi specs
*
* @exception IllegalArgumentException If an error occurs while parsing
* the options in 'pl'
* */
public static ROIScaler createInstance(Quantizer src,
J2KImageWriteParamJava wp){
Vector roiVector = new Vector();
ROIMaskGenerator maskGen = null;
/* XXX: need investigation
// Check parameters
pl.checkList(OPT_PREFIX,pl.toNameArray(pinfo));
*/
// Get parameters and check if there are and ROIs specified
String roiopt = wp.getROIs().getSpecified();
if (roiopt == null) {
// No ROIs specified! Create ROIScaler with no mask generator
return new ROIScaler(src,null,false,-1,false,wp);
}
// Check if the lowest resolution levels should belong to the ROI
int sLev = wp.getStartLevelROI();
// Check if the ROIs are block-aligned
boolean useBlockAligned = wp.getAlignROI();
// Check if generic mask generation is specified
boolean onlyRect = false;
// Parse the ROIs
parseROIs(roiopt,src.getNumComps(),roiVector);
ROI[] roiArray = new ROI[roiVector.size()];
roiVector.copyInto(roiArray);
// If onlyRect has been forced, check if there are any non-rectangular
// ROIs specified. Currently, only the presence of circular ROIs will
// make this false
if(onlyRect) {
for(int i=roiArray.length-1 ; i>=0 ; i--)
if(!roiArray[i].rect){
onlyRect=false;
break;
}
}
if(onlyRect){
// It's possible to use the fast ROI mask generation when only
// rectangular ROIs are specified.
maskGen = new RectROIMaskGenerator(roiArray,src.getNumComps());
}
else{
// It's necessary to use the generic mask generation
maskGen = new ArbROIMaskGenerator(roiArray,src.getNumComps(),src);
}
return new ROIScaler(src,maskGen,true,sLev,useBlockAligned,wp);
}
/**
* This function parses the values given for the ROIs with the argument
* -Rroi. Currently only circular and rectangular ROIs are supported.
*
*
A rectangular ROI is indicated by a 'R' followed the coordinates
* for the upper left corner of the ROI and then its width and height.
*
*
A circular ROI is indicated by a 'C' followed by the coordinates of
* the circle center and then the radius.
*
*
Before the R and C values, the component that are affected by the
* ROI are indicated.
*
* @param roiopt The info on the ROIs
*
* @param nc number of components
*
* @param roiVector The vcector containing the ROI parsed from the cmd line
*
* @return The ROIs specified in roiopt
* */
protected static Vector parseROIs(String roiopt, int nc,Vector roiVector){
ROI[] ROIs;
ROI roi;
StringTokenizer stok;
char tok;
int nrOfROIs = 0;
char c;
int comp,ulx,uly,w,h,x,y,rad;
boolean[] roiInComp = null;
stok = new StringTokenizer(roiopt);
String word;
while(stok.hasMoreTokens()){
word = stok.nextToken();
switch(word.charAt(0)){
case 'c': // Components specification
roiInComp = ModuleSpec.parseIdx(word,nc);
break;
case 'R': // Rectangular ROI to be read
nrOfROIs++;
try{
word = stok.nextToken();
ulx = (new Integer(word)).intValue();
word = stok.nextToken();
uly = (new Integer(word)).intValue();
word = stok.nextToken();
w = (new Integer(word)).intValue();
word = stok.nextToken();
h = (new Integer(word)).intValue();
}
catch(NumberFormatException e){
throw new IllegalArgumentException("Bad parameter for "+
"'-Rroi R' option : "+
word);
}
catch(NoSuchElementException f){
throw new IllegalArgumentException("Wrong number of "+
"parameters for "+
"h'-Rroi R' option.");
}
// If the ROI is component-specific, check which comps.
if(roiInComp != null)
for(int i=0; iThe function calls on a ROIMaskGenerator to get the mask for
* scaling the coefficients in the current block.
*
* The data returned by this method is a copy of the orignal
* data. Therfore it can be modified "in place" without any problems after
* being returned. The 'offset' of the returned data is 0, and the 'scanw'
* is the same as the code-block width. See the 'CBlkWTData' class.
*
* @param n The component for which to return the next code-block.
*
* @param cblk If non-null this object will be used to return the new
* code-block. If null a new one will be allocated and returned. If the
* "data" array of the object is non-null it will be reused, if possible,
* to return the data.
*
* @return The next code-block in the current tile for component 'n', or
* null if all code-blocks for the current tile have been returned.
*
* @see CBlkWTData
* */
public CBlkWTData getNextCodeBlock(int n, CBlkWTData cblk) {
return getNextInternCodeBlock(n,cblk);
}
/**
* This function gets a datablk from the entropy coder. The sample sin the
* block, which consists of the quantized coefficients from the quantizer,
* are scaled by the values given for any ROIs specified.
*
*
The function calls on a ROIMaskGenerator to get the mask for
* scaling the coefficients in the current block.
*
* @param c The component for which to return the next code-block.
*
* @param cblk If non-null this object will be used to return the new
* code-block. If null a new one will be allocated and returned. If the
* "data" array of the object is non-null it will be reused, if possible,
* to return the data.
*
* @return The next code-block in the current tile for component 'n', or
* null if all code-blocks for the current tile have been returned.
*
* @see CBlkWTData
* */
public CBlkWTData getNextInternCodeBlock(int c, CBlkWTData cblk){
int mi,i,j,k,wrap;
int ulx, uly, w, h;
DataBlkInt mask = roiMask; // local copy of mask
int[] maskData; // local copy of mask data
int[] data; // local copy of quantized data
int tmp;
int bitMask = 0x7FFFFFFF;
SubbandAn root,sb;
int maxBits = 0; // local copy
boolean roiInTile;
boolean sbInMask;
int nROIcoeff = 0;
// Get codeblock's data from quantizer
cblk = src.getNextCodeBlock(c,cblk);
// If there is no ROI in the image, or if we already got all
// code-blocks
if (!roi || cblk == null) {
return cblk;
}
data = (int[])cblk.getData();
sb = cblk.sb;
ulx = cblk.ulx;
uly = cblk.uly;
w = cblk.w;
h = cblk.h;
sbInMask= (sb.resLvl<=useStartLevel);
// Check that there is an array for the mask and set it to zero
maskData = mask.getDataInt(); // local copy of mask data
if(maskData==null || w*h>maskData.length){
maskData = new int[w*h];
mask.setDataInt(maskData);
}
else{
for(i=w*h-1;i>=0;i--)
maskData[i]=0;
}
mask.ulx = ulx;
mask.uly = uly;
mask.w = w;
mask.h = h;
// Get ROI mask from generator
root = src.getAnSubbandTree(tIdx,c);
maxBits = maxMagBits[tIdx][c];
roiInTile = mg.getROIMask(mask,root,maxBits,c);
// If there is no ROI in this tile, return the code-block untouched
if(!roiInTile && (!sbInMask)) {
cblk.nROIbp = 0;
return cblk;
}
// Update field containing the number of ROI magnitude bit-planes
cblk.nROIbp = cblk.magbits;
// If the ROI should adhere to the code-block's boundaries or if the
// entire subband belongs to the ROI mask, The code-block is set to
// belong entirely to the ROI with the highest scaling value
if(sbInMask) {
// Scale the wmse so that instead of scaling the coefficients, the
// wmse is scaled.
cblk.wmseScaling *= (float)(1<<(maxBits<<1));
cblk.nROIcoeff = w*h;
return cblk;
}
// In 'block aligned' mode, the code-block is set to belong entirely
// to the ROI with the highest scaling value if one coefficient, at
// least, belongs to the ROI
if(blockAligned) {
wrap=cblk.scanw-w;
mi=h*w-1;
i=cblk.offset+cblk.scanw*(h-1)+w-1;
int nroicoeff = 0;
for(j=h;j>0;j--){
for(k=w-1;k>=0;k--,i--,mi--){
if (maskData[mi] != 0) {
nroicoeff++;
}
}
i -= wrap;
}
if(nroicoeff!=0) { // Include the subband
cblk.wmseScaling *= (float)(1<<(maxBits<<1));
cblk.nROIcoeff = w*h;
}
return cblk;
}
// Scale background coefficients
bitMask=(((1<0;j--){
for(k=w;k>0;k--,i--,mi--){
tmp=data[i];
if (maskData[mi] != 0) {
// ROI coeff. We need to erase fractional bits to ensure
// that they do not conflict with BG coeffs. This is only
// strictly necessary for ROI coeffs. which non-fractional
// magnitude is zero, but much better BG quality can be
// achieved if done if reset to zero since coding zeros is
// much more efficient (the entropy coder knows nothing
// about ROI and cannot avoid coding the ROI fractional
// bits, otherwise this would not be necessary).
data[i] = (0x80000000 & tmp) | (tmp & bitMask);
nROIcoeff++;
}
else {
// BG coeff. it is not necessary to erase fractional bits
data[i] = (0x80000000 & tmp) |
((tmp & 0x7FFFFFFF) >> maxBits);
}
}
i-=wrap;
}
// Modify the number of significant bit-planes in the code-block
cblk.magbits += maxBits;
// Store the number of ROI coefficients present in the code-block
cblk.nROIcoeff = nROIcoeff;
return cblk;
}
/**
* This function returns the ROI mask generator.
*
* @return The roi mask generator
*/
public ROIMaskGenerator getROIMaskGenerator(){
return mg;
}
/**
* This function returns the blockAligned flag
*
* @return Flag indicating whether the ROIs were block aligned
*/
public boolean getBlockAligned(){
return blockAligned;
}
/**
* This function returns the flag indicating if any ROI functionality used
*
* @return Flag indicating whether there are ROIs in the image
*/
public boolean useRoi(){
return roi;
}
/**
* Returns the parameters that are used in this class and
* implementing classes. It returns a 2D String array. Each of the
* 1D arrays is for a different option, and they have 3
* elements. The first element is the option name, the second one
* is the synopsis, the third one is a long description of what
* the parameter is and the fourth is its default value. The
* synopsis or description may be 'null', in which case it is
* assumed that there is no synopsis or description of the option,
* respectively. Null may be returned if no options are supported.
*
* @return the options name, their synopsis and their explanation,
* or null if no options are supported.
* */
public static String[][] getParameterInfo() {
return pinfo;
}
/**
* Changes the current tile, given the new indexes. An
* IllegalArgumentException is thrown if the indexes do not
* correspond to a valid tile.
*
* @param x The horizontal index of the tile.
*
* @param y The vertical index of the new tile.
* */
public void setTile(int x, int y) {
super.setTile(x,y);
if(roi)
mg.tileChanged();
}
/**
* Advances to the next tile, in standard scan-line order (by rows then
* columns). An NoNextElementException is thrown if the current tile is
* the last one (i.e. there is no next tile).
* */
public void nextTile() {
super.nextTile();
if(roi)
mg.tileChanged();
}
/**
* Calculates the maximum amount of magnitude bits for each
* tile-component, and stores it in the 'maxMagBits' array. This is called
* by the constructor
*
* @param encSpec The encoder specifications for addition of roi specs
* */
private void calcMaxMagBits(J2KImageWriteParamJava wp) {
int tmp;
MaxShiftSpec rois = wp.getROIs();
int nt = src.getNumTiles();
int nc = src.getNumComps();
maxMagBits = new int[nt][nc];
src.setTile(0,0);
for (int t=0; t=0; c--) {
tmp = src.getMaxMagBits(c);
maxMagBits[t][c] = tmp;
rois.setTileCompVal(t,c,new Integer(tmp));
}
if( t