-
Notifications
You must be signed in to change notification settings - Fork 4
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
92f6e0f
6f7700c
296f0ee
2e817a6
1a54857
7a7a073
4d56340
96d8fd6
c770c82
3bd2736
4ecc826
e4730d1
13a3a1a
62566b2
5518322
3bbd8c9
ea1b78f
44434c9
93dbc4e
2e133d2
568c711
c3890a9
058abc3
1953355
3180276
5a46b83
53fde3d
c5f7b19
70443fb
614bd6d
cc9fd22
112d079
d55c7a5
a7351fe
1e735c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,46 +72,55 @@ static protected class Param | |
{ | ||
public int tileWidth; | ||
public int tileHeight; | ||
public long minC; | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() ) | ||
{ | ||
|
@@ -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 | ||
|
@@ -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++ ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
|
||
|
||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.