Skip to content

Commit ca52946

Browse files
committed
New BlockAvg Downsample variants
downsample by averaging over arbitrarily sized blocks (like in BDV export)
1 parent a6cdbba commit ca52946

File tree

6 files changed

+853
-29
lines changed

6 files changed

+853
-29
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*-
2+
* #%L
3+
* ImgLib2: a general-purpose, multidimensional image processing library.
4+
* %%
5+
* Copyright (C) 2009 - 2024 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld,
6+
* John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke,
7+
* Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner,
8+
* Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert,
9+
* Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin,
10+
* Jean-Yves Tinevez and Michael Zinsmaier.
11+
* %%
12+
* Redistribution and use in source and binary forms, with or without
13+
* modification, are permitted provided that the following conditions are met:
14+
*
15+
* 1. Redistributions of source code must retain the above copyright notice,
16+
* this list of conditions and the following disclaimer.
17+
* 2. Redistributions in binary form must reproduce the above copyright notice,
18+
* this list of conditions and the following disclaimer in the documentation
19+
* and/or other materials provided with the distribution.
20+
*
21+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
25+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31+
* POSSIBILITY OF SUCH DAMAGE.
32+
* #L%
33+
*/
34+
package net.imglib2.algorithm.blocks.downsample;
35+
36+
import static net.imglib2.util.Util.safeInt;
37+
38+
import java.util.Arrays;
39+
40+
import net.imglib2.Interval;
41+
import net.imglib2.algorithm.blocks.BlockProcessor;
42+
import net.imglib2.algorithm.blocks.util.BlockProcessorSourceInterval;
43+
import net.imglib2.blocks.TempArray;
44+
import net.imglib2.type.PrimitiveType;
45+
import net.imglib2.util.Intervals;
46+
47+
abstract class AbstractDownsampleAvgBlock< T extends AbstractDownsampleAvgBlock< T, P >, P > extends AbstractDownsample< T, P >
48+
{
49+
final int[] downsamplingFactors;
50+
51+
AbstractDownsampleAvgBlock( final int[] downsamplingFactors, final PrimitiveType primitiveType )
52+
{
53+
super( downsampleInDim( downsamplingFactors ), primitiveType );
54+
this.downsamplingFactors = downsamplingFactors;
55+
}
56+
57+
private static boolean[] downsampleInDim( final int[] downsamplingFactors )
58+
{
59+
final int n = downsamplingFactors.length;
60+
final boolean[] downsampleInDim = new boolean[ n ];
61+
for ( int d = 0; d < n; d++ )
62+
{
63+
if ( downsamplingFactors[ d ] < 1 )
64+
throw new IllegalArgumentException();
65+
downsampleInDim[ d ] = ( downsamplingFactors[ d ] > 1 );
66+
}
67+
return downsampleInDim;
68+
}
69+
70+
AbstractDownsampleAvgBlock( T downsample )
71+
{
72+
super( downsample );
73+
downsamplingFactors = downsample.downsamplingFactors;
74+
}
75+
76+
@Override
77+
public void setTargetInterval( final Interval interval )
78+
{
79+
boolean destSizeChanged = false;
80+
for ( int d = 0; d < n; ++d )
81+
{
82+
final long tpos = interval.min( d );
83+
sourcePos[ d ] = tpos * downsamplingFactors[ d ];
84+
85+
final int tdim = safeInt( interval.dimension( d ) );
86+
if ( tdim != destSize[ d ] )
87+
{
88+
destSize[ d ] = tdim;
89+
sourceSize[ d ] = tdim * downsamplingFactors[ d ];
90+
destSizeChanged = true;
91+
}
92+
}
93+
94+
if ( destSizeChanged )
95+
recomputeTempArraySizes();
96+
}
97+
98+
@Override
99+
public void setTargetInterval( final long[] pos, final int[] size )
100+
{
101+
boolean destSizeChanged = false;
102+
for ( int d = 0; d < n; ++d )
103+
{
104+
sourcePos[ d ] = pos[ d ] * downsamplingFactors[ d ];
105+
106+
final int tdim = safeInt( size[ d ] );
107+
if ( tdim != destSize[ d ] )
108+
{
109+
destSize[ d ] = tdim;
110+
sourceSize[ d ] = tdim * downsamplingFactors[ d ];
111+
destSizeChanged = true;
112+
}
113+
}
114+
115+
if ( destSizeChanged )
116+
recomputeTempArraySizes();
117+
}
118+
}

src/main/java/net/imglib2/algorithm/blocks/downsample/Downsample.java

Lines changed: 183 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import net.imglib2.algorithm.blocks.BlockSupplier;
4040
import net.imglib2.algorithm.blocks.ClampType;
4141
import net.imglib2.algorithm.blocks.ComputationType;
42+
import net.imglib2.algorithm.blocks.downsample.DownsampleBlockProcessors.AvgBlockDouble;
43+
import net.imglib2.algorithm.blocks.downsample.DownsampleBlockProcessors.AvgBlockFloat;
4244
import net.imglib2.algorithm.blocks.downsample.DownsampleBlockProcessors.CenterDouble;
4345
import net.imglib2.algorithm.blocks.downsample.DownsampleBlockProcessors.CenterFloat;
4446
import net.imglib2.algorithm.blocks.downsample.DownsampleBlockProcessors.HalfPixelDouble;
@@ -103,7 +105,11 @@
103105
* pixels are centered on input pixels.
104106
* </li>
105107
* </ul>
106-
*
108+
* <p>
109+
* The {@link #downsample(ComputationType, int[])} methods support downsampling
110+
* with arbitrary integer factor by averaging blocks of pixels. (With {@code
111+
* factors={2,2,2,...}}, this is equivalent to downsampling with {@link
112+
* Offset#HALF_PIXEL}).
107113
*/
108114
public class Downsample
109115
{
@@ -152,6 +158,34 @@ public static long[] getDownsampledDimensions( final long[] imgDimensions, final
152158
return destSize;
153159
}
154160

161+
/**
162+
* Returns recommended size of the downsampled image, given an input image
163+
* of size {@code imgDimensions}. In each dimension, the recommended size is
164+
* input {@code imgDimension} / {@code downsamplingFactor}, rounding up
165+
* (e.g., a 5 pixel input image downsampled with factor 2, should yield a 3
166+
* pixel image).
167+
*
168+
* @param imgDimensions
169+
* dimensions of the input image
170+
* @param downsamplingFactors
171+
* in each dimension {@code d}, {@code downsamplingFactors[d]}
172+
* pixels in the input image should be averaged to one output pixel.
173+
* {@code downsamplingFactors} is expanded or truncated to the
174+
* necessary size. For example, if {@code downsamplingFactors=={2,2,1}} and
175+
* the operator is applied to a 2D image, {@code downsamplingFactors} is
176+
* truncated to {@code {2, 2}}. If the operator is applied to a 5D image,
177+
* {@code downsamplingFactors} is expanded to {@code {2, 2, 1, 1, 1}}
178+
*
179+
* @return the recommended size of the downsampled image
180+
*/
181+
public static long[] getDownsampledDimensions( final long[] imgDimensions, final int[] downsamplingFactors )
182+
{
183+
final int[] dFactorsX = Util.expandArray( downsamplingFactors, imgDimensions.length );
184+
final long[] destSize = new long[ imgDimensions.length ];
185+
Arrays.setAll( destSize, d -> ( imgDimensions[ d ] + dFactorsX[ d ] - 1 ) / dFactorsX[ d ] );
186+
return destSize;
187+
}
188+
155189
/**
156190
* Specify where downsampled pixels should be placed.
157191
*/
@@ -335,6 +369,82 @@ Function< BlockSupplier< T >, UnaryBlockOperator< T, T > > downsample( final Com
335369
};
336370
}
337371

372+
/**
373+
* Downsample (by the given {@code downsamplingFactors}) blocks of the
374+
* standard ImgLib2 {@code RealType}s.
375+
* <p>
376+
* Supported types are {@code UnsignedByteType}, {@code UnsignedShortType},
377+
* {@code UnsignedIntType}, {@code ByteType}, {@code ShortType}, {@code
378+
* IntType}, {@code LongType}, {@code FloatType}, {@code DoubleType}).
379+
* <p>
380+
* Precision for intermediate values is chosen as to represent the
381+
* input/output type without loss of precision. That is, {@code FLOAT} for
382+
* u8, i8, u16, i16, i32, f32, and otherwise {@code DOUBLE} for u32, i64,
383+
* f64.
384+
* <p>
385+
* The returned factory function creates an operator matching the
386+
* type and dimensionality of a given input {@code BlockSupplier<T>}.
387+
*
388+
* @param downsamplingFactors
389+
* in each dimension {@code d}, {@code downsamplingFactors[d]}
390+
* pixels in the input image should be averaged to one output pixel.
391+
* {@code downsamplingFactors} is expanded or truncated to the
392+
* necessary size. For example, if {@code downsamplingFactors=={2,2,1}} and
393+
* the operator is applied to a 2D image, {@code downsamplingFactors} is
394+
* truncated to {@code {2, 2}}. If the operator is applied to a 5D image,
395+
* {@code downsamplingFactors} is expanded to {@code {2, 2, 1, 1, 1}}
396+
* @param <T>
397+
* the input/output type
398+
*
399+
* @return factory for {@code UnaryBlockOperator} to downsample blocks of type {@code T}
400+
*/
401+
public static < T extends NativeType< T > >
402+
Function< BlockSupplier< T >, UnaryBlockOperator< T, T > > downsample( final int[] downsamplingFactors )
403+
{
404+
return downsample( ComputationType.AUTO, downsamplingFactors );
405+
}
406+
407+
/**
408+
* Downsample (by the given {@code downsamplingFactors}) blocks of the
409+
* standard ImgLib2 {@code RealType}s.
410+
* <p>
411+
* Supported types are {@code UnsignedByteType}, {@code UnsignedShortType},
412+
* {@code UnsignedIntType}, {@code ByteType}, {@code ShortType}, {@code
413+
* IntType}, {@code LongType}, {@code FloatType}, {@code DoubleType}).
414+
* <p>
415+
* The returned factory function creates an operator matching the
416+
* type and dimensionality of a given input {@code BlockSupplier<T>}.
417+
*
418+
* @param computationType
419+
* specifies in which precision intermediate values should be
420+
* computed. For {@code AUTO}, the type that can represent the
421+
* input/output type without loss of precision is picked. That is,
422+
* {@code FLOAT} for u8, i8, u16, i16, i32, f32, and otherwise {@code
423+
* DOUBLE} for u32, i64, f64.
424+
* @param downsamplingFactors
425+
* in each dimension {@code d}, {@code downsamplingFactors[d]}
426+
* pixels in the input image should be averaged to one output pixel.
427+
* {@code downsamplingFactors} is expanded or truncated to the
428+
* necessary size. For example, if {@code downsamplingFactors=={2,2,1}} and
429+
* the operator is applied to a 2D image, {@code downsamplingFactors} is
430+
* truncated to {@code {2, 2}}. If the operator is applied to a 5D image,
431+
* {@code downsamplingFactors} is expanded to {@code {2, 2, 1, 1, 1}}
432+
* @param <T>
433+
* the input/output type
434+
*
435+
* @return factory for {@code UnaryBlockOperator} to downsample blocks of type {@code T}
436+
*/
437+
public static < T extends NativeType< T > >
438+
Function< BlockSupplier< T >, UnaryBlockOperator< T, T > > downsample( final ComputationType computationType, final int[] downsamplingFactors )
439+
{
440+
return s -> {
441+
final T type = s.getType();
442+
final int n = s.numDimensions();
443+
final int[] expandedDownsamplingFactors = Util.expandArray( downsamplingFactors, n );
444+
return createOperator( type, computationType, expandedDownsamplingFactors );
445+
};
446+
}
447+
338448
/**
339449
* Create a {@code UnaryBlockOperator} to downsample (by factor 2) blocks of
340450
* the standard ImgLib2 {@code RealType}. The {@code downsampleInDim}
@@ -373,25 +483,45 @@ Function< BlockSupplier< T >, UnaryBlockOperator< T, T > > downsample( final Com
373483
public static < T extends NativeType< T > >
374484
UnaryBlockOperator< T, T > createOperator( final T type, final ComputationType computationType, final Offset offset, final boolean[] downsampleInDim )
375485
{
376-
final boolean processAsFloat;
486+
final UnaryBlockOperator< ?, ? > op = processAsFloat( computationType, type )
487+
? downsampleFloat( offset, downsampleInDim )
488+
: downsampleDouble( offset, downsampleInDim );
489+
return op.adaptSourceType( type, ClampType.NONE ).adaptTargetType( type, ClampType.NONE );
490+
}
491+
492+
private static < T extends NativeType< T > > boolean processAsFloat( final ComputationType computationType, final T type )
493+
{
377494
switch ( computationType )
378495
{
379496
case FLOAT:
380-
processAsFloat = true;
381-
break;
497+
return true;
382498
case DOUBLE:
383-
processAsFloat = false;
384-
break;
385-
default:
499+
return true;
386500
case AUTO:
501+
default:
387502
final PrimitiveType pt = type.getNativeTypeFactory().getPrimitiveType();
388-
processAsFloat = pt.equals( FLOAT ) || pt.getByteCount() < FLOAT.getByteCount();
389-
break;
503+
return pt.equals( FLOAT ) || pt.getByteCount() < FLOAT.getByteCount();
390504
}
391-
final UnaryBlockOperator< ?, ? > op = processAsFloat
392-
? downsampleFloat( offset, downsampleInDim )
393-
: downsampleDouble( offset, downsampleInDim );
394-
return op.adaptSourceType( type, ClampType.NONE ).adaptTargetType( type, ClampType.NONE );
505+
}
506+
507+
private static UnaryBlockOperator< FloatType, FloatType > downsampleFloat( final Offset offset, final boolean[] downsampleInDim )
508+
{
509+
final FloatType type = new FloatType();
510+
final int n = downsampleInDim.length;
511+
return new DefaultUnaryBlockOperator<>( type, type, n, n,
512+
offset == Offset.HALF_PIXEL
513+
? new HalfPixelFloat( downsampleInDim )
514+
: new CenterFloat( downsampleInDim ) );
515+
}
516+
517+
private static UnaryBlockOperator< DoubleType, DoubleType > downsampleDouble( final Offset offset, final boolean[] downsampleInDim )
518+
{
519+
final DoubleType type = new DoubleType();
520+
final int n = downsampleInDim.length;
521+
return new DefaultUnaryBlockOperator<>( type, type, n, n,
522+
offset == Offset.HALF_PIXEL
523+
? new HalfPixelDouble( downsampleInDim )
524+
: new CenterDouble( downsampleInDim ) );
395525
}
396526

397527
/**
@@ -435,23 +565,51 @@ UnaryBlockOperator< T, T > createOperator( final T type, final ComputationType c
435565
return createOperator( type, computationType, offset, downsampleInDim );
436566
}
437567

438-
private static UnaryBlockOperator< FloatType, FloatType > downsampleFloat( final Offset offset, final boolean[] downsampleInDim )
568+
/**
569+
* Create a {@code UnaryBlockOperator} to downsample (by the given {@code
570+
* downsamplingFactors}) blocks of the standard ImgLib2 {@code RealType}.
571+
* <p>
572+
* Supported types are {@code UnsignedByteType}, {@code UnsignedShortType},
573+
* {@code UnsignedIntType}, {@code ByteType}, {@code ShortType}, {@code
574+
* IntType}, {@code LongType}, {@code FloatType}, {@code DoubleType}).
575+
*
576+
* @param type
577+
* instance of the input type
578+
* @param computationType
579+
* specifies in which precision intermediate values should be
580+
* computed. For {@code AUTO}, the type that can represent the
581+
* input/output type without loss of precision is picked. That is,
582+
* {@code FLOAT} for u8, i8, u16, i16, i32, f32, and otherwise {@code
583+
* DOUBLE} for u32, i64, f64.
584+
* @param downsamplingFactors
585+
* in each dimension {@code d}, {@code downsamplingFactors[d]}
586+
* pixels in the input image should be averaged to one output pixel.
587+
* @param <T>
588+
* the input/output type
589+
*
590+
* @return {@code UnaryBlockOperator} to downsample blocks of type {@code T}
591+
*/
592+
public static < T extends NativeType< T > >
593+
UnaryBlockOperator< T, T > createOperator( final T type, final ComputationType computationType, final int[] downsamplingFactors )
594+
{
595+
final UnaryBlockOperator< ?, ? > op = processAsFloat( computationType, type )
596+
? downsampleFloat( downsamplingFactors )
597+
: downsampleDouble( downsamplingFactors );
598+
return op.adaptSourceType( type, ClampType.NONE ).adaptTargetType( type, ClampType.NONE );
599+
}
600+
601+
private static UnaryBlockOperator< FloatType, FloatType > downsampleFloat( final int[] downsamplingFactors )
439602
{
440603
final FloatType type = new FloatType();
441-
final int n = downsampleInDim.length;
442-
return new DefaultUnaryBlockOperator<>( type, type, n, n,
443-
offset == Offset.HALF_PIXEL
444-
? new HalfPixelFloat( downsampleInDim )
445-
: new CenterFloat( downsampleInDim ) );
604+
final int n = downsamplingFactors.length;
605+
return new DefaultUnaryBlockOperator<>( type, type, n, n, new AvgBlockFloat( downsamplingFactors ) );
446606
}
447607

448-
private static UnaryBlockOperator< DoubleType, DoubleType > downsampleDouble( final Offset offset, final boolean[] downsampleInDim )
608+
private static UnaryBlockOperator< DoubleType, DoubleType > downsampleDouble( final int[] downsamplingFactors )
449609
{
450610
final DoubleType type = new DoubleType();
451-
final int n = downsampleInDim.length;
452-
return new DefaultUnaryBlockOperator<>( type, type, n, n,
453-
offset == Offset.HALF_PIXEL
454-
? new HalfPixelDouble( downsampleInDim )
455-
: new CenterDouble( downsampleInDim ) );
611+
final int n = downsamplingFactors.length;
612+
return new DefaultUnaryBlockOperator<>( type, type, n, n, new AvgBlockDouble( downsamplingFactors ) );
456613
}
614+
457615
}

0 commit comments

Comments
 (0)