Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for scaling a specific region #2

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
92f6e0f
Changes to support the scaling a certain region specified by start/en…
cgoina Oct 2, 2015
6f7700c
fixed the z increment
cgoina Oct 2, 2015
296f0ee
restored the tile format
cgoina Oct 2, 2015
2e817a6
Changed the scaler to work both with and without region boundaries
cgoina Oct 6, 2015
1a54857
do not append the extension in the code - let the user specify that i…
cgoina Oct 6, 2015
7a7a073
removed print statements
cgoina Oct 8, 2015
4d56340
prevent crating an absolute path when no output path is provided
cgoina Oct 28, 2015
96d8fd6
remove commented code
cgoina Oct 28, 2015
c770c82
debug message to show successful loading of a tile
cgoina Oct 28, 2015
3bd2736
commented debug message
cgoina Oct 28, 2015
4ecc826
changed the tiler to ignore empty tiles as well
cgoina Nov 3, 2015
e4730d1
limited the cache size to prevent OOM errors
cgoina Nov 13, 2015
13a3a1a
configurable bg value
cgoina Nov 18, 2015
62566b2
changes to allow me to save an orthoview at the proper offset
cgoina Dec 8, 2015
5518322
use the scale level to determine the z step
cgoina Dec 10, 2015
3bbd8c9
put z in the tile loading debug message
cgoina Dec 10, 2015
ea1b78f
changed the export parameters to be pixel coordinates at level 0
cgoina Dec 11, 2015
44434c9
made tile cache size a configurable parameter
cgoina Dec 11, 2015
93dbc4e
used explicit pixel coordinates for exported max instead of the width
cgoina Dec 11, 2015
2e133d2
don't set the tile cache size by default
cgoina Dec 11, 2015
568c711
put the previous exportMin[R,C], exportMax[R,C] parameters back such …
cgoina Dec 11, 2015
c3890a9
option to set the background for the scaler as well
cgoina Dec 24, 2015
058abc3
http url writer
cgoina May 17, 2016
1953355
removed unused import
cgoina May 17, 2016
3180276
output error messages
cgoina May 17, 2016
5a46b83
change scaler to read from file or url
cgoina May 19, 2016
53fde3d
don't attempt to read image if status is not OK
cgoina May 19, 2016
c5f7b19
moved the tile reading to Utils
cgoina May 22, 2016
70443fb
added scale level to the print statement
cgoina Jun 2, 2016
614bd6d
close the connection on read
cgoina Jun 3, 2016
cc9fd22
print the zoom level
cgoina Jun 3, 2016
112d079
make the orientation not dependent of the order
cgoina Jun 3, 2016
d55c7a5
added guava cache as the previous cache based on linkedhashmap didn't…
cgoina Jun 30, 2016
a7351fe
removed unnecessary imports
cgoina Jun 30, 2016
1e735c6
removed the weak keys
cgoina Jun 30, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -663,16 +663,15 @@ protected int[] fetchPixels2( final long r, final long c, final long z )
if ( cachedEntry != null )
return cachedEntry.data;
}

final String urlString = String.format( urlFormat, s, scale, c * tileWidth, r * tileHeight, z, tileWidth, tileHeight, r, c );

final int[] pixels = new int[ tileWidth * tileHeight ];
try
{
final URL url = new URL( urlString );
// final Image image = toolkit.createImage( url );
final BufferedImage jpg = ImageIO.read( url );

final BufferedImage jpg = ImageIO.read( url );

/* This gymnastic is necessary to get reproducible gray
* values, just opening a JPG or PNG, even when saved by
* ImageIO, and grabbing its pixels results in gray values
Expand All @@ -682,7 +681,7 @@ protected int[] fetchPixels2( final long r, final long c, final long z )
image.createGraphics().drawImage( jpg, 0, 0, null );
final PixelGrabber pg = new PixelGrabber( image, 0, 0, tileWidth, tileHeight, pixels, 0, tileWidth );
pg.grabPixels();

cache.put( key, new SoftReference< Entry >( new Entry( key, pixels ) ) );
// System.out.println( "success loading r=" + r + " c=" + c + " url(" + urlString + ")" );

Expand Down
13 changes: 9 additions & 4 deletions src/main/java/org/catmaid/Downsampler.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ final static public void downsampleBytes( final byte[] aPixels, final byte[] bPi
}
}
}
final static public void downsampleRGB( final int[] aPixels, final int[] bPixels, final int wa, final int ha )

final static public boolean downsampleRGB( final int[] aPixels, final int[] bPixels, final int wa, final int ha )
{
assert aPixels.length == wa * ha && bPixels.length == wa / 2 * ( ha / 2 ) : "Input dimensions do not match.";

Expand All @@ -108,15 +108,20 @@ final static public void downsampleRGB( final int[] aPixels, final int[] bPixels
final int wb = wa / 2;
final int hb = ha / 2;
final int nb = hb * wb;

boolean bresult = false;
for ( int ya = 0, yb = 0; yb < nb; ya += wa2, yb += wb )
{
final int ya1 = ya + wa;
for ( int xa = 0, xb = 0; xb < wb; xa += 2, ++xb )
{
final int xa1 = xa + 1;
bPixels[ yb + xb ] = averageColor( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels );
int c = averageColor( ya + xa, ya + xa1, ya1 + xa, ya1 + xa1, aPixels );
bPixels[ yb + xb ] = c;
if ((c & 0xFFFFFF) != 0) {
bresult = true; // return true since at least one pixel is non black
}
}
}
return bresult;
}
}
158 changes: 86 additions & 72 deletions src/main/java/org/catmaid/ScaleCATMAID.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,46 +72,55 @@ static protected class Param
{
public int tileWidth;
public int tileHeight;
public long minC;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think that this is useful because scaling usually processes an entire z-section from full resolution down until it becomes a single tile or less. Processing only parts of it does not make sense. What is the rational behind this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this would make it possible to parallelize by x and y but I can see why the entire z-section needs to be available in order for the scaling to be acurate. However we need a mechanism to keep going even when no immediate tiles are found since empty tiles may not be written. This is something that Eric requested.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about accuracy but about being possible at all. We cannot scale to the final level without having scaled everything beforehand. One could certainly generate one new scale level at a time, do that in chunks in parallel and after that do the next scale level in chunks. I think that's a good idea but not what's implemented here. Do you want to do that? As an alternative tool?

You are right about the mechanism to keep going despite missing tiles on the road. It was convenient to assume that if there is no data we could stop because it works without knowing about the width and height of the dataset. For that, however, we need only the physical width and height of the dataset, so only two parameters, conveniently in pixel coordinates at scale level 0 to not confuse stuff. These parameters are only required if ignoreEmptyTiles is true. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we need all four parameters considering that the retiler can take all four params - I don't think it hurts having them anyway. If you prefer them to be in pixel coordinates at scale 0 that's perfectly fine and it is consistent with the retiler.

public long maxC;
public long minR;
public long maxR;
public long minZ;
public long maxZ;
public String tileFormat;
public String format;
public float quality;
public int type;
public boolean ignoreEmptyTiles;
}

private ScaleCATMAID(){}

static protected Param parseParameters()
{

private ScaleCATMAID() {}

static protected Param parseParameters() {
final Param p = new Param();
p.tileWidth = Integer.parseInt( System.getProperty( "tileWidth", "256" ) );
p.tileHeight = Integer.parseInt( System.getProperty( "tileHeight", "256" ) );


p.minC = Long.parseLong( System.getProperty( "minC", "0" ) );
p.maxC = Long.parseLong( System.getProperty( "maxC", "" + Integer.MAX_VALUE ) );
p.minR = Long.parseLong( System.getProperty( "minR", "0" ) );
p.maxR = Long.parseLong( System.getProperty( "maxR", "" + Integer.MAX_VALUE ) );
p.minZ = Long.parseLong( System.getProperty( "minZ", "0" ) );
p.maxZ = Long.parseLong( System.getProperty( "maxZ", "" + Long.MAX_VALUE ) );
final String basePath = System.getProperty( "basePath", "" );
p.tileFormat = System.getProperty( "tileFormat", basePath + "%5$d/%8$d_%9$d_%1$d.jpg" );

System.out.println( p.tileFormat );

p.format = System.getProperty( "format", "jpg" );
final String tileFormat = System.getProperty( "tileFormat", "%5$d/%8$d_%9$d_%1$d");
p.tileFormat = basePath + "/" + tileFormat + "." + p.format;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely no. Perlman wanted to use other streaming mechanisms that may not end in "." + p.format. I would rather get rid of basePath although it has been useful for lazy parameter setting. The idea tis that tileFormat is as flexible as possible and describes the entire string.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I restored that


System.out.println("Tile pattern: " + p.tileFormat );

p.quality = Float.parseFloat( System.getProperty( "quality", "0.85" ) );
final String type = System.getProperty( "type", "rgb" );
if ( type.equalsIgnoreCase( "gray" ) || type.equalsIgnoreCase( "grey" ) )
p.type = BufferedImage.TYPE_BYTE_GRAY;
else
p.type = BufferedImage.TYPE_INT_RGB;

p.ignoreEmptyTiles = Boolean.valueOf(System.getProperty( "ignoreEmptyTiles"));

return p;
}

final static protected BufferedImage open(
final String path,
final BufferedImage alternative,
final int type )
{
// System.out.println( path );
final File file = new File( path );
if ( file.exists() )
{
Expand All @@ -127,13 +136,12 @@ final static protected BufferedImage open(
else
return alternative;
}



/**
* Generate scaled tiles from a range of an existing scale level 0 tile
* stack.
*
* @param tileFormat format string adfdressing tiles including basePath
* @param tileFormat format string addressing tiles including basePath
* @param tileWidth
* @param tileHeight
* @param minZ the first z-index to be scaled
Expand All @@ -142,120 +150,126 @@ final static protected BufferedImage open(
* @param quality quality for jpg-compression if format is "jpg"
* @param type the type of export tiles, e.g.
* {@link BufferedImage#TYPE_BYTE_GRAY}
* @param ignoreEmptyTiles - if true don't save empty tiles
*
* @throws Exception
*/
final public static void scale(
final String tileFormat,
final int tileWidth,
final int tileHeight,
final long minX,
final long maxX,
final long minY,
final long maxY,
final long minZ,
final long maxZ,
final String format,
final float quality,
final int type ) throws Exception
final int type,
final boolean ignoreEmptyTiles) throws Exception
{
final BufferedImage alternative = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB );

final int[] targetPixels = new int[ tileWidth * tileHeight ];
final BufferedImage target = new BufferedImage( tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB );

final BufferedImage sourceImage = new BufferedImage( tileWidth * 2, tileHeight * 2, BufferedImage.TYPE_INT_RGB );
final Graphics2D g = sourceImage.createGraphics();
final int[] sourcePixels = new int[ tileWidth * tileHeight * 4 ];
Z: for ( long z = minZ; z <= maxZ; ++z )

for ( long z = minZ; z <= maxZ; z++ )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++z does not store z before increment in a register and is therefore the right thing to do here

{
System.out.println( "z-index: " + z );
S: for ( int s = 1; true; ++s )
boolean workToDo = true;
for ( int s = 1; workToDo; ++s )
{
System.out.println( " scale: " + s );
System.out.println( "scale: " + s );
final int iScale = 1 << s;
final double scale = 1.0 / iScale;

final int s1 = s - 1;
final int iScale1 = 1 << s1;
final double scale1 = 1.0 / iScale1;

boolean proceedY = true;
Y: for ( long y = 0; proceedY; y += tileHeight )
int nResultTiles = 0;

workToDo = false;
for ( long y = minY / iScale1; y < maxY / iScale1; y += 2 * tileHeight )
{
final long yt = y / tileHeight;
boolean proceedX = true;
for ( long x = 0; proceedX; x += tileWidth )
workToDo = true;
final long yt = y / (2 * tileHeight);
for ( long x = minX / iScale1; x < maxX / iScale1; x += 2 * tileWidth )
{
final long xt = x / tileWidth;
nResultTiles++;
final long xt = x / (2 * tileWidth);
final Image imp1 = open(
String.format( tileFormat, s1, scale1, x * iScale1, y * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt, 2 * xt ),
alternative,
type );

if ( imp1 == alternative )
if ( x == 0 )
if ( y == 0 )
break Z;
else
continue S;
else
continue Y;

final Image imp2 = open(
String.format( tileFormat, s1, scale1, ( x + tileWidth ) * iScale1, y * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt, 2 * xt + 1 ),
alternative,
type );

proceedX = imp2 != alternative;


final Image imp3 = open(
String.format( tileFormat, s1, scale1, x * iScale1, ( y + tileHeight ) * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt + 1, 2 * xt ),
alternative,
type );

proceedY = imp3 != alternative;

if ( x == 0 && y == 0 && !( proceedX || proceedY) )
break S;


final Image imp4 = open(
String.format( tileFormat, s1, scale1, ( x + tileWidth ) * iScale1, ( y + tileHeight ) * iScale1, z, tileWidth * iScale1, tileHeight * iScale1, 2 * yt + 1, 2 * xt + 1 ),
alternative,
type );


if (imp1 == alternative && imp2 == alternative && imp3 == alternative && imp4 == alternative) {
continue;
}

g.drawImage( imp1, 0, 0, null );
g.drawImage( imp2, tileWidth, 0, null );
g.drawImage( imp3, 0, tileHeight, null );
g.drawImage( imp4, tileWidth, tileHeight, null );

final PixelGrabber pg = new PixelGrabber( sourceImage, 0, 0, tileWidth * 2, tileHeight * 2, sourcePixels, 0, tileWidth * 2 );
pg.grabPixels();

Downsampler.downsampleRGB( sourcePixels, targetPixels, tileWidth * 2, tileHeight * 2 );

target.getRaster().setDataElements( 0, 0, tileWidth, tileHeight, targetPixels );
final BufferedImage targetCopy = Util.draw( target, type );

Util.writeTile(
targetCopy,
String.format( tileFormat, s, scale, x * iScale, y * iScale, z, tileWidth * iScale, tileHeight * iScale, yt, xt ),
format,
quality );
}
boolean notEmpty = Downsampler.downsampleRGB( sourcePixels, targetPixels, tileWidth * 2, tileHeight * 2 );

if (notEmpty || !ignoreEmptyTiles) {
target.getRaster().setDataElements( 0, 0, tileWidth, tileHeight, targetPixels );
final BufferedImage targetCopy = Util.draw( target, type );

Util.writeTile(
targetCopy,
String.format( tileFormat, s, scale, x * iScale, y * iScale, z, tileWidth * iScale, tileHeight * iScale, yt, xt ),
format,
quality );
}
} // end for x
} // end for y
if (nResultTiles <= 1) {
workToDo = false;
}
}
}
} // end for s
} // end for z
}

final static public void scale( final Param p ) throws Exception
{
scale(
p.tileFormat,
p.tileWidth,
p.tileHeight,
p.minZ,
p.maxZ,
p.format,
p.quality,
p.type );
scale(p.tileFormat,
p.tileWidth,
p.tileHeight,
p.minC * p.tileWidth,
(p.maxC + 1) * p.tileWidth, // include maxC
p.minR * p.tileHeight,
(p.maxR + 1) * p.tileHeight, // include maxR
p.minZ,
p.maxZ,
p.format,
p.quality,
p.type,
p.ignoreEmptyTiles);
}


Expand Down
16 changes: 8 additions & 8 deletions src/main/java/org/catmaid/TileCATMAID.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,21 +184,21 @@ static protected Param parseParameters()
/* CATMAID source stack */
final String sourceBaseUrl = System.getProperty( "sourceBaseUrl", "" );
p.sourceUrlFormat = System.getProperty( "sourceUrlFormat", sourceBaseUrl + "%5$d/%8$d_%9$d_%1$d.jpg" );

p.sourceWidth = Long.parseLong( System.getProperty( "sourceWidth", "0" ) );
p.sourceHeight = Long.parseLong( System.getProperty( "sourceHeight", "0" ) );
p.sourceDepth = Long.parseLong( System.getProperty( "sourceDepth", "0" ) );
p.sourceScaleLevel = Long.parseLong( System.getProperty( "sourceScaleLevel", "0" ) );

final int scaleXYDiv = 1 << p.sourceScaleLevel;

p.sourceTileWidth = Integer.parseInt( System.getProperty( "sourceTileWidth", "256" ) );
p.sourceTileHeight = Integer.parseInt( System.getProperty( "sourceTileHeight", "256" ) );
p.sourceResXY = Double.parseDouble( System.getProperty( "sourceResXY", "1.0" ) );
p.sourceResZ = Double.parseDouble( System.getProperty( "sourceResZ", "1.0" ) );

final double scaleZDiv = scaleXYDiv * p.sourceResXY / p.sourceResZ;

/* export */
final long minX = Long.parseLong( System.getProperty( "minX", "0" ) );
final long minY = Long.parseLong( System.getProperty( "minY", "0" ) );
Expand Down Expand Up @@ -235,7 +235,7 @@ else if ( orientation.equalsIgnoreCase( "zy" ) )
p.sourceInterval.dimension( 1 ) / scaleXYDiv,
( long )( p.sourceInterval.dimension( 2 ) / scaleZDiv ) );
}

p.tileWidth = Integer.parseInt( System.getProperty( "tileWidth", "256" ) );
p.tileHeight = Integer.parseInt( System.getProperty( "tileHeight", "256" ) );
p.minZ = Long.parseLong( System.getProperty( "exportMinZ", "0" ) );
Expand All @@ -250,9 +250,9 @@ else if ( orientation.equalsIgnoreCase( "zy" ) )
p.maxC = Long.parseLong( System.getProperty(
"exportMaxC",
Long.toString( ( long )Math.ceil( ( double )orientedSourceInterval.dimension( 0 ) / ( double )p.tileWidth ) - 1 ) ) );

p.exportPath = System.getProperty( "exportBasePath", "" );
p.tilePattern = System.getProperty( "tilePattern", "<z>/<r>_<c>_<s>" );
p.tilePattern = System.getProperty( "tilePattern", "%5$d/%8$d_%9$d_%1$d" ); // default is z/row_col_scale
p.format = System.getProperty( "format", "jpg" );
p.quality = Float.parseFloat( System.getProperty( "quality", "0.85" ) );
final String type = System.getProperty( "type", "rgb" );
Expand Down
Loading