From ff4f669db8f3eb48610e439d876fc0b849d0607f Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Thu, 8 Jan 2026 14:20:04 -0500 Subject: [PATCH 01/31] Add some testing code for flat field correction --- data/dataset.xml | 506 ++++++++++++++++++ .../flatfield/TestDecoratorChain.java | 222 ++++++++ .../flatfield/TestFlatfieldCorrection.java | 153 ++++++ 3 files changed, 881 insertions(+) create mode 100644 data/dataset.xml create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java diff --git a/data/dataset.xml b/data/dataset.xml new file mode 100644 index 000000000..f86feddc5 --- /dev/null +++ b/data/dataset.xml @@ -0,0 +1,506 @@ + + + . + + + dataset.n5/ + + + + 0 + 0 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 187 + 0 + + + + 1 + 1 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 188 + 0 + + + + 2 + 2 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 189 + 0 + + + + 3 + 3 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 200 + 0 + + + + 4 + 4 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 201 + 0 + + + + 5 + 5 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 202 + 0 + + + + 6 + 6 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 213 + 0 + + + + 7 + 7 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 214 + 0 + + + + 8 + 8 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 215 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 187 + 187 + + + 188 + 188 + + + 189 + 189 + + + 200 + 200 + + + 201 + 201 + + + 202 + 202 + + + 213 + 213 + + + 214 + 214 + + + 215 + 215 + + + + + 0 + 0 + + + + + 0 + + + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Stitching Transform + 1.0 0.0 0.0 0.4554158176323426 0.0 1.0 0.0 -0.13328548693164066 0.0 0.0 1.0 1.039028825990859 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9996881730582423 1.0178764373025146E-4 9.91613051380682E-5 0.296854107149204 -0.0014206520238486176 1.000352820575225 5.493263412035365E-5 0.4978096475705985 -4.038421786319725E-4 1.2989941922307108E-4 1.0000050536353644 0.012322662821996552 + + + Stitching Transform + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9999774841758134 -2.0323268523581142E-4 5.914840097558037E-5 0.41302203806414345 -6.595441757221898E-4 1.0005468325585005 1.0422412713430604E-4 0.86847428183128 -3.8743282755243446E-4 6.197381562939316E-4 0.9999038186675331 -0.1051593717912322 + + + Stitching Transform + 1.0 0.0 0.0 -0.2804882451294475 0.0 1.0 0.0 0.15177465825263425 0.0 0.0 1.0 -0.8673431810167168 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9996509004376033 0.0011789057647756168 6.625470971101606E-5 -0.4380062409126003 -5.886838131605057E-6 0.9993980714516947 8.378533632140301E-4 0.21066285389381184 1.1914663433102846E-4 -0.0017577181435329034 0.9993905308006584 -0.10732512771106033 + + + Stitching Transform + 1.0 0.0 0.0 -0.8442948117588571 0.0 1.0 0.0 7.4417831077170336 0.0 0.0 1.0 -8.789096260073075 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9993691627809789 6.213286683890014E-4 9.303551548369867E-5 -0.5698358880199796 -0.0012153705700172891 0.9994415380582407 8.680618674785683E-4 0.916909696194346 -1.5083252038943456E-4 -0.0011408852434367028 0.9994660587646744 0.2820529225572497 + + + Stitching Transform + 1.0 0.0 0.0 -0.9089191675130128 0.0 1.0 0.0 7.4256749717825095 0.0 0.0 1.0 -10.328335265676746 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9995958883424029 1.1960194298687558E-4 1.3045003320240472E-4 -0.5288425017288729 -4.5021629309568165E-4 0.9995801176422028 9.375713906809582E-4 1.7071573589623064 -2.5232116962087617E-4 -5.217759052761294E-4 0.9995631970154002 0.266678832277585 + + + Stitching Transform + 1.0 0.0 0.0 -1.1369886493304193 0.0 1.0 0.0 7.212246076053502 0.0 0.0 1.0 -11.06907324837572 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.001169864797628 0.00527491302811622 1.4584877195363756E-4 -0.15773640915627343 -0.004686469222584484 0.9996923176367982 -0.002087984887367355 1.2420606082221906 -6.295077531490363E-4 0.005598468303174577 1.0011602899054004 1.9104004620529875 + + + Stitching Transform + 1.0 0.0 0.0 -0.2626855827553527 0.0 1.0 0.0 -6.7600081391679225 0.0 0.0 1.0 12.896136661870298 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0009007956899272 0.0053216640997549155 1.371415077664928E-4 0.31426019006657296 -0.005800996293005373 0.9993339550765709 -0.0019903541975732236 0.5557333216408067 -4.823233633192067E-4 0.003500523337610362 1.0009341748352047 1.2744275908022205 + + + Stitching Transform + 1.0 0.0 0.0 -0.7700626936842326 0.0 1.0 0.0 -7.432744962459225 0.0 0.0 1.0 11.579421273826847 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0012387020759832 0.005337931651988534 5.916593012930141E-5 0.6833023552548335 -0.0048548568558627265 0.9989044018136208 -0.0019280609543394106 -0.26569722464665557 -3.915489271687177E-4 0.0016823628452441262 1.0009459560649534 0.679636329046033 + + + Stitching Transform + 1.0 0.0 0.0 -1.2414564955437868 0.0 1.0 0.0 -8.130736624699523 0.0 0.0 1.0 10.981609779561097 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + tpId_0_viewSetupId_0/beads + tpId_0_viewSetupId_1/beads + tpId_0_viewSetupId_2/beads + tpId_0_viewSetupId_3/beads + tpId_0_viewSetupId_4/beads + tpId_0_viewSetupId_5/beads + tpId_0_viewSetupId_6/beads + tpId_0_viewSetupId_7/beads + tpId_0_viewSetupId_8/beads + + + + + + 1.0 0.0 0.0 0.32830997041742194 0.0 1.0 0.0 -0.2780143304040763 0.0 0.0 1.0 0.8627255545425214 + 0.9872797935430719 + -19840.08 + -256.0 102.0 -1236.0 -200.0 422.0 1236.0 + + + 1.0 0.0 0.0 0.31408745860667864 0.0 1.0 0.0 -0.21794513304740804 0.0 0.0 1.0 1.0669536191755924 + 0.9890595837012269 + -13460.56 + 199.0 102.0 -1236.0 255.0 422.0 1236.0 + + + 1.0 0.0 0.0 0.3219589445005795 0.0 1.0 0.0 -0.2912676156699092 0.0 0.0 1.0 0.7868782074069713 + 0.989534384651222 + -23513.68 + -256.0 -160.0 -1236.0 -200.0 159.0 1236.0 + + + 1.0 0.0 0.0 0.32849982720156845 0.0 1.0 0.0 -0.299139087662013 0.0 0.0 1.0 1.1721464806346376 + 0.9875872479386237 + -17134.16 + 199.0 -160.0 -1236.0 255.0 159.0 1236.0 + + + 1.0 0.0 0.0 0.2950324994815787 0.0 1.0 0.0 1.3873944732041537 0.0 0.0 1.0 0.8156317724537985 + 0.9350710728845969 + -27187.28 + -256.0 -423.0 -1236.0 -200.0 -103.0 1236.0 + + + 1.0 0.0 0.0 0.28614119325933984 0.0 1.0 0.0 1.3775777622511782 0.0 0.0 1.0 1.3157733733060013 + 0.9339835065140643 + -20807.760000000002 + 199.0 -423.0 -1236.0 255.0 -103.0 1236.0 + + + 1.0 0.0 0.0 -0.022274627811640357 0.0 1.0 0.0 7.064784613790181 0.0 0.0 1.0 -9.66095411700826 + 0.9267757024498638 + -23251.28 + -256.0 102.0 -1236.0 -200.0 159.0 1236.0 + + + 1.0 0.0 0.0 -0.09643953149634399 0.0 1.0 0.0 7.007672103883749 0.0 0.0 1.0 -9.036354555841172 + 0.9321167659568985 + -16871.76 + 199.0 102.0 -1236.0 255.0 159.0 1236.0 + + + 1.0 0.0 0.0 0.31868072554544824 0.0 1.0 0.0 -14.060647711779382 0.0 0.0 1.0 22.925437346686294 + 0.9354709581463394 + -26924.88 + -256.0 -160.0 -1236.0 -200.0 -103.0 1236.0 + + + 1.0 0.0 0.0 0.42744418243105997 0.0 1.0 0.0 -14.049728759960828 0.0 0.0 1.0 23.294215724982678 + 0.9410862556623087 + -20545.36 + 199.0 -160.0 -1236.0 255.0 -103.0 1236.0 + + + 1.0 0.0 0.0 -1.5104784240610343 0.0 1.0 0.0 7.395826789692137 0.0 0.0 1.0 -9.997150408536527 + 0.9162468747756625 + -29175.120000000003 + -712.0 102.0 -1236.0 -200.0 159.0 1236.0 + + + 1.0 0.0 0.0 -1.9429844985963882 0.0 1.0 0.0 7.478610381875683 0.0 0.0 1.0 -9.992593031616707 + 0.9304678530064963 + -22795.6 + -256.0 102.0 -1236.0 255.0 159.0 1236.0 + + + 1.0 0.0 0.0 -2.0782351526913487 0.0 1.0 0.0 7.41086456502083 0.0 0.0 1.0 -9.69656279859555 + 0.9357936880330161 + -16416.08 + 199.0 102.0 -1236.0 711.0 159.0 1236.0 + + + 1.0 0.0 0.0 -0.3287299635215959 0.0 1.0 0.0 -14.747678576491978 0.0 0.0 1.0 21.81115327961561 + 0.9138108463084885 + -32848.72 + -712.0 -160.0 -1236.0 -200.0 -103.0 1236.0 + + + 1.0 0.0 0.0 0.34904618022437717 0.0 1.0 0.0 -15.354558165871083 0.0 0.0 1.0 21.82603817266454 + 0.8990291118916934 + -26469.2 + -256.0 -160.0 -1236.0 255.0 -103.0 1236.0 + + + 1.0 0.0 0.0 1.0216345489996854 0.0 1.0 0.0 -15.042586536834278 0.0 0.0 1.0 21.61643113924515 + 0.8887321660239942 + -20089.68 + 199.0 -160.0 -1236.0 711.0 -103.0 1236.0 + + + 1.0 0.0 0.0 -0.6616285141958542 0.0 1.0 0.0 7.55184164375774 0.0 0.0 1.0 -11.190099606327976 + 0.9526419402381631 + -28719.440000000002 + -256.0 102.0 -1236.0 -200.0 159.0 1236.0 + + + 1.0 0.0 0.0 -0.7271388208706355 0.0 1.0 0.0 7.638504842226183 0.0 0.0 1.0 -11.471001585951399 + 0.9439136410878279 + -22339.92 + 199.0 102.0 -1236.0 255.0 159.0 1236.0 + + + 1.0 0.0 0.0 -0.284636513100736 0.0 1.0 0.0 -15.462312909779143 0.0 0.0 1.0 21.767295071746958 + 0.9233340706532531 + -32393.04 + -256.0 -160.0 -1236.0 -200.0 -103.0 1236.0 + + + 1.0 0.0 0.0 -0.13258697351179194 0.0 1.0 0.0 -14.947295037284562 0.0 0.0 1.0 19.954530783329574 + 0.9243536847543689 + -26013.52 + 199.0 -160.0 -1236.0 255.0 -103.0 1236.0 + + + + diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java new file mode 100644 index 000000000..0debd74ee --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java @@ -0,0 +1,222 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.io.File; +import java.util.HashMap; + +import ij.ImageJ; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.MultiResolutionImgLoader; +import mpicbg.spim.data.sequence.SequenceDescription; +import mpicbg.spim.data.sequence.ViewId; +import mpicbg.spim.data.sequence.ViewSetup; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.numeric.real.FloatType; +import net.preibisch.mvrecon.fiji.spimdata.SpimData2; +import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.splitting.SplitMultiResolutionImgLoader; + +/** + * Test class demonstrating the full decorator chain: + * + * BaseLoader (N5/OME-Zarr) + * → MultiResolutionFlatfieldCorrectionWrappedImgLoader (applies correction) + * → SplitMultiResolutionImgLoader (splits into regions) + * + * This shows how to compose multiple wrapper/decorator layers while maintaining + * the MultiResolutionImgLoader interface throughout the chain. + */ +public class TestDecoratorChain +{ + // Mapping from ViewSetup ID to Tile ID (from dataset.xml) + private static final int[][] SETUP_TO_TILE = { + { 0, 187 }, + { 1, 188 }, + { 2, 189 }, + { 3, 200 }, + { 4, 201 }, + { 5, 202 }, + { 6, 213 }, + { 7, 214 }, + { 8, 215 } + }; + + public static void main( String[] args ) throws SpimDataException + { + // ========== CONFIGURATION ========== + final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; + final String xmlPath = basePath + "data/dataset.xml"; + final String correctionPath = basePath + "dark_and_flatfields/"; + + // Which setup to demonstrate (0-8) + final int setupToShow = 0; + final int timepoint = 0; + + // ========== STEP 1: Load dataset and get base loader ========== + System.out.println( "=== STEP 1: Loading dataset ===" ); + System.out.println( "XML path: " + xmlPath ); + + final SpimData2 data = new XmlIoSpimData2().load( xmlPath ); + final SequenceDescription seqDesc = data.getSequenceDescription(); + + // The base loader - this could be N5ImageLoader, AllenOMEZarrLoader, etc. + final MultiResolutionImgLoader baseLoader = + (MultiResolutionImgLoader) seqDesc.getImgLoader(); + + System.out.println( "Base loader type: " + baseLoader.getClass().getSimpleName() ); + + // ========== STEP 2: Wrap with flatfield correction ========== + System.out.println( "\n=== STEP 2: Wrapping with flatfield correction ===" ); + + final MultiResolutionFlatfieldCorrectionWrappedImgLoader correctedLoader = + new MultiResolutionFlatfieldCorrectionWrappedImgLoader( baseLoader, true ); + + // Configure correction images for each view setup + for ( int[] mapping : SETUP_TO_TILE ) + { + final int setupId = mapping[ 0 ]; + final int tileId = mapping[ 1 ]; + + // Find darkfield file + final File darkfield = new File( correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif" ); + + // Find flatfield file (try both naming conventions) + File flatfield = new File( correctionPath + "setup" + tileId + "-flatfield.tif" ); + if ( !flatfield.exists() ) + flatfield = new File( correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif" ); + + final ViewId viewId = new ViewId( timepoint, setupId ); + + if ( darkfield.exists() ) + { + correctedLoader.setDarkImage( viewId, darkfield ); + System.out.println( " Setup " + setupId + ": darkfield = " + darkfield.getName() ); + } + + if ( flatfield.exists() ) + { + correctedLoader.setBrightImage( viewId, flatfield ); + System.out.println( " Setup " + setupId + ": flatfield = " + flatfield.getName() ); + } + } + + // ========== STEP 3: Wrap with splitting ========== + System.out.println( "\n=== STEP 3: Wrapping with splitting ===" ); + + // Get original image dimensions for the setup we're demonstrating + final ViewSetup vs = seqDesc.getViewSetups().get( setupToShow ); + final long[] dims = new long[ 3 ]; + vs.getSize().dimensions( dims ); + System.out.println( "Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2] ); + + // Create a simple 2x1 split in X dimension + // Split the 512-wide image into two 256-wide regions + final long splitX = dims[ 0 ] / 2; + + // Define the mappings for split regions + // New setup IDs 100, 101 will map to original setup 0, with different X intervals + final HashMap< Integer, Integer > new2oldSetupId = new HashMap<>(); + final HashMap< Integer, Interval > newSetupId2Interval = new HashMap<>(); + + // Split region 0: left half [0, splitX) x [0, dimY) x [0, dimZ) + new2oldSetupId.put( 100, setupToShow ); + newSetupId2Interval.put( 100, new FinalInterval( + new long[] { 0, 0, 0 }, + new long[] { splitX - 1, dims[1] - 1, dims[2] - 1 } + )); + + // Split region 1: right half [splitX, dimX) x [0, dimY) x [0, dimZ) + new2oldSetupId.put( 101, setupToShow ); + newSetupId2Interval.put( 101, new FinalInterval( + new long[] { splitX, 0, 0 }, + new long[] { dims[0] - 1, dims[1] - 1, dims[2] - 1 } + )); + + System.out.println( "Created 2 split regions:" ); + System.out.println( " Setup 100: X=[0, " + (splitX-1) + "] (left half)" ); + System.out.println( " Setup 101: X=[" + splitX + ", " + (dims[0]-1) + "] (right half)" ); + + // Create the split loader wrapping the CORRECTED loader + // This is the key: correction is applied BEFORE splitting + final SplitMultiResolutionImgLoader splitLoader = new SplitMultiResolutionImgLoader( + correctedLoader, // <-- corrected loader, not base loader! + new2oldSetupId, + newSetupId2Interval, + seqDesc + ); + + // ========== STEP 4: Display comparison images ========== + System.out.println( "\n=== STEP 4: Displaying images ===" ); + new ImageJ(); + + final int tileId = SETUP_TO_TILE[ setupToShow ][ 1 ]; + + // 4a. Show UNCORRECTED original (full image, level 0) + System.out.println( "Loading uncorrected image..." ); + final RandomAccessibleInterval< FloatType > uncorrected = + baseLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + // 4b. Show CORRECTED (full image, level 0) + System.out.println( "Loading corrected image..." ); + final RandomAccessibleInterval< FloatType > corrected = + correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + // 4c. Show CORRECTED + SPLIT (left half, level 0) + System.out.println( "Loading corrected + split (left half)..." ); + final RandomAccessibleInterval< FloatType > splitLeft = + splitLoader.getSetupImgLoader( 100 ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( splitLeft, "3. Corrected+Split LEFT - Setup 100" ); + + // 4d. Show CORRECTED + SPLIT (right half, level 0) + System.out.println( "Loading corrected + split (right half)..." ); + final RandomAccessibleInterval< FloatType > splitRight = + splitLoader.getSetupImgLoader( 101 ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( splitRight, "4. Corrected+Split RIGHT - Setup 101" ); + + // ========== Summary ========== + System.out.println( "\n=== DECORATOR CHAIN SUMMARY ===" ); + System.out.println( "Layer 1 (innermost): " + baseLoader.getClass().getSimpleName() ); + System.out.println( "Layer 2 (middle): " + correctedLoader.getClass().getSimpleName() ); + System.out.println( "Layer 3 (outermost): " + splitLoader.getClass().getSimpleName() ); + System.out.println( "" ); + System.out.println( "Data flow:" ); + System.out.println( " Request for split region 100 or 101" ); + System.out.println( " → SplitMultiResolutionImgLoader maps to setup " + setupToShow + " with interval" ); + System.out.println( " → MultiResolutionFlatfieldCorrectionWrappedImgLoader applies correction" ); + System.out.println( " → Base loader fetches raw pixels from N5" ); + System.out.println( " → Corrected pixels flow back up through the chain" ); + System.out.println( " → Split interval is extracted and returned" ); + System.out.println( "" ); + System.out.println( "Compare the images to verify:" ); + System.out.println( " - Image 1 vs 2: See flatfield correction effect" ); + System.out.println( " - Image 2 vs 3+4: Verify split regions match the corrected full image" ); + System.out.println( "" ); + System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)" ); + } +} diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java new file mode 100644 index 000000000..9de1a0f7f --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java @@ -0,0 +1,153 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.io.File; + +import ij.ImageJ; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.ImgLoader; +import mpicbg.spim.data.sequence.ViewId; +import mpicbg.spim.data.sequence.ViewSetup; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.numeric.real.FloatType; +import net.preibisch.mvrecon.fiji.spimdata.SpimData2; +import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; + +/** + * Test class for on-the-fly flatfield/darkfield correction. + * + * Loads dataset.xml, wraps the ImgLoader with flatfield correction, + * and displays corrected vs uncorrected images for comparison. + */ +public class TestFlatfieldCorrection +{ + // Mapping from ViewSetup ID to Tile ID (from dataset.xml) + private static final int[][] SETUP_TO_TILE = { + { 0, 187 }, + { 1, 188 }, + { 2, 189 }, + { 3, 200 }, + { 4, 201 }, + { 5, 202 }, + { 6, 213 }, + { 7, 214 }, + { 8, 215 } + }; + + public static void main( String[] args ) throws SpimDataException + { + // Paths - adjust these as needed + final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; + final String xmlPath = basePath + "data/dataset.xml"; + final String correctionPath = basePath + "dark_and_flatfields/"; + + // Which setup to display (0-8) + final int setupToShow = 0; + final int timepoint = 0; + + // Load the dataset + System.out.println( "Loading dataset from: " + xmlPath ); + final SpimData2 data = new XmlIoSpimData2().load( xmlPath ); + + // Get the original ImgLoader + final ImgLoader originalImgLoader = data.getSequenceDescription().getImgLoader(); + + // Create the flatfield-corrected wrapper + final DefaultFlatfieldCorrectionWrappedImgLoader ffcImgLoader = + new DefaultFlatfieldCorrectionWrappedImgLoader( originalImgLoader, true ); + + // Configure correction images for each view setup + System.out.println( "\nConfiguring flatfield correction:" ); + for ( int[] mapping : SETUP_TO_TILE ) + { + final int setupId = mapping[ 0 ]; + final int tileId = mapping[ 1 ]; + + // Find darkfield file + final File darkfield = new File( correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif" ); + + // Find flatfield file (try both naming conventions) + File flatfield = new File( correctionPath + "setup" + tileId + "-flatfield.tif" ); + if ( !flatfield.exists() ) + flatfield = new File( correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif" ); + + // Set the correction images + final ViewId viewId = new ViewId( timepoint, setupId ); + + if ( darkfield.exists() ) + { + ffcImgLoader.setDarkImage( viewId, darkfield ); + System.out.println( " Setup " + setupId + " (tile " + tileId + "): darkfield = " + darkfield.getName() ); + } + else + { + System.out.println( " Setup " + setupId + " (tile " + tileId + "): WARNING - darkfield not found: " + darkfield.getAbsolutePath() ); + } + + if ( flatfield.exists() ) + { + ffcImgLoader.setBrightImage( viewId, flatfield ); + System.out.println( " Setup " + setupId + " (tile " + tileId + "): flatfield = " + flatfield.getName() ); + } + else + { + System.out.println( " Setup " + setupId + " (tile " + tileId + "): WARNING - flatfield not found" ); + } + } + + // Start ImageJ + new ImageJ(); + + // Get tile ID for display title + int tileId = SETUP_TO_TILE[ setupToShow ][ 1 ]; + final ViewSetup vs = data.getSequenceDescription().getViewSetups().get( setupToShow ); + System.out.println( "\nDisplaying setup " + setupToShow + " (tile " + tileId + ")" ); + System.out.println( " Dimensions: " + vs.getSize() ); + + // Load and display UNCORRECTED image + System.out.println( "Loading uncorrected image..." ); + ffcImgLoader.setActive( false ); + data.getSequenceDescription().setImgLoader( ffcImgLoader ); + + final RandomAccessibleInterval< FloatType > uncorrected = + data.getSequenceDescription().getImgLoader() + .getSetupImgLoader( setupToShow ) + .getFloatImage( timepoint, false ); + ImageJFunctions.show( uncorrected, "Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + // Load and display CORRECTED image + System.out.println( "Loading corrected image..." ); + ffcImgLoader.setActive( true ); + + final RandomAccessibleInterval< FloatType > corrected = + data.getSequenceDescription().getImgLoader() + .getSetupImgLoader( setupToShow ) + .getFloatImage( timepoint, false ); + ImageJFunctions.show( corrected, "Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + System.out.println( "\nDone! Compare the two images to verify correction." ); + System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast to compare intensity distributions." ); + } +} From 5af74dedfeaf8d7aa6b8f13ed45989250f9c6d2a Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Thu, 8 Jan 2026 14:21:02 -0500 Subject: [PATCH 02/31] Add viewer image loader and a test for flat field correction --- .../TestViewerFlatfieldCorrection.java | 257 +++++++++ ...erFlatfieldCorrectionWrappedImgLoader.java | 522 ++++++++++++++++++ 2 files changed, 779 insertions(+) create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java new file mode 100644 index 000000000..7f2feb38f --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java @@ -0,0 +1,257 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.io.File; +import java.util.HashMap; + +import bdv.ViewerImgLoader; +import bdv.ViewerSetupImgLoader; +import ij.ImageJ; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.SequenceDescription; +import mpicbg.spim.data.sequence.ViewId; +import mpicbg.spim.data.sequence.ViewSetup; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.numeric.real.FloatType; +import net.preibisch.mvrecon.fiji.spimdata.SpimData2; +import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.splitting.SplitViewerImgLoader; + +/** + * Test class for ViewerFlatfieldCorrectionWrappedImgLoader. + * + * Demonstrates the full ViewerImgLoader-compatible decorator chain: + * N5ImageLoader (ViewerImgLoader) + * -> ViewerFlatfieldCorrectionWrappedImgLoader (ViewerImgLoader) + * -> SplitViewerImgLoader (ViewerImgLoader) + * + * This maintains full BDV compatibility throughout the chain, including: + * - Cache control delegation + * - Volatile image support + * - Multi-resolution mipmap levels + */ +public class TestViewerFlatfieldCorrection +{ + // Mapping from ViewSetup ID to Tile ID (from dataset.xml) + private static final int[][] SETUP_TO_TILE = { + { 0, 187 }, + { 1, 188 }, + { 2, 189 }, + { 3, 200 }, + { 4, 201 }, + { 5, 202 }, + { 6, 213 }, + { 7, 214 }, + { 8, 215 } + }; + + public static void main( String[] args ) throws SpimDataException + { + // ========== CONFIGURATION ========== + final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; + final String xmlPath = basePath + "data/dataset.xml"; + final String correctionPath = basePath + "dark_and_flatfields/"; + + // Which setup to demonstrate (0-8) + final int setupToShow = 0; + final int timepoint = 0; + + // ========== STEP 1: Load dataset and get base ViewerImgLoader ========== + System.out.println( "=== STEP 1: Loading dataset ===" ); + System.out.println( "XML path: " + xmlPath ); + + final SpimData2 data = new XmlIoSpimData2().load( xmlPath ); + final SequenceDescription seqDesc = data.getSequenceDescription(); + + // Verify the base loader is a ViewerImgLoader + if ( !( seqDesc.getImgLoader() instanceof ViewerImgLoader ) ) + { + System.err.println( "ERROR: Base loader is not a ViewerImgLoader!" ); + System.err.println( "Loader type: " + seqDesc.getImgLoader().getClass().getName() ); + return; + } + + final ViewerImgLoader baseLoader = (ViewerImgLoader) seqDesc.getImgLoader(); + System.out.println( "Base loader type: " + baseLoader.getClass().getSimpleName() ); + System.out.println( "Base loader implements ViewerImgLoader: YES" ); + + // ========== STEP 2: Wrap with ViewerFlatfieldCorrectionWrappedImgLoader ========== + System.out.println( "\n=== STEP 2: Wrapping with ViewerFlatfieldCorrectionWrappedImgLoader ===" ); + + final ViewerFlatfieldCorrectionWrappedImgLoader correctedLoader = + new ViewerFlatfieldCorrectionWrappedImgLoader( baseLoader, true ); + + // Configure correction images for each view setup + for ( int[] mapping : SETUP_TO_TILE ) + { + final int setupId = mapping[ 0 ]; + final int tileId = mapping[ 1 ]; + + // Find darkfield file + final File darkfield = new File( correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif" ); + + // Find flatfield file (try both naming conventions) + File flatfield = new File( correctionPath + "setup" + tileId + "-flatfield.tif" ); + if ( !flatfield.exists() ) + flatfield = new File( correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif" ); + + final ViewId viewId = new ViewId( timepoint, setupId ); + + if ( darkfield.exists() ) + { + correctedLoader.setDarkImage( viewId, darkfield ); + System.out.println( " Setup " + setupId + ": darkfield = " + darkfield.getName() ); + } + + if ( flatfield.exists() ) + { + correctedLoader.setBrightImage( viewId, flatfield ); + System.out.println( " Setup " + setupId + ": flatfield = " + flatfield.getName() ); + } + } + + System.out.println( "Corrected loader implements ViewerImgLoader: " + + ( correctedLoader instanceof ViewerImgLoader ? "YES" : "NO" ) ); + + // ========== STEP 3: Wrap with SplitViewerImgLoader ========== + System.out.println( "\n=== STEP 3: Wrapping with SplitViewerImgLoader ===" ); + + // Get original image dimensions for the setup we're demonstrating + final ViewSetup vs = seqDesc.getViewSetups().get( setupToShow ); + final long[] dims = new long[ 3 ]; + vs.getSize().dimensions( dims ); + System.out.println( "Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2] ); + + // Create a simple 2x1 split in X dimension + final long splitX = dims[ 0 ] / 2; + + // Define the mappings for split regions + final HashMap< Integer, Integer > new2oldSetupId = new HashMap<>(); + final HashMap< Integer, Interval > newSetupId2Interval = new HashMap<>(); + + // Split region 0: left half + new2oldSetupId.put( 100, setupToShow ); + newSetupId2Interval.put( 100, new FinalInterval( + new long[] { 0, 0, 0 }, + new long[] { splitX - 1, dims[1] - 1, dims[2] - 1 } + )); + + // Split region 1: right half + new2oldSetupId.put( 101, setupToShow ); + newSetupId2Interval.put( 101, new FinalInterval( + new long[] { splitX, 0, 0 }, + new long[] { dims[0] - 1, dims[1] - 1, dims[2] - 1 } + )); + + System.out.println( "Created 2 split regions:" ); + System.out.println( " Setup 100: X=[0, " + (splitX-1) + "] (left half)" ); + System.out.println( " Setup 101: X=[" + splitX + ", " + (dims[0]-1) + "] (right half)" ); + + // Create the split loader wrapping the CORRECTED ViewerImgLoader + final SplitViewerImgLoader splitLoader = new SplitViewerImgLoader( + correctedLoader, // <-- ViewerImgLoader compatible! + new2oldSetupId, + newSetupId2Interval, + seqDesc + ); + + System.out.println( "Split loader implements ViewerImgLoader: " + + ( splitLoader instanceof ViewerImgLoader ? "YES" : "NO" ) ); + + // ========== STEP 4: Test ViewerImgLoader-specific features ========== + System.out.println( "\n=== STEP 4: Testing ViewerImgLoader features ===" ); + + // Test cache control delegation + System.out.println( "Cache control available: " + ( splitLoader.getCacheControl() != null ) ); + + // Test mipmap levels + final ViewerSetupImgLoader< ?, ? > setupImgLoader = splitLoader.getSetupImgLoader( 100 ); + System.out.println( "Number of mipmap levels: " + setupImgLoader.numMipmapLevels() ); + + final double[][] resolutions = setupImgLoader.getMipmapResolutions(); + System.out.println( "Mipmap resolutions:" ); + for ( int level = 0; level < resolutions.length; level++ ) + { + System.out.println( " Level " + level + ": " + + resolutions[level][0] + " x " + resolutions[level][1] + " x " + resolutions[level][2] ); + } + + // ========== STEP 5: Display comparison images ========== + System.out.println( "\n=== STEP 5: Displaying images ===" ); + new ImageJ(); + + final int tileId = SETUP_TO_TILE[ setupToShow ][ 1 ]; + + // 5a. Show UNCORRECTED original at level 0 + System.out.println( "Loading uncorrected image (level 0)..." ); + correctedLoader.setActive( false ); + final RandomAccessibleInterval< FloatType > uncorrected = + correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + // 5b. Show CORRECTED at level 0 + System.out.println( "Loading corrected image (level 0)..." ); + correctedLoader.setActive( true ); + final RandomAccessibleInterval< FloatType > corrected = + correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + // 5c. Show CORRECTED + SPLIT (left half) at level 0 + System.out.println( "Loading corrected + split (left half, level 0)..." ); + final RandomAccessibleInterval< FloatType > splitLeft = + splitLoader.getSetupImgLoader( 100 ).getFloatImage( timepoint, 0, false ); + ImageJFunctions.show( splitLeft, "3. Corrected+Split LEFT - Setup 100" ); + + // 5d. Show at different mipmap level if available + if ( setupImgLoader.numMipmapLevels() > 1 ) + { + System.out.println( "Loading corrected + split (left half, level 1)..." ); + final RandomAccessibleInterval< FloatType > splitLeftLevel1 = + splitLoader.getSetupImgLoader( 100 ).getFloatImage( timepoint, 1, false ); + ImageJFunctions.show( splitLeftLevel1, "4. Corrected+Split LEFT (Level 1) - Setup 100" ); + } + + // ========== Summary ========== + System.out.println( "\n=== VIEWERIMGLOADER CHAIN SUMMARY ===" ); + System.out.println( "Layer 1 (innermost): " + baseLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); + System.out.println( "Layer 2 (middle): " + correctedLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); + System.out.println( "Layer 3 (outermost): " + splitLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); + System.out.println( "" ); + System.out.println( "All layers maintain ViewerImgLoader compatibility:" ); + System.out.println( " - Cache control: delegated through chain" ); + System.out.println( " - Volatile images: supported at all levels" ); + System.out.println( " - Multi-resolution: " + setupImgLoader.numMipmapLevels() + " mipmap levels available" ); + System.out.println( "" ); + System.out.println( "Compare the images to verify:" ); + System.out.println( " - Image 1 vs 2: See flatfield correction effect" ); + System.out.println( " - Image 2 vs 3: Verify split region matches corrected full image" ); + if ( setupImgLoader.numMipmapLevels() > 1 ) + System.out.println( " - Image 3 vs 4: Compare different mipmap levels" ); + System.out.println( "" ); + System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)" ); + } +} diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java new file mode 100644 index 000000000..6175bb289 --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -0,0 +1,522 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import bdv.ViewerImgLoader; +import bdv.ViewerSetupImgLoader; +import bdv.cache.CacheControl; +import ij.IJ; +import ij.ImagePlus; +import mpicbg.spim.data.generic.sequence.ImgLoaderHint; +import mpicbg.spim.data.generic.sequence.ImgLoaderHints; +import mpicbg.spim.data.sequence.MultiResolutionImgLoader; +import mpicbg.spim.data.sequence.MultiResolutionSetupImgLoader; +import mpicbg.spim.data.sequence.ViewId; +import mpicbg.spim.data.sequence.VoxelDimensions; +import net.imglib2.Dimensions; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.Volatile; +import net.imglib2.converter.RealTypeConverters; +import net.imglib2.img.Img; +import net.imglib2.img.ImgFactory; +import net.imglib2.img.array.ArrayImgFactory; +import net.imglib2.img.cell.CellImgFactory; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Pair; +import net.imglib2.util.ValuePair; +import net.imglib2.view.Views; +import net.preibisch.mvrecon.process.fusion.FusionTools; + +/** + * Flatfield correction wrapper for ViewerImgLoader. + * + * This class wraps a ViewerImgLoader and applies flatfield (bright/dark image) correction + * on-the-fly. It implements both ViewerImgLoader and MultiResolutionImgLoader interfaces, + * making it compatible with BigDataViewer's caching and async loading infrastructure. + * + * The correction formula is: + * corrected = (source - dark) * mean(bright - dark) / (bright - dark) + * + * Usage in decorator chain: + * N5ImageLoader (ViewerImgLoader) + * -> ViewerFlatfieldCorrectionWrappedImgLoader (ViewerImgLoader) + * -> SplitViewerImgLoader (ViewerImgLoader) + */ +public class ViewerFlatfieldCorrectionWrappedImgLoader + implements ViewerImgLoader, MultiResolutionImgLoader +{ + private final ViewerImgLoader wrappedImgLoader; + private boolean active; + private boolean cacheResult; + + /** Maps ViewId to (brightFile, darkFile) pair */ + protected final Map< ViewId, Pair< File, File > > fileMap; + + /** Cached loaded correction images */ + protected final Map< File, RandomAccessibleInterval< FloatType > > raiMap; + + /** Downsampled bright/dark images for each mipmap level */ + private final Map< Pair< File, List< Integer > >, RandomAccessibleInterval< FloatType > > dsRaiMap; + + public ViewerFlatfieldCorrectionWrappedImgLoader( final ViewerImgLoader wrappedImgLoader ) + { + this( wrappedImgLoader, true ); + } + + public ViewerFlatfieldCorrectionWrappedImgLoader( final ViewerImgLoader wrappedImgLoader, final boolean cacheResult ) + { + this.wrappedImgLoader = wrappedImgLoader; + this.active = true; + this.cacheResult = cacheResult; + this.fileMap = new HashMap<>(); + this.raiMap = new HashMap<>(); + this.dsRaiMap = new HashMap<>(); + } + + // ========== ViewerImgLoader interface ========== + + @Override + public ViewerFlatfieldCorrectionWrappedSetupImgLoader< ?, ? > getSetupImgLoader( final int setupId ) + { + return new ViewerFlatfieldCorrectionWrappedSetupImgLoader<>( setupId ); + } + + @Override + public CacheControl getCacheControl() + { + return wrappedImgLoader.getCacheControl(); + } + + @Override + public void setNumFetcherThreads( final int n ) + { + wrappedImgLoader.setNumFetcherThreads( n ); + } + + // ========== Configuration methods ========== + + public ViewerImgLoader getWrappedImgLoader() + { + return wrappedImgLoader; + } + + public void setActive( final boolean active ) + { + this.active = active; + } + + public boolean isActive() + { + return active; + } + + public boolean isCached() + { + return cacheResult; + } + + public void setCached( final boolean cached ) + { + this.cacheResult = cached; + } + + public void setBrightImage( final ViewId vId, final File imgFile ) + { + if ( !fileMap.containsKey( vId ) ) + fileMap.put( vId, new ValuePair<>( null, null ) ); + + final Pair< File, File > oldPair = fileMap.get( vId ); + fileMap.put( vId, new ValuePair<>( imgFile, oldPair.getB() ) ); + } + + public void setDarkImage( final ViewId vId, final File imgFile ) + { + if ( !fileMap.containsKey( vId ) ) + fileMap.put( vId, new ValuePair<>( null, null ) ); + + final Pair< File, File > oldPair = fileMap.get( vId ); + fileMap.put( vId, new ValuePair<>( oldPair.getA(), imgFile ) ); + } + + // ========== Image loading helpers ========== + + protected RandomAccessibleInterval< FloatType > getBrightImg( final ViewId vId ) + { + if ( !fileMap.containsKey( vId ) ) + return null; + + final File fileToLoad = fileMap.get( vId ).getA(); + if ( fileToLoad == null ) + return null; + + loadFileIfNecessary( fileToLoad ); + return raiMap.get( fileToLoad ); + } + + protected RandomAccessibleInterval< FloatType > getDarkImg( final ViewId vId ) + { + if ( !fileMap.containsKey( vId ) ) + return null; + + final File fileToLoad = fileMap.get( vId ).getB(); + if ( fileToLoad == null ) + return null; + + loadFileIfNecessary( fileToLoad ); + return raiMap.get( fileToLoad ); + } + + protected void loadFileIfNecessary( final File file ) + { + if ( raiMap.containsKey( file ) ) + return; + + final ImagePlus imp = IJ.openImage( file.getAbsolutePath() ); + @SuppressWarnings("unchecked") + final RandomAccessibleInterval< FloatType > img = + (RandomAccessibleInterval< FloatType >) (RandomAccessibleInterval) ImageJFunctions.convertFloat( imp ).copy(); + + raiMap.put( file, img ); + } + + protected RandomAccessibleInterval< FloatType > getOrCreateBrightImgDownsampled( + final ViewId vId, + final int[] downsamplingFactors ) + { + if ( !fileMap.containsKey( vId ) || fileMap.get( vId ).getA() == null ) + return null; + + final ArrayList< Integer > dsFactorList = new ArrayList<>(); + for ( final int i : downsamplingFactors ) + dsFactorList.add( i ); + + final ValuePair< File, List< Integer > > key = new ValuePair<>( fileMap.get( vId ).getA(), dsFactorList ); + + if ( !dsRaiMap.containsKey( key ) ) + { + final RandomAccessibleInterval< FloatType > brightImg = getBrightImg( vId ); + + if ( brightImg == null ) + return null; + + // Add singleton z-dimension for downsampleHDF5 to work + final RandomAccessibleInterval< FloatType > downsampled = + MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5( + Views.addDimension( brightImg, 0, 0 ), downsamplingFactors ); + dsRaiMap.put( key, downsampled ); + } + + return dsRaiMap.get( key ); + } + + protected RandomAccessibleInterval< FloatType > getOrCreateDarkImgDownsampled( + final ViewId vId, + final int[] downsamplingFactors ) + { + if ( !fileMap.containsKey( vId ) || fileMap.get( vId ).getB() == null ) + return null; + + final ArrayList< Integer > dsFactorList = new ArrayList<>(); + for ( final int i : downsamplingFactors ) + dsFactorList.add( i ); + + final ValuePair< File, List< Integer > > key = new ValuePair<>( fileMap.get( vId ).getB(), dsFactorList ); + + if ( !dsRaiMap.containsKey( key ) ) + { + final RandomAccessibleInterval< FloatType > darkImg = getDarkImg( vId ); + + if ( darkImg == null ) + return null; + + // Add singleton z-dimension for downsampleHDF5 to work + final RandomAccessibleInterval< FloatType > downsampled = + MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5( + Views.addDimension( darkImg, 0, 0 ), downsamplingFactors ); + dsRaiMap.put( key, downsampled ); + } + + return dsRaiMap.get( key ); + } + + // ========== Inner class: ViewerSetupImgLoader implementation ========== + + public class ViewerFlatfieldCorrectionWrappedSetupImgLoader< T extends RealType< T > & NativeType< T >, V extends Volatile< T > & RealType< V > & NativeType< V > > + implements ViewerSetupImgLoader< T, V >, MultiResolutionSetupImgLoader< T > + { + private final int setupId; + + ViewerFlatfieldCorrectionWrappedSetupImgLoader( final int setupId ) + { + this.setupId = setupId; + } + + @SuppressWarnings("unchecked") + private ViewerSetupImgLoader< T, V > getUnderlyingViewerSetupImgLoader() + { + return (ViewerSetupImgLoader< T, V >) wrappedImgLoader.getSetupImgLoader( setupId ); + } + + @SuppressWarnings("unchecked") + private MultiResolutionSetupImgLoader< T > getUnderlyingMultiResSetupImgLoader() + { + // The wrapped ViewerImgLoader should also be a MultiResolutionImgLoader + return (MultiResolutionSetupImgLoader< T >) ((MultiResolutionImgLoader) wrappedImgLoader).getSetupImgLoader( setupId ); + } + + // ========== Regular image access ========== + + @Override + public RandomAccessibleInterval< T > getImage( final int timepointId, final ImgLoaderHint... hints ) + { + return getImage( timepointId, 0, hints ); + } + + @Override + public RandomAccessibleInterval< T > getImage( final int timepointId, final int level, final ImgLoaderHint... hints ) + { + final ViewerSetupImgLoader< T, V > viewerSetupIL = getUnderlyingViewerSetupImgLoader(); + final MultiResolutionSetupImgLoader< T > multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); + + if ( !active ) + return viewerSetupIL.getImage( timepointId, level, hints ); + + final int n = multiResSetupIL.getImageSize( timepointId ).numDimensions(); + + // Calculate downsampling factors for this mipmap level + final int[] dsFactors = new int[ n ]; + final double[] dsD = viewerSetupIL.getMipmapResolutions()[ level ]; + for ( int d = 0; d < n; d++ ) + dsFactors[ d ] = (int) dsD[ d ]; + // Don't downsample z for 2D correction images + dsFactors[ n - 1 ] = 1; + + @SuppressWarnings("unchecked") + RandomAccessibleInterval< T > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( + viewerSetupIL.getImage( timepointId, level, hints ), + getOrCreateBrightImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), + getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ) ); + + // Handle LOAD_COMPLETELY hint + boolean loadCompletelyRequested = false; + for ( final ImgLoaderHint hint : hints ) + if ( hint == ImgLoaderHints.LOAD_COMPLETELY ) + loadCompletelyRequested = true; + + if ( loadCompletelyRequested ) + { + long numPx = 1; + for ( int d = 0; d < rai.numDimensions(); d++ ) + numPx *= rai.dimension( d ); + + final ImgFactory< T > imgFactory; + if ( Math.log( numPx ) / Math.log( 2 ) < 31 ) + imgFactory = new ArrayImgFactory<>(); + else + imgFactory = new CellImgFactory<>(); + + final Img< T > loadedImg = imgFactory.create( rai, getImageType() ); + RealTypeConverters.copyFromTo( Views.extendZero( rai ), loadedImg ); + + rai = loadedImg; + } + else if ( cacheResult ) + { + final int[] cellSize = new int[ rai.numDimensions() ]; + Arrays.fill( cellSize, 1 ); + for ( int d = 0; d < rai.numDimensions() - 1; d++ ) + cellSize[ d ] = (int) rai.dimension( d ); + rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, + Views.iterable( rai ).firstElement().createVariable(), cellSize ); + } + + return rai; + } + + // ========== Volatile image access ========== + + @Override + public RandomAccessibleInterval< V > getVolatileImage( final int timepointId, final int level, final ImgLoaderHint... hints ) + { + final ViewerSetupImgLoader< T, V > viewerSetupIL = getUnderlyingViewerSetupImgLoader(); + final MultiResolutionSetupImgLoader< T > multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); + + if ( !active ) + return viewerSetupIL.getVolatileImage( timepointId, level, hints ); + + final int n = multiResSetupIL.getImageSize( timepointId ).numDimensions(); + + // Calculate downsampling factors for this mipmap level + final int[] dsFactors = new int[ n ]; + final double[] dsD = viewerSetupIL.getMipmapResolutions()[ level ]; + for ( int d = 0; d < n; d++ ) + dsFactors[ d ] = (int) dsD[ d ]; + dsFactors[ n - 1 ] = 1; + + // Apply correction to volatile image + // Note: The volatile validity flag propagation may not be perfect, + // but BDV will re-request invalid pixels automatically + @SuppressWarnings("unchecked") + final RandomAccessibleInterval< V > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( + viewerSetupIL.getVolatileImage( timepointId, level, hints ), + getOrCreateBrightImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), + getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), + getVolatileImageType() ); + + return rai; + } + + // ========== Float image access ========== + + @Override + public RandomAccessibleInterval< FloatType > getFloatImage( final int timepointId, final boolean normalize, final ImgLoaderHint... hints ) + { + return getFloatImage( timepointId, 0, normalize, hints ); + } + + @Override + public RandomAccessibleInterval< FloatType > getFloatImage( final int timepointId, final int level, final boolean normalize, final ImgLoaderHint... hints ) + { + final ViewerSetupImgLoader< T, V > viewerSetupIL = getUnderlyingViewerSetupImgLoader(); + final MultiResolutionSetupImgLoader< T > multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); + + if ( !active ) + return multiResSetupIL.getFloatImage( timepointId, level, normalize, hints ); + + final int n = multiResSetupIL.getImageSize( timepointId ).numDimensions(); + + final int[] dsFactors = new int[ n ]; + final double[] dsD = viewerSetupIL.getMipmapResolutions()[ level ]; + for ( int d = 0; d < n; d++ ) + dsFactors[ d ] = (int) dsD[ d ]; + dsFactors[ n - 1 ] = 1; + + RandomAccessibleInterval< FloatType > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( + viewerSetupIL.getImage( timepointId, level, hints ), + getOrCreateBrightImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), + getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), + new FloatType() ); + + if ( normalize ) + { + rai = new VirtuallyNormalizedRandomAccessibleInterval<>( rai ); + } + + // Handle caching/loading + boolean loadCompletelyRequested = false; + for ( final ImgLoaderHint hint : hints ) + if ( hint == ImgLoaderHints.LOAD_COMPLETELY ) + loadCompletelyRequested = true; + + if ( loadCompletelyRequested ) + { + long numPx = 1; + for ( int d = 0; d < rai.numDimensions(); d++ ) + numPx *= rai.dimension( d ); + + final ImgFactory< FloatType > imgFactory; + if ( Math.log( numPx ) / Math.log( 2 ) < 31 ) + imgFactory = new ArrayImgFactory<>(); + else + imgFactory = new CellImgFactory<>(); + + final Img< FloatType > loadedImg = imgFactory.create( rai, new FloatType() ); + RealTypeConverters.copyFromTo( Views.extendZero( rai ), loadedImg ); + + rai = loadedImg; + } + else if ( cacheResult ) + { + final int[] cellSize = new int[ rai.numDimensions() ]; + Arrays.fill( cellSize, 1 ); + for ( int d = 0; d < rai.numDimensions() - 1; d++ ) + cellSize[ d ] = (int) rai.dimension( d ); + rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, + new FloatType(), cellSize ); + } + + return rai; + } + + // ========== Metadata delegation ========== + + @Override + public T getImageType() + { + return getUnderlyingViewerSetupImgLoader().getImageType(); + } + + @Override + public V getVolatileImageType() + { + return getUnderlyingViewerSetupImgLoader().getVolatileImageType(); + } + + @Override + public double[][] getMipmapResolutions() + { + return getUnderlyingViewerSetupImgLoader().getMipmapResolutions(); + } + + @Override + public AffineTransform3D[] getMipmapTransforms() + { + return getUnderlyingViewerSetupImgLoader().getMipmapTransforms(); + } + + @Override + public int numMipmapLevels() + { + return getUnderlyingViewerSetupImgLoader().numMipmapLevels(); + } + + @Override + public Dimensions getImageSize( final int timepointId ) + { + return getUnderlyingMultiResSetupImgLoader().getImageSize( timepointId ); + } + + @Override + public Dimensions getImageSize( final int timepointId, final int level ) + { + return getUnderlyingMultiResSetupImgLoader().getImageSize( timepointId, level ); + } + + @Override + public VoxelDimensions getVoxelSize( final int timepointId ) + { + return getUnderlyingMultiResSetupImgLoader().getVoxelSize( timepointId ); + } + } +} From 73128efefe16b73e22c49dfeaef620806d4c0edc Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Thu, 8 Jan 2026 14:54:08 -0500 Subject: [PATCH 03/31] Use exising xml capabilities to orchestrate flat field correction --- data/dataset_corrected.xml | 417 ++++++++++++++++++ .../flatfield/TestFlatfieldCorrection.java | 142 +++--- 2 files changed, 471 insertions(+), 88 deletions(-) create mode 100644 data/dataset_corrected.xml diff --git a/data/dataset_corrected.xml b/data/dataset_corrected.xml new file mode 100644 index 000000000..f3cdd3221 --- /dev/null +++ b/data/dataset_corrected.xml @@ -0,0 +1,417 @@ + + + . + + + + + dataset.n5/ + + + + + dark_and_flatfields/setup187-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup187-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup188-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup188-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup189-flatfield.tif + dark_and_flatfields/setup189-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup200-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup200-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup201-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup201-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup202-flatfield.tif + dark_and_flatfields/setup202-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup213-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup213-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup214-flatfield.tif + dark_and_flatfields/setup214-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup215-flatfield.tif + dark_and_flatfields/setup215-AVG_darkfield-fromdata.tif + + + + + + 0 + 0 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 187 + 0 + + + + 1 + 1 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 188 + 0 + + + + 2 + 2 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 189 + 0 + + + + 3 + 3 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 200 + 0 + + + + 4 + 4 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 201 + 0 + + + + 5 + 5 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 202 + 0 + + + + 6 + 6 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 213 + 0 + + + + 7 + 7 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 214 + 0 + + + + 8 + 8 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 215 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 187 + 187 + + + 188 + 188 + + + 189 + 189 + + + 200 + 200 + + + 201 + 201 + + + 202 + 202 + + + 213 + 213 + + + 214 + 214 + + + 215 + 215 + + + + + 0 + 0 + + + + + 0 + + + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Stitching Transform + 1.0 0.0 0.0 0.4554158176323426 0.0 1.0 0.0 -0.13328548693164066 0.0 0.0 1.0 1.039028825990859 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9996881730582423 1.0178764373025146E-4 9.91613051380682E-5 0.296854107149204 -0.0014206520238486176 1.000352820575225 5.493263412035365E-5 0.4978096475705985 -4.038421786319725E-4 1.2989941922307108E-4 1.0000050536353644 0.012322662821996552 + + + Stitching Transform + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9999774841758134 -2.0323268523581142E-4 5.914840097558037E-5 0.41302203806414345 -6.595441757221898E-4 1.0005468325585005 1.0422412713430604E-4 0.86847428183128 -3.8743282755243446E-4 6.197381562939316E-4 0.9999038186675331 -0.1051593717912322 + + + Stitching Transform + 1.0 0.0 0.0 -0.2804882451294475 0.0 1.0 0.0 0.15177465825263425 0.0 0.0 1.0 -0.8673431810167168 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9996509004376033 0.0011789057647756168 6.625470971101606E-5 -0.4380062409126003 -5.886838131605057E-6 0.9993980714516947 8.378533632140301E-4 0.21066285389381184 1.1914663433102846E-4 -0.0017577181435329034 0.9993905308006584 -0.10732512771106033 + + + Stitching Transform + 1.0 0.0 0.0 -0.8442948117588571 0.0 1.0 0.0 7.4417831077170336 0.0 0.0 1.0 -8.789096260073075 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9993691627809789 6.213286683890014E-4 9.303551548369867E-5 -0.5698358880199796 -0.0012153705700172891 0.9994415380582407 8.680618674785683E-4 0.916909696194346 -1.5083252038943456E-4 -0.0011408852434367028 0.9994660587646744 0.2820529225572497 + + + Stitching Transform + 1.0 0.0 0.0 -0.9089191675130128 0.0 1.0 0.0 7.4256749717825095 0.0 0.0 1.0 -10.328335265676746 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9995958883424029 1.1960194298687558E-4 1.3045003320240472E-4 -0.5288425017288729 -4.5021629309568165E-4 0.9995801176422028 9.375713906809582E-4 1.7071573589623064 -2.5232116962087617E-4 -5.217759052761294E-4 0.9995631970154002 0.266678832277585 + + + Stitching Transform + 1.0 0.0 0.0 -1.1369886493304193 0.0 1.0 0.0 7.212246076053502 0.0 0.0 1.0 -11.06907324837572 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.001169864797628 0.00527491302811622 1.4584877195363756E-4 -0.15773640915627343 -0.004686469222584484 0.9996923176367982 -0.002087984887367355 1.2420606082221906 -6.295077531490363E-4 0.005598468303174577 1.0011602899054004 1.9104004620529875 + + + Stitching Transform + 1.0 0.0 0.0 -0.2626855827553527 0.0 1.0 0.0 -6.7600081391679225 0.0 0.0 1.0 12.896136661870298 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0009007956899272 0.0053216640997549155 1.371415077664928E-4 0.31426019006657296 -0.005800996293005373 0.9993339550765709 -0.0019903541975732236 0.5557333216408067 -4.823233633192067E-4 0.003500523337610362 1.0009341748352047 1.2744275908022205 + + + Stitching Transform + 1.0 0.0 0.0 -0.7700626936842326 0.0 1.0 0.0 -7.432744962459225 0.0 0.0 1.0 11.579421273826847 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0012387020759832 0.005337931651988534 5.916593012930141E-5 0.6833023552548335 -0.0048548568558627265 0.9989044018136208 -0.0019280609543394106 -0.26569722464665557 -3.915489271687177E-4 0.0016823628452441262 1.0009459560649534 0.679636329046033 + + + Stitching Transform + 1.0 0.0 0.0 -1.2414564955437868 0.0 1.0 0.0 -8.130736624699523 0.0 0.0 1.0 10.981609779561097 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + + + + + diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java index 9de1a0f7f..7932041a3 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java @@ -22,13 +22,9 @@ */ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; -import java.io.File; - import ij.ImageJ; import mpicbg.spim.data.SpimDataException; import mpicbg.spim.data.sequence.ImgLoader; -import mpicbg.spim.data.sequence.ViewId; -import mpicbg.spim.data.sequence.ViewSetup; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.FloatType; @@ -36,118 +32,88 @@ import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; /** - * Test class for on-the-fly flatfield/darkfield correction. + * Test class for XML-based on-the-fly flatfield/darkfield correction. + * + * Loads dataset_corrected.xml which has flatfield correction configured + * directly in the ImageLoader section. No manual configuration needed! * - * Loads dataset.xml, wraps the ImgLoader with flatfield correction, - * and displays corrected vs uncorrected images for comparison. + * The XML wraps the N5 loader with MultiResolutionFlatfieldCorrectionWrappedImgLoader + * and specifies bright/dark images for each view setup. */ public class TestFlatfieldCorrection { - // Mapping from ViewSetup ID to Tile ID (from dataset.xml) - private static final int[][] SETUP_TO_TILE = { - { 0, 187 }, - { 1, 188 }, - { 2, 189 }, - { 3, 200 }, - { 4, 201 }, - { 5, 202 }, - { 6, 213 }, - { 7, 214 }, - { 8, 215 } - }; public static void main( String[] args ) throws SpimDataException { - // Paths - adjust these as needed + // Paths final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; - final String xmlPath = basePath + "data/dataset.xml"; - final String correctionPath = basePath + "dark_and_flatfields/"; + final String correctedXmlPath = basePath + "data/dataset_corrected.xml"; + final String uncorrectedXmlPath = basePath + "data/dataset.xml"; // Which setup to display (0-8) final int setupToShow = 0; final int timepoint = 0; - // Load the dataset - System.out.println( "Loading dataset from: " + xmlPath ); - final SpimData2 data = new XmlIoSpimData2().load( xmlPath ); + // ========== Load CORRECTED dataset (from XML with flatfield config) ========== + System.out.println( "=== Loading CORRECTED dataset ===" ); + System.out.println( "XML path: " + correctedXmlPath ); - // Get the original ImgLoader - final ImgLoader originalImgLoader = data.getSequenceDescription().getImgLoader(); + final SpimData2 correctedData = new XmlIoSpimData2().load( correctedXmlPath ); + final ImgLoader correctedImgLoader = correctedData.getSequenceDescription().getImgLoader(); - // Create the flatfield-corrected wrapper - final DefaultFlatfieldCorrectionWrappedImgLoader ffcImgLoader = - new DefaultFlatfieldCorrectionWrappedImgLoader( originalImgLoader, true ); + System.out.println( "ImgLoader type: " + correctedImgLoader.getClass().getSimpleName() ); - // Configure correction images for each view setup - System.out.println( "\nConfiguring flatfield correction:" ); - for ( int[] mapping : SETUP_TO_TILE ) + // Verify it's a flatfield-corrected loader + if ( correctedImgLoader instanceof FlatfieldCorrectionWrappedImgLoader ) { - final int setupId = mapping[ 0 ]; - final int tileId = mapping[ 1 ]; - - // Find darkfield file - final File darkfield = new File( correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif" ); - - // Find flatfield file (try both naming conventions) - File flatfield = new File( correctionPath + "setup" + tileId + "-flatfield.tif" ); - if ( !flatfield.exists() ) - flatfield = new File( correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif" ); - - // Set the correction images - final ViewId viewId = new ViewId( timepoint, setupId ); - - if ( darkfield.exists() ) - { - ffcImgLoader.setDarkImage( viewId, darkfield ); - System.out.println( " Setup " + setupId + " (tile " + tileId + "): darkfield = " + darkfield.getName() ); - } - else - { - System.out.println( " Setup " + setupId + " (tile " + tileId + "): WARNING - darkfield not found: " + darkfield.getAbsolutePath() ); - } - - if ( flatfield.exists() ) - { - ffcImgLoader.setBrightImage( viewId, flatfield ); - System.out.println( " Setup " + setupId + " (tile " + tileId + "): flatfield = " + flatfield.getName() ); - } - else - { - System.out.println( " Setup " + setupId + " (tile " + tileId + "): WARNING - flatfield not found" ); - } + final FlatfieldCorrectionWrappedImgLoader< ? > ffcLoader = + (FlatfieldCorrectionWrappedImgLoader< ? >) correctedImgLoader; + System.out.println( " Correction active: " + ffcLoader.isActive() ); + System.out.println( " Caching enabled: " + ffcLoader.isCached() ); + System.out.println( " Wrapped loader: " + ffcLoader.getWrappedImgLoder().getClass().getSimpleName() ); } - // Start ImageJ + // ========== Load UNCORRECTED dataset (original XML) ========== + System.out.println( "\n=== Loading UNCORRECTED dataset ===" ); + System.out.println( "XML path: " + uncorrectedXmlPath ); + + final SpimData2 uncorrectedData = new XmlIoSpimData2().load( uncorrectedXmlPath ); + final ImgLoader uncorrectedImgLoader = uncorrectedData.getSequenceDescription().getImgLoader(); + + System.out.println( "ImgLoader type: " + uncorrectedImgLoader.getClass().getSimpleName() ); + + // ========== Display images for comparison ========== new ImageJ(); - // Get tile ID for display title - int tileId = SETUP_TO_TILE[ setupToShow ][ 1 ]; - final ViewSetup vs = data.getSequenceDescription().getViewSetups().get( setupToShow ); - System.out.println( "\nDisplaying setup " + setupToShow + " (tile " + tileId + ")" ); - System.out.println( " Dimensions: " + vs.getSize() ); + // Get tile ID from ViewSetup metadata (no hardcoded mapping needed!) + final int tileId = correctedData.getSequenceDescription() + .getViewSetups().get( setupToShow ).getTile().getId(); + + System.out.println( "\n=== Displaying setup " + setupToShow + " (tile " + tileId + ") ===" ); + System.out.println( " Dimensions: " + correctedData.getSequenceDescription() + .getViewSetups().get( setupToShow ).getSize() ); // Load and display UNCORRECTED image System.out.println( "Loading uncorrected image..." ); - ffcImgLoader.setActive( false ); - data.getSequenceDescription().setImgLoader( ffcImgLoader ); - final RandomAccessibleInterval< FloatType > uncorrected = - data.getSequenceDescription().getImgLoader() - .getSetupImgLoader( setupToShow ) - .getFloatImage( timepoint, false ); - ImageJFunctions.show( uncorrected, "Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + uncorrectedImgLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, false ); + ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); // Load and display CORRECTED image System.out.println( "Loading corrected image..." ); - ffcImgLoader.setActive( true ); - final RandomAccessibleInterval< FloatType > corrected = - data.getSequenceDescription().getImgLoader() - .getSetupImgLoader( setupToShow ) - .getFloatImage( timepoint, false ); - ImageJFunctions.show( corrected, "Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); - - System.out.println( "\nDone! Compare the two images to verify correction." ); - System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast to compare intensity distributions." ); + correctedImgLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, false ); + ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + + // ========== Summary ========== + System.out.println( "\n=== SUMMARY ===" ); + System.out.println( "Flatfield correction is now configured in the XML!" ); + System.out.println( "No manual setBrightImage()/setDarkImage() calls needed." ); + System.out.println( "" ); + System.out.println( "Compare the two images to verify correction:" ); + System.out.println( " - Image 1: Raw data from N5" ); + System.out.println( " - Image 2: Corrected with flatfield/darkfield" ); + System.out.println( "" ); + System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)" ); } } From 6e757e2398ce1800629deeb16b5f8cc3db31d6df Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Thu, 8 Jan 2026 15:36:35 -0500 Subject: [PATCH 04/31] Add xml capabilities for flat field correction viewer loader --- data/dataset_corrected_viewer.xml | 417 ++++++++++++++++++ .../TestViewerFlatfieldCorrection.java | 124 +++--- ...erFlatfieldCorrectionWrappedImgLoader.java | 175 ++++++++ 3 files changed, 644 insertions(+), 72 deletions(-) create mode 100644 data/dataset_corrected_viewer.xml create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java diff --git a/data/dataset_corrected_viewer.xml b/data/dataset_corrected_viewer.xml new file mode 100644 index 000000000..4bd50659d --- /dev/null +++ b/data/dataset_corrected_viewer.xml @@ -0,0 +1,417 @@ + + + . + + + + + dataset.n5/ + + + + + dark_and_flatfields/setup187-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup187-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup188-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup188-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup189-flatfield.tif + dark_and_flatfields/setup189-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup200-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup200-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup201-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup201-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup202-flatfield.tif + dark_and_flatfields/setup202-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup213-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup213-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup214-flatfield.tif + dark_and_flatfields/setup214-AVG_darkfield-fromdata.tif + + + dark_and_flatfields/setup215-flatfield.tif + dark_and_flatfields/setup215-AVG_darkfield-fromdata.tif + + + + + + 0 + 0 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 187 + 0 + + + + 1 + 1 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 188 + 0 + + + + 2 + 2 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 189 + 0 + + + + 3 + 3 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 200 + 0 + + + + 4 + 4 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 201 + 0 + + + + 5 + 5 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 202 + 0 + + + + 6 + 6 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 213 + 0 + + + + 7 + 7 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 214 + 0 + + + + 8 + 8 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 215 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 187 + 187 + + + 188 + 188 + + + 189 + 189 + + + 200 + 200 + + + 201 + 201 + + + 202 + 202 + + + 213 + 213 + + + 214 + 214 + + + 215 + 215 + + + + + 0 + 0 + + + + + 0 + + + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Stitching Transform + 1.0 0.0 0.0 0.4554158176323426 0.0 1.0 0.0 -0.13328548693164066 0.0 0.0 1.0 1.039028825990859 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9996881730582423 1.0178764373025146E-4 9.91613051380682E-5 0.296854107149204 -0.0014206520238486176 1.000352820575225 5.493263412035365E-5 0.4978096475705985 -4.038421786319725E-4 1.2989941922307108E-4 1.0000050536353644 0.012322662821996552 + + + Stitching Transform + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9999774841758134 -2.0323268523581142E-4 5.914840097558037E-5 0.41302203806414345 -6.595441757221898E-4 1.0005468325585005 1.0422412713430604E-4 0.86847428183128 -3.8743282755243446E-4 6.197381562939316E-4 0.9999038186675331 -0.1051593717912322 + + + Stitching Transform + 1.0 0.0 0.0 -0.2804882451294475 0.0 1.0 0.0 0.15177465825263425 0.0 0.0 1.0 -0.8673431810167168 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9996509004376033 0.0011789057647756168 6.625470971101606E-5 -0.4380062409126003 -5.886838131605057E-6 0.9993980714516947 8.378533632140301E-4 0.21066285389381184 1.1914663433102846E-4 -0.0017577181435329034 0.9993905308006584 -0.10732512771106033 + + + Stitching Transform + 1.0 0.0 0.0 -0.8442948117588571 0.0 1.0 0.0 7.4417831077170336 0.0 0.0 1.0 -8.789096260073075 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9993691627809789 6.213286683890014E-4 9.303551548369867E-5 -0.5698358880199796 -0.0012153705700172891 0.9994415380582407 8.680618674785683E-4 0.916909696194346 -1.5083252038943456E-4 -0.0011408852434367028 0.9994660587646744 0.2820529225572497 + + + Stitching Transform + 1.0 0.0 0.0 -0.9089191675130128 0.0 1.0 0.0 7.4256749717825095 0.0 0.0 1.0 -10.328335265676746 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 0.9995958883424029 1.1960194298687558E-4 1.3045003320240472E-4 -0.5288425017288729 -4.5021629309568165E-4 0.9995801176422028 9.375713906809582E-4 1.7071573589623064 -2.5232116962087617E-4 -5.217759052761294E-4 0.9995631970154002 0.266678832277585 + + + Stitching Transform + 1.0 0.0 0.0 -1.1369886493304193 0.0 1.0 0.0 7.212246076053502 0.0 0.0 1.0 -11.06907324837572 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 -160.0 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.001169864797628 0.00527491302811622 1.4584877195363756E-4 -0.15773640915627343 -0.004686469222584484 0.9996923176367982 -0.002087984887367355 1.2420606082221906 -6.295077531490363E-4 0.005598468303174577 1.0011602899054004 1.9104004620529875 + + + Stitching Transform + 1.0 0.0 0.0 -0.2626855827553527 0.0 1.0 0.0 -6.7600081391679225 0.0 0.0 1.0 12.896136661870298 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0009007956899272 0.0053216640997549155 1.371415077664928E-4 0.31426019006657296 -0.005800996293005373 0.9993339550765709 -0.0019903541975732236 0.5557333216408067 -4.823233633192067E-4 0.003500523337610362 1.0009341748352047 1.2744275908022205 + + + Stitching Transform + 1.0 0.0 0.0 -0.7700626936842326 0.0 1.0 0.0 -7.432744962459225 0.0 0.0 1.0 11.579421273826847 + + + Translation to Regular Grid + 1.0 0.0 0.0 -256.0 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0012387020759832 0.005337931651988534 5.916593012930141E-5 0.6833023552548335 -0.0048548568558627265 0.9989044018136208 -0.0019280609543394106 -0.26569722464665557 -3.915489271687177E-4 0.0016823628452441262 1.0009459560649534 0.679636329046033 + + + Stitching Transform + 1.0 0.0 0.0 -1.2414564955437868 0.0 1.0 0.0 -8.130736624699523 0.0 0.0 1.0 10.981609779561097 + + + Translation to Regular Grid + 1.0 0.0 0.0 -711.6800000000001 0.0 1.0 0.0 -422.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + + + + + diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java index 7f2feb38f..a531c1d9a 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java @@ -22,15 +22,14 @@ */ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; -import java.io.File; import java.util.HashMap; import bdv.ViewerImgLoader; import bdv.ViewerSetupImgLoader; import ij.ImageJ; import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.MultiResolutionSetupImgLoader; import mpicbg.spim.data.sequence.SequenceDescription; -import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.data.sequence.ViewSetup; import net.imglib2.FinalInterval; import net.imglib2.Interval; @@ -42,13 +41,16 @@ import net.preibisch.mvrecon.fiji.spimdata.imgloaders.splitting.SplitViewerImgLoader; /** - * Test class for ViewerFlatfieldCorrectionWrappedImgLoader. + * Test class for XML-based ViewerFlatfieldCorrectionWrappedImgLoader. * * Demonstrates the full ViewerImgLoader-compatible decorator chain: * N5ImageLoader (ViewerImgLoader) * -> ViewerFlatfieldCorrectionWrappedImgLoader (ViewerImgLoader) * -> SplitViewerImgLoader (ViewerImgLoader) * + * Loads dataset_corrected_viewer.xml which has flatfield correction configured + * directly in the ImageLoader section. No manual configuration needed! + * * This maintains full BDV compatibility throughout the chain, including: * - Cache control delegation * - Volatile image support @@ -56,92 +58,64 @@ */ public class TestViewerFlatfieldCorrection { - // Mapping from ViewSetup ID to Tile ID (from dataset.xml) - private static final int[][] SETUP_TO_TILE = { - { 0, 187 }, - { 1, 188 }, - { 2, 189 }, - { 3, 200 }, - { 4, 201 }, - { 5, 202 }, - { 6, 213 }, - { 7, 214 }, - { 8, 215 } - }; - public static void main( String[] args ) throws SpimDataException { - // ========== CONFIGURATION ========== + // Paths final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; - final String xmlPath = basePath + "data/dataset.xml"; - final String correctionPath = basePath + "dark_and_flatfields/"; + final String correctedXmlPath = basePath + "data/dataset_corrected_viewer.xml"; + final String uncorrectedXmlPath = basePath + "data/dataset.xml"; // Which setup to demonstrate (0-8) final int setupToShow = 0; final int timepoint = 0; - // ========== STEP 1: Load dataset and get base ViewerImgLoader ========== - System.out.println( "=== STEP 1: Loading dataset ===" ); - System.out.println( "XML path: " + xmlPath ); + // ========== STEP 1: Load CORRECTED dataset (from XML with flatfield config) ========== + System.out.println( "=== STEP 1: Loading CORRECTED dataset ==="); + System.out.println( "XML path: " + correctedXmlPath ); - final SpimData2 data = new XmlIoSpimData2().load( xmlPath ); - final SequenceDescription seqDesc = data.getSequenceDescription(); + final SpimData2 correctedData = new XmlIoSpimData2().load( correctedXmlPath ); + final SequenceDescription correctedSeqDesc = correctedData.getSequenceDescription(); - // Verify the base loader is a ViewerImgLoader - if ( !( seqDesc.getImgLoader() instanceof ViewerImgLoader ) ) + // Verify it's a ViewerFlatfieldCorrectionWrappedImgLoader + if ( !( correctedSeqDesc.getImgLoader() instanceof ViewerFlatfieldCorrectionWrappedImgLoader ) ) { - System.err.println( "ERROR: Base loader is not a ViewerImgLoader!" ); - System.err.println( "Loader type: " + seqDesc.getImgLoader().getClass().getName() ); + System.err.println( "ERROR: Expected ViewerFlatfieldCorrectionWrappedImgLoader!" ); + System.err.println( "Loader type: " + correctedSeqDesc.getImgLoader().getClass().getName() ); return; } - final ViewerImgLoader baseLoader = (ViewerImgLoader) seqDesc.getImgLoader(); - System.out.println( "Base loader type: " + baseLoader.getClass().getSimpleName() ); - System.out.println( "Base loader implements ViewerImgLoader: YES" ); + final ViewerFlatfieldCorrectionWrappedImgLoader correctedLoader = + (ViewerFlatfieldCorrectionWrappedImgLoader) correctedSeqDesc.getImgLoader(); - // ========== STEP 2: Wrap with ViewerFlatfieldCorrectionWrappedImgLoader ========== - System.out.println( "\n=== STEP 2: Wrapping with ViewerFlatfieldCorrectionWrappedImgLoader ===" ); + System.out.println( "Corrected loader type: " + correctedLoader.getClass().getSimpleName() ); + System.out.println( " Correction active: " + correctedLoader.isActive() ); + System.out.println( " Caching enabled: " + correctedLoader.isCached() ); + System.out.println( " Wrapped loader: " + correctedLoader.getWrappedImgLoader().getClass().getSimpleName() ); + System.out.println( " Implements ViewerImgLoader: " + ( correctedLoader instanceof ViewerImgLoader ? "YES" : "NO" ) ); - final ViewerFlatfieldCorrectionWrappedImgLoader correctedLoader = - new ViewerFlatfieldCorrectionWrappedImgLoader( baseLoader, true ); + // ========== STEP 2: Load UNCORRECTED dataset (original XML) ========== + System.out.println( "\n=== STEP 2: Loading UNCORRECTED dataset ===" ); + System.out.println( "XML path: " + uncorrectedXmlPath ); - // Configure correction images for each view setup - for ( int[] mapping : SETUP_TO_TILE ) + final SpimData2 uncorrectedData = new XmlIoSpimData2().load( uncorrectedXmlPath ); + final SequenceDescription uncorrectedSeqDesc = uncorrectedData.getSequenceDescription(); + + // Verify the base loader is a ViewerImgLoader + if ( !( uncorrectedSeqDesc.getImgLoader() instanceof ViewerImgLoader ) ) { - final int setupId = mapping[ 0 ]; - final int tileId = mapping[ 1 ]; - - // Find darkfield file - final File darkfield = new File( correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif" ); - - // Find flatfield file (try both naming conventions) - File flatfield = new File( correctionPath + "setup" + tileId + "-flatfield.tif" ); - if ( !flatfield.exists() ) - flatfield = new File( correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif" ); - - final ViewId viewId = new ViewId( timepoint, setupId ); - - if ( darkfield.exists() ) - { - correctedLoader.setDarkImage( viewId, darkfield ); - System.out.println( " Setup " + setupId + ": darkfield = " + darkfield.getName() ); - } - - if ( flatfield.exists() ) - { - correctedLoader.setBrightImage( viewId, flatfield ); - System.out.println( " Setup " + setupId + ": flatfield = " + flatfield.getName() ); - } + System.err.println( "ERROR: Base loader is not a ViewerImgLoader!" ); + System.err.println( "Loader type: " + uncorrectedSeqDesc.getImgLoader().getClass().getName() ); + return; } - System.out.println( "Corrected loader implements ViewerImgLoader: " + - ( correctedLoader instanceof ViewerImgLoader ? "YES" : "NO" ) ); + final ViewerImgLoader uncorrectedLoader = (ViewerImgLoader) uncorrectedSeqDesc.getImgLoader(); + System.out.println( "Uncorrected loader type: " + uncorrectedLoader.getClass().getSimpleName() ); - // ========== STEP 3: Wrap with SplitViewerImgLoader ========== - System.out.println( "\n=== STEP 3: Wrapping with SplitViewerImgLoader ===" ); + // ========== STEP 3: Create SplitViewerImgLoader wrapping the corrected loader ========== + System.out.println( "\n=== STEP 3: Creating SplitViewerImgLoader ===" ); // Get original image dimensions for the setup we're demonstrating - final ViewSetup vs = seqDesc.getViewSetups().get( setupToShow ); + final ViewSetup vs = correctedSeqDesc.getViewSetups().get( setupToShow ); final long[] dims = new long[ 3 ]; vs.getSize().dimensions( dims ); System.out.println( "Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2] ); @@ -176,7 +150,7 @@ public static void main( String[] args ) throws SpimDataException correctedLoader, // <-- ViewerImgLoader compatible! new2oldSetupId, newSetupId2Interval, - seqDesc + correctedSeqDesc ); System.out.println( "Split loader implements ViewerImgLoader: " + @@ -204,18 +178,21 @@ public static void main( String[] args ) throws SpimDataException System.out.println( "\n=== STEP 5: Displaying images ===" ); new ImageJ(); - final int tileId = SETUP_TO_TILE[ setupToShow ][ 1 ]; + // Get tile ID from ViewSetup metadata + final int tileId = correctedData.getSequenceDescription() + .getViewSetups().get( setupToShow ).getTile().getId(); // 5a. Show UNCORRECTED original at level 0 System.out.println( "Loading uncorrected image (level 0)..." ); - correctedLoader.setActive( false ); + @SuppressWarnings("unchecked") + final MultiResolutionSetupImgLoader< FloatType > uncorrectedSetupLoader = + (MultiResolutionSetupImgLoader< FloatType >) uncorrectedLoader.getSetupImgLoader( setupToShow ); final RandomAccessibleInterval< FloatType > uncorrected = - correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); + uncorrectedSetupLoader.getFloatImage( timepoint, 0, false ); ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); // 5b. Show CORRECTED at level 0 System.out.println( "Loading corrected image (level 0)..." ); - correctedLoader.setActive( true ); final RandomAccessibleInterval< FloatType > corrected = correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); @@ -237,10 +214,13 @@ public static void main( String[] args ) throws SpimDataException // ========== Summary ========== System.out.println( "\n=== VIEWERIMGLOADER CHAIN SUMMARY ===" ); - System.out.println( "Layer 1 (innermost): " + baseLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); + System.out.println( "Layer 1 (innermost): " + correctedLoader.getWrappedImgLoader().getClass().getSimpleName() + " [ViewerImgLoader]" ); System.out.println( "Layer 2 (middle): " + correctedLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); System.out.println( "Layer 3 (outermost): " + splitLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); System.out.println( "" ); + System.out.println( "Flatfield correction is now configured in the XML!" ); + System.out.println( "No manual setBrightImage()/setDarkImage() calls needed." ); + System.out.println( "" ); System.out.println( "All layers maintain ViewerImgLoader compatibility:" ); System.out.println( " - Cache control: delegated through chain" ); System.out.println( " - Volatile images: supported at all levels" ); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java new file mode 100644 index 000000000..d019975e8 --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java @@ -0,0 +1,175 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import static mpicbg.spim.data.XmlKeys.IMGLOADER_FORMAT_ATTRIBUTE_NAME; +import static mpicbg.spim.data.XmlKeys.IMGLOADER_TAG; +import static mpicbg.spim.data.XmlKeys.TIMEPOINTS_TIMEPOINT_TAG; +import static mpicbg.spim.data.XmlKeys.VIEWSETUP_TAG; + +import java.io.File; +import java.util.Map; + +import org.jdom2.DataConversionException; +import org.jdom2.Element; + +import bdv.ViewerImgLoader; +import mpicbg.spim.data.SpimDataInstantiationException; +import mpicbg.spim.data.XmlHelpers; +import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription; +import mpicbg.spim.data.generic.sequence.BasicImgLoader; +import mpicbg.spim.data.generic.sequence.ImgLoaderIo; +import mpicbg.spim.data.generic.sequence.ImgLoaders; +import mpicbg.spim.data.generic.sequence.XmlIoBasicImgLoader; +import mpicbg.spim.data.sequence.ImgLoader; +import mpicbg.spim.data.sequence.ViewId; +import net.imglib2.util.Pair; + +/** + * XML I/O handler for ViewerFlatfieldCorrectionWrappedImgLoader. + * + * Registers format "spimreconstruction.wrapped.flatfield.viewer" for + * ViewerImgLoader-based flatfield correction wrappers. + */ +@ImgLoaderIo(format = "spimreconstruction.wrapped.flatfield.viewer", type = ViewerFlatfieldCorrectionWrappedImgLoader.class) +public class XmlIoViewerFlatfieldCorrectionWrappedImgLoader + implements XmlIoBasicImgLoader< ViewerFlatfieldCorrectionWrappedImgLoader > +{ + public final static String WRAPPED_IMGLOADER_TAG = "WrappedImgLoader"; + public final static String FLATFIELDS_TAG = "FlatFields"; + public final static String FLATFIELD_TAG = "FlatField"; + public final static String BRIGHTIMG_TAG = "BrightImg"; + public final static String DARKIMG_TAG = "DarkImg"; + public final static String ACTIVE_TAG = "Active"; + public final static String CACHED_TAG = "Cached"; + + @Override + public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File basePath, + AbstractSequenceDescription< ?, ?, ? > sequenceDescription) + { + Element wrappedImgLoaderEl = elem.getChild( WRAPPED_IMGLOADER_TAG ).getChild( IMGLOADER_TAG ); + XmlIoBasicImgLoader< ? > xmlIoWrapped = null; + try + { + xmlIoWrapped = ImgLoaders + .createXmlIoForFormat( wrappedImgLoaderEl.getAttributeValue( IMGLOADER_FORMAT_ATTRIBUTE_NAME ) ); + } + catch ( SpimDataInstantiationException e ) + { + e.printStackTrace(); + return null; + } + + boolean cached = false; + boolean active = false; + try + { + cached = elem.getAttribute( CACHED_TAG ).getBooleanValue(); + active = elem.getAttribute( ACTIVE_TAG ).getBooleanValue(); + } + catch ( DataConversionException e ) + { + e.printStackTrace(); + } + + BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml( wrappedImgLoaderEl, basePath, sequenceDescription ); + + // Verify wrapped loader is a ViewerImgLoader + if ( !( wrappedImgLoader instanceof ViewerImgLoader ) ) + { + System.err.println( "ViewerFlatfieldCorrectionWrappedImgLoader requires a ViewerImgLoader, but got: " + + wrappedImgLoader.getClass().getName() ); + return null; + } + + ViewerFlatfieldCorrectionWrappedImgLoader res = + new ViewerFlatfieldCorrectionWrappedImgLoader( (ViewerImgLoader) wrappedImgLoader, cached ); + + Element flatfields = elem.getChild( FLATFIELDS_TAG ); + for ( Element flatfield : flatfields.getChildren() ) + { + int tp = Integer.parseInt( flatfield.getAttributeValue( TIMEPOINTS_TIMEPOINT_TAG ) ); + int vs = Integer.parseInt( flatfield.getAttributeValue( VIEWSETUP_TAG ) ); + File brightImg = XmlHelpers.loadPath( flatfield, BRIGHTIMG_TAG, basePath ); + File darkImg = XmlHelpers.loadPath( flatfield, DARKIMG_TAG, basePath ); + res.setBrightImage( new ViewId( tp, vs ), brightImg ); + res.setDarkImage( new ViewId( tp, vs ), darkImg ); + } + + res.setActive( active ); + return res; + } + + @Override + public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File basePath) + { + final Map< ViewId, Pair< File, File > > fileMap = imgLoader.fileMap; + + final Element wholeElem = new Element( IMGLOADER_TAG ); + wholeElem.setAttribute( IMGLOADER_FORMAT_ATTRIBUTE_NAME, + this.getClass().getAnnotation( ImgLoaderIo.class ).format() ); + final Element wrappedIL = new Element( WRAPPED_IMGLOADER_TAG ); + + wholeElem.setAttribute( ACTIVE_TAG, Boolean.toString( imgLoader.isActive() ) ); + wholeElem.setAttribute( CACHED_TAG, Boolean.toString( imgLoader.isCached() ) ); + + try + { + @SuppressWarnings({ "unchecked", "rawtypes" }) + XmlIoBasicImgLoader loaderIO = ImgLoaders + .createXmlIoForImgLoaderClass( imgLoader.getWrappedImgLoader().getClass() ); + @SuppressWarnings("unchecked") + Element wrappedInner = loaderIO.toXml( imgLoader.getWrappedImgLoader(), basePath ); + wrappedIL.addContent( wrappedInner ); + } + catch ( SpimDataInstantiationException e ) + { + e.printStackTrace(); + return null; + } + + final Element elFlatfields = new Element( FLATFIELDS_TAG ); + + for ( ViewId vid : fileMap.keySet() ) + { + final Pair< File, File > files = fileMap.get( vid ); + if ( files == null || ( files.getA() == null && files.getB() == null ) ) + continue; + + final Element elFlatfield = new Element( FLATFIELD_TAG ); + elFlatfield.setAttribute( TIMEPOINTS_TIMEPOINT_TAG, Integer.toString( vid.getTimePointId() ) ); + elFlatfield.setAttribute( VIEWSETUP_TAG, Integer.toString( vid.getViewSetupId() ) ); + + if ( files.getA() != null ) + elFlatfield.addContent( XmlHelpers.pathElement( BRIGHTIMG_TAG, files.getA(), basePath ) ); + if ( files.getB() != null ) + elFlatfield.addContent( XmlHelpers.pathElement( DARKIMG_TAG, files.getB(), basePath ) ); + + elFlatfields.addContent( elFlatfield ); + } + + wholeElem.addContent( wrappedIL ); + wholeElem.addContent( elFlatfields ); + return wholeElem; + } +} From 815815a0a9c0eee3ed550b139f7adfdfdbe09f51 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Thu, 8 Jan 2026 15:53:33 -0500 Subject: [PATCH 05/31] Slightly adjust coding style for readability --- .../flatfield/TestDecoratorChain.java | 177 ++++---- .../flatfield/TestFlatfieldCorrection.java | 77 ++-- .../TestViewerFlatfieldCorrection.java | 186 ++++---- ...erFlatfieldCorrectionWrappedImgLoader.java | 416 ++++++++---------- ...erFlatfieldCorrectionWrappedImgLoader.java | 124 +++--- 5 files changed, 451 insertions(+), 529 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java index 0debd74ee..178f28e1f 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java @@ -50,23 +50,21 @@ * This shows how to compose multiple wrapper/decorator layers while maintaining * the MultiResolutionImgLoader interface throughout the chain. */ -public class TestDecoratorChain -{ +public class TestDecoratorChain { // Mapping from ViewSetup ID to Tile ID (from dataset.xml) private static final int[][] SETUP_TO_TILE = { - { 0, 187 }, - { 1, 188 }, - { 2, 189 }, - { 3, 200 }, - { 4, 201 }, - { 5, 202 }, - { 6, 213 }, - { 7, 214 }, - { 8, 215 } + {0, 187}, + {1, 188}, + {2, 189}, + {3, 200}, + {4, 201}, + {5, 202}, + {6, 213}, + {7, 214}, + {8, 215} }; - public static void main( String[] args ) throws SpimDataException - { + public static void main(String[] args) throws SpimDataException { // ========== CONFIGURATION ========== final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; final String xmlPath = basePath + "data/dataset.xml"; @@ -77,88 +75,85 @@ public static void main( String[] args ) throws SpimDataException final int timepoint = 0; // ========== STEP 1: Load dataset and get base loader ========== - System.out.println( "=== STEP 1: Loading dataset ===" ); - System.out.println( "XML path: " + xmlPath ); + System.out.println("=== STEP 1: Loading dataset ==="); + System.out.println("XML path: " + xmlPath); - final SpimData2 data = new XmlIoSpimData2().load( xmlPath ); + final SpimData2 data = new XmlIoSpimData2().load(xmlPath); final SequenceDescription seqDesc = data.getSequenceDescription(); // The base loader - this could be N5ImageLoader, AllenOMEZarrLoader, etc. final MultiResolutionImgLoader baseLoader = (MultiResolutionImgLoader) seqDesc.getImgLoader(); - System.out.println( "Base loader type: " + baseLoader.getClass().getSimpleName() ); + System.out.println("Base loader type: " + baseLoader.getClass().getSimpleName()); // ========== STEP 2: Wrap with flatfield correction ========== - System.out.println( "\n=== STEP 2: Wrapping with flatfield correction ===" ); + System.out.println("\n=== STEP 2: Wrapping with flatfield correction ==="); final MultiResolutionFlatfieldCorrectionWrappedImgLoader correctedLoader = - new MultiResolutionFlatfieldCorrectionWrappedImgLoader( baseLoader, true ); + new MultiResolutionFlatfieldCorrectionWrappedImgLoader(baseLoader, true); // Configure correction images for each view setup - for ( int[] mapping : SETUP_TO_TILE ) - { - final int setupId = mapping[ 0 ]; - final int tileId = mapping[ 1 ]; + for (int[] mapping : SETUP_TO_TILE) { + final int setupId = mapping[0]; + final int tileId = mapping[1]; // Find darkfield file - final File darkfield = new File( correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif" ); + final File darkfield = new File(correctionPath + "setup" + tileId + "-AVG_darkfield-fromdata.tif"); // Find flatfield file (try both naming conventions) - File flatfield = new File( correctionPath + "setup" + tileId + "-flatfield.tif" ); - if ( !flatfield.exists() ) - flatfield = new File( correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif" ); + File flatfield = new File(correctionPath + "setup" + tileId + "-flatfield.tif"); + if (!flatfield.exists()) + flatfield = new File(correctionPath + "setup" + tileId + "-flatfield (fixed by mirroring).tif"); - final ViewId viewId = new ViewId( timepoint, setupId ); + final ViewId viewId = new ViewId(timepoint, setupId); - if ( darkfield.exists() ) - { - correctedLoader.setDarkImage( viewId, darkfield ); - System.out.println( " Setup " + setupId + ": darkfield = " + darkfield.getName() ); + if (darkfield.exists()) { + correctedLoader.setDarkImage(viewId, darkfield); + System.out.println(" Setup " + setupId + ": darkfield = " + darkfield.getName()); } - if ( flatfield.exists() ) - { - correctedLoader.setBrightImage( viewId, flatfield ); - System.out.println( " Setup " + setupId + ": flatfield = " + flatfield.getName() ); + if (flatfield.exists()) { + correctedLoader.setBrightImage(viewId, flatfield); + System.out.println(" Setup " + setupId + ": flatfield = " + flatfield.getName()); } } // ========== STEP 3: Wrap with splitting ========== - System.out.println( "\n=== STEP 3: Wrapping with splitting ===" ); + System.out.println("\n=== STEP 3: Wrapping with splitting ==="); // Get original image dimensions for the setup we're demonstrating - final ViewSetup vs = seqDesc.getViewSetups().get( setupToShow ); - final long[] dims = new long[ 3 ]; - vs.getSize().dimensions( dims ); - System.out.println( "Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2] ); + final ViewSetup vs = seqDesc.getViewSetups().get(setupToShow); + final long[] dims = new long[3]; + vs.getSize().dimensions(dims); + System.out.println("Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2]); // Create a simple 2x1 split in X dimension // Split the 512-wide image into two 256-wide regions - final long splitX = dims[ 0 ] / 2; + final long splitX = dims[0] / 2; // Define the mappings for split regions // New setup IDs 100, 101 will map to original setup 0, with different X intervals - final HashMap< Integer, Integer > new2oldSetupId = new HashMap<>(); - final HashMap< Integer, Interval > newSetupId2Interval = new HashMap<>(); + final HashMap new2oldSetupId = new HashMap<>(); + final HashMap newSetupId2Interval = new HashMap<>(); // Split region 0: left half [0, splitX) x [0, dimY) x [0, dimZ) - new2oldSetupId.put( 100, setupToShow ); - newSetupId2Interval.put( 100, new FinalInterval( - new long[] { 0, 0, 0 }, - new long[] { splitX - 1, dims[1] - 1, dims[2] - 1 } + new2oldSetupId.put(100, setupToShow); + newSetupId2Interval.put(100, new FinalInterval( + new long[] {0, 0, 0}, + new long[] {splitX - 1, dims[1] - 1, dims[2] - 1} )); // Split region 1: right half [splitX, dimX) x [0, dimY) x [0, dimZ) - new2oldSetupId.put( 101, setupToShow ); - newSetupId2Interval.put( 101, new FinalInterval( - new long[] { splitX, 0, 0 }, - new long[] { dims[0] - 1, dims[1] - 1, dims[2] - 1 } + new2oldSetupId.put(101, setupToShow); + newSetupId2Interval.put(101, new FinalInterval( + new long[] {splitX, 0, 0}, + new long[] {dims[0] - 1, dims[1] - 1, dims[2] - 1} )); - System.out.println( "Created 2 split regions:" ); - System.out.println( " Setup 100: X=[0, " + (splitX-1) + "] (left half)" ); - System.out.println( " Setup 101: X=[" + splitX + ", " + (dims[0]-1) + "] (right half)" ); + System.out.println("Created 2 split regions:"); + System.out.println(" Setup 100: X=[0, " + (splitX-1) + "] (left half)"); + System.out.println(" Setup 101: X=[" + splitX + ", " + (dims[0]-1) + "] (right half)"); // Create the split loader wrapping the CORRECTED loader // This is the key: correction is applied BEFORE splitting @@ -170,53 +165,53 @@ public static void main( String[] args ) throws SpimDataException ); // ========== STEP 4: Display comparison images ========== - System.out.println( "\n=== STEP 4: Displaying images ===" ); + System.out.println("\n=== STEP 4: Displaying images ==="); new ImageJ(); - final int tileId = SETUP_TO_TILE[ setupToShow ][ 1 ]; + final int tileId = SETUP_TO_TILE[setupToShow][1]; // 4a. Show UNCORRECTED original (full image, level 0) - System.out.println( "Loading uncorrected image..." ); - final RandomAccessibleInterval< FloatType > uncorrected = - baseLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + System.out.println("Loading uncorrected image..."); + final RandomAccessibleInterval uncorrected = + baseLoader.getSetupImgLoader(setupToShow).getFloatImage(timepoint, 0, false); + ImageJFunctions.show(uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")"); // 4b. Show CORRECTED (full image, level 0) - System.out.println( "Loading corrected image..." ); - final RandomAccessibleInterval< FloatType > corrected = - correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + System.out.println("Loading corrected image..."); + final RandomAccessibleInterval corrected = + correctedLoader.getSetupImgLoader(setupToShow).getFloatImage(timepoint, 0, false); + ImageJFunctions.show(corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")"); // 4c. Show CORRECTED + SPLIT (left half, level 0) - System.out.println( "Loading corrected + split (left half)..." ); - final RandomAccessibleInterval< FloatType > splitLeft = - splitLoader.getSetupImgLoader( 100 ).getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( splitLeft, "3. Corrected+Split LEFT - Setup 100" ); + System.out.println("Loading corrected + split (left half)..."); + final RandomAccessibleInterval splitLeft = + splitLoader.getSetupImgLoader(100).getFloatImage(timepoint, 0, false); + ImageJFunctions.show(splitLeft, "3. Corrected+Split LEFT - Setup 100"); // 4d. Show CORRECTED + SPLIT (right half, level 0) - System.out.println( "Loading corrected + split (right half)..." ); - final RandomAccessibleInterval< FloatType > splitRight = - splitLoader.getSetupImgLoader( 101 ).getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( splitRight, "4. Corrected+Split RIGHT - Setup 101" ); + System.out.println("Loading corrected + split (right half)..."); + final RandomAccessibleInterval splitRight = + splitLoader.getSetupImgLoader(101).getFloatImage(timepoint, 0, false); + ImageJFunctions.show(splitRight, "4. Corrected+Split RIGHT - Setup 101"); // ========== Summary ========== - System.out.println( "\n=== DECORATOR CHAIN SUMMARY ===" ); - System.out.println( "Layer 1 (innermost): " + baseLoader.getClass().getSimpleName() ); - System.out.println( "Layer 2 (middle): " + correctedLoader.getClass().getSimpleName() ); - System.out.println( "Layer 3 (outermost): " + splitLoader.getClass().getSimpleName() ); - System.out.println( "" ); - System.out.println( "Data flow:" ); - System.out.println( " Request for split region 100 or 101" ); - System.out.println( " → SplitMultiResolutionImgLoader maps to setup " + setupToShow + " with interval" ); - System.out.println( " → MultiResolutionFlatfieldCorrectionWrappedImgLoader applies correction" ); - System.out.println( " → Base loader fetches raw pixels from N5" ); - System.out.println( " → Corrected pixels flow back up through the chain" ); - System.out.println( " → Split interval is extracted and returned" ); - System.out.println( "" ); - System.out.println( "Compare the images to verify:" ); - System.out.println( " - Image 1 vs 2: See flatfield correction effect" ); - System.out.println( " - Image 2 vs 3+4: Verify split regions match the corrected full image" ); - System.out.println( "" ); - System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)" ); + System.out.println("\n=== DECORATOR CHAIN SUMMARY ==="); + System.out.println("Layer 1 (innermost): " + baseLoader.getClass().getSimpleName()); + System.out.println("Layer 2 (middle): " + correctedLoader.getClass().getSimpleName()); + System.out.println("Layer 3 (outermost): " + splitLoader.getClass().getSimpleName()); + System.out.println(); + System.out.println("Data flow:"); + System.out.println(" Request for split region 100 or 101"); + System.out.println(" → SplitMultiResolutionImgLoader maps to setup " + setupToShow + " with interval"); + System.out.println(" → MultiResolutionFlatfieldCorrectionWrappedImgLoader applies correction"); + System.out.println(" → Base loader fetches raw pixels from N5"); + System.out.println(" → Corrected pixels flow back up through the chain"); + System.out.println(" → Split interval is extracted and returned"); + System.out.println(); + System.out.println("Compare the images to verify:"); + System.out.println(" - Image 1 vs 2: See flatfield correction effect"); + System.out.println(" - Image 2 vs 3+4: Verify split regions match the corrected full image"); + System.out.println(); + System.out.println("Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)"); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java index 7932041a3..cb996338b 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java @@ -40,11 +40,9 @@ * The XML wraps the N5 loader with MultiResolutionFlatfieldCorrectionWrappedImgLoader * and specifies bright/dark images for each view setup. */ -public class TestFlatfieldCorrection -{ +public class TestFlatfieldCorrection { - public static void main( String[] args ) throws SpimDataException - { + public static void main(String[] args) throws SpimDataException { // Paths final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; final String correctedXmlPath = basePath + "data/dataset_corrected.xml"; @@ -55,65 +53,64 @@ public static void main( String[] args ) throws SpimDataException final int timepoint = 0; // ========== Load CORRECTED dataset (from XML with flatfield config) ========== - System.out.println( "=== Loading CORRECTED dataset ===" ); - System.out.println( "XML path: " + correctedXmlPath ); + System.out.println("=== Loading CORRECTED dataset ==="); + System.out.println("XML path: " + correctedXmlPath); - final SpimData2 correctedData = new XmlIoSpimData2().load( correctedXmlPath ); + final SpimData2 correctedData = new XmlIoSpimData2().load(correctedXmlPath); final ImgLoader correctedImgLoader = correctedData.getSequenceDescription().getImgLoader(); - System.out.println( "ImgLoader type: " + correctedImgLoader.getClass().getSimpleName() ); + System.out.println("ImgLoader type: " + correctedImgLoader.getClass().getSimpleName()); // Verify it's a flatfield-corrected loader - if ( correctedImgLoader instanceof FlatfieldCorrectionWrappedImgLoader ) - { - final FlatfieldCorrectionWrappedImgLoader< ? > ffcLoader = - (FlatfieldCorrectionWrappedImgLoader< ? >) correctedImgLoader; - System.out.println( " Correction active: " + ffcLoader.isActive() ); - System.out.println( " Caching enabled: " + ffcLoader.isCached() ); - System.out.println( " Wrapped loader: " + ffcLoader.getWrappedImgLoder().getClass().getSimpleName() ); + if (correctedImgLoader instanceof FlatfieldCorrectionWrappedImgLoader) { + final FlatfieldCorrectionWrappedImgLoader ffcLoader = + (FlatfieldCorrectionWrappedImgLoader) correctedImgLoader; + System.out.println(" Correction active: " + ffcLoader.isActive()); + System.out.println(" Caching enabled: " + ffcLoader.isCached()); + System.out.println(" Wrapped loader: " + ffcLoader.getWrappedImgLoder().getClass().getSimpleName()); } // ========== Load UNCORRECTED dataset (original XML) ========== - System.out.println( "\n=== Loading UNCORRECTED dataset ===" ); - System.out.println( "XML path: " + uncorrectedXmlPath ); + System.out.println("\n=== Loading UNCORRECTED dataset ==="); + System.out.println("XML path: " + uncorrectedXmlPath); - final SpimData2 uncorrectedData = new XmlIoSpimData2().load( uncorrectedXmlPath ); + final SpimData2 uncorrectedData = new XmlIoSpimData2().load(uncorrectedXmlPath); final ImgLoader uncorrectedImgLoader = uncorrectedData.getSequenceDescription().getImgLoader(); - System.out.println( "ImgLoader type: " + uncorrectedImgLoader.getClass().getSimpleName() ); + System.out.println("ImgLoader type: " + uncorrectedImgLoader.getClass().getSimpleName()); // ========== Display images for comparison ========== new ImageJ(); // Get tile ID from ViewSetup metadata (no hardcoded mapping needed!) final int tileId = correctedData.getSequenceDescription() - .getViewSetups().get( setupToShow ).getTile().getId(); + .getViewSetups().get(setupToShow).getTile().getId(); - System.out.println( "\n=== Displaying setup " + setupToShow + " (tile " + tileId + ") ===" ); - System.out.println( " Dimensions: " + correctedData.getSequenceDescription() - .getViewSetups().get( setupToShow ).getSize() ); + System.out.println("\n=== Displaying setup " + setupToShow + " (tile " + tileId + ") ==="); + System.out.println(" Dimensions: " + correctedData.getSequenceDescription() + .getViewSetups().get(setupToShow).getSize()); // Load and display UNCORRECTED image - System.out.println( "Loading uncorrected image..." ); - final RandomAccessibleInterval< FloatType > uncorrected = - uncorrectedImgLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, false ); - ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + System.out.println("Loading uncorrected image..."); + final RandomAccessibleInterval uncorrected = + uncorrectedImgLoader.getSetupImgLoader(setupToShow).getFloatImage(timepoint, false); + ImageJFunctions.show(uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")"); // Load and display CORRECTED image - System.out.println( "Loading corrected image..." ); - final RandomAccessibleInterval< FloatType > corrected = - correctedImgLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, false ); - ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + System.out.println("Loading corrected image..."); + final RandomAccessibleInterval corrected = + correctedImgLoader.getSetupImgLoader(setupToShow).getFloatImage(timepoint, false); + ImageJFunctions.show(corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")"); // ========== Summary ========== - System.out.println( "\n=== SUMMARY ===" ); - System.out.println( "Flatfield correction is now configured in the XML!" ); - System.out.println( "No manual setBrightImage()/setDarkImage() calls needed." ); - System.out.println( "" ); - System.out.println( "Compare the two images to verify correction:" ); - System.out.println( " - Image 1: Raw data from N5" ); - System.out.println( " - Image 2: Corrected with flatfield/darkfield" ); - System.out.println( "" ); - System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)" ); + System.out.println("\n=== SUMMARY ==="); + System.out.println("Flatfield correction is now configured in the XML!"); + System.out.println("No manual setBrightImage()/setDarkImage() calls needed."); + System.out.println(); + System.out.println("Compare the two images to verify correction:"); + System.out.println(" - Image 1: Raw data from N5"); + System.out.println(" - Image 2: Corrected with flatfield/darkfield"); + System.out.println(); + System.out.println("Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)"); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java index a531c1d9a..b348f3130 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java @@ -56,10 +56,8 @@ * - Volatile image support * - Multi-resolution mipmap levels */ -public class TestViewerFlatfieldCorrection -{ - public static void main( String[] args ) throws SpimDataException - { +public class TestViewerFlatfieldCorrection { + public static void main(String[] args) throws SpimDataException { // Paths final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; final String correctedXmlPath = basePath + "data/dataset_corrected_viewer.xml"; @@ -70,80 +68,78 @@ public static void main( String[] args ) throws SpimDataException final int timepoint = 0; // ========== STEP 1: Load CORRECTED dataset (from XML with flatfield config) ========== - System.out.println( "=== STEP 1: Loading CORRECTED dataset ==="); - System.out.println( "XML path: " + correctedXmlPath ); + System.out.println("=== STEP 1: Loading CORRECTED dataset ==="); + System.out.println("XML path: " + correctedXmlPath); - final SpimData2 correctedData = new XmlIoSpimData2().load( correctedXmlPath ); + final SpimData2 correctedData = new XmlIoSpimData2().load(correctedXmlPath); final SequenceDescription correctedSeqDesc = correctedData.getSequenceDescription(); // Verify it's a ViewerFlatfieldCorrectionWrappedImgLoader - if ( !( correctedSeqDesc.getImgLoader() instanceof ViewerFlatfieldCorrectionWrappedImgLoader ) ) - { - System.err.println( "ERROR: Expected ViewerFlatfieldCorrectionWrappedImgLoader!" ); - System.err.println( "Loader type: " + correctedSeqDesc.getImgLoader().getClass().getName() ); + if (!(correctedSeqDesc.getImgLoader() instanceof ViewerFlatfieldCorrectionWrappedImgLoader)) { + System.err.println("ERROR: Expected ViewerFlatfieldCorrectionWrappedImgLoader!"); + System.err.println("Loader type: " + correctedSeqDesc.getImgLoader().getClass().getName()); return; } final ViewerFlatfieldCorrectionWrappedImgLoader correctedLoader = (ViewerFlatfieldCorrectionWrappedImgLoader) correctedSeqDesc.getImgLoader(); - System.out.println( "Corrected loader type: " + correctedLoader.getClass().getSimpleName() ); - System.out.println( " Correction active: " + correctedLoader.isActive() ); - System.out.println( " Caching enabled: " + correctedLoader.isCached() ); - System.out.println( " Wrapped loader: " + correctedLoader.getWrappedImgLoader().getClass().getSimpleName() ); - System.out.println( " Implements ViewerImgLoader: " + ( correctedLoader instanceof ViewerImgLoader ? "YES" : "NO" ) ); + System.out.println("Corrected loader type: " + correctedLoader.getClass().getSimpleName()); + System.out.println(" Correction active: " + correctedLoader.isActive()); + System.out.println(" Caching enabled: " + correctedLoader.isCached()); + System.out.println(" Wrapped loader: " + correctedLoader.getWrappedImgLoader().getClass().getSimpleName()); + System.out.println(" Implements ViewerImgLoader: " + (correctedLoader instanceof ViewerImgLoader ? "YES" : "NO")); // ========== STEP 2: Load UNCORRECTED dataset (original XML) ========== - System.out.println( "\n=== STEP 2: Loading UNCORRECTED dataset ===" ); - System.out.println( "XML path: " + uncorrectedXmlPath ); + System.out.println("\n=== STEP 2: Loading UNCORRECTED dataset ==="); + System.out.println("XML path: " + uncorrectedXmlPath); - final SpimData2 uncorrectedData = new XmlIoSpimData2().load( uncorrectedXmlPath ); + final SpimData2 uncorrectedData = new XmlIoSpimData2().load(uncorrectedXmlPath); final SequenceDescription uncorrectedSeqDesc = uncorrectedData.getSequenceDescription(); // Verify the base loader is a ViewerImgLoader - if ( !( uncorrectedSeqDesc.getImgLoader() instanceof ViewerImgLoader ) ) - { - System.err.println( "ERROR: Base loader is not a ViewerImgLoader!" ); - System.err.println( "Loader type: " + uncorrectedSeqDesc.getImgLoader().getClass().getName() ); + if (!(uncorrectedSeqDesc.getImgLoader() instanceof ViewerImgLoader)) { + System.err.println("ERROR: Base loader is not a ViewerImgLoader!"); + System.err.println("Loader type: " + uncorrectedSeqDesc.getImgLoader().getClass().getName()); return; } final ViewerImgLoader uncorrectedLoader = (ViewerImgLoader) uncorrectedSeqDesc.getImgLoader(); - System.out.println( "Uncorrected loader type: " + uncorrectedLoader.getClass().getSimpleName() ); + System.out.println("Uncorrected loader type: " + uncorrectedLoader.getClass().getSimpleName()); // ========== STEP 3: Create SplitViewerImgLoader wrapping the corrected loader ========== - System.out.println( "\n=== STEP 3: Creating SplitViewerImgLoader ===" ); + System.out.println("\n=== STEP 3: Creating SplitViewerImgLoader ==="); // Get original image dimensions for the setup we're demonstrating - final ViewSetup vs = correctedSeqDesc.getViewSetups().get( setupToShow ); - final long[] dims = new long[ 3 ]; - vs.getSize().dimensions( dims ); - System.out.println( "Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2] ); + final ViewSetup vs = correctedSeqDesc.getViewSetups().get(setupToShow); + final long[] dims = new long[3]; + vs.getSize().dimensions(dims); + System.out.println("Original image size: " + dims[0] + " x " + dims[1] + " x " + dims[2]); // Create a simple 2x1 split in X dimension - final long splitX = dims[ 0 ] / 2; + final long splitX = dims[0] / 2; // Define the mappings for split regions - final HashMap< Integer, Integer > new2oldSetupId = new HashMap<>(); - final HashMap< Integer, Interval > newSetupId2Interval = new HashMap<>(); + final HashMap new2oldSetupId = new HashMap<>(); + final HashMap newSetupId2Interval = new HashMap<>(); // Split region 0: left half - new2oldSetupId.put( 100, setupToShow ); - newSetupId2Interval.put( 100, new FinalInterval( - new long[] { 0, 0, 0 }, - new long[] { splitX - 1, dims[1] - 1, dims[2] - 1 } + new2oldSetupId.put(100, setupToShow); + newSetupId2Interval.put(100, new FinalInterval( + new long[] {0, 0, 0}, + new long[] {splitX - 1, dims[1] - 1, dims[2] - 1} )); // Split region 1: right half - new2oldSetupId.put( 101, setupToShow ); - newSetupId2Interval.put( 101, new FinalInterval( - new long[] { splitX, 0, 0 }, - new long[] { dims[0] - 1, dims[1] - 1, dims[2] - 1 } + new2oldSetupId.put(101, setupToShow); + newSetupId2Interval.put(101, new FinalInterval( + new long[] {splitX, 0, 0}, + new long[] {dims[0] - 1, dims[1] - 1, dims[2] - 1} )); - System.out.println( "Created 2 split regions:" ); - System.out.println( " Setup 100: X=[0, " + (splitX-1) + "] (left half)" ); - System.out.println( " Setup 101: X=[" + splitX + ", " + (dims[0]-1) + "] (right half)" ); + System.out.println("Created 2 split regions:"); + System.out.println(" Setup 100: X=[0, " + (splitX-1) + "] (left half)"); + System.out.println(" Setup 101: X=[" + splitX + ", " + (dims[0]-1) + "] (right half)"); // Create the split loader wrapping the CORRECTED ViewerImgLoader final SplitViewerImgLoader splitLoader = new SplitViewerImgLoader( @@ -153,85 +149,83 @@ public static void main( String[] args ) throws SpimDataException correctedSeqDesc ); - System.out.println( "Split loader implements ViewerImgLoader: " + - ( splitLoader instanceof ViewerImgLoader ? "YES" : "NO" ) ); + System.out.println("Split loader implements ViewerImgLoader: " + + (splitLoader instanceof ViewerImgLoader ? "YES" : "NO")); // ========== STEP 4: Test ViewerImgLoader-specific features ========== - System.out.println( "\n=== STEP 4: Testing ViewerImgLoader features ===" ); + System.out.println("\n=== STEP 4: Testing ViewerImgLoader features ==="); // Test cache control delegation - System.out.println( "Cache control available: " + ( splitLoader.getCacheControl() != null ) ); + System.out.println("Cache control available: " + (splitLoader.getCacheControl() != null)); // Test mipmap levels - final ViewerSetupImgLoader< ?, ? > setupImgLoader = splitLoader.getSetupImgLoader( 100 ); - System.out.println( "Number of mipmap levels: " + setupImgLoader.numMipmapLevels() ); + final ViewerSetupImgLoader setupImgLoader = splitLoader.getSetupImgLoader(100); + System.out.println("Number of mipmap levels: " + setupImgLoader.numMipmapLevels()); final double[][] resolutions = setupImgLoader.getMipmapResolutions(); - System.out.println( "Mipmap resolutions:" ); - for ( int level = 0; level < resolutions.length; level++ ) - { - System.out.println( " Level " + level + ": " + - resolutions[level][0] + " x " + resolutions[level][1] + " x " + resolutions[level][2] ); + System.out.println("Mipmap resolutions:"); + for (int level = 0; level < resolutions.length; level++) { + System.out.println(" Level " + level + ": " + + resolutions[level][0] + " x " + resolutions[level][1] + " x " + resolutions[level][2]); } // ========== STEP 5: Display comparison images ========== - System.out.println( "\n=== STEP 5: Displaying images ===" ); + System.out.println("\n=== STEP 5: Displaying images ==="); new ImageJ(); // Get tile ID from ViewSetup metadata final int tileId = correctedData.getSequenceDescription() - .getViewSetups().get( setupToShow ).getTile().getId(); + .getViewSetups().get(setupToShow).getTile().getId(); // 5a. Show UNCORRECTED original at level 0 - System.out.println( "Loading uncorrected image (level 0)..." ); + System.out.println("Loading uncorrected image (level 0)..."); @SuppressWarnings("unchecked") - final MultiResolutionSetupImgLoader< FloatType > uncorrectedSetupLoader = - (MultiResolutionSetupImgLoader< FloatType >) uncorrectedLoader.getSetupImgLoader( setupToShow ); - final RandomAccessibleInterval< FloatType > uncorrected = - uncorrectedSetupLoader.getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + final MultiResolutionSetupImgLoader uncorrectedSetupLoader = + (MultiResolutionSetupImgLoader) uncorrectedLoader.getSetupImgLoader(setupToShow); + final RandomAccessibleInterval uncorrected = + uncorrectedSetupLoader.getFloatImage(timepoint, 0, false); + ImageJFunctions.show(uncorrected, "1. Uncorrected - Setup " + setupToShow + " (tile " + tileId + ")"); // 5b. Show CORRECTED at level 0 - System.out.println( "Loading corrected image (level 0)..." ); - final RandomAccessibleInterval< FloatType > corrected = - correctedLoader.getSetupImgLoader( setupToShow ).getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")" ); + System.out.println("Loading corrected image (level 0)..."); + final RandomAccessibleInterval corrected = + correctedLoader.getSetupImgLoader(setupToShow).getFloatImage(timepoint, 0, false); + ImageJFunctions.show(corrected, "2. Corrected - Setup " + setupToShow + " (tile " + tileId + ")"); // 5c. Show CORRECTED + SPLIT (left half) at level 0 - System.out.println( "Loading corrected + split (left half, level 0)..." ); - final RandomAccessibleInterval< FloatType > splitLeft = - splitLoader.getSetupImgLoader( 100 ).getFloatImage( timepoint, 0, false ); - ImageJFunctions.show( splitLeft, "3. Corrected+Split LEFT - Setup 100" ); + System.out.println("Loading corrected + split (left half, level 0)..."); + final RandomAccessibleInterval splitLeft = + splitLoader.getSetupImgLoader(100).getFloatImage(timepoint, 0, false); + ImageJFunctions.show(splitLeft, "3. Corrected+Split LEFT - Setup 100"); // 5d. Show at different mipmap level if available - if ( setupImgLoader.numMipmapLevels() > 1 ) - { - System.out.println( "Loading corrected + split (left half, level 1)..." ); - final RandomAccessibleInterval< FloatType > splitLeftLevel1 = - splitLoader.getSetupImgLoader( 100 ).getFloatImage( timepoint, 1, false ); - ImageJFunctions.show( splitLeftLevel1, "4. Corrected+Split LEFT (Level 1) - Setup 100" ); + if (setupImgLoader.numMipmapLevels() > 1) { + System.out.println("Loading corrected + split (left half, level 1)..."); + final RandomAccessibleInterval splitLeftLevel1 = + splitLoader.getSetupImgLoader(100).getFloatImage(timepoint, 1, false); + ImageJFunctions.show(splitLeftLevel1, "4. Corrected+Split LEFT (Level 1) - Setup 100"); } // ========== Summary ========== - System.out.println( "\n=== VIEWERIMGLOADER CHAIN SUMMARY ===" ); - System.out.println( "Layer 1 (innermost): " + correctedLoader.getWrappedImgLoader().getClass().getSimpleName() + " [ViewerImgLoader]" ); - System.out.println( "Layer 2 (middle): " + correctedLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); - System.out.println( "Layer 3 (outermost): " + splitLoader.getClass().getSimpleName() + " [ViewerImgLoader]" ); - System.out.println( "" ); - System.out.println( "Flatfield correction is now configured in the XML!" ); - System.out.println( "No manual setBrightImage()/setDarkImage() calls needed." ); - System.out.println( "" ); - System.out.println( "All layers maintain ViewerImgLoader compatibility:" ); - System.out.println( " - Cache control: delegated through chain" ); - System.out.println( " - Volatile images: supported at all levels" ); - System.out.println( " - Multi-resolution: " + setupImgLoader.numMipmapLevels() + " mipmap levels available" ); - System.out.println( "" ); - System.out.println( "Compare the images to verify:" ); - System.out.println( " - Image 1 vs 2: See flatfield correction effect" ); - System.out.println( " - Image 2 vs 3: Verify split region matches corrected full image" ); - if ( setupImgLoader.numMipmapLevels() > 1 ) - System.out.println( " - Image 3 vs 4: Compare different mipmap levels" ); - System.out.println( "" ); - System.out.println( "Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)" ); + System.out.println("\n=== VIEWERIMGLOADER CHAIN SUMMARY ==="); + System.out.println("Layer 1 (innermost): " + correctedLoader.getWrappedImgLoader().getClass().getSimpleName() + " [ViewerImgLoader]"); + System.out.println("Layer 2 (middle): " + correctedLoader.getClass().getSimpleName() + " [ViewerImgLoader]"); + System.out.println("Layer 3 (outermost): " + splitLoader.getClass().getSimpleName() + " [ViewerImgLoader]"); + System.out.println(); + System.out.println("Flatfield correction is now configured in the XML!"); + System.out.println("No manual setBrightImage()/setDarkImage() calls needed."); + System.out.println(); + System.out.println("All layers maintain ViewerImgLoader compatibility:"); + System.out.println(" - Cache control: delegated through chain"); + System.out.println(" - Volatile images: supported at all levels"); + System.out.println(" - Multi-resolution: " + setupImgLoader.numMipmapLevels() + " mipmap levels available"); + System.out.println(); + System.out.println("Compare the images to verify:"); + System.out.println(" - Image 1 vs 2: See flatfield correction effect"); + System.out.println(" - Image 2 vs 3: Verify split region matches corrected full image"); + if (setupImgLoader.numMipmapLevels() > 1) + System.out.println(" - Image 3 vs 4: Compare different mipmap levels"); + System.out.println(); + System.out.println("Tip: Use Image > Adjust > Brightness/Contrast (Ctrl+Shift+C)"); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index 6175bb289..db5839e36 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -74,28 +74,25 @@ * -> SplitViewerImgLoader (ViewerImgLoader) */ public class ViewerFlatfieldCorrectionWrappedImgLoader - implements ViewerImgLoader, MultiResolutionImgLoader -{ + implements ViewerImgLoader, MultiResolutionImgLoader { private final ViewerImgLoader wrappedImgLoader; private boolean active; private boolean cacheResult; /** Maps ViewId to (brightFile, darkFile) pair */ - protected final Map< ViewId, Pair< File, File > > fileMap; + protected final Map> fileMap; /** Cached loaded correction images */ - protected final Map< File, RandomAccessibleInterval< FloatType > > raiMap; + protected final Map> raiMap; /** Downsampled bright/dark images for each mipmap level */ - private final Map< Pair< File, List< Integer > >, RandomAccessibleInterval< FloatType > > dsRaiMap; + private final Map>, RandomAccessibleInterval> dsRaiMap; - public ViewerFlatfieldCorrectionWrappedImgLoader( final ViewerImgLoader wrappedImgLoader ) - { - this( wrappedImgLoader, true ); + public ViewerFlatfieldCorrectionWrappedImgLoader(final ViewerImgLoader wrappedImgLoader) { + this(wrappedImgLoader, true); } - public ViewerFlatfieldCorrectionWrappedImgLoader( final ViewerImgLoader wrappedImgLoader, final boolean cacheResult ) - { + public ViewerFlatfieldCorrectionWrappedImgLoader(final ViewerImgLoader wrappedImgLoader, final boolean cacheResult) { this.wrappedImgLoader = wrappedImgLoader; this.active = true; this.cacheResult = cacheResult; @@ -107,258 +104,229 @@ public ViewerFlatfieldCorrectionWrappedImgLoader( final ViewerImgLoader wrappedI // ========== ViewerImgLoader interface ========== @Override - public ViewerFlatfieldCorrectionWrappedSetupImgLoader< ?, ? > getSetupImgLoader( final int setupId ) - { - return new ViewerFlatfieldCorrectionWrappedSetupImgLoader<>( setupId ); + public ViewerFlatfieldCorrectionWrappedSetupImgLoader getSetupImgLoader(final int setupId) { + return new ViewerFlatfieldCorrectionWrappedSetupImgLoader<>(setupId); } @Override - public CacheControl getCacheControl() - { + public CacheControl getCacheControl() { return wrappedImgLoader.getCacheControl(); } @Override - public void setNumFetcherThreads( final int n ) - { - wrappedImgLoader.setNumFetcherThreads( n ); + public void setNumFetcherThreads(final int n) { + wrappedImgLoader.setNumFetcherThreads(n); } // ========== Configuration methods ========== - public ViewerImgLoader getWrappedImgLoader() - { + public ViewerImgLoader getWrappedImgLoader() { return wrappedImgLoader; } - public void setActive( final boolean active ) - { + public void setActive(final boolean active) { this.active = active; } - public boolean isActive() - { + public boolean isActive() { return active; } - public boolean isCached() - { + public boolean isCached() { return cacheResult; } - public void setCached( final boolean cached ) - { + public void setCached(final boolean cached) { this.cacheResult = cached; } - public void setBrightImage( final ViewId vId, final File imgFile ) - { - if ( !fileMap.containsKey( vId ) ) - fileMap.put( vId, new ValuePair<>( null, null ) ); + public void setBrightImage(final ViewId vId, final File imgFile) { + if (!fileMap.containsKey(vId)) + fileMap.put(vId, new ValuePair<>(null, null)); - final Pair< File, File > oldPair = fileMap.get( vId ); - fileMap.put( vId, new ValuePair<>( imgFile, oldPair.getB() ) ); + fileMap.compute(vId, (k, oldPair) -> new ValuePair<>(imgFile, oldPair.getB())); } - public void setDarkImage( final ViewId vId, final File imgFile ) - { - if ( !fileMap.containsKey( vId ) ) - fileMap.put( vId, new ValuePair<>( null, null ) ); + public void setDarkImage(final ViewId vId, final File imgFile) { + if (!fileMap.containsKey(vId)) + fileMap.put(vId, new ValuePair<>(null, null)); - final Pair< File, File > oldPair = fileMap.get( vId ); - fileMap.put( vId, new ValuePair<>( oldPair.getA(), imgFile ) ); + fileMap.compute(vId, (k, oldPair) -> new ValuePair<>(oldPair.getA(), imgFile)); } // ========== Image loading helpers ========== - protected RandomAccessibleInterval< FloatType > getBrightImg( final ViewId vId ) - { - if ( !fileMap.containsKey( vId ) ) + protected RandomAccessibleInterval getBrightImg(final ViewId vId) { + if (!fileMap.containsKey(vId)) return null; - final File fileToLoad = fileMap.get( vId ).getA(); - if ( fileToLoad == null ) + final File fileToLoad = fileMap.get(vId).getA(); + if (fileToLoad == null) return null; - loadFileIfNecessary( fileToLoad ); - return raiMap.get( fileToLoad ); + loadFileIfNecessary(fileToLoad); + return raiMap.get(fileToLoad); } - protected RandomAccessibleInterval< FloatType > getDarkImg( final ViewId vId ) - { - if ( !fileMap.containsKey( vId ) ) + protected RandomAccessibleInterval getDarkImg(final ViewId vId) { + if (!fileMap.containsKey(vId)) return null; - final File fileToLoad = fileMap.get( vId ).getB(); - if ( fileToLoad == null ) + final File fileToLoad = fileMap.get(vId).getB(); + if (fileToLoad == null) return null; - loadFileIfNecessary( fileToLoad ); - return raiMap.get( fileToLoad ); + loadFileIfNecessary(fileToLoad); + return raiMap.get(fileToLoad); } - protected void loadFileIfNecessary( final File file ) - { - if ( raiMap.containsKey( file ) ) + protected void loadFileIfNecessary(final File file) { + if (raiMap.containsKey(file)) return; - final ImagePlus imp = IJ.openImage( file.getAbsolutePath() ); - @SuppressWarnings("unchecked") - final RandomAccessibleInterval< FloatType > img = - (RandomAccessibleInterval< FloatType >) (RandomAccessibleInterval) ImageJFunctions.convertFloat( imp ).copy(); + final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); + final RandomAccessibleInterval img = ImageJFunctions.convertFloat(imp).copy(); - raiMap.put( file, img ); + raiMap.put(file, img); } - protected RandomAccessibleInterval< FloatType > getOrCreateBrightImgDownsampled( + protected RandomAccessibleInterval getOrCreateBrightImgDownsampled( final ViewId vId, - final int[] downsamplingFactors ) - { - if ( !fileMap.containsKey( vId ) || fileMap.get( vId ).getA() == null ) + final int[] downsamplingFactors) { + if (!fileMap.containsKey(vId) || fileMap.get(vId).getA() == null) return null; - final ArrayList< Integer > dsFactorList = new ArrayList<>(); - for ( final int i : downsamplingFactors ) - dsFactorList.add( i ); + final ArrayList dsFactorList = new ArrayList<>(); + for (final int i : downsamplingFactors) + dsFactorList.add(i); - final ValuePair< File, List< Integer > > key = new ValuePair<>( fileMap.get( vId ).getA(), dsFactorList ); + final ValuePair> key = new ValuePair<>(fileMap.get(vId).getA(), dsFactorList); - if ( !dsRaiMap.containsKey( key ) ) - { - final RandomAccessibleInterval< FloatType > brightImg = getBrightImg( vId ); + if (!dsRaiMap.containsKey(key)) { + final RandomAccessibleInterval brightImg = getBrightImg(vId); - if ( brightImg == null ) + if (brightImg == null) return null; // Add singleton z-dimension for downsampleHDF5 to work - final RandomAccessibleInterval< FloatType > downsampled = + final RandomAccessibleInterval downsampled = MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5( - Views.addDimension( brightImg, 0, 0 ), downsamplingFactors ); - dsRaiMap.put( key, downsampled ); + Views.addDimension(brightImg, 0, 0), downsamplingFactors); + dsRaiMap.put(key, downsampled); } - return dsRaiMap.get( key ); + return dsRaiMap.get(key); } - protected RandomAccessibleInterval< FloatType > getOrCreateDarkImgDownsampled( + protected RandomAccessibleInterval getOrCreateDarkImgDownsampled( final ViewId vId, - final int[] downsamplingFactors ) - { - if ( !fileMap.containsKey( vId ) || fileMap.get( vId ).getB() == null ) + final int[] downsamplingFactors) { + if (!fileMap.containsKey(vId) || fileMap.get(vId).getB() == null) return null; - final ArrayList< Integer > dsFactorList = new ArrayList<>(); - for ( final int i : downsamplingFactors ) - dsFactorList.add( i ); + final ArrayList dsFactorList = new ArrayList<>(); + for (final int i : downsamplingFactors) + dsFactorList.add(i); - final ValuePair< File, List< Integer > > key = new ValuePair<>( fileMap.get( vId ).getB(), dsFactorList ); + final ValuePair> key = new ValuePair<>(fileMap.get(vId).getB(), dsFactorList); - if ( !dsRaiMap.containsKey( key ) ) - { - final RandomAccessibleInterval< FloatType > darkImg = getDarkImg( vId ); + if (!dsRaiMap.containsKey(key)) { + final RandomAccessibleInterval darkImg = getDarkImg(vId); - if ( darkImg == null ) + if (darkImg == null) return null; // Add singleton z-dimension for downsampleHDF5 to work - final RandomAccessibleInterval< FloatType > downsampled = + final RandomAccessibleInterval downsampled = MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5( - Views.addDimension( darkImg, 0, 0 ), downsamplingFactors ); - dsRaiMap.put( key, downsampled ); + Views.addDimension(darkImg, 0, 0), downsamplingFactors); + dsRaiMap.put(key, downsampled); } - return dsRaiMap.get( key ); + return dsRaiMap.get(key); } // ========== Inner class: ViewerSetupImgLoader implementation ========== - public class ViewerFlatfieldCorrectionWrappedSetupImgLoader< T extends RealType< T > & NativeType< T >, V extends Volatile< T > & RealType< V > & NativeType< V > > - implements ViewerSetupImgLoader< T, V >, MultiResolutionSetupImgLoader< T > - { + public class ViewerFlatfieldCorrectionWrappedSetupImgLoader & NativeType, V extends Volatile & RealType & NativeType> + implements ViewerSetupImgLoader, MultiResolutionSetupImgLoader { private final int setupId; - ViewerFlatfieldCorrectionWrappedSetupImgLoader( final int setupId ) - { + ViewerFlatfieldCorrectionWrappedSetupImgLoader(final int setupId) { this.setupId = setupId; } @SuppressWarnings("unchecked") - private ViewerSetupImgLoader< T, V > getUnderlyingViewerSetupImgLoader() - { - return (ViewerSetupImgLoader< T, V >) wrappedImgLoader.getSetupImgLoader( setupId ); + private ViewerSetupImgLoader getUnderlyingViewerSetupImgLoader() { + return (ViewerSetupImgLoader) wrappedImgLoader.getSetupImgLoader(setupId); } @SuppressWarnings("unchecked") - private MultiResolutionSetupImgLoader< T > getUnderlyingMultiResSetupImgLoader() - { + private MultiResolutionSetupImgLoader getUnderlyingMultiResSetupImgLoader() { // The wrapped ViewerImgLoader should also be a MultiResolutionImgLoader - return (MultiResolutionSetupImgLoader< T >) ((MultiResolutionImgLoader) wrappedImgLoader).getSetupImgLoader( setupId ); + return (MultiResolutionSetupImgLoader) ((MultiResolutionImgLoader) wrappedImgLoader).getSetupImgLoader(setupId); } // ========== Regular image access ========== @Override - public RandomAccessibleInterval< T > getImage( final int timepointId, final ImgLoaderHint... hints ) - { - return getImage( timepointId, 0, hints ); + public RandomAccessibleInterval getImage(final int timepointId, final ImgLoaderHint... hints) { + return getImage(timepointId, 0, hints); } @Override - public RandomAccessibleInterval< T > getImage( final int timepointId, final int level, final ImgLoaderHint... hints ) - { - final ViewerSetupImgLoader< T, V > viewerSetupIL = getUnderlyingViewerSetupImgLoader(); - final MultiResolutionSetupImgLoader< T > multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); + public RandomAccessibleInterval getImage(final int timepointId, final int level, final ImgLoaderHint... hints) { + final ViewerSetupImgLoader viewerSetupIL = getUnderlyingViewerSetupImgLoader(); + final MultiResolutionSetupImgLoader multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); - if ( !active ) - return viewerSetupIL.getImage( timepointId, level, hints ); + if (!active) + return viewerSetupIL.getImage(timepointId, level, hints); - final int n = multiResSetupIL.getImageSize( timepointId ).numDimensions(); + final int n = multiResSetupIL.getImageSize(timepointId).numDimensions(); // Calculate downsampling factors for this mipmap level - final int[] dsFactors = new int[ n ]; - final double[] dsD = viewerSetupIL.getMipmapResolutions()[ level ]; - for ( int d = 0; d < n; d++ ) - dsFactors[ d ] = (int) dsD[ d ]; + final int[] dsFactors = new int[n]; + final double[] dsD = viewerSetupIL.getMipmapResolutions()[level]; + for (int d = 0; d < n; d++) + dsFactors[d] = (int) dsD[d]; // Don't downsample z for 2D correction images - dsFactors[ n - 1 ] = 1; + dsFactors[n - 1] = 1; - @SuppressWarnings("unchecked") - RandomAccessibleInterval< T > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( - viewerSetupIL.getImage( timepointId, level, hints ), - getOrCreateBrightImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), - getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ) ); + RandomAccessibleInterval rai = FlatFieldCorrectedRandomAccessibleIntervals.create( + viewerSetupIL.getImage(timepointId, level, hints), + getOrCreateBrightImgDownsampled(new ViewId(timepointId, setupId), dsFactors), + getOrCreateDarkImgDownsampled(new ViewId(timepointId, setupId), dsFactors)); // Handle LOAD_COMPLETELY hint boolean loadCompletelyRequested = false; - for ( final ImgLoaderHint hint : hints ) - if ( hint == ImgLoaderHints.LOAD_COMPLETELY ) - loadCompletelyRequested = true; + for (final ImgLoaderHint hint : hints) + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { + loadCompletelyRequested = true; + break; + } - if ( loadCompletelyRequested ) - { + if (loadCompletelyRequested) { long numPx = 1; - for ( int d = 0; d < rai.numDimensions(); d++ ) - numPx *= rai.dimension( d ); + for (int d = 0; d < rai.numDimensions(); d++) + numPx *= rai.dimension(d); - final ImgFactory< T > imgFactory; - if ( Math.log( numPx ) / Math.log( 2 ) < 31 ) + final ImgFactory imgFactory; + if (Math.log(numPx) / Math.log(2) < 31) imgFactory = new ArrayImgFactory<>(); else imgFactory = new CellImgFactory<>(); - final Img< T > loadedImg = imgFactory.create( rai, getImageType() ); - RealTypeConverters.copyFromTo( Views.extendZero( rai ), loadedImg ); + final Img loadedImg = imgFactory.create(rai, getImageType()); + RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; - } - else if ( cacheResult ) - { - final int[] cellSize = new int[ rai.numDimensions() ]; - Arrays.fill( cellSize, 1 ); - for ( int d = 0; d < rai.numDimensions() - 1; d++ ) - cellSize[ d ] = (int) rai.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + } else if (cacheResult) { + final int[] cellSize = new int[rai.numDimensions()]; + Arrays.fill(cellSize, 1); + for (int d = 0; d < rai.numDimensions() - 1; d++) + cellSize[d] = (int) rai.dimension(d); + rai = FusionTools.cacheRandomAccessibleInterval(rai, Long.MAX_VALUE, + Views.iterable(rai).firstElement().createVariable(), cellSize); } return rai; @@ -367,103 +335,95 @@ else if ( cacheResult ) // ========== Volatile image access ========== @Override - public RandomAccessibleInterval< V > getVolatileImage( final int timepointId, final int level, final ImgLoaderHint... hints ) - { - final ViewerSetupImgLoader< T, V > viewerSetupIL = getUnderlyingViewerSetupImgLoader(); - final MultiResolutionSetupImgLoader< T > multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); + public RandomAccessibleInterval getVolatileImage(final int timepointId, final int level, final ImgLoaderHint... hints) { + final ViewerSetupImgLoader viewerSetupIL = getUnderlyingViewerSetupImgLoader(); + final MultiResolutionSetupImgLoader multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); - if ( !active ) - return viewerSetupIL.getVolatileImage( timepointId, level, hints ); + if (!active) + return viewerSetupIL.getVolatileImage(timepointId, level, hints); - final int n = multiResSetupIL.getImageSize( timepointId ).numDimensions(); + final int n = multiResSetupIL.getImageSize(timepointId).numDimensions(); // Calculate downsampling factors for this mipmap level - final int[] dsFactors = new int[ n ]; - final double[] dsD = viewerSetupIL.getMipmapResolutions()[ level ]; - for ( int d = 0; d < n; d++ ) - dsFactors[ d ] = (int) dsD[ d ]; - dsFactors[ n - 1 ] = 1; + final int[] dsFactors = new int[n]; + final double[] dsD = viewerSetupIL.getMipmapResolutions()[level]; + for (int d = 0; d < n; d++) + dsFactors[d] = (int) dsD[d]; + dsFactors[n - 1] = 1; // Apply correction to volatile image // Note: The volatile validity flag propagation may not be perfect, // but BDV will re-request invalid pixels automatically - @SuppressWarnings("unchecked") - final RandomAccessibleInterval< V > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( - viewerSetupIL.getVolatileImage( timepointId, level, hints ), - getOrCreateBrightImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), - getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), - getVolatileImageType() ); - - return rai; + return FlatFieldCorrectedRandomAccessibleIntervals.create( + viewerSetupIL.getVolatileImage(timepointId, level, hints), + getOrCreateBrightImgDownsampled(new ViewId(timepointId, setupId), dsFactors), + getOrCreateDarkImgDownsampled(new ViewId(timepointId, setupId), dsFactors), + getVolatileImageType()); } // ========== Float image access ========== @Override - public RandomAccessibleInterval< FloatType > getFloatImage( final int timepointId, final boolean normalize, final ImgLoaderHint... hints ) - { - return getFloatImage( timepointId, 0, normalize, hints ); + public RandomAccessibleInterval getFloatImage(final int timepointId, final boolean normalize, final ImgLoaderHint... hints) { + return getFloatImage(timepointId, 0, normalize, hints); } @Override - public RandomAccessibleInterval< FloatType > getFloatImage( final int timepointId, final int level, final boolean normalize, final ImgLoaderHint... hints ) - { - final ViewerSetupImgLoader< T, V > viewerSetupIL = getUnderlyingViewerSetupImgLoader(); - final MultiResolutionSetupImgLoader< T > multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); - - if ( !active ) - return multiResSetupIL.getFloatImage( timepointId, level, normalize, hints ); - - final int n = multiResSetupIL.getImageSize( timepointId ).numDimensions(); - - final int[] dsFactors = new int[ n ]; - final double[] dsD = viewerSetupIL.getMipmapResolutions()[ level ]; - for ( int d = 0; d < n; d++ ) - dsFactors[ d ] = (int) dsD[ d ]; - dsFactors[ n - 1 ] = 1; - - RandomAccessibleInterval< FloatType > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( - viewerSetupIL.getImage( timepointId, level, hints ), - getOrCreateBrightImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), - getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ), - new FloatType() ); - - if ( normalize ) - { - rai = new VirtuallyNormalizedRandomAccessibleInterval<>( rai ); + public RandomAccessibleInterval getFloatImage(final int timepointId, final int level, final boolean normalize, final ImgLoaderHint... hints) { + final ViewerSetupImgLoader viewerSetupIL = getUnderlyingViewerSetupImgLoader(); + final MultiResolutionSetupImgLoader multiResSetupIL = getUnderlyingMultiResSetupImgLoader(); + + if (!active) + return multiResSetupIL.getFloatImage(timepointId, level, normalize, hints); + + final int n = multiResSetupIL.getImageSize(timepointId).numDimensions(); + + final int[] dsFactors = new int[n]; + final double[] dsD = viewerSetupIL.getMipmapResolutions()[level]; + for (int d = 0; d < n; d++) + dsFactors[d] = (int) dsD[d]; + dsFactors[n - 1] = 1; + + RandomAccessibleInterval rai = FlatFieldCorrectedRandomAccessibleIntervals.create( + viewerSetupIL.getImage(timepointId, level, hints), + getOrCreateBrightImgDownsampled(new ViewId(timepointId, setupId), dsFactors), + getOrCreateDarkImgDownsampled(new ViewId(timepointId, setupId), dsFactors), + new FloatType()); + + if (normalize) { + rai = new VirtuallyNormalizedRandomAccessibleInterval<>(rai); } // Handle caching/loading boolean loadCompletelyRequested = false; - for ( final ImgLoaderHint hint : hints ) - if ( hint == ImgLoaderHints.LOAD_COMPLETELY ) - loadCompletelyRequested = true; + for (final ImgLoaderHint hint : hints) + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { + loadCompletelyRequested = true; + break; + } - if ( loadCompletelyRequested ) - { + if (loadCompletelyRequested) { long numPx = 1; - for ( int d = 0; d < rai.numDimensions(); d++ ) - numPx *= rai.dimension( d ); + for (int d = 0; d < rai.numDimensions(); d++) + numPx *= rai.dimension(d); - final ImgFactory< FloatType > imgFactory; - if ( Math.log( numPx ) / Math.log( 2 ) < 31 ) + final ImgFactory imgFactory; + if (Math.log(numPx) / Math.log(2) < 31) imgFactory = new ArrayImgFactory<>(); else imgFactory = new CellImgFactory<>(); - final Img< FloatType > loadedImg = imgFactory.create( rai, new FloatType() ); - RealTypeConverters.copyFromTo( Views.extendZero( rai ), loadedImg ); + final Img loadedImg = imgFactory.create(rai, new FloatType()); + RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; - } - else if ( cacheResult ) - { - final int[] cellSize = new int[ rai.numDimensions() ]; - Arrays.fill( cellSize, 1 ); - for ( int d = 0; d < rai.numDimensions() - 1; d++ ) - cellSize[ d ] = (int) rai.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, - new FloatType(), cellSize ); + } else if (cacheResult) { + final int[] cellSize = new int[rai.numDimensions()]; + Arrays.fill(cellSize, 1); + for (int d = 0; d < rai.numDimensions() - 1; d++) + cellSize[d] = (int) rai.dimension(d); + rai = FusionTools.cacheRandomAccessibleInterval(rai, Long.MAX_VALUE, + new FloatType(), cellSize); } return rai; @@ -472,51 +432,43 @@ else if ( cacheResult ) // ========== Metadata delegation ========== @Override - public T getImageType() - { + public T getImageType() { return getUnderlyingViewerSetupImgLoader().getImageType(); } @Override - public V getVolatileImageType() - { + public V getVolatileImageType() { return getUnderlyingViewerSetupImgLoader().getVolatileImageType(); } @Override - public double[][] getMipmapResolutions() - { + public double[][] getMipmapResolutions() { return getUnderlyingViewerSetupImgLoader().getMipmapResolutions(); } @Override - public AffineTransform3D[] getMipmapTransforms() - { + public AffineTransform3D[] getMipmapTransforms() { return getUnderlyingViewerSetupImgLoader().getMipmapTransforms(); } @Override - public int numMipmapLevels() - { + public int numMipmapLevels() { return getUnderlyingViewerSetupImgLoader().numMipmapLevels(); } @Override - public Dimensions getImageSize( final int timepointId ) - { - return getUnderlyingMultiResSetupImgLoader().getImageSize( timepointId ); + public Dimensions getImageSize(final int timepointId) { + return getUnderlyingMultiResSetupImgLoader().getImageSize(timepointId); } @Override - public Dimensions getImageSize( final int timepointId, final int level ) - { - return getUnderlyingMultiResSetupImgLoader().getImageSize( timepointId, level ); + public Dimensions getImageSize(final int timepointId, final int level) { + return getUnderlyingMultiResSetupImgLoader().getImageSize(timepointId, level); } @Override - public VoxelDimensions getVoxelSize( final int timepointId ) - { - return getUnderlyingMultiResSetupImgLoader().getVoxelSize( timepointId ); + public VoxelDimensions getVoxelSize(final int timepointId) { + return getUnderlyingMultiResSetupImgLoader().getVoxelSize(timepointId); } } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java index d019975e8..2c0573549 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java @@ -41,7 +41,6 @@ import mpicbg.spim.data.generic.sequence.ImgLoaderIo; import mpicbg.spim.data.generic.sequence.ImgLoaders; import mpicbg.spim.data.generic.sequence.XmlIoBasicImgLoader; -import mpicbg.spim.data.sequence.ImgLoader; import mpicbg.spim.data.sequence.ViewId; import net.imglib2.util.Pair; @@ -53,8 +52,7 @@ */ @ImgLoaderIo(format = "spimreconstruction.wrapped.flatfield.viewer", type = ViewerFlatfieldCorrectionWrappedImgLoader.class) public class XmlIoViewerFlatfieldCorrectionWrappedImgLoader - implements XmlIoBasicImgLoader< ViewerFlatfieldCorrectionWrappedImgLoader > -{ + implements XmlIoBasicImgLoader { public final static String WRAPPED_IMGLOADER_TAG = "WrappedImgLoader"; public final static String FLATFIELDS_TAG = "FlatFields"; public final static String FLATFIELD_TAG = "FlatField"; @@ -65,111 +63,97 @@ public class XmlIoViewerFlatfieldCorrectionWrappedImgLoader @Override public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File basePath, - AbstractSequenceDescription< ?, ?, ? > sequenceDescription) - { - Element wrappedImgLoaderEl = elem.getChild( WRAPPED_IMGLOADER_TAG ).getChild( IMGLOADER_TAG ); - XmlIoBasicImgLoader< ? > xmlIoWrapped = null; - try - { + AbstractSequenceDescription sequenceDescription) { + Element wrappedImgLoaderEl = elem.getChild(WRAPPED_IMGLOADER_TAG).getChild(IMGLOADER_TAG); + XmlIoBasicImgLoader xmlIoWrapped; + try { xmlIoWrapped = ImgLoaders - .createXmlIoForFormat( wrappedImgLoaderEl.getAttributeValue( IMGLOADER_FORMAT_ATTRIBUTE_NAME ) ); - } - catch ( SpimDataInstantiationException e ) - { + .createXmlIoForFormat(wrappedImgLoaderEl.getAttributeValue(IMGLOADER_FORMAT_ATTRIBUTE_NAME)); + } catch (SpimDataInstantiationException e) { e.printStackTrace(); return null; } boolean cached = false; boolean active = false; - try - { - cached = elem.getAttribute( CACHED_TAG ).getBooleanValue(); - active = elem.getAttribute( ACTIVE_TAG ).getBooleanValue(); - } - catch ( DataConversionException e ) - { + try { + cached = elem.getAttribute(CACHED_TAG).getBooleanValue(); + active = elem.getAttribute(ACTIVE_TAG).getBooleanValue(); + } catch (DataConversionException e) { e.printStackTrace(); } - BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml( wrappedImgLoaderEl, basePath, sequenceDescription ); + BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml(wrappedImgLoaderEl, basePath, sequenceDescription); // Verify wrapped loader is a ViewerImgLoader - if ( !( wrappedImgLoader instanceof ViewerImgLoader ) ) - { - System.err.println( "ViewerFlatfieldCorrectionWrappedImgLoader requires a ViewerImgLoader, but got: " - + wrappedImgLoader.getClass().getName() ); + if (!(wrappedImgLoader instanceof ViewerImgLoader)) { + System.err.println("ViewerFlatfieldCorrectionWrappedImgLoader requires a ViewerImgLoader, but got: " + + wrappedImgLoader.getClass().getName()); return null; } ViewerFlatfieldCorrectionWrappedImgLoader res = - new ViewerFlatfieldCorrectionWrappedImgLoader( (ViewerImgLoader) wrappedImgLoader, cached ); - - Element flatfields = elem.getChild( FLATFIELDS_TAG ); - for ( Element flatfield : flatfields.getChildren() ) - { - int tp = Integer.parseInt( flatfield.getAttributeValue( TIMEPOINTS_TIMEPOINT_TAG ) ); - int vs = Integer.parseInt( flatfield.getAttributeValue( VIEWSETUP_TAG ) ); - File brightImg = XmlHelpers.loadPath( flatfield, BRIGHTIMG_TAG, basePath ); - File darkImg = XmlHelpers.loadPath( flatfield, DARKIMG_TAG, basePath ); - res.setBrightImage( new ViewId( tp, vs ), brightImg ); - res.setDarkImage( new ViewId( tp, vs ), darkImg ); + new ViewerFlatfieldCorrectionWrappedImgLoader((ViewerImgLoader) wrappedImgLoader, cached); + + Element flatfields = elem.getChild(FLATFIELDS_TAG); + for (Element flatfield : flatfields.getChildren()) { + int tp = Integer.parseInt(flatfield.getAttributeValue(TIMEPOINTS_TIMEPOINT_TAG)); + int vs = Integer.parseInt(flatfield.getAttributeValue(VIEWSETUP_TAG)); + File brightImg = XmlHelpers.loadPath(flatfield, BRIGHTIMG_TAG, basePath); + File darkImg = XmlHelpers.loadPath(flatfield, DARKIMG_TAG, basePath); + res.setBrightImage(new ViewId(tp, vs), brightImg); + res.setDarkImage(new ViewId(tp, vs), darkImg); } - res.setActive( active ); + res.setActive(active); return res; } @Override - public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File basePath) - { - final Map< ViewId, Pair< File, File > > fileMap = imgLoader.fileMap; + public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File basePath) { + final Map> fileMap = imgLoader.fileMap; - final Element wholeElem = new Element( IMGLOADER_TAG ); - wholeElem.setAttribute( IMGLOADER_FORMAT_ATTRIBUTE_NAME, - this.getClass().getAnnotation( ImgLoaderIo.class ).format() ); - final Element wrappedIL = new Element( WRAPPED_IMGLOADER_TAG ); + final Element wholeElem = new Element(IMGLOADER_TAG); + wholeElem.setAttribute(IMGLOADER_FORMAT_ATTRIBUTE_NAME, + this.getClass().getAnnotation(ImgLoaderIo.class).format()); + final Element wrappedIL = new Element(WRAPPED_IMGLOADER_TAG); - wholeElem.setAttribute( ACTIVE_TAG, Boolean.toString( imgLoader.isActive() ) ); - wholeElem.setAttribute( CACHED_TAG, Boolean.toString( imgLoader.isCached() ) ); + wholeElem.setAttribute(ACTIVE_TAG, Boolean.toString(imgLoader.isActive())); + wholeElem.setAttribute(CACHED_TAG, Boolean.toString(imgLoader.isCached())); - try - { - @SuppressWarnings({ "unchecked", "rawtypes" }) + try { + @SuppressWarnings({"rawtypes"}) XmlIoBasicImgLoader loaderIO = ImgLoaders - .createXmlIoForImgLoaderClass( imgLoader.getWrappedImgLoader().getClass() ); + .createXmlIoForImgLoaderClass(imgLoader.getWrappedImgLoader().getClass()); @SuppressWarnings("unchecked") - Element wrappedInner = loaderIO.toXml( imgLoader.getWrappedImgLoader(), basePath ); - wrappedIL.addContent( wrappedInner ); - } - catch ( SpimDataInstantiationException e ) - { + Element wrappedInner = loaderIO.toXml(imgLoader.getWrappedImgLoader(), basePath); + wrappedIL.addContent(wrappedInner); + } catch (SpimDataInstantiationException e) { e.printStackTrace(); return null; } - final Element elFlatfields = new Element( FLATFIELDS_TAG ); + final Element elFlatfields = new Element(FLATFIELDS_TAG); - for ( ViewId vid : fileMap.keySet() ) - { - final Pair< File, File > files = fileMap.get( vid ); - if ( files == null || ( files.getA() == null && files.getB() == null ) ) + for (ViewId vid : fileMap.keySet()) { + final Pair files = fileMap.get(vid); + if (files == null || (files.getA() == null && files.getB() == null)) continue; - final Element elFlatfield = new Element( FLATFIELD_TAG ); - elFlatfield.setAttribute( TIMEPOINTS_TIMEPOINT_TAG, Integer.toString( vid.getTimePointId() ) ); - elFlatfield.setAttribute( VIEWSETUP_TAG, Integer.toString( vid.getViewSetupId() ) ); + final Element elFlatfield = new Element(FLATFIELD_TAG); + elFlatfield.setAttribute(TIMEPOINTS_TIMEPOINT_TAG, Integer.toString(vid.getTimePointId())); + elFlatfield.setAttribute(VIEWSETUP_TAG, Integer.toString(vid.getViewSetupId())); - if ( files.getA() != null ) - elFlatfield.addContent( XmlHelpers.pathElement( BRIGHTIMG_TAG, files.getA(), basePath ) ); - if ( files.getB() != null ) - elFlatfield.addContent( XmlHelpers.pathElement( DARKIMG_TAG, files.getB(), basePath ) ); + if (files.getA() != null) + elFlatfield.addContent(XmlHelpers.pathElement(BRIGHTIMG_TAG, files.getA(), basePath)); + if (files.getB() != null) + elFlatfield.addContent(XmlHelpers.pathElement(DARKIMG_TAG, files.getB(), basePath)); - elFlatfields.addContent( elFlatfield ); + elFlatfields.addContent(elFlatfield); } - wholeElem.addContent( wrappedIL ); - wholeElem.addContent( elFlatfields ); + wholeElem.addContent(wrappedIL); + wholeElem.addContent(elFlatfields); return wholeElem; } } From f25371a756f44b66df1421332ddb6b5c4d479d3b Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 10:40:10 -0500 Subject: [PATCH 06/31] Refactor some bright/darkfield loading code --- .../LazyLoadingFlatFieldCorrectionMap.java | 74 ++++++++---------- ...onFlatfieldCorrectionWrappedImgLoader.java | 75 ++++++++----------- 2 files changed, 64 insertions(+), 85 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java index 1c98182e6..540ad66ed 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java @@ -25,6 +25,7 @@ import java.io.File; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import ij.IJ; import ij.ImagePlus; @@ -41,7 +42,9 @@ public abstract class LazyLoadingFlatFieldCorrectionMap im protected final Map< File, RandomAccessibleInterval< FloatType > > raiMap; protected final Map> fileMap; - + + private static final Pair NULL_PAIR = new ValuePair<>(null, null); + public LazyLoadingFlatFieldCorrectionMap() { raiMap = new HashMap<>(); @@ -49,62 +52,49 @@ public LazyLoadingFlatFieldCorrectionMap() } @Override - public void setBrightImage(ViewId vId, File imgFile) - { - if (!fileMap.containsKey( vId )) - fileMap.put( vId, new ValuePair< File, File >( null, null ) ); - - final Pair< File, File > oldPair = fileMap.get( vId ); - fileMap.put( vId, new ValuePair< File, File >( imgFile, oldPair.getB() ) ); + public void setBrightImage(ViewId vId, File imgFile) { + final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); + fileMap.put(vId, new ValuePair<>(imgFile, oldPair.getB())); } @Override - public void setDarkImage(ViewId vId, File imgFile) - { - if (!fileMap.containsKey( vId )) - fileMap.put( vId, new ValuePair< File, File >( null, null ) ); - - final Pair< File, File > oldPair = fileMap.get( vId ); - fileMap.put( vId, new ValuePair< File, File >( oldPair.getA(), imgFile ) ); + public void setDarkImage(ViewId vId, File imgFile) { + final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); + fileMap.put(vId, new ValuePair<>(oldPair.getA(), imgFile)); } - - protected RandomAccessibleInterval< FloatType > getBrightImg(ViewId vId) - { - if (!fileMap.containsKey( vId )) - return null; - final File fileToLoad = fileMap.get( vId ).getA(); - - if (fileToLoad == null) - return null; + protected RandomAccessibleInterval getBrightImg(ViewId vId) { + return getImg(vId, Pair::getA); + } - loadFileIfNecessary( fileToLoad ); - return raiMap.get( fileToLoad ); + protected RandomAccessibleInterval getDarkImg(ViewId vId) { + return getImg(vId, Pair::getB); } - protected RandomAccessibleInterval< FloatType > getDarkImg(ViewId vId) - { - if (!fileMap.containsKey( vId )) + /** + * Get image for view id; the brightfield is stored in the A element of the pair, the darkfield in B + * @param vId view id + * @param fileSelector function to select file from pair + * @return image, or null if not set + */ + private RandomAccessibleInterval getImg(ViewId vId, Function, File> fileSelector) { + if (!fileMap.containsKey(vId)) return null; - final File fileToLoad = fileMap.get( vId ).getB(); - + final File fileToLoad = fileSelector.apply(fileMap.get(vId)); if (fileToLoad == null) return null; - loadFileIfNecessary( fileToLoad ); - return raiMap.get( fileToLoad ); + return loadFileIfNecessary(fileToLoad); } - protected void loadFileIfNecessary(File file) - { - if (raiMap.containsKey( file )) - return; - - final ImagePlus imp = IJ.openImage( file.getAbsolutePath() ); - final RandomAccessibleInterval< FloatType > img = ImageJFunctions.convertFloat( imp ).copy(); - - raiMap.put( file, img ); + private RandomAccessibleInterval loadFileIfNecessary(File file) { + if (!raiMap.containsKey(file)) { + final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); + final RandomAccessibleInterval img = ImageJFunctions.convertFloat(imp).copy(); + raiMap.put(file, img); + } + return raiMap.get(file); } public static void main(String[] args) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index 1edc09e1d..d4e57c715 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -28,6 +28,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import bdv.export.WriteSequenceToHdf5; import ij.ImageJ; @@ -55,6 +57,7 @@ import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Pair; import net.imglib2.util.ValuePair; +import net.imglib2.view.IntervalView; import net.imglib2.view.Views; import net.preibisch.mvrecon.fiji.plugin.queryXML.LoadParseQueryXML; import net.preibisch.mvrecon.fiji.spimdata.SpimData2; @@ -66,7 +69,7 @@ public class MultiResolutionFlatfieldCorrectionWrappedImgLoader extends LazyLoadingFlatFieldCorrectionMap< MultiResolutionImgLoader > implements MultiResolutionImgLoader { - private MultiResolutionImgLoader wrappedImgLoader; + private final MultiResolutionImgLoader wrappedImgLoader; private boolean active; private boolean cacheResult; @@ -89,55 +92,41 @@ public MultiResolutionFlatfieldCorrectionWrappedImgLoader(MultiResolutionImgLoad dsRaiMap = new HashMap<>(); } - protected RandomAccessibleInterval< FloatType > getOrCreateBrightImgDownsampled(ViewId vId, - int[] downsamplingFactors) - { - ArrayList< Integer > dsFactorList = new ArrayList< Integer >(); - for ( int i : downsamplingFactors ) - dsFactorList.add( i ); - - final ValuePair< File, List< Integer > > key = new ValuePair<>( fileMap.get( vId ).getA(), dsFactorList ); - - if ( !dsRaiMap.containsKey( key ) ) - { - final RandomAccessibleInterval< FloatType > brightImg = getBrightImg( vId ); - - if ( brightImg == null ) - return null; - - // NB: we add a singleton z-dimension here for downsampleHDF5 to - // work - final RandomAccessibleInterval< FloatType > downsampled = downsampleHDF5( - Views.addDimension( brightImg, 0, 0 ), downsamplingFactors ); - dsRaiMap.put( key, downsampled ); - } - - return dsRaiMap.get( key ); + protected RandomAccessibleInterval< FloatType > getOrCreateBrightImgDownsampled(ViewId vId, int[] downsamplingFactors) { + return getOrCreateDownsampledImg(vId, downsamplingFactors, Pair::getA, this::getBrightImg); } - protected RandomAccessibleInterval< FloatType > getOrCreateDarkImgDownsampled(ViewId vId, int[] downsamplingFactors) - { - ArrayList< Integer > dsFactorList = new ArrayList< Integer >(); - for ( int i : downsamplingFactors ) - dsFactorList.add( i ); - - final ValuePair< File, List< Integer > > key = new ValuePair<>( fileMap.get( vId ).getB(), dsFactorList ); - - if ( !dsRaiMap.containsKey( key ) ) - { - final RandomAccessibleInterval< FloatType > darkImg = getDarkImg( vId ); + protected RandomAccessibleInterval< FloatType > getOrCreateDarkImgDownsampled(ViewId vId, int[] downsamplingFactors) { + return getOrCreateDownsampledImg(vId, downsamplingFactors, Pair::getB, this::getDarkImg); + } - if ( darkImg == null ) + /** + * Generic method to get a downsampled image or do downsampling on the fly. The bright image + * is stored in the A element of the pair, the dark image in B. + */ + private RandomAccessibleInterval getOrCreateDownsampledImg( + ViewId vId, + int[] downsamplingFactors, + Function, File> fileSelector, + Function> imgGetter + ) { + // Convert to a list here to have a proper hash code for the map key + List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); + final ValuePair> key = new ValuePair<>(fileSelector.apply(fileMap.get(vId)), dsFactorList); + + if (!dsRaiMap.containsKey(key)) { + final RandomAccessibleInterval img = imgGetter.apply(vId); + + if (img == null) return null; - // NB: we add a singleton z-dimension here for downsampleHDF5 to - // work - final RandomAccessibleInterval< FloatType > downsampled = downsampleHDF5( - Views.addDimension( darkImg, 0, 0 ), downsamplingFactors ); - dsRaiMap.put( key, downsampled ); + // Add a singleton z-dimension here for downsampleHDF5 to work + final IntervalView extendedImg = Views.addDimension(img, 0, 0); + final RandomAccessibleInterval downsampled = downsampleHDF5(extendedImg, downsamplingFactors); + dsRaiMap.put(key, downsampled); } - return dsRaiMap.get( key ); + return dsRaiMap.get(key); } @Override From 4f4b596f5d75c0b58525402d3f5400b29b314d79 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 11:03:13 -0500 Subject: [PATCH 07/31] Use new downsampling code in correction --- ...onFlatfieldCorrectionWrappedImgLoader.java | 116 +++++------------- 1 file changed, 33 insertions(+), 83 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index d4e57c715..65282445d 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -23,7 +23,6 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -31,7 +30,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import bdv.export.WriteSequenceToHdf5; import ij.ImageJ; import mpicbg.spim.data.generic.sequence.ImgLoaderHint; import mpicbg.spim.data.generic.sequence.ImgLoaderHints; @@ -39,12 +37,11 @@ import mpicbg.spim.data.sequence.MultiResolutionSetupImgLoader; import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.data.sequence.VoxelDimensions; -import net.imglib2.Cursor; import net.imglib2.Dimensions; -import net.imglib2.FinalDimensions; -import net.imglib2.RandomAccess; -import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.blocks.BlockAlgoUtils; +import net.imglib2.algorithm.blocks.BlockSupplier; +import net.imglib2.algorithm.blocks.downsample.Downsample; import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; @@ -57,7 +54,6 @@ import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Pair; import net.imglib2.util.ValuePair; -import net.imglib2.view.IntervalView; import net.imglib2.view.Views; import net.preibisch.mvrecon.fiji.plugin.queryXML.LoadParseQueryXML; import net.preibisch.mvrecon.fiji.spimdata.SpimData2; @@ -120,9 +116,7 @@ private RandomAccessibleInterval getOrCreateDownsampledImg( if (img == null) return null; - // Add a singleton z-dimension here for downsampleHDF5 to work - final IntervalView extendedImg = Views.addDimension(img, 0, 0); - final RandomAccessibleInterval downsampled = downsampleHDF5(extendedImg, downsamplingFactors); + final RandomAccessibleInterval downsampled = downsampleHDF5(img, downsamplingFactors); dsRaiMap.put(key, downsampled); } @@ -387,84 +381,40 @@ public Dimensions getImageSize(int timepointId, int level) } /** - * downsampling code form {@link WriteSequenceToHdf5}, distilled into one - * method - * - * @param input - * image to downsample - * @param dsFactor - * factors to downsample by - * @param - * the image type - * @return downsampled image + * Downsample an image using the imglib2-algorithm blocks API. + * + * @param input image to downsample + * @param dsFactor factors to downsample by (may have more dimensions than input) + * @param the image type + * @return downsampled image, or input unchanged if no downsampling needed */ - public static & NativeType< T >> RandomAccessibleInterval< T > downsampleHDF5( - RandomAccessibleInterval< T > input, final int[] dsFactor) - { - final long[] blockMin = new long[input.numDimensions()]; - - final long[] outDim = new long[input.numDimensions()]; - for ( int d = 0; d < input.numDimensions(); d++ ) - outDim[d] = Math.max( input.dimension( d ) / dsFactor[d], 1 ); - - final Img< T > downsampled = new ArrayImgFactory< T >().create( new FinalDimensions( outDim ), - Views.iterable( input ).firstElement().createVariable() ); - final RandomAccess< T > randomAccess = Views.extendBorder( input ).randomAccess(); - - final Cursor< T > out = downsampled.cursor(); - - double scale = 1; - for ( int f : dsFactor ) - scale *= f; - scale = 1.0 / scale; - - final int numBlockPixels = (int) ( outDim[0] * outDim[1] * outDim[2] ); - final double[] accumulator = new double[numBlockPixels]; - - randomAccess.setPosition( blockMin ); - - final int ox = (int) outDim[0]; - final int oy = (int) outDim[1]; - final int oz = (int) outDim[2]; + public static & NativeType> RandomAccessibleInterval downsampleHDF5( + RandomAccessibleInterval input, + final int[] dsFactor + ) { + final int n = input.numDimensions(); + + // Build effective factors matching input dimensions, check if downsampling needed + boolean needsDownsampling = false; + final int[] effectiveFactors = new int[n]; + for (int d = 0; d < n; d++) { + effectiveFactors[d] = (d < dsFactor.length) ? dsFactor[d] : 1; + if (effectiveFactors[d] > 1) + needsDownsampling = true; + } - final int sx = ox * dsFactor[0]; - final int sy = oy * dsFactor[1]; - final int sz = oz * dsFactor[2]; + // Return input unchanged if all factors are 1 + if (!needsDownsampling) + return input; - int i = 0; - for ( int z = 0, bz = 0; z < sz; ++z ) - { - for ( int y = 0, by = 0; y < sy; ++y ) - { - for ( int x = 0, bx = 0; x < sx; ++x ) - { - accumulator[i] += randomAccess.get().getRealDouble(); - randomAccess.fwd( 0 ); - if ( ++bx == dsFactor[0] ) - { - bx = 0; - ++i; - } - } - randomAccess.move( -sx, 0 ); - randomAccess.fwd( 1 ); - if ( ++by == dsFactor[1] ) - by = 0; - else - i -= ox; - } - randomAccess.move( -sy, 1 ); - randomAccess.fwd( 2 ); - if ( ++bz == dsFactor[2] ) - bz = 0; - else - i -= ox * oy; - } + final long[] outDim = new long[n]; + for (int d = 0; d < n; d++) + outDim[d] = Math.max(input.dimension(d) / effectiveFactors[d], 1); - for ( int j = 0; j < numBlockPixels; ++j ) - out.next().setReal( accumulator[j] * scale ); + final BlockSupplier blocks = BlockSupplier.of(input) + .andThen(Downsample.downsample(effectiveFactors)); - return downsampled; + return BlockAlgoUtils.cellImg(blocks, outDim, new int[]{64}); } public static void main(String[] args) From 882ac04b5f86b3620fad2c223fbfad50f758c993 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 11:13:31 -0500 Subject: [PATCH 08/31] Refactor bright/darkfield loading in viewer implementation --- ...erFlatfieldCorrectionWrappedImgLoader.java | 62 ++++++++----------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index db5839e36..34d1c9def 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -28,6 +28,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import bdv.ViewerImgLoader; import bdv.ViewerSetupImgLoader; @@ -192,54 +194,40 @@ protected void loadFileIfNecessary(final File file) { protected RandomAccessibleInterval getOrCreateBrightImgDownsampled( final ViewId vId, - final int[] downsamplingFactors) { - if (!fileMap.containsKey(vId) || fileMap.get(vId).getA() == null) - return null; - - final ArrayList dsFactorList = new ArrayList<>(); - for (final int i : downsamplingFactors) - dsFactorList.add(i); - - final ValuePair> key = new ValuePair<>(fileMap.get(vId).getA(), dsFactorList); - - if (!dsRaiMap.containsKey(key)) { - final RandomAccessibleInterval brightImg = getBrightImg(vId); - - if (brightImg == null) - return null; - - // Add singleton z-dimension for downsampleHDF5 to work - final RandomAccessibleInterval downsampled = - MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5( - Views.addDimension(brightImg, 0, 0), downsamplingFactors); - dsRaiMap.put(key, downsampled); - } - - return dsRaiMap.get(key); + final int[] downsamplingFactors + ) { + return getOrCreateDownsampledImg(vId, downsamplingFactors, Pair::getA, this::getBrightImg); } protected RandomAccessibleInterval getOrCreateDarkImgDownsampled( final ViewId vId, - final int[] downsamplingFactors) { - if (!fileMap.containsKey(vId) || fileMap.get(vId).getB() == null) - return null; - - final ArrayList dsFactorList = new ArrayList<>(); - for (final int i : downsamplingFactors) - dsFactorList.add(i); + final int[] downsamplingFactors + ) { + return getOrCreateDownsampledImg(vId, downsamplingFactors, Pair::getB, this::getDarkImg); + } - final ValuePair> key = new ValuePair<>(fileMap.get(vId).getB(), dsFactorList); + /** + * Generic method to get a downsampled image or do downsampling on the fly. The bright image + * is stored in the A element of the pair, the dark image in B. + */ + private RandomAccessibleInterval getOrCreateDownsampledImg( + ViewId vId, + int[] downsamplingFactors, + Function, File> fileSelector, + Function> imgGetter + ) { + // Convert to a list here to have a proper hash code for the map key + List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); + final ValuePair> key = new ValuePair<>(fileSelector.apply(fileMap.get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { - final RandomAccessibleInterval darkImg = getDarkImg(vId); + final RandomAccessibleInterval img = imgGetter.apply(vId); - if (darkImg == null) + if (img == null) return null; - // Add singleton z-dimension for downsampleHDF5 to work final RandomAccessibleInterval downsampled = - MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5( - Views.addDimension(darkImg, 0, 0), downsamplingFactors); + MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5(img, downsamplingFactors); dsRaiMap.put(key, downsampled); } From 26b859cf0c1df8ef7bb9e13f0f2b3cd568bfe832 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 11:31:21 -0500 Subject: [PATCH 09/31] Fix some IDE warnings --- ...onFlatfieldCorrectionWrappedImgLoader.java | 78 +++++++++++-------- ...erFlatfieldCorrectionWrappedImgLoader.java | 36 ++++----- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index 65282445d..33455919c 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -57,7 +57,6 @@ import net.imglib2.view.Views; import net.preibisch.mvrecon.fiji.plugin.queryXML.LoadParseQueryXML; import net.preibisch.mvrecon.fiji.spimdata.SpimData2; -import net.preibisch.mvrecon.fiji.spimdata.imgloaders.filemap2.FileMapImgLoaderLOCI2; import net.preibisch.mvrecon.process.fusion.FusionTools; @@ -168,8 +167,11 @@ public RandomAccessibleInterval< T > getImage(int timepointId, int level, ImgLoa final MultiResolutionSetupImgLoader< ? > wrpSetupIL = wrappedImgLoader.getSetupImgLoader( setupId ); - if(!active) - return (RandomAccessibleInterval< T >) wrpSetupIL.getImage( timepointId, level, hints ); + if(!active) { + @SuppressWarnings("unchecked") + RandomAccessibleInterval image = (RandomAccessibleInterval) wrpSetupIL.getImage(timepointId, level, hints); + return image; + } final int n = wrpSetupIL.getImageSize( timepointId ).numDimensions(); @@ -188,9 +190,12 @@ public RandomAccessibleInterval< T > getImage(int timepointId, int level, ImgLoa getOrCreateDarkImgDownsampled( new ViewId( timepointId, setupId ), dsFactors ) ); boolean loadCompletelyRequested = false; - for (ImgLoaderHint hint : hints) - if (hint == ImgLoaderHints.LOAD_COMPLETELY) + for (ImgLoaderHint hint : hints) { + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { loadCompletelyRequested = true; + break; + } + } if (loadCompletelyRequested) { @@ -199,13 +204,14 @@ public RandomAccessibleInterval< T > getImage(int timepointId, int level, ImgLoa numPx *= rai.dimension( d ); final ImgFactory< T > imgFactory; - if (Math.log(numPx) / Math.log( 2 ) < 31) - imgFactory = new ArrayImgFactory(); - else - imgFactory = new CellImgFactory(); + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(getImageType()); + } else { + imgFactory = new CellImgFactory<>(getImageType()); + } - Img< T > loadedImg = imgFactory.create( rai, getImageType() ); - RealTypeConverters.copyFromTo( Views.extendZero( rai ), loadedImg ); + Img loadedImg = imgFactory.create(rai); + RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; } @@ -215,8 +221,8 @@ else if ( cacheResult ) Arrays.fill( cellSize, 1 ); for ( int d = 0; d < rai.numDimensions() - 1; d++ ) cellSize[d] = (int) rai.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + rai = FusionTools.cacheRandomAccessibleInterval( + rai, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } return rai; } @@ -251,9 +257,12 @@ public RandomAccessibleInterval< FloatType > getFloatImage(int timepointId, int RandomAccessibleInterval< FloatType > raiNormalized = new VirtuallyNormalizedRandomAccessibleInterval<>( rai ); boolean loadCompletelyRequested = false; - for (ImgLoaderHint hint : hints) - if (hint == ImgLoaderHints.LOAD_COMPLETELY) + for (ImgLoaderHint hint : hints) { + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { loadCompletelyRequested = true; + break; + } + } if (loadCompletelyRequested) { @@ -262,13 +271,14 @@ public RandomAccessibleInterval< FloatType > getFloatImage(int timepointId, int numPx *= raiNormalized.dimension( d ); final ImgFactory< FloatType > imgFactory; - if (Math.log(numPx) / Math.log( 2 ) < 31) - imgFactory = new ArrayImgFactory(); - else - imgFactory = new CellImgFactory(); + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(new FloatType()); + } else { + imgFactory = new CellImgFactory<>(new FloatType()); + } - Img< FloatType > loadedImg = imgFactory.create( raiNormalized, new FloatType() ); - FileMapImgLoaderLOCI2.copy(Views.extendZero( raiNormalized ), loadedImg); + Img loadedImg = imgFactory.create(raiNormalized); + RealTypeConverters.copyFromTo(Views.extendZero(raiNormalized), loadedImg); raiNormalized = loadedImg; } @@ -278,17 +288,19 @@ else if ( cacheResult ) Arrays.fill( cellSize, 1 ); for ( int d = 0; d < raiNormalized.numDimensions() - 1; d++ ) cellSize[d] = (int) raiNormalized.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( raiNormalized, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + rai = FusionTools.cacheRandomAccessibleInterval( + raiNormalized, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } rai = raiNormalized; } - else - { + else { boolean loadCompletelyRequested = false; - for (ImgLoaderHint hint : hints) - if (hint == ImgLoaderHints.LOAD_COMPLETELY) + for (ImgLoaderHint hint : hints) { + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { loadCompletelyRequested = true; + break; + } + } if (loadCompletelyRequested) { @@ -298,12 +310,12 @@ else if ( cacheResult ) final ImgFactory< FloatType > imgFactory; if (Math.log(numPx) / Math.log( 2 ) < 31) - imgFactory = new ArrayImgFactory(); + imgFactory = new ArrayImgFactory<>(new FloatType()); else - imgFactory = new CellImgFactory(); + imgFactory = new CellImgFactory<>(new FloatType()); - Img< FloatType > loadedImg = imgFactory.create( rai, new FloatType() ); - FileMapImgLoaderLOCI2.copy(Views.extendZero( rai ), loadedImg); + Img loadedImg = imgFactory.create(rai); + RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; } @@ -313,8 +325,8 @@ else if ( cacheResult ) Arrays.fill( cellSize, 1 ); for ( int d = 0; d < rai.numDimensions() - 1; d++ ) cellSize[d] = (int) rai.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + rai = FusionTools.cacheRandomAccessibleInterval( + rai, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index 34d1c9def..f35ba7a4b 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -23,7 +23,6 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -81,6 +80,8 @@ public class ViewerFlatfieldCorrectionWrappedImgLoader private boolean active; private boolean cacheResult; + private static final Pair NULL_PAIR = new ValuePair<>(null, null); + /** Maps ViewId to (brightFile, darkFile) pair */ protected final Map> fileMap; @@ -143,17 +144,13 @@ public void setCached(final boolean cached) { } public void setBrightImage(final ViewId vId, final File imgFile) { - if (!fileMap.containsKey(vId)) - fileMap.put(vId, new ValuePair<>(null, null)); - - fileMap.compute(vId, (k, oldPair) -> new ValuePair<>(imgFile, oldPair.getB())); + final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); + fileMap.put(vId, new ValuePair<>(imgFile, oldPair.getB())); } public void setDarkImage(final ViewId vId, final File imgFile) { - if (!fileMap.containsKey(vId)) - fileMap.put(vId, new ValuePair<>(null, null)); - - fileMap.compute(vId, (k, oldPair) -> new ValuePair<>(oldPair.getA(), imgFile)); + final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); + fileMap.put(vId, new ValuePair<>(oldPair.getA(), imgFile)); } // ========== Image loading helpers ========== @@ -299,12 +296,13 @@ public RandomAccessibleInterval getImage(final int timepointId, final int lev numPx *= rai.dimension(d); final ImgFactory imgFactory; - if (Math.log(numPx) / Math.log(2) < 31) - imgFactory = new ArrayImgFactory<>(); - else - imgFactory = new CellImgFactory<>(); + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(getImageType()); + } else { + imgFactory = new CellImgFactory<>(getImageType()); + } - final Img loadedImg = imgFactory.create(rai, getImageType()); + final Img loadedImg = imgFactory.create(rai); RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; @@ -313,8 +311,8 @@ public RandomAccessibleInterval getImage(final int timepointId, final int lev Arrays.fill(cellSize, 1); for (int d = 0; d < rai.numDimensions() - 1; d++) cellSize[d] = (int) rai.dimension(d); - rai = FusionTools.cacheRandomAccessibleInterval(rai, Long.MAX_VALUE, - Views.iterable(rai).firstElement().createVariable(), cellSize); + rai = FusionTools.cacheRandomAccessibleInterval( + rai, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } return rai; @@ -397,11 +395,11 @@ public RandomAccessibleInterval getFloatImage(final int timepointId, final ImgFactory imgFactory; if (Math.log(numPx) / Math.log(2) < 31) - imgFactory = new ArrayImgFactory<>(); + imgFactory = new ArrayImgFactory<>(new FloatType()); else - imgFactory = new CellImgFactory<>(); + imgFactory = new CellImgFactory<>(new FloatType()); - final Img loadedImg = imgFactory.create(rai, new FloatType()); + final Img loadedImg = imgFactory.create(rai); RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; From cbebfe53355342394b922a44413adb1a11e352fc Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 14:03:16 -0500 Subject: [PATCH 10/31] Add a small script to convert tiffs to zarrs for testing --- .../flatfield/ConvertFlatfieldsToZarr.java | 303 ++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java new file mode 100644 index 000000000..872fa2b6a --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java @@ -0,0 +1,303 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.janelia.saalfeldlab.n5.Compression; +import org.janelia.saalfeldlab.n5.GzipCompression; +import org.janelia.saalfeldlab.n5.N5Writer; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.universe.StorageFormat; + +import ij.IJ; +import ij.ImagePlus; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.ViewSetup; +import net.imglib2.img.Img; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.numeric.real.FloatType; +import net.preibisch.mvrecon.fiji.spimdata.SpimData2; +import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; +import util.URITools; + +/** + * Utility to convert TIFF-based flatfield images to Zarr v3 format. + * + * This creates single-shard Zarr containers for each flatfield image, + * suitable for cloud storage or local chunked access. + * + * Also generates a test XML file with the Zarr paths configured. + */ +public class ConvertFlatfieldsToZarr { + + /** + * Convert a single TIFF image to Zarr v3 format. + * + * @param inputTiff path to input TIFF file + * @param outputZarr path for output .zarr container + * @throws IOException if writing fails + */ + public static void convertTiffToZarr(final File inputTiff, final File outputZarr) throws IOException { + System.out.println("Converting: " + inputTiff.getName() + " -> " + outputZarr.getName()); + + // Load TIFF via ImageJ + final ImagePlus imp = IJ.openImage(inputTiff.getAbsolutePath()); + if (imp == null) + throw new IOException("Failed to load TIFF: " + inputTiff); + + final Img img = ImageJFunctions.convertFloat(imp); + + // Create Zarr v3 writer + final N5Writer writer = URITools.instantiateN5Writer(StorageFormat.ZARR, outputZarr.toURI()); + + // Use a single block/shard for the entire image (flatfields are typically small) + final int[] blockSize = new int[img.numDimensions()]; + for (int d = 0; d < img.numDimensions(); d++) + blockSize[d] = (int) img.dimension(d); + + // Save (N5Utils.save creates the dataset internally) + final Compression compression = new GzipCompression(); + N5Utils.save(img, writer, "/", blockSize, compression); + + writer.close(); + System.out.println(" Created: " + outputZarr.getAbsolutePath()); + } + + /** + * Convert all flatfield TIFFs in a directory to Zarr format. + * + * @param inputDir directory containing TIFF files + * @param outputDir directory for output .zarr containers + * @return map of original filename (without extension) to output Zarr file + * @throws IOException if conversion fails + */ + public static Map convertDirectory(final File inputDir, final File outputDir) throws IOException { + if (!outputDir.exists()) + outputDir.mkdirs(); + + final Map converted = new HashMap<>(); + + final File[] tiffFiles = inputDir.listFiles((dir, name) -> + name.toLowerCase().endsWith(".tif") || name.toLowerCase().endsWith(".tiff")); + + if (tiffFiles == null || tiffFiles.length == 0) { + System.out.println("No TIFF files found in: " + inputDir); + return converted; + } + + for (final File tiff : tiffFiles) { + final String baseName = tiff.getName().replaceAll("\\.(tif|tiff)$", ""); + final File zarrOut = new File(outputDir, baseName + ".zarr"); + + convertTiffToZarr(tiff, zarrOut); + converted.put(baseName, zarrOut); + } + + return converted; + } + + /** + * Generate a test XML file with Zarr-based flatfield correction. + * + * @param baseXmlPath path to the original dataset XML + * @param outputXmlPath path for the new XML with flatfield correction + * @param flatfieldDir directory containing .zarr flatfield files + * @param brightPattern pattern for bright image names (e.g., "flatfield_tile%d") + * @param darkPattern pattern for dark image names (e.g., "darkfield_tile%d") + * @throws SpimDataException if XML loading fails + * @throws IOException if XML writing fails + */ + public static void generateTestXml( + final String baseXmlPath, + final String outputXmlPath, + final File flatfieldDir, + final String brightPattern, + final String darkPattern) throws SpimDataException, IOException { + + System.out.println("\n=== Generating Test XML ==="); + System.out.println("Base XML: " + baseXmlPath); + System.out.println("Output XML: " + outputXmlPath); + + // Load original dataset to get view setup info + final SpimData2 data = new XmlIoSpimData2().load(baseXmlPath); + + // Build XML content manually to wrap the original loader with flatfield correction + final StringBuilder xml = new StringBuilder(); + xml.append("\n"); + xml.append("\n"); + xml.append(" .\n"); + xml.append(" \n"); + + // ImageLoader section with flatfield wrapper + xml.append(" \n"); + + // Reference the original loader - read from original XML + xml.append(" \n"); + xml.append(" \n"); + xml.append(" \n"); + xml.append(" dataset.n5\n"); + xml.append(" \n"); + xml.append(" \n"); + + // Flatfield configuration for each view setup + xml.append(" \n"); + + for (final ViewSetup vs : data.getSequenceDescription().getViewSetupsOrdered()) { + final int setupId = vs.getId(); + final int tileId = vs.getTile().getId(); + + // Construct Zarr file names based on patterns + final String brightName = String.format(brightPattern, tileId) + ".zarr"; + final String darkName = String.format(darkPattern, tileId) + ".zarr"; + + final File brightFile = new File(flatfieldDir, brightName); + final File darkFile = new File(flatfieldDir, darkName); + + // Only include if files exist + final boolean hasBright = brightFile.exists(); + final boolean hasDark = darkFile.exists(); + + if (hasBright || hasDark) { + xml.append(" \n"); + if (hasBright) + xml.append(" ").append(brightFile.getName()).append("\n"); + if (hasDark) + xml.append(" ").append(darkFile.getName()).append("\n"); + xml.append(" \n"); + + System.out.println(" Setup " + setupId + " (tile " + tileId + "): " + + (hasBright ? "bright=" + brightName : "") + + (hasDark ? " dark=" + darkName : "")); + } + } + + xml.append(" \n"); + xml.append(" \n"); + + // Copy ViewSetups from original + xml.append(" \n"); + xml.append(" \n"); + for (final ViewSetup vs : data.getSequenceDescription().getViewSetupsOrdered()) { + xml.append(" \n"); + xml.append(" ").append(vs.getId()).append("\n"); + xml.append(" ").append(vs.getName()).append("\n"); + xml.append(" ").append(vs.getSize().dimension(0)).append(" ") + .append(vs.getSize().dimension(1)).append(" ") + .append(vs.getSize().dimension(2)).append("\n"); + xml.append(" \n"); + xml.append(" ").append(vs.getVoxelSize().unit()).append("\n"); + xml.append(" ").append(vs.getVoxelSize().dimension(0)).append(" ") + .append(vs.getVoxelSize().dimension(1)).append(" ") + .append(vs.getVoxelSize().dimension(2)).append("\n"); + xml.append(" \n"); + xml.append(" \n"); + xml.append(" ").append(vs.getChannel().getId()).append("\n"); + xml.append(" ").append(vs.getTile().getId()).append("\n"); + xml.append(" ").append(vs.getIllumination().getId()).append("\n"); + xml.append(" ").append(vs.getAngle().getId()).append("\n"); + xml.append(" \n"); + xml.append(" \n"); + } + xml.append(" \n"); + + // Timepoints + xml.append(" \n"); + xml.append(" 0\n"); + xml.append(" 0\n"); + xml.append(" \n"); + + xml.append(" \n"); + + // ViewRegistrations (identity transforms) + xml.append(" \n"); + for (final ViewSetup vs : data.getSequenceDescription().getViewSetupsOrdered()) { + xml.append(" \n"); + xml.append(" \n"); + xml.append(" 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0\n"); + xml.append(" \n"); + xml.append(" \n"); + } + xml.append(" \n"); + + xml.append("\n"); + + // Write XML file + try (FileWriter writer = new FileWriter(outputXmlPath)) { + writer.write(xml.toString()); + } + + System.out.println("\nCreated: " + outputXmlPath); + } + + /** + * Example main method demonstrating conversion and XML generation. + */ + public static void main(String[] args) throws Exception { + // Configuration - adjust these paths for your setup + final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; + final String dataPath = basePath + "data/"; + + // Input: directory with TIFF flatfields + final File tiffDir = new File(basePath, "dark_and_flatfields"); + + // Output: directory for Zarr flatfields + final File zarrDir = new File(basePath, "dark_and_flatfields_zarr"); + + // Step 1: Convert all TIFFs to Zarr + System.out.println("=== Step 1: Converting TIFFs to Zarr v3 ===\n"); + + if (tiffDir.exists()) { + convertDirectory(tiffDir, zarrDir); + } else { + throw new IOException("Input TIFF directory does not exist: " + tiffDir.getAbsolutePath()); + } + + // Step 2: Generate test XML + System.out.println("\n=== Step 2: Generating Test XML ===\n"); + + final String baseXml = dataPath + "dataset.xml"; + final String outputXml = dataPath + "dataset_corrected_zarr.xml"; + + if (new File(baseXml).exists()) { + generateTestXml( + baseXml, + outputXml, + zarrDir, + "flatfield_tile%d", // pattern for bright images + "darkfield_tile%d" // pattern for dark images + ); + } else { + System.out.println("Base XML not found: " + baseXml); + System.out.println("Skipping XML generation."); + } + + System.out.println("\n=== Done! ==="); + System.out.println("To test, load: " + outputXml); + } +} From e2582d085df49e1ae6a0bb2dd510d88a97c32ddd Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 14:03:49 -0500 Subject: [PATCH 11/31] First LLM-generated attempt at using N5 api for dark/flatfield IO --- .../popup/FlatFieldCorrectionPopup.java | 20 +-- .../FlatfieldCorrectionWrappedImgLoader.java | 35 ++++- .../LazyLoadingFlatFieldCorrectionMap.java | 140 +++++++++++++----- ...onFlatfieldCorrectionWrappedImgLoader.java | 7 +- .../TestViewerFlatfieldCorrection.java | 2 +- ...erFlatfieldCorrectionWrappedImgLoader.java | 127 ++++++++++++---- ...lIoFlatfieldCorrectedWrappedImgLoader.java | 127 ++++++++-------- ...erFlatfieldCorrectionWrappedImgLoader.java | 36 +++-- 8 files changed, 342 insertions(+), 152 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java index 6fd4d69b8..1f683da7a 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java @@ -27,6 +27,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; +import java.net.URI; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -101,10 +102,10 @@ public void actionPerformed(ActionEvent e) .isCached() : true ); - Map< ViewId, Pair< File, File > > fileMap = null; + Map> uriMap = null; if ( alreadyFF ) - fileMap = ( (LazyLoadingFlatFieldCorrectionMap< ImgLoader >) data.getSequenceDescription() - .getImgLoader() ).getFileMap(); + uriMap = ((LazyLoadingFlatFieldCorrectionMap) data.getSequenceDescription() + .getImgLoader()).getUriMap(); for ( Channel c : channels ) for ( Illumination ill : illums ) @@ -120,13 +121,12 @@ public void actionPerformed(ActionEvent e) } ).findAny().orElseGet( null ); if ( anyViewId != null ) - if ( fileMap.containsKey( anyViewId ) ) - { - Pair< File, File > files = fileMap.get( anyViewId ); - if ( files.getA() != null ) - bright = files.getA().getAbsolutePath(); - if ( files.getB() != null ) - dark = files.getB().getAbsolutePath(); + if (uriMap.containsKey(anyViewId)) { + Pair uris = uriMap.get(anyViewId); + if (uris.getA() != null) + bright = new File(uris.getA()).getAbsolutePath(); + if (uris.getB() != null) + dark = new File(uris.getB()).getAbsolutePath(); } } gdp.addMessage( "Channel: " + c.getName() + ", Illumination: " + ill.getName() + ":" ); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java index 55666c3df..be80c7f53 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java @@ -23,6 +23,7 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; +import java.net.URI; import mpicbg.spim.data.sequence.ImgLoader; import mpicbg.spim.data.sequence.ViewId; @@ -34,6 +35,36 @@ public interface FlatfieldCorrectionWrappedImgLoader exten public boolean isActive(); public void setCached(boolean cached); public boolean isCached(); - public void setBrightImage(ViewId vId, File imgFile); - public void setDarkImage(ViewId vId, File imgFile); + + /** + * Set the bright (flatfield) image for a view. + * @param vId view id + * @param imgUri URI to the bright image (supports file://, s3://, gs://, local paths) + */ + public void setBrightImage(ViewId vId, URI imgUri); + + /** + * Set the dark (darkfield) image for a view. + * @param vId view id + * @param imgUri URI to the dark image (supports file://, s3://, gs://, local paths) + */ + public void setDarkImage(ViewId vId, URI imgUri); + + /** + * Set the bright (flatfield) image for a view from a local file. + * @param vId view id + * @param imgFile local file path to the bright image + */ + default void setBrightImage(ViewId vId, File imgFile) { + setBrightImage(vId, imgFile == null ? null : imgFile.toURI()); + } + + /** + * Set the dark (darkfield) image for a view from a local file. + * @param vId view id + * @param imgFile local file path to the dark image + */ + default void setDarkImage(ViewId vId, File imgFile) { + setDarkImage(vId, imgFile == null ? null : imgFile.toURI()); + } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java index 540ad66ed..44407d246 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java @@ -9,12 +9,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -23,44 +23,51 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.universe.StorageFormat; + import ij.IJ; import ij.ImagePlus; import mpicbg.spim.data.sequence.ImgLoader; import mpicbg.spim.data.sequence.ViewId; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Cast; import net.imglib2.util.Pair; import net.imglib2.util.ValuePair; +import util.URITools; -public abstract class LazyLoadingFlatFieldCorrectionMap implements FlatfieldCorrectionWrappedImgLoader< IL > +public abstract class LazyLoadingFlatFieldCorrectionMap implements FlatfieldCorrectionWrappedImgLoader { - - protected final Map< File, RandomAccessibleInterval< FloatType > > raiMap; - protected final Map> fileMap; + protected final Map> raiMap; + protected final Map> uriMap; - private static final Pair NULL_PAIR = new ValuePair<>(null, null); + private static final Pair NULL_PAIR = new ValuePair<>(null, null); public LazyLoadingFlatFieldCorrectionMap() { raiMap = new HashMap<>(); - fileMap = new HashMap<>(); + uriMap = new HashMap<>(); } - + @Override - public void setBrightImage(ViewId vId, File imgFile) { - final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); - fileMap.put(vId, new ValuePair<>(imgFile, oldPair.getB())); + public void setBrightImage(ViewId vId, URI imgUri) { + final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); + uriMap.put(vId, new ValuePair<>(imgUri, oldPair.getB())); } @Override - public void setDarkImage(ViewId vId, File imgFile) { - final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); - fileMap.put(vId, new ValuePair<>(oldPair.getA(), imgFile)); + public void setDarkImage(ViewId vId, URI imgUri) { + final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); + uriMap.put(vId, new ValuePair<>(oldPair.getA(), imgUri)); } protected RandomAccessibleInterval getBrightImg(ViewId vId) { @@ -74,43 +81,98 @@ protected RandomAccessibleInterval getDarkImg(ViewId vId) { /** * Get image for view id; the brightfield is stored in the A element of the pair, the darkfield in B * @param vId view id - * @param fileSelector function to select file from pair + * @param uriSelector function to select URI from pair * @return image, or null if not set */ - private RandomAccessibleInterval getImg(ViewId vId, Function, File> fileSelector) { - if (!fileMap.containsKey(vId)) + private RandomAccessibleInterval getImg(ViewId vId, Function, URI> uriSelector) { + if (!uriMap.containsKey(vId)) return null; - final File fileToLoad = fileSelector.apply(fileMap.get(vId)); - if (fileToLoad == null) + final URI uriToLoad = uriSelector.apply(uriMap.get(vId)); + if (uriToLoad == null) return null; - return loadFileIfNecessary(fileToLoad); + return loadImageIfNecessary(uriToLoad); } - - private RandomAccessibleInterval loadFileIfNecessary(File file) { - if (!raiMap.containsKey(file)) { - final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); - final RandomAccessibleInterval img = ImageJFunctions.convertFloat(imp).copy(); - raiMap.put(file, img); + + /** + * Load an image from a URI. Supports: + * - Local TIFF files (via ImageJ) + * - Local/cloud Zarr v3 containers (via N5 API) + * - Local/cloud N5 containers (via N5 API) + * + * @param uri URI to the image + * @return the loaded image as FloatType + */ + private RandomAccessibleInterval loadImageIfNecessary(URI uri) { + if (!raiMap.containsKey(uri)) { + RandomAccessibleInterval img; + + if (isChunkedFormat(uri)) { + // Use N5/Zarr API for chunked formats + final StorageFormat format = detectStorageFormat(uri); + final N5Reader reader = URITools.instantiateN5Reader(format, uri); + final RandomAccessibleInterval raw = N5Utils.open(reader, "/"); + img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); + } else { + // Legacy TIFF path via ImageJ + final File file = new File(uri); + final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); + if (imp == null) + throw new RuntimeException("Failed to load image from: " + uri); + img = ImageJFunctions.convertFloat(imp).copy(); + } + + raiMap.put(uri, img); } - return raiMap.get(file); + return raiMap.get(uri); + } + + /** + * Determine if the URI points to a chunked format (N5/Zarr) vs TIFF. + */ + private static boolean isChunkedFormat(URI uri) { + final String scheme = uri.getScheme(); + // Cloud URIs are always chunked format + if ("s3".equals(scheme) || "gs".equals(scheme)) + return true; + + // Check path for .zarr or .n5 extension + final String path = uri.getPath(); + if (path == null) + return false; + + final String lowerPath = path.toLowerCase(); + return lowerPath.endsWith(".zarr") || lowerPath.endsWith(".n5") + || lowerPath.contains(".zarr/") || lowerPath.contains(".n5/"); + } + + /** + * Detect the storage format from the URI. + */ + private static StorageFormat detectStorageFormat(URI uri) { + final String path = uri.getPath(); + if (path != null && path.toLowerCase().contains(".n5")) + return StorageFormat.N5; + // Default to Zarr v3 for cloud and .zarr paths + return StorageFormat.ZARR; } - + public static void main(String[] args) { - DefaultFlatfieldCorrectionWrappedImgLoader testImgLoader = new DefaultFlatfieldCorrectionWrappedImgLoader( null ); - testImgLoader.setBrightImage( new ViewId(0,0), new File("/Users/David/Desktop/ell2.tif" )); - RandomAccessibleInterval< FloatType > brightImg = testImgLoader.getBrightImg( new ViewId( 0, 0 ) ); - - ImageJFunctions.show( brightImg ); - + DefaultFlatfieldCorrectionWrappedImgLoader testImgLoader = new DefaultFlatfieldCorrectionWrappedImgLoader(null); + testImgLoader.setBrightImage(new ViewId(0, 0), new File("/Users/David/Desktop/ell2.tif")); + RandomAccessibleInterval brightImg = testImgLoader.getBrightImg(new ViewId(0, 0)); + + ImageJFunctions.show(brightImg); } - public Map< ViewId, Pair< File, File > > getFileMap() + /** + * Get the URI map for bright/dark images per view. + * @return map from ViewId to (brightUri, darkUri) pair + */ + public Map> getUriMap() { - return fileMap; + return uriMap; } - - } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index 33455919c..79d910da3 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -23,6 +23,7 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; +import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -69,7 +70,7 @@ public class MultiResolutionFlatfieldCorrectionWrappedImgLoader private boolean cacheResult; /* downsampled bright/dark images */ - private final Map< Pair< File, List< Integer > >, RandomAccessibleInterval< FloatType > > dsRaiMap; + private final Map>, RandomAccessibleInterval> dsRaiMap; public MultiResolutionFlatfieldCorrectionWrappedImgLoader(MultiResolutionImgLoader wrappedImgLoader) { @@ -102,12 +103,12 @@ protected RandomAccessibleInterval< FloatType > getOrCreateDarkImgDownsampled(Vi private RandomAccessibleInterval getOrCreateDownsampledImg( ViewId vId, int[] downsamplingFactors, - Function, File> fileSelector, + Function, URI> uriSelector, Function> imgGetter ) { // Convert to a list here to have a proper hash code for the map key List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); - final ValuePair> key = new ValuePair<>(fileSelector.apply(fileMap.get(vId)), dsFactorList); + final ValuePair> key = new ValuePair<>(uriSelector.apply(uriMap.get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { final RandomAccessibleInterval img = imgGetter.apply(vId); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java index b348f3130..661ace0da 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java @@ -60,7 +60,7 @@ public class TestViewerFlatfieldCorrection { public static void main(String[] args) throws SpimDataException { // Paths final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; - final String correctedXmlPath = basePath + "data/dataset_corrected_viewer.xml"; + final String correctedXmlPath = basePath + "data/dataset_corrected_zarr.xml"; final String uncorrectedXmlPath = basePath + "data/dataset.xml"; // Which setup to demonstrate (0-8) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index f35ba7a4b..626886b25 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -23,6 +23,7 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; +import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -30,6 +31,10 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.universe.StorageFormat; + import bdv.ViewerImgLoader; import bdv.ViewerSetupImgLoader; import bdv.cache.CacheControl; @@ -54,10 +59,12 @@ import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Cast; import net.imglib2.util.Pair; import net.imglib2.util.ValuePair; import net.imglib2.view.Views; import net.preibisch.mvrecon.process.fusion.FusionTools; +import util.URITools; /** * Flatfield correction wrapper for ViewerImgLoader. @@ -80,16 +87,16 @@ public class ViewerFlatfieldCorrectionWrappedImgLoader private boolean active; private boolean cacheResult; - private static final Pair NULL_PAIR = new ValuePair<>(null, null); + private static final Pair NULL_PAIR = new ValuePair<>(null, null); - /** Maps ViewId to (brightFile, darkFile) pair */ - protected final Map> fileMap; + /** Maps ViewId to (brightUri, darkUri) pair */ + protected final Map> uriMap; /** Cached loaded correction images */ - protected final Map> raiMap; + protected final Map> raiMap; /** Downsampled bright/dark images for each mipmap level */ - private final Map>, RandomAccessibleInterval> dsRaiMap; + private final Map>, RandomAccessibleInterval> dsRaiMap; public ViewerFlatfieldCorrectionWrappedImgLoader(final ViewerImgLoader wrappedImgLoader) { this(wrappedImgLoader, true); @@ -99,7 +106,7 @@ public ViewerFlatfieldCorrectionWrappedImgLoader(final ViewerImgLoader wrappedIm this.wrappedImgLoader = wrappedImgLoader; this.active = true; this.cacheResult = cacheResult; - this.fileMap = new HashMap<>(); + this.uriMap = new HashMap<>(); this.raiMap = new HashMap<>(); this.dsRaiMap = new HashMap<>(); } @@ -143,50 +150,108 @@ public void setCached(final boolean cached) { this.cacheResult = cached; } + public void setBrightImage(final ViewId vId, final URI imgUri) { + final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); + uriMap.put(vId, new ValuePair<>(imgUri, oldPair.getB())); + } + + public void setDarkImage(final ViewId vId, final URI imgUri) { + final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); + uriMap.put(vId, new ValuePair<>(oldPair.getA(), imgUri)); + } + public void setBrightImage(final ViewId vId, final File imgFile) { - final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); - fileMap.put(vId, new ValuePair<>(imgFile, oldPair.getB())); + setBrightImage(vId, imgFile == null ? null : imgFile.toURI()); } public void setDarkImage(final ViewId vId, final File imgFile) { - final Pair oldPair = fileMap.getOrDefault(vId, NULL_PAIR); - fileMap.put(vId, new ValuePair<>(oldPair.getA(), imgFile)); + setDarkImage(vId, imgFile == null ? null : imgFile.toURI()); } // ========== Image loading helpers ========== protected RandomAccessibleInterval getBrightImg(final ViewId vId) { - if (!fileMap.containsKey(vId)) + if (!uriMap.containsKey(vId)) return null; - final File fileToLoad = fileMap.get(vId).getA(); - if (fileToLoad == null) + final URI uriToLoad = uriMap.get(vId).getA(); + if (uriToLoad == null) return null; - loadFileIfNecessary(fileToLoad); - return raiMap.get(fileToLoad); + loadImageIfNecessary(uriToLoad); + return raiMap.get(uriToLoad); } protected RandomAccessibleInterval getDarkImg(final ViewId vId) { - if (!fileMap.containsKey(vId)) + if (!uriMap.containsKey(vId)) return null; - final File fileToLoad = fileMap.get(vId).getB(); - if (fileToLoad == null) + final URI uriToLoad = uriMap.get(vId).getB(); + if (uriToLoad == null) return null; - loadFileIfNecessary(fileToLoad); - return raiMap.get(fileToLoad); + loadImageIfNecessary(uriToLoad); + return raiMap.get(uriToLoad); } - protected void loadFileIfNecessary(final File file) { - if (raiMap.containsKey(file)) + /** + * Load an image from a URI. Supports: + * - Local TIFF files (via ImageJ) + * - Local/cloud Zarr v3 containers (via N5 API) + * - Local/cloud N5 containers (via N5 API) + */ + protected void loadImageIfNecessary(final URI uri) { + if (raiMap.containsKey(uri)) return; - final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); - final RandomAccessibleInterval img = ImageJFunctions.convertFloat(imp).copy(); + RandomAccessibleInterval img; + + if (isChunkedFormat(uri)) { + // Use N5/Zarr API for chunked formats + final StorageFormat format = detectStorageFormat(uri); + final N5Reader reader = URITools.instantiateN5Reader(format, uri); + final RandomAccessibleInterval raw = N5Utils.open(reader, "/"); + img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); + } else { + // Legacy TIFF path via ImageJ + final File file = new File(uri); + final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); + if (imp == null) + throw new RuntimeException("Failed to load image from: " + uri); + img = ImageJFunctions.convertFloat(imp).copy(); + } + + raiMap.put(uri, img); + } + + /** + * Determine if the URI points to a chunked format (N5/Zarr) vs TIFF. + */ + private static boolean isChunkedFormat(final URI uri) { + final String scheme = uri.getScheme(); + // Cloud URIs are always chunked format + if ("s3".equals(scheme) || "gs".equals(scheme)) + return true; + + // Check path for .zarr or .n5 extension + final String path = uri.getPath(); + if (path == null) + return false; + + final String lowerPath = path.toLowerCase(); + return lowerPath.endsWith(".zarr") || lowerPath.endsWith(".n5") + || lowerPath.contains(".zarr/") || lowerPath.contains(".n5/"); + } - raiMap.put(file, img); + /** + * Detect the storage format from the URI. + */ + private static StorageFormat detectStorageFormat(final URI uri) { + final String path = uri.getPath(); + if (path != null && path.toLowerCase().contains(".n5")) + return StorageFormat.N5; + // Default to Zarr v3 for cloud and .zarr paths + return StorageFormat.ZARR; } protected RandomAccessibleInterval getOrCreateBrightImgDownsampled( @@ -210,12 +275,12 @@ protected RandomAccessibleInterval getOrCreateDarkImgDownsampled( private RandomAccessibleInterval getOrCreateDownsampledImg( ViewId vId, int[] downsamplingFactors, - Function, File> fileSelector, + Function, URI> uriSelector, Function> imgGetter ) { // Convert to a list here to have a proper hash code for the map key List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); - final ValuePair> key = new ValuePair<>(fileSelector.apply(fileMap.get(vId)), dsFactorList); + final ValuePair> key = new ValuePair<>(uriSelector.apply(uriMap.get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { final RandomAccessibleInterval img = imgGetter.apply(vId); @@ -231,6 +296,14 @@ private RandomAccessibleInterval getOrCreateDownsampledImg( return dsRaiMap.get(key); } + /** + * Get the URI map for bright/dark images per view. + * @return map from ViewId to (brightUri, darkUri) pair + */ + public Map> getUriMap() { + return uriMap; + } + // ========== Inner class: ViewerSetupImgLoader implementation ========== public class ViewerFlatfieldCorrectionWrappedSetupImgLoader & NativeType, V extends Volatile & RealType & NativeType> diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java index 70e59c01f..f3d3c0e49 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java @@ -9,12 +9,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -28,6 +28,7 @@ import static mpicbg.spim.data.XmlKeys.VIEWSETUP_TAG; import java.io.File; +import java.net.URI; import java.util.Map; import org.jdom2.DataConversionException; @@ -44,12 +45,11 @@ import mpicbg.spim.data.sequence.MultiResolutionImgLoader; import mpicbg.spim.data.sequence.ViewId; import net.imglib2.util.Pair; -import net.preibisch.legacy.io.IOFunctions; @ImgLoaderIo(format = "spimreconstruction.wrapped.flatfield.default", type = DefaultFlatfieldCorrectionWrappedImgLoader.class) public class XmlIoFlatfieldCorrectedWrappedImgLoader - implements XmlIoBasicImgLoader< FlatfieldCorrectionWrappedImgLoader< ? extends ImgLoader > > + implements XmlIoBasicImgLoader> { public final static String WRAPPED_IMGLOADER_TAG = "WrappedImgLoader"; public final static String FLATFIELDS_TAG = "FlatFields"; @@ -60,17 +60,24 @@ public class XmlIoFlatfieldCorrectedWrappedImgLoader public final static String CACHED_TAG = "Cached"; @Override - public FlatfieldCorrectionWrappedImgLoader< ? extends ImgLoader > fromXml(Element elem, File basePath, - AbstractSequenceDescription< ?, ?, ? > sequenceDescription) + public FlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File basePath, + AbstractSequenceDescription sequenceDescription) { - Element wrappedImgLoaderEl = elem.getChild( WRAPPED_IMGLOADER_TAG ).getChild( IMGLOADER_TAG ); - XmlIoBasicImgLoader< ? > xmlIoWrapped = null; + return fromXml(elem, basePath == null ? null : basePath.toURI(), sequenceDescription); + } + + @Override + public FlatfieldCorrectionWrappedImgLoader fromXml(Element elem, URI basePathURI, + AbstractSequenceDescription sequenceDescription) + { + Element wrappedImgLoaderEl = elem.getChild(WRAPPED_IMGLOADER_TAG).getChild(IMGLOADER_TAG); + XmlIoBasicImgLoader xmlIoWrapped = null; try { xmlIoWrapped = ImgLoaders - .createXmlIoForFormat( wrappedImgLoaderEl.getAttributeValue( IMGLOADER_FORMAT_ATTRIBUTE_NAME ) ); + .createXmlIoForFormat(wrappedImgLoaderEl.getAttributeValue(IMGLOADER_FORMAT_ATTRIBUTE_NAME)); } - catch ( SpimDataInstantiationException e ) + catch (SpimDataInstantiationException e) { e.printStackTrace(); return null; @@ -80,92 +87,96 @@ public class XmlIoFlatfieldCorrectedWrappedImgLoader boolean active = false; try { - cached = elem.getAttribute( CACHED_TAG ).getBooleanValue(); - active = elem.getAttribute( ACTIVE_TAG ).getBooleanValue(); + cached = elem.getAttribute(CACHED_TAG).getBooleanValue(); + active = elem.getAttribute(ACTIVE_TAG).getBooleanValue(); } - catch ( DataConversionException e ) + catch (DataConversionException e) { e.printStackTrace(); } - BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml( wrappedImgLoaderEl, basePath, sequenceDescription ); + BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml(wrappedImgLoaderEl, basePathURI, sequenceDescription); - FlatfieldCorrectionWrappedImgLoader< ? extends ImgLoader > res = null; + FlatfieldCorrectionWrappedImgLoader res = null; - if ( MultiResolutionImgLoader.class.isInstance( wrappedImgLoader ) ) - res = new MultiResolutionFlatfieldCorrectionWrappedImgLoader( (MultiResolutionImgLoader) wrappedImgLoader, - cached ); - else if ( ImgLoader.class.isInstance( wrappedImgLoader ) ) - res = new DefaultFlatfieldCorrectionWrappedImgLoader( (ImgLoader) wrappedImgLoader, cached ); + if (MultiResolutionImgLoader.class.isInstance(wrappedImgLoader)) + res = new MultiResolutionFlatfieldCorrectionWrappedImgLoader((MultiResolutionImgLoader) wrappedImgLoader, + cached); + else if (ImgLoader.class.isInstance(wrappedImgLoader)) + res = new DefaultFlatfieldCorrectionWrappedImgLoader((ImgLoader) wrappedImgLoader, cached); else return null; - Element flatfields = elem.getChild( FLATFIELDS_TAG ); - for ( Element flatfield : flatfields.getChildren() ) + Element flatfields = elem.getChild(FLATFIELDS_TAG); + for (Element flatfield : flatfields.getChildren()) { - int tp = Integer.parseInt( flatfield.getAttributeValue( TIMEPOINTS_TIMEPOINT_TAG ) ); - int vs = Integer.parseInt( flatfield.getAttributeValue( VIEWSETUP_TAG ) ); - File brightImg = XmlHelpers.loadPath( flatfield, BRIGHTIMG_TAG, basePath ); - File darkImg = XmlHelpers.loadPath( flatfield, DARKIMG_TAG, basePath ); - res.setBrightImage( new ViewId( tp, vs ), brightImg ); - res.setDarkImage( new ViewId( tp, vs ), darkImg ); + int tp = Integer.parseInt(flatfield.getAttributeValue(TIMEPOINTS_TIMEPOINT_TAG)); + int vs = Integer.parseInt(flatfield.getAttributeValue(VIEWSETUP_TAG)); + URI brightImg = XmlHelpers.loadPathURI(flatfield, BRIGHTIMG_TAG, basePathURI); + URI darkImg = XmlHelpers.loadPathURI(flatfield, DARKIMG_TAG, basePathURI); + res.setBrightImage(new ViewId(tp, vs), brightImg); + res.setDarkImage(new ViewId(tp, vs), darkImg); } - res.setActive( active ); + res.setActive(active); return res; } @Override - public Element toXml(FlatfieldCorrectionWrappedImgLoader< ? extends ImgLoader > imgLoader, File basePath) + public Element toXml(FlatfieldCorrectionWrappedImgLoader imgLoader, File basePath) { + return toXml(imgLoader, basePath == null ? null : basePath.toURI()); + } - final Map< ViewId, Pair< File, File > > fileMap = ( (LazyLoadingFlatFieldCorrectionMap< ? extends ImgLoader >) imgLoader ).fileMap; + @Override + public Element toXml(FlatfieldCorrectionWrappedImgLoader imgLoader, URI basePathURI) + { + final Map> uriMap = ((LazyLoadingFlatFieldCorrectionMap) imgLoader).getUriMap(); - final Element wholeElem = new Element( IMGLOADER_TAG ); - wholeElem.setAttribute( IMGLOADER_FORMAT_ATTRIBUTE_NAME, - this.getClass().getAnnotation( ImgLoaderIo.class ).format() ); - final Element wrappedIL = new Element( WRAPPED_IMGLOADER_TAG ); + final Element wholeElem = new Element(IMGLOADER_TAG); + wholeElem.setAttribute(IMGLOADER_FORMAT_ATTRIBUTE_NAME, + this.getClass().getAnnotation(ImgLoaderIo.class).format()); + final Element wrappedIL = new Element(WRAPPED_IMGLOADER_TAG); - wholeElem.setAttribute( ACTIVE_TAG, Boolean.toString( imgLoader.isActive() ) ); - wholeElem.setAttribute( CACHED_TAG, Boolean.toString( imgLoader.isCached() ) ); + wholeElem.setAttribute(ACTIVE_TAG, Boolean.toString(imgLoader.isActive())); + wholeElem.setAttribute(CACHED_TAG, Boolean.toString(imgLoader.isCached())); try { - XmlIoBasicImgLoader< ImgLoader > loaderIO = (XmlIoBasicImgLoader< ImgLoader >) ImgLoaders - .createXmlIoForImgLoaderClass( imgLoader.getWrappedImgLoder().getClass() ); - Element wrappedInner = loaderIO.toXml( (ImgLoader) imgLoader.getWrappedImgLoder(), basePath ); - wrappedIL.addContent( wrappedInner ); - + @SuppressWarnings("unchecked") + XmlIoBasicImgLoader loaderIO = (XmlIoBasicImgLoader) ImgLoaders + .createXmlIoForImgLoaderClass(imgLoader.getWrappedImgLoder().getClass()); + Element wrappedInner = loaderIO.toXml((ImgLoader) imgLoader.getWrappedImgLoder(), basePathURI); + wrappedIL.addContent(wrappedInner); } - catch ( SpimDataInstantiationException e ) + catch (SpimDataInstantiationException e) { e.printStackTrace(); return null; } - final Element elFlatfields = new Element( FLATFIELDS_TAG ); + final Element elFlatfields = new Element(FLATFIELDS_TAG); - for ( ViewId vid : fileMap.keySet() ) + for (ViewId vid : uriMap.keySet()) { - final Pair< File, File > files = fileMap.get( vid ); - if ( files == null || ( files.getA() == null && files.getB() == null ) ) + final Pair uris = uriMap.get(vid); + if (uris == null || (uris.getA() == null && uris.getB() == null)) continue; - final Element elFlatfield = new Element( FLATFIELD_TAG ); - elFlatfield.setAttribute( TIMEPOINTS_TIMEPOINT_TAG, Integer.toString( vid.getTimePointId() ) ); - elFlatfield.setAttribute( VIEWSETUP_TAG, Integer.toString( vid.getViewSetupId() ) ); + final Element elFlatfield = new Element(FLATFIELD_TAG); + elFlatfield.setAttribute(TIMEPOINTS_TIMEPOINT_TAG, Integer.toString(vid.getTimePointId())); + elFlatfield.setAttribute(VIEWSETUP_TAG, Integer.toString(vid.getViewSetupId())); - if ( files.getA() != null ) - elFlatfield.addContent( XmlHelpers.pathElement( BRIGHTIMG_TAG, files.getA(), basePath ) ); - if ( files.getB() != null ) - elFlatfield.addContent( XmlHelpers.pathElement( DARKIMG_TAG, files.getB(), basePath ) ); + if (uris.getA() != null) + elFlatfield.addContent(XmlHelpers.pathElementURI(BRIGHTIMG_TAG, uris.getA(), basePathURI)); + if (uris.getB() != null) + elFlatfield.addContent(XmlHelpers.pathElementURI(DARKIMG_TAG, uris.getB(), basePathURI)); - elFlatfields.addContent( elFlatfield ); + elFlatfields.addContent(elFlatfield); } - wholeElem.addContent( wrappedIL ); - wholeElem.addContent( elFlatfields ); + wholeElem.addContent(wrappedIL); + wholeElem.addContent(elFlatfields); return wholeElem; } - } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java index 2c0573549..230d75551 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java @@ -28,6 +28,7 @@ import static mpicbg.spim.data.XmlKeys.VIEWSETUP_TAG; import java.io.File; +import java.net.URI; import java.util.Map; import org.jdom2.DataConversionException; @@ -64,6 +65,12 @@ public class XmlIoViewerFlatfieldCorrectionWrappedImgLoader @Override public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File basePath, AbstractSequenceDescription sequenceDescription) { + return fromXml(elem, basePath == null ? null : basePath.toURI(), sequenceDescription); + } + + @Override + public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, URI basePathURI, + AbstractSequenceDescription sequenceDescription) { Element wrappedImgLoaderEl = elem.getChild(WRAPPED_IMGLOADER_TAG).getChild(IMGLOADER_TAG); XmlIoBasicImgLoader xmlIoWrapped; try { @@ -83,7 +90,7 @@ public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File base e.printStackTrace(); } - BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml(wrappedImgLoaderEl, basePath, sequenceDescription); + BasicImgLoader wrappedImgLoader = xmlIoWrapped.fromXml(wrappedImgLoaderEl, basePathURI, sequenceDescription); // Verify wrapped loader is a ViewerImgLoader if (!(wrappedImgLoader instanceof ViewerImgLoader)) { @@ -99,8 +106,8 @@ public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File base for (Element flatfield : flatfields.getChildren()) { int tp = Integer.parseInt(flatfield.getAttributeValue(TIMEPOINTS_TIMEPOINT_TAG)); int vs = Integer.parseInt(flatfield.getAttributeValue(VIEWSETUP_TAG)); - File brightImg = XmlHelpers.loadPath(flatfield, BRIGHTIMG_TAG, basePath); - File darkImg = XmlHelpers.loadPath(flatfield, DARKIMG_TAG, basePath); + URI brightImg = XmlHelpers.loadPathURI(flatfield, BRIGHTIMG_TAG, basePathURI); + URI darkImg = XmlHelpers.loadPathURI(flatfield, DARKIMG_TAG, basePathURI); res.setBrightImage(new ViewId(tp, vs), brightImg); res.setDarkImage(new ViewId(tp, vs), darkImg); } @@ -111,7 +118,12 @@ public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File base @Override public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File basePath) { - final Map> fileMap = imgLoader.fileMap; + return toXml(imgLoader, basePath == null ? null : basePath.toURI()); + } + + @Override + public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, URI basePathURI) { + final Map> uriMap = imgLoader.getUriMap(); final Element wholeElem = new Element(IMGLOADER_TAG); wholeElem.setAttribute(IMGLOADER_FORMAT_ATTRIBUTE_NAME, @@ -126,7 +138,7 @@ public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File b XmlIoBasicImgLoader loaderIO = ImgLoaders .createXmlIoForImgLoaderClass(imgLoader.getWrappedImgLoader().getClass()); @SuppressWarnings("unchecked") - Element wrappedInner = loaderIO.toXml(imgLoader.getWrappedImgLoader(), basePath); + Element wrappedInner = loaderIO.toXml(imgLoader.getWrappedImgLoader(), basePathURI); wrappedIL.addContent(wrappedInner); } catch (SpimDataInstantiationException e) { e.printStackTrace(); @@ -135,19 +147,19 @@ public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File b final Element elFlatfields = new Element(FLATFIELDS_TAG); - for (ViewId vid : fileMap.keySet()) { - final Pair files = fileMap.get(vid); - if (files == null || (files.getA() == null && files.getB() == null)) + for (ViewId vid : uriMap.keySet()) { + final Pair uris = uriMap.get(vid); + if (uris == null || (uris.getA() == null && uris.getB() == null)) continue; final Element elFlatfield = new Element(FLATFIELD_TAG); elFlatfield.setAttribute(TIMEPOINTS_TIMEPOINT_TAG, Integer.toString(vid.getTimePointId())); elFlatfield.setAttribute(VIEWSETUP_TAG, Integer.toString(vid.getViewSetupId())); - if (files.getA() != null) - elFlatfield.addContent(XmlHelpers.pathElement(BRIGHTIMG_TAG, files.getA(), basePath)); - if (files.getB() != null) - elFlatfield.addContent(XmlHelpers.pathElement(DARKIMG_TAG, files.getB(), basePath)); + if (uris.getA() != null) + elFlatfield.addContent(XmlHelpers.pathElementURI(BRIGHTIMG_TAG, uris.getA(), basePathURI)); + if (uris.getB() != null) + elFlatfield.addContent(XmlHelpers.pathElementURI(DARKIMG_TAG, uris.getB(), basePathURI)); elFlatfields.addContent(elFlatfield); } From 1755b697f22656331ab9ca47443bbe6c7312a06d Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 14:11:33 -0500 Subject: [PATCH 12/31] Fix xml file generation in test setup --- .../flatfield/ConvertFlatfieldsToZarr.java | 208 +++++++----------- 1 file changed, 80 insertions(+), 128 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java index 872fa2b6a..28b79c8bf 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java @@ -22,11 +22,15 @@ */ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; +import java.io.BufferedReader; import java.io.File; +import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.janelia.saalfeldlab.n5.Compression; import org.janelia.saalfeldlab.n5.GzipCompression; @@ -36,13 +40,9 @@ import ij.IJ; import ij.ImagePlus; -import mpicbg.spim.data.SpimDataException; -import mpicbg.spim.data.sequence.ViewSetup; import net.imglib2.img.Img; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.FloatType; -import net.preibisch.mvrecon.fiji.spimdata.SpimData2; -import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; import util.URITools; /** @@ -122,137 +122,88 @@ public static Map convertDirectory(final File inputDir, final File } /** - * Generate a test XML file with Zarr-based flatfield correction. + * Generate a test XML file with Zarr-based flatfield correction by copying + * an existing working XML and updating the flatfield paths to point to Zarr files. * - * @param baseXmlPath path to the original dataset XML - * @param outputXmlPath path for the new XML with flatfield correction - * @param flatfieldDir directory containing .zarr flatfield files - * @param brightPattern pattern for bright image names (e.g., "flatfield_tile%d") - * @param darkPattern pattern for dark image names (e.g., "darkfield_tile%d") - * @throws SpimDataException if XML loading fails - * @throws IOException if XML writing fails + * @param sourceXmlPath path to a working flatfield-corrected XML (with TIFF paths) + * @param outputXmlPath path for the new XML with Zarr flatfield paths + * @param zarrDir directory containing .zarr flatfield files + * @param convertedFiles map of base names to Zarr files from conversion + * @throws IOException if reading/writing fails */ public static void generateTestXml( - final String baseXmlPath, + final String sourceXmlPath, final String outputXmlPath, - final File flatfieldDir, - final String brightPattern, - final String darkPattern) throws SpimDataException, IOException { + final File zarrDir, + final Map convertedFiles) throws IOException { System.out.println("\n=== Generating Test XML ==="); - System.out.println("Base XML: " + baseXmlPath); + System.out.println("Source XML: " + sourceXmlPath); System.out.println("Output XML: " + outputXmlPath); - // Load original dataset to get view setup info - final SpimData2 data = new XmlIoSpimData2().load(baseXmlPath); - - // Build XML content manually to wrap the original loader with flatfield correction - final StringBuilder xml = new StringBuilder(); - xml.append("\n"); - xml.append("\n"); - xml.append(" .\n"); - xml.append(" \n"); - - // ImageLoader section with flatfield wrapper - xml.append(" \n"); - - // Reference the original loader - read from original XML - xml.append(" \n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" dataset.n5\n"); - xml.append(" \n"); - xml.append(" \n"); - - // Flatfield configuration for each view setup - xml.append(" \n"); - - for (final ViewSetup vs : data.getSequenceDescription().getViewSetupsOrdered()) { - final int setupId = vs.getId(); - final int tileId = vs.getTile().getId(); - - // Construct Zarr file names based on patterns - final String brightName = String.format(brightPattern, tileId) + ".zarr"; - final String darkName = String.format(darkPattern, tileId) + ".zarr"; - - final File brightFile = new File(flatfieldDir, brightName); - final File darkFile = new File(flatfieldDir, darkName); - - // Only include if files exist - final boolean hasBright = brightFile.exists(); - final boolean hasDark = darkFile.exists(); - - if (hasBright || hasDark) { - xml.append(" \n"); - if (hasBright) - xml.append(" ").append(brightFile.getName()).append("\n"); - if (hasDark) - xml.append(" ").append(darkFile.getName()).append("\n"); - xml.append(" \n"); - - System.out.println(" Setup " + setupId + " (tile " + tileId + "): " + - (hasBright ? "bright=" + brightName : "") + - (hasDark ? " dark=" + darkName : "")); + // Read the source XML + final StringBuilder content = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new FileReader(sourceXmlPath))) { + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append("\n"); } } - xml.append(" \n"); - xml.append(" \n"); - - // Copy ViewSetups from original - xml.append(" \n"); - xml.append(" \n"); - for (final ViewSetup vs : data.getSequenceDescription().getViewSetupsOrdered()) { - xml.append(" \n"); - xml.append(" ").append(vs.getId()).append("\n"); - xml.append(" ").append(vs.getName()).append("\n"); - xml.append(" ").append(vs.getSize().dimension(0)).append(" ") - .append(vs.getSize().dimension(1)).append(" ") - .append(vs.getSize().dimension(2)).append("\n"); - xml.append(" \n"); - xml.append(" ").append(vs.getVoxelSize().unit()).append("\n"); - xml.append(" ").append(vs.getVoxelSize().dimension(0)).append(" ") - .append(vs.getVoxelSize().dimension(1)).append(" ") - .append(vs.getVoxelSize().dimension(2)).append("\n"); - xml.append(" \n"); - xml.append(" \n"); - xml.append(" ").append(vs.getChannel().getId()).append("\n"); - xml.append(" ").append(vs.getTile().getId()).append("\n"); - xml.append(" ").append(vs.getIllumination().getId()).append("\n"); - xml.append(" ").append(vs.getAngle().getId()).append("\n"); - xml.append(" \n"); - xml.append(" \n"); - } - xml.append(" \n"); - - // Timepoints - xml.append(" \n"); - xml.append(" 0\n"); - xml.append(" 0\n"); - xml.append(" \n"); - - xml.append(" \n"); - - // ViewRegistrations (identity transforms) - xml.append(" \n"); - for (final ViewSetup vs : data.getSequenceDescription().getViewSetupsOrdered()) { - xml.append(" \n"); - xml.append(" \n"); - xml.append(" 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0\n"); - xml.append(" \n"); - xml.append(" \n"); - } - xml.append(" \n"); + String xml = content.toString(); + + // Pattern to match BrightImg and DarkImg paths + final Pattern brightPattern = Pattern.compile("()([^<]+)()"); + final Pattern darkPattern = Pattern.compile("()([^<]+)()"); - xml.append("\n"); + // Replace TIFF paths with Zarr paths + xml = replaceFlatfieldPaths(xml, brightPattern, zarrDir, convertedFiles); + xml = replaceFlatfieldPaths(xml, darkPattern, zarrDir, convertedFiles); - // Write XML file + // Write output XML try (FileWriter writer = new FileWriter(outputXmlPath)) { - writer.write(xml.toString()); + writer.write(xml); + } + + System.out.println("Created: " + outputXmlPath); + } + + /** + * Replace flatfield TIFF paths with Zarr paths in XML content. + */ + private static String replaceFlatfieldPaths( + String xml, + final Pattern pattern, + final File zarrDir, + final Map convertedFiles) { + + final Matcher matcher = pattern.matcher(xml); + final StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + final String openTag = matcher.group(1); + final String oldPath = matcher.group(2); + final String closeTag = matcher.group(3); + + // Extract base name from old path (remove directory and extension) + String baseName = new File(oldPath).getName(); + baseName = baseName.replaceAll("\\.(tif|tiff)$", ""); + + // Find corresponding Zarr file + String newPath = oldPath; // default: keep original if not found + if (convertedFiles.containsKey(baseName)) { + // Use relative path from zarrDir + newPath = zarrDir.getName() + "/" + convertedFiles.get(baseName).getName(); + System.out.println(" " + oldPath + " -> " + newPath); + } else { + System.out.println(" WARNING: No Zarr found for: " + baseName); + } + + matcher.appendReplacement(result, Matcher.quoteReplacement(openTag + newPath + closeTag)); } + matcher.appendTail(result); - System.out.println("\nCreated: " + outputXmlPath); + return result.toString(); } /** @@ -266,34 +217,35 @@ public static void main(String[] args) throws Exception { // Input: directory with TIFF flatfields final File tiffDir = new File(basePath, "dark_and_flatfields"); - // Output: directory for Zarr flatfields + // Output: directory for Zarr flatfields (in data folder, next to other data) final File zarrDir = new File(basePath, "dark_and_flatfields_zarr"); // Step 1: Convert all TIFFs to Zarr System.out.println("=== Step 1: Converting TIFFs to Zarr v3 ===\n"); + Map convertedFiles; if (tiffDir.exists()) { - convertDirectory(tiffDir, zarrDir); + convertedFiles = convertDirectory(tiffDir, zarrDir); } else { throw new IOException("Input TIFF directory does not exist: " + tiffDir.getAbsolutePath()); } - // Step 2: Generate test XML + // Step 2: Generate test XML by copying from working TIFF-based XML System.out.println("\n=== Step 2: Generating Test XML ===\n"); - final String baseXml = dataPath + "dataset.xml"; + // Use the working flatfield-corrected XML as source + final String sourceXml = dataPath + "dataset_corrected_viewer.xml"; final String outputXml = dataPath + "dataset_corrected_zarr.xml"; - if (new File(baseXml).exists()) { + if (new File(sourceXml).exists()) { generateTestXml( - baseXml, + sourceXml, outputXml, zarrDir, - "flatfield_tile%d", // pattern for bright images - "darkfield_tile%d" // pattern for dark images + convertedFiles ); } else { - System.out.println("Base XML not found: " + baseXml); + System.out.println("Source XML not found: " + sourceXml); System.out.println("Skipping XML generation."); } From 135b1e35976b6bc1c9e63ed5442ff07dcc01eee5 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 15:53:17 -0500 Subject: [PATCH 13/31] Fix some bug in zarr conversion for testing --- .../imgloaders/flatfield/ConvertFlatfieldsToZarr.java | 11 +++++++---- .../flatfield/LazyLoadingFlatFieldCorrectionMap.java | 2 +- .../ViewerFlatfieldCorrectionWrappedImgLoader.java | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java index 28b79c8bf..66fa3377b 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java @@ -33,7 +33,7 @@ import java.util.regex.Pattern; import org.janelia.saalfeldlab.n5.Compression; -import org.janelia.saalfeldlab.n5.GzipCompression; +import org.janelia.saalfeldlab.n5.DataType; import org.janelia.saalfeldlab.n5.N5Writer; import org.janelia.saalfeldlab.n5.imglib2.N5Utils; import org.janelia.saalfeldlab.n5.universe.StorageFormat; @@ -43,6 +43,7 @@ import net.imglib2.img.Img; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.FloatType; +import org.janelia.scicomp.n5.zstandard.ZstandardCompression; import util.URITools; /** @@ -80,9 +81,11 @@ public static void convertTiffToZarr(final File inputTiff, final File outputZarr for (int d = 0; d < img.numDimensions(); d++) blockSize[d] = (int) img.dimension(d); - // Save (N5Utils.save creates the dataset internally) - final Compression compression = new GzipCompression(); - N5Utils.save(img, writer, "/", blockSize, compression); + // Save at root (empty string for Zarr v3) + // Save a block manually to work around a N5Utils.save issue for now + final Compression compression = new ZstandardCompression(); + writer.createDataset("/", img.dimensionsAsLongArray(), blockSize, DataType.FLOAT32, compression); + N5Utils.saveBlock(img, writer, "", new long[]{0, 0}); writer.close(); System.out.println(" Created: " + outputZarr.getAbsolutePath()); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java index 44407d246..d9d858efa 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java @@ -112,7 +112,7 @@ private RandomAccessibleInterval loadImageIfNecessary(URI uri) { // Use N5/Zarr API for chunked formats final StorageFormat format = detectStorageFormat(uri); final N5Reader reader = URITools.instantiateN5Reader(format, uri); - final RandomAccessibleInterval raw = N5Utils.open(reader, "/"); + final RandomAccessibleInterval raw = N5Utils.open(reader, ""); img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); } else { // Legacy TIFF path via ImageJ diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index 626886b25..265df6973 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -210,7 +210,7 @@ protected void loadImageIfNecessary(final URI uri) { // Use N5/Zarr API for chunked formats final StorageFormat format = detectStorageFormat(uri); final N5Reader reader = URITools.instantiateN5Reader(format, uri); - final RandomAccessibleInterval raw = N5Utils.open(reader, "/"); + final RandomAccessibleInterval raw = N5Utils.open(reader, ""); img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); } else { // Legacy TIFF path via ImageJ From 13396d3e0ea50c6307e1c402c3b1283a06b6ed9c Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 16:15:32 -0500 Subject: [PATCH 14/31] Fix some IDE warnings --- .../FlatFieldCorrectedRandomAccessibleInterval.java | 8 +++----- .../FlatFieldCorrectedRandomAccessibleIntervals.java | 11 +++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java index 5e70dc356..b126ca6eb 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java @@ -28,12 +28,10 @@ import net.imglib2.Point; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; -import net.imglib2.Sampler; import net.imglib2.type.numeric.RealType; import net.imglib2.util.Pair; import net.imglib2.util.RealSum; import net.imglib2.util.ValuePair; -import net.imglib2.view.Views; /* * @@ -148,7 +146,7 @@ public static

, Q extends RealType< Q >> double getMeanC final RealSum sum = new RealSum(); long count = 0; - final Cursor< P > brightCursor = Views.iterable( brightImg ).cursor(); + final Cursor< P > brightCursor = brightImg.cursor(); final RandomAccess< Q > darkRA = darkImg.randomAccess(); while (brightCursor.hasNext()) @@ -172,7 +170,7 @@ public static

> Pair getMinMax(RandomAcc double min = Double.MAX_VALUE; double max = - Double.MAX_VALUE; - for (final P pixel : Views.iterable( img )) + for (final P pixel : img) { double value = pixel.getRealDouble(); @@ -183,7 +181,7 @@ public static

> Pair getMinMax(RandomAcc min = value; } - return new ValuePair< Double, Double >( min, max ); + return new ValuePair<>( min, max ); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java index 4cb23038b..535ac9ffa 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java @@ -25,7 +25,6 @@ import java.util.Arrays; import bdv.util.ConstantRandomAccessible; -import bdv.viewer.overlay.SourceInfoOverlayRenderer; import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.RealType; @@ -39,7 +38,7 @@ public static < R extends RealType< R >, S extends RealType< S >, T extends Real RandomAccessibleInterval< S > brightImg, RandomAccessibleInterval< T > darkImg ) { - R type = Views.iterable( sourceImg ).firstElement().createVariable(); + R type = sourceImg.firstElement().createVariable(); return create( sourceImg, brightImg, darkImg, type ); } public static , R extends RealType< R >, S extends RealType< S >, T extends RealType< T >> RandomAccessibleInterval< O > create( @@ -85,20 +84,20 @@ public static , R extends RealType< R >, S extends RealT { // assume bright and dark images constant -> should return original // TODO: 'optimize' by really returning sourceImg? - final ConstantRandomAccessible< FloatType > constantBright = new ConstantRandomAccessible( new FloatType(1.0f), sourceImg.numDimensions() ); - final ConstantRandomAccessible< FloatType > constantDark = new ConstantRandomAccessible( new FloatType(0.0f), sourceImg.numDimensions() ); + final ConstantRandomAccessible< FloatType > constantBright = new ConstantRandomAccessible<>( new FloatType(1.0f), sourceImg.numDimensions() ); + final ConstantRandomAccessible< FloatType > constantDark = new ConstantRandomAccessible<>( new FloatType(0.0f), sourceImg.numDimensions() ); return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( constantBright, sourceImg ), Views.interval( constantDark, sourceImg ) ); } else if (brightImg == null) { // assume bright image == constant - final ConstantRandomAccessible< FloatType > constantBright = new ConstantRandomAccessible( new FloatType(1.0f), sourceImg.numDimensions() ); + final ConstantRandomAccessible< FloatType > constantBright = new ConstantRandomAccessible<>( new FloatType(1.0f), sourceImg.numDimensions() ); return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( constantBright, sourceImg ), Views.interval( Views.extendBorder( darkImg ), intervalDark ) ); } else if (darkImg == null) { // assume dark image == constant == 0; - final ConstantRandomAccessible< FloatType > constantDark = new ConstantRandomAccessible( new FloatType(0.0f), sourceImg.numDimensions() ); + final ConstantRandomAccessible< FloatType > constantDark = new ConstantRandomAccessible<>( new FloatType(0.0f), sourceImg.numDimensions() ); return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( Views.extendBorder( brightImg ), intervalBright ), Views.interval( constantDark, sourceImg ) ); } From 28fc7228de09d5190d501df6a594c757a44dc269 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 16:25:09 -0500 Subject: [PATCH 15/31] Remove unnecessary array copies --- ...ieldCorrectedRandomAccessibleInterval.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java index b126ca6eb..161801803 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java @@ -55,6 +55,9 @@ public FlatFieldCorrectedRandomAccessibleInterval(O outputType, RandomAccessible this.brightImg = brightImg; this.darkImg = darkImg; + if (brightImg.numDimensions() > sourceImg.numDimensions() || darkImg.numDimensions() > sourceImg.numDimensions()) + throw new IllegalArgumentException("Bright-/darkfield images have more dimensions than source image!"); + meanBrightCorrected = getMeanCorrected( brightImg, darkImg ); type = outputType; } @@ -105,20 +108,15 @@ public FlatFieldCorrectedRandomAccess() @Override public O get() { - // NB: the flat field images seem to be 3D with 1 z slice - // if they were truly 2D, we would use position.length - 1 - final long[] positionBright = new long[ nDimBright ]; - final long[] positionDark = new long[ nDimDark ]; - // only copy position of n-1 dimensions - System.arraycopy( position, 0, positionBright, 0, nDimBright ); - System.arraycopy( position, 0, positionDark, 0, nDimDark ); - - sourceRA.setPosition( position ); - brightRA.setPosition( positionBright ); - darkRA.setPosition( positionDark ); - - final double corrBright = brightRA.get().getRealDouble() - darkRA.get().getRealDouble(); - final double corrImg = sourceRA.get().getRealDouble() - darkRA.get().getRealDouble(); + // Use the fact that bright and dark must be of dimensionality <= source, + // and that coordinates outside the dimensionality are ignored + sourceRA.setPosition(position); + brightRA.setPosition(position); + darkRA.setPosition(position); + + final double darkValue = darkRA.get().getRealDouble(); + final double corrBright = brightRA.get().getRealDouble() - darkValue; + final double corrImg = sourceRA.get().getRealDouble() - darkValue; if (corrBright == 0) value.setReal( 0.0 ); From 91f03b9079898c82166f60bdf74f55aa191209e4 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 17:04:00 -0500 Subject: [PATCH 16/31] Fix some IDE warnings --- ...ltFlatfieldCorrectionWrappedImgLoader.java | 98 ++++++++++--------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java index a2e482f97..5d4d23e1f 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java @@ -33,7 +33,6 @@ import mpicbg.spim.data.sequence.ViewId; import mpicbg.spim.data.sequence.VoxelDimensions; import net.imglib2.Dimensions; -import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.Img; @@ -47,14 +46,13 @@ import net.imglib2.view.Views; import net.preibisch.mvrecon.fiji.plugin.queryXML.LoadParseQueryXML; import net.preibisch.mvrecon.fiji.spimdata.SpimData2; -import net.preibisch.mvrecon.fiji.spimdata.imgloaders.filemap2.FileMapImgLoaderLOCI2; import net.preibisch.mvrecon.process.fusion.FusionTools; public class DefaultFlatfieldCorrectionWrappedImgLoader extends LazyLoadingFlatFieldCorrectionMap< ImgLoader > implements ImgLoader { - private ImgLoader wrappedImgLoader; + private final ImgLoader wrappedImgLoader; private boolean active; private boolean cacheResult; @@ -106,23 +104,27 @@ class DefaultFlatfieldCorrectionWrappedSetupImgLoader & } @Override - public RandomAccessibleInterval< T > getImage(int timepointId, ImgLoaderHint... hints) - { - if (!active) - return (RandomAccessibleInterval< T >) wrappedImgLoader.getSetupImgLoader( setupId ).getImage( timepointId, - hints ); + public RandomAccessibleInterval< T > getImage(int timepointId, ImgLoaderHint... hints) { + if (!active) { + @SuppressWarnings("unchecked") final RandomAccessibleInterval img = + (RandomAccessibleInterval) wrappedImgLoader.getSetupImgLoader(setupId).getImage(timepointId, hints); + return img; + } @SuppressWarnings("unchecked") - RandomAccessibleInterval< T > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( - (RandomAccessibleInterval< T >) wrappedImgLoader.getSetupImgLoader( setupId ).getImage( timepointId, - hints ), - getBrightImg( new ViewId( timepointId, setupId ) ), - getDarkImg( new ViewId( timepointId, setupId ) ) ); + RandomAccessibleInterval rai = FlatFieldCorrectedRandomAccessibleIntervals.create( + (RandomAccessibleInterval) wrappedImgLoader.getSetupImgLoader(setupId).getImage(timepointId, + hints), + getBrightImg(new ViewId(timepointId, setupId)), + getDarkImg(new ViewId(timepointId, setupId))); boolean loadCompletelyRequested = false; - for (ImgLoaderHint hint : hints) - if (hint == ImgLoaderHints.LOAD_COMPLETELY) + for (ImgLoaderHint hint : hints) { + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { loadCompletelyRequested = true; + break; + } + } if (loadCompletelyRequested) { @@ -131,12 +133,13 @@ public RandomAccessibleInterval< T > getImage(int timepointId, ImgLoaderHint... numPx *= rai.dimension( d ); final ImgFactory< T > imgFactory; - if (Math.log(numPx) / Math.log( 2 ) < 31) - imgFactory = new ArrayImgFactory(); - else - imgFactory = new CellImgFactory(); + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(getImageType()); + } else { + imgFactory = new CellImgFactory<>(getImageType()); + } - Img< T > loadedImg = imgFactory.create( rai, getImageType() ); + Img loadedImg = imgFactory.create(rai); RealTypeConverters.copyFromTo( Views.extendZero( rai ), loadedImg ); rai = loadedImg; @@ -148,8 +151,8 @@ else if ( cacheResult ) Arrays.fill( cellSize, 1 ); for ( int d = 0; d < rai.numDimensions() - 1; d++ ) cellSize[d] = (int) rai.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + rai = FusionTools.cacheRandomAccessibleInterval( + rai, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } return rai; @@ -161,8 +164,7 @@ public RandomAccessibleInterval< FloatType > getFloatImage(int timepointId, bool ImgLoaderHint... hints) { if (!active) - return (RandomAccessibleInterval< FloatType >) wrappedImgLoader.getSetupImgLoader( setupId ).getFloatImage( timepointId, - false, hints ); + return wrappedImgLoader.getSetupImgLoader(setupId).getFloatImage(timepointId, false, hints); @SuppressWarnings("unchecked") RandomAccessibleInterval< FloatType > rai = FlatFieldCorrectedRandomAccessibleIntervals.create( @@ -176,9 +178,12 @@ public RandomAccessibleInterval< FloatType > getFloatImage(int timepointId, bool RandomAccessibleInterval< FloatType > raiNormalized = new VirtuallyNormalizedRandomAccessibleInterval<>( rai ); boolean loadCompletelyRequested = false; - for (ImgLoaderHint hint : hints) - if (hint == ImgLoaderHints.LOAD_COMPLETELY) + for (ImgLoaderHint hint : hints) { + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { loadCompletelyRequested = true; + break; + } + } if (loadCompletelyRequested) { @@ -187,13 +192,14 @@ public RandomAccessibleInterval< FloatType > getFloatImage(int timepointId, bool numPx *= raiNormalized.dimension( d ); final ImgFactory< FloatType > imgFactory; - if (Math.log(numPx) / Math.log( 2 ) < 31) - imgFactory = new ArrayImgFactory(); - else - imgFactory = new CellImgFactory(); + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(new FloatType()); + } else { + imgFactory = new CellImgFactory<>(new FloatType()); + } - Img< FloatType > loadedImg = imgFactory.create( raiNormalized, new FloatType() ); - FileMapImgLoaderLOCI2.copy(Views.extendZero( raiNormalized ), loadedImg); + Img loadedImg = imgFactory.create(raiNormalized); + RealTypeConverters.copyFromTo(Views.extendZero(raiNormalized), loadedImg); raiNormalized = loadedImg; } @@ -203,17 +209,20 @@ else if ( cacheResult ) Arrays.fill( cellSize, 1 ); for ( int d = 0; d < raiNormalized.numDimensions() - 1; d++ ) cellSize[d] = (int) raiNormalized.dimension( d ); - raiNormalized = FusionTools.cacheRandomAccessibleInterval( raiNormalized, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + raiNormalized = FusionTools.cacheRandomAccessibleInterval( + raiNormalized, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } rai = raiNormalized; } else { boolean loadCompletelyRequested = false; - for (ImgLoaderHint hint : hints) - if (hint == ImgLoaderHints.LOAD_COMPLETELY) + for (ImgLoaderHint hint : hints) { + if (hint == ImgLoaderHints.LOAD_COMPLETELY) { loadCompletelyRequested = true; + break; + } + } if (loadCompletelyRequested) { @@ -222,13 +231,14 @@ else if ( cacheResult ) numPx *= rai.dimension( d ); final ImgFactory< FloatType > imgFactory; - if (Math.log(numPx) / Math.log( 2 ) < 31) - imgFactory = new ArrayImgFactory(); - else - imgFactory = new CellImgFactory(); + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(new FloatType()); + } else { + imgFactory = new CellImgFactory<>(new FloatType()); + } - Img< FloatType > loadedImg = imgFactory.create( rai, new FloatType() ); - FileMapImgLoaderLOCI2.copy(Views.extendZero( rai ), loadedImg); + Img< FloatType > loadedImg = imgFactory.create(rai); + RealTypeConverters.copyFromTo(Views.extendZero(rai), loadedImg); rai = loadedImg; } @@ -238,8 +248,8 @@ else if ( cacheResult ) Arrays.fill( cellSize, 1 ); for ( int d = 0; d < rai.numDimensions() - 1; d++ ) cellSize[d] = (int) rai.dimension( d ); - rai = FusionTools.cacheRandomAccessibleInterval( rai, Long.MAX_VALUE, - Views.iterable( rai ).firstElement().createVariable(), cellSize ); + rai = FusionTools.cacheRandomAccessibleInterval( + rai, Long.MAX_VALUE, rai.firstElement().createVariable(), cellSize); } } From 48304d03b9ee2f0b22b4286aae0c0532ecded5d5 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 17:10:22 -0500 Subject: [PATCH 17/31] Pull out common flatfield loading code --- .../flatfield/FlatfieldImageLoader.java | 181 ++++++++++++++++++ .../LazyLoadingFlatFieldCorrectionMap.java | 112 +---------- ...onFlatfieldCorrectionWrappedImgLoader.java | 2 +- ...erFlatfieldCorrectionWrappedImgLoader.java | 156 ++++----------- 4 files changed, 221 insertions(+), 230 deletions(-) create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java new file mode 100644 index 000000000..32cd3d3e2 --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java @@ -0,0 +1,181 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.io.File; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.janelia.saalfeldlab.n5.N5Reader; +import org.janelia.saalfeldlab.n5.imglib2.N5Utils; +import org.janelia.saalfeldlab.n5.universe.StorageFormat; + +import ij.IJ; +import ij.ImagePlus; +import mpicbg.spim.data.sequence.ViewId; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.converter.RealTypeConverters; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Cast; +import net.imglib2.util.Pair; +import net.imglib2.util.ValuePair; +import util.URITools; + +/** + * Helper class for loading flatfield correction images from various sources. + * + * This class handles: + * - URI-based storage of bright/dark image paths per view + * - Lazy loading and caching of images + * - Auto-detection of format (TIFF vs N5/Zarr) + * - Support for cloud storage (S3, GCS) + */ +public class FlatfieldImageLoader { + + protected final Map> raiMap; + protected final Map> uriMap; + + private static final Pair NULL_PAIR = new ValuePair<>(null, null); + + public FlatfieldImageLoader() { + raiMap = new HashMap<>(); + uriMap = new HashMap<>(); + } + + public void setBrightImage(ViewId vId, URI imgUri) { + final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); + uriMap.put(vId, new ValuePair<>(imgUri, oldPair.getB())); + } + + public void setDarkImage(ViewId vId, URI imgUri) { + final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); + uriMap.put(vId, new ValuePair<>(oldPair.getA(), imgUri)); + } + + public void setBrightImage(ViewId vId, File imgFile) { + setBrightImage(vId, imgFile == null ? null : imgFile.toURI()); + } + + public void setDarkImage(ViewId vId, File imgFile) { + setDarkImage(vId, imgFile == null ? null : imgFile.toURI()); + } + + public RandomAccessibleInterval getBrightImg(ViewId vId) { + return getImg(vId, Pair::getA); + } + + public RandomAccessibleInterval getDarkImg(ViewId vId) { + return getImg(vId, Pair::getB); + } + + /** + * Get image for view id; the brightfield is stored in the A element of the pair, the darkfield in B + * @param vId view id + * @param uriSelector function to select URI from pair + * @return image, or null if not set + */ + private RandomAccessibleInterval getImg(ViewId vId, Function, URI> uriSelector) { + if (!uriMap.containsKey(vId)) + return null; + + final URI uriToLoad = uriSelector.apply(uriMap.get(vId)); + if (uriToLoad == null) + return null; + + return loadImageIfNecessary(uriToLoad); + } + + /** + * Load an image from a URI. Supports: + * - Local TIFF files (via ImageJ) + * - Local/cloud Zarr v3 containers (via N5 API) + * - Local/cloud N5 containers (via N5 API) + * + * @param uri URI to the image + * @return the loaded image as FloatType + */ + public RandomAccessibleInterval loadImageIfNecessary(URI uri) { + if (!raiMap.containsKey(uri)) { + RandomAccessibleInterval img; + + if (isChunkedFormat(uri)) { + // Use N5/Zarr API for chunked formats + final StorageFormat format = detectStorageFormat(uri); + final N5Reader reader = URITools.instantiateN5Reader(format, uri); + final RandomAccessibleInterval raw = N5Utils.open(reader, ""); + img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); + } else { + // Legacy TIFF path via ImageJ + final File file = new File(uri); + final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); + if (imp == null) + throw new RuntimeException("Failed to load image from: " + uri); + img = ImageJFunctions.convertFloat(imp).copy(); + } + + raiMap.put(uri, img); + } + return raiMap.get(uri); + } + + /** + * Determine if the URI points to a chunked format (N5/Zarr) vs TIFF. + */ + public static boolean isChunkedFormat(URI uri) { + final String scheme = uri.getScheme(); + // Cloud URIs are always chunked format + if ("s3".equals(scheme) || "gs".equals(scheme)) + return true; + + // Check path for .zarr or .n5 extension + final String path = uri.getPath(); + if (path == null) + return false; + + final String lowerPath = path.toLowerCase(); + return lowerPath.endsWith(".zarr") || lowerPath.endsWith(".n5") + || lowerPath.contains(".zarr/") || lowerPath.contains(".n5/"); + } + + /** + * Detect the storage format from the URI. + */ + public static StorageFormat detectStorageFormat(URI uri) { + final String path = uri.getPath(); + if (path != null && path.toLowerCase().contains(".n5")) + return StorageFormat.N5; + // Default to Zarr v3 for cloud and .zarr paths + return StorageFormat.ZARR; + } + + /** + * Get the URI map for bright/dark images per view. + * @return map from ViewId to (brightUri, darkUri) pair + */ + public Map> getUriMap() { + return uriMap; + } +} diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java index d9d858efa..eda2b2a59 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java @@ -24,138 +24,40 @@ import java.io.File; import java.net.URI; -import java.util.HashMap; import java.util.Map; -import java.util.function.Function; -import org.janelia.saalfeldlab.n5.N5Reader; -import org.janelia.saalfeldlab.n5.imglib2.N5Utils; -import org.janelia.saalfeldlab.n5.universe.StorageFormat; - -import ij.IJ; -import ij.ImagePlus; import mpicbg.spim.data.sequence.ImgLoader; import mpicbg.spim.data.sequence.ViewId; import net.imglib2.RandomAccessibleInterval; -import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.FloatType; -import net.imglib2.util.Cast; import net.imglib2.util.Pair; -import net.imglib2.util.ValuePair; -import util.URITools; public abstract class LazyLoadingFlatFieldCorrectionMap implements FlatfieldCorrectionWrappedImgLoader { - protected final Map> raiMap; - protected final Map> uriMap; - - private static final Pair NULL_PAIR = new ValuePair<>(null, null); + protected final FlatfieldImageLoader imageLoader; public LazyLoadingFlatFieldCorrectionMap() { - raiMap = new HashMap<>(); - uriMap = new HashMap<>(); + imageLoader = new FlatfieldImageLoader(); } @Override public void setBrightImage(ViewId vId, URI imgUri) { - final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); - uriMap.put(vId, new ValuePair<>(imgUri, oldPair.getB())); + imageLoader.setBrightImage(vId, imgUri); } @Override public void setDarkImage(ViewId vId, URI imgUri) { - final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); - uriMap.put(vId, new ValuePair<>(oldPair.getA(), imgUri)); + imageLoader.setDarkImage(vId, imgUri); } protected RandomAccessibleInterval getBrightImg(ViewId vId) { - return getImg(vId, Pair::getA); + return imageLoader.getBrightImg(vId); } protected RandomAccessibleInterval getDarkImg(ViewId vId) { - return getImg(vId, Pair::getB); - } - - /** - * Get image for view id; the brightfield is stored in the A element of the pair, the darkfield in B - * @param vId view id - * @param uriSelector function to select URI from pair - * @return image, or null if not set - */ - private RandomAccessibleInterval getImg(ViewId vId, Function, URI> uriSelector) { - if (!uriMap.containsKey(vId)) - return null; - - final URI uriToLoad = uriSelector.apply(uriMap.get(vId)); - if (uriToLoad == null) - return null; - - return loadImageIfNecessary(uriToLoad); - } - - /** - * Load an image from a URI. Supports: - * - Local TIFF files (via ImageJ) - * - Local/cloud Zarr v3 containers (via N5 API) - * - Local/cloud N5 containers (via N5 API) - * - * @param uri URI to the image - * @return the loaded image as FloatType - */ - private RandomAccessibleInterval loadImageIfNecessary(URI uri) { - if (!raiMap.containsKey(uri)) { - RandomAccessibleInterval img; - - if (isChunkedFormat(uri)) { - // Use N5/Zarr API for chunked formats - final StorageFormat format = detectStorageFormat(uri); - final N5Reader reader = URITools.instantiateN5Reader(format, uri); - final RandomAccessibleInterval raw = N5Utils.open(reader, ""); - img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); - } else { - // Legacy TIFF path via ImageJ - final File file = new File(uri); - final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); - if (imp == null) - throw new RuntimeException("Failed to load image from: " + uri); - img = ImageJFunctions.convertFloat(imp).copy(); - } - - raiMap.put(uri, img); - } - return raiMap.get(uri); - } - - /** - * Determine if the URI points to a chunked format (N5/Zarr) vs TIFF. - */ - private static boolean isChunkedFormat(URI uri) { - final String scheme = uri.getScheme(); - // Cloud URIs are always chunked format - if ("s3".equals(scheme) || "gs".equals(scheme)) - return true; - - // Check path for .zarr or .n5 extension - final String path = uri.getPath(); - if (path == null) - return false; - - final String lowerPath = path.toLowerCase(); - return lowerPath.endsWith(".zarr") || lowerPath.endsWith(".n5") - || lowerPath.contains(".zarr/") || lowerPath.contains(".n5/"); - } - - /** - * Detect the storage format from the URI. - */ - private static StorageFormat detectStorageFormat(URI uri) { - final String path = uri.getPath(); - if (path != null && path.toLowerCase().contains(".n5")) - return StorageFormat.N5; - // Default to Zarr v3 for cloud and .zarr paths - return StorageFormat.ZARR; + return imageLoader.getDarkImg(vId); } public static void main(String[] args) @@ -173,6 +75,6 @@ public static void main(String[] args) */ public Map> getUriMap() { - return uriMap; + return imageLoader.getUriMap(); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index 79d910da3..77fed988d 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -108,7 +108,7 @@ private RandomAccessibleInterval getOrCreateDownsampledImg( ) { // Convert to a list here to have a proper hash code for the map key List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); - final ValuePair> key = new ValuePair<>(uriSelector.apply(uriMap.get(vId)), dsFactorList); + final ValuePair> key = new ValuePair<>(uriSelector.apply(getUriMap().get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { final RandomAccessibleInterval img = imgGetter.apply(vId); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index 265df6973..7c8a58321 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -31,15 +31,9 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.janelia.saalfeldlab.n5.N5Reader; -import org.janelia.saalfeldlab.n5.imglib2.N5Utils; -import org.janelia.saalfeldlab.n5.universe.StorageFormat; - import bdv.ViewerImgLoader; import bdv.ViewerSetupImgLoader; import bdv.cache.CacheControl; -import ij.IJ; -import ij.ImagePlus; import mpicbg.spim.data.generic.sequence.ImgLoaderHint; import mpicbg.spim.data.generic.sequence.ImgLoaderHints; import mpicbg.spim.data.sequence.MultiResolutionImgLoader; @@ -54,17 +48,14 @@ import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.img.cell.CellImgFactory; -import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.FloatType; -import net.imglib2.util.Cast; import net.imglib2.util.Pair; import net.imglib2.util.ValuePair; import net.imglib2.view.Views; import net.preibisch.mvrecon.process.fusion.FusionTools; -import util.URITools; /** * Flatfield correction wrapper for ViewerImgLoader. @@ -83,17 +74,13 @@ */ public class ViewerFlatfieldCorrectionWrappedImgLoader implements ViewerImgLoader, MultiResolutionImgLoader { + private final ViewerImgLoader wrappedImgLoader; private boolean active; private boolean cacheResult; - private static final Pair NULL_PAIR = new ValuePair<>(null, null); - - /** Maps ViewId to (brightUri, darkUri) pair */ - protected final Map> uriMap; - - /** Cached loaded correction images */ - protected final Map> raiMap; + /** Helper for loading flatfield images */ + private final FlatfieldImageLoader imageLoader; /** Downsampled bright/dark images for each mipmap level */ private final Map>, RandomAccessibleInterval> dsRaiMap; @@ -106,28 +93,10 @@ public ViewerFlatfieldCorrectionWrappedImgLoader(final ViewerImgLoader wrappedIm this.wrappedImgLoader = wrappedImgLoader; this.active = true; this.cacheResult = cacheResult; - this.uriMap = new HashMap<>(); - this.raiMap = new HashMap<>(); + this.imageLoader = new FlatfieldImageLoader(); this.dsRaiMap = new HashMap<>(); } - // ========== ViewerImgLoader interface ========== - - @Override - public ViewerFlatfieldCorrectionWrappedSetupImgLoader getSetupImgLoader(final int setupId) { - return new ViewerFlatfieldCorrectionWrappedSetupImgLoader<>(setupId); - } - - @Override - public CacheControl getCacheControl() { - return wrappedImgLoader.getCacheControl(); - } - - @Override - public void setNumFetcherThreads(final int n) { - wrappedImgLoader.setNumFetcherThreads(n); - } - // ========== Configuration methods ========== public ViewerImgLoader getWrappedImgLoader() { @@ -151,107 +120,54 @@ public void setCached(final boolean cached) { } public void setBrightImage(final ViewId vId, final URI imgUri) { - final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); - uriMap.put(vId, new ValuePair<>(imgUri, oldPair.getB())); + imageLoader.setBrightImage(vId, imgUri); } public void setDarkImage(final ViewId vId, final URI imgUri) { - final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); - uriMap.put(vId, new ValuePair<>(oldPair.getA(), imgUri)); + imageLoader.setDarkImage(vId, imgUri); } public void setBrightImage(final ViewId vId, final File imgFile) { - setBrightImage(vId, imgFile == null ? null : imgFile.toURI()); + imageLoader.setBrightImage(vId, imgFile); } public void setDarkImage(final ViewId vId, final File imgFile) { - setDarkImage(vId, imgFile == null ? null : imgFile.toURI()); + imageLoader.setDarkImage(vId, imgFile); } - // ========== Image loading helpers ========== - - protected RandomAccessibleInterval getBrightImg(final ViewId vId) { - if (!uriMap.containsKey(vId)) - return null; - - final URI uriToLoad = uriMap.get(vId).getA(); - if (uriToLoad == null) - return null; - - loadImageIfNecessary(uriToLoad); - return raiMap.get(uriToLoad); + /** + * Get the URI map for bright/dark images per view. + * @return map from ViewId to (brightUri, darkUri) pair + */ + public Map> getUriMap() { + return imageLoader.getUriMap(); } - protected RandomAccessibleInterval getDarkImg(final ViewId vId) { - if (!uriMap.containsKey(vId)) - return null; - - final URI uriToLoad = uriMap.get(vId).getB(); - if (uriToLoad == null) - return null; + // ========== ViewerImgLoader interface ========== - loadImageIfNecessary(uriToLoad); - return raiMap.get(uriToLoad); + @Override + public ViewerFlatfieldCorrectionWrappedSetupImgLoader getSetupImgLoader(final int setupId) { + return new ViewerFlatfieldCorrectionWrappedSetupImgLoader<>(setupId); } - /** - * Load an image from a URI. Supports: - * - Local TIFF files (via ImageJ) - * - Local/cloud Zarr v3 containers (via N5 API) - * - Local/cloud N5 containers (via N5 API) - */ - protected void loadImageIfNecessary(final URI uri) { - if (raiMap.containsKey(uri)) - return; - - RandomAccessibleInterval img; - - if (isChunkedFormat(uri)) { - // Use N5/Zarr API for chunked formats - final StorageFormat format = detectStorageFormat(uri); - final N5Reader reader = URITools.instantiateN5Reader(format, uri); - final RandomAccessibleInterval raw = N5Utils.open(reader, ""); - img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); - } else { - // Legacy TIFF path via ImageJ - final File file = new File(uri); - final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); - if (imp == null) - throw new RuntimeException("Failed to load image from: " + uri); - img = ImageJFunctions.convertFloat(imp).copy(); - } + @Override + public CacheControl getCacheControl() { + return wrappedImgLoader.getCacheControl(); + } - raiMap.put(uri, img); + @Override + public void setNumFetcherThreads(final int n) { + wrappedImgLoader.setNumFetcherThreads(n); } - /** - * Determine if the URI points to a chunked format (N5/Zarr) vs TIFF. - */ - private static boolean isChunkedFormat(final URI uri) { - final String scheme = uri.getScheme(); - // Cloud URIs are always chunked format - if ("s3".equals(scheme) || "gs".equals(scheme)) - return true; - - // Check path for .zarr or .n5 extension - final String path = uri.getPath(); - if (path == null) - return false; - - final String lowerPath = path.toLowerCase(); - return lowerPath.endsWith(".zarr") || lowerPath.endsWith(".n5") - || lowerPath.contains(".zarr/") || lowerPath.contains(".n5/"); + // ========== Image loading helpers ========== + + protected RandomAccessibleInterval getBrightImg(final ViewId vId) { + return imageLoader.getBrightImg(vId); } - /** - * Detect the storage format from the URI. - */ - private static StorageFormat detectStorageFormat(final URI uri) { - final String path = uri.getPath(); - if (path != null && path.toLowerCase().contains(".n5")) - return StorageFormat.N5; - // Default to Zarr v3 for cloud and .zarr paths - return StorageFormat.ZARR; + protected RandomAccessibleInterval getDarkImg(final ViewId vId) { + return imageLoader.getDarkImg(vId); } protected RandomAccessibleInterval getOrCreateBrightImgDownsampled( @@ -280,7 +196,7 @@ private RandomAccessibleInterval getOrCreateDownsampledImg( ) { // Convert to a list here to have a proper hash code for the map key List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); - final ValuePair> key = new ValuePair<>(uriSelector.apply(uriMap.get(vId)), dsFactorList); + final ValuePair> key = new ValuePair<>(uriSelector.apply(imageLoader.getUriMap().get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { final RandomAccessibleInterval img = imgGetter.apply(vId); @@ -296,14 +212,6 @@ private RandomAccessibleInterval getOrCreateDownsampledImg( return dsRaiMap.get(key); } - /** - * Get the URI map for bright/dark images per view. - * @return map from ViewId to (brightUri, darkUri) pair - */ - public Map> getUriMap() { - return uriMap; - } - // ========== Inner class: ViewerSetupImgLoader implementation ========== public class ViewerFlatfieldCorrectionWrappedSetupImgLoader & NativeType, V extends Volatile & RealType & NativeType> From 6088e8293bfdcf18d9e368601892d9f7397f8617 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Fri, 9 Jan 2026 17:25:43 -0500 Subject: [PATCH 18/31] Use input cell size for downsampled flatfields --- ...ionFlatfieldCorrectionWrappedImgLoader.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index 77fed988d..f6a84a326 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -47,6 +47,8 @@ import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgFactory; +import net.imglib2.img.cell.AbstractCellImg; +import net.imglib2.img.cell.CellGrid; import net.imglib2.img.cell.CellImgFactory; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.realtransform.AffineTransform3D; @@ -395,6 +397,9 @@ public Dimensions getImageSize(int timepointId, int level) /** * Downsample an image using the imglib2-algorithm blocks API. + *

+ * If the input is a cell/chunked image, the output cell size is computed + * to align with input chunk boundaries (input chunk size / downsampling factor). * * @param input image to downsample * @param dsFactor factors to downsample by (may have more dimensions than input) @@ -424,10 +429,21 @@ public static & NativeType> RandomAccessibleInterval blocks = BlockSupplier.of(input) .andThen(Downsample.downsample(effectiveFactors)); - return BlockAlgoUtils.cellImg(blocks, outDim, new int[]{64}); + return BlockAlgoUtils.cellImg(blocks, outDim, cellSize); } public static void main(String[] args) From 4c728f966e606e9ee485af4cea7404ab48d6a599 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Mon, 2 Feb 2026 11:01:24 -0500 Subject: [PATCH 19/31] Add class to store info about flatfield storage --- .../flatfield/FlatfieldImageInfo.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageInfo.java diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageInfo.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageInfo.java new file mode 100644 index 000000000..3f241d5be --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageInfo.java @@ -0,0 +1,122 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import java.net.URI; +import java.util.Objects; + +import org.janelia.saalfeldlab.n5.universe.StorageFormat; + +/** + * Data class holding flatfield image metadata: URI, format, and dataset path. + */ +public class FlatfieldImageInfo { + + private final URI uri; + private final StorageFormat format; + private final String dataset; + + /** + * Create a FlatfieldImageInfo with all parameters. + * + * @param uri the URI to the flatfield image + * @param format the storage format (TIF uses null, others use StorageFormat enum) + * @param dataset the dataset path within the container (null or empty for root) + */ + public FlatfieldImageInfo(final URI uri, final StorageFormat format, final String dataset) { + this.uri = uri; + this.format = format; + this.dataset = dataset; + } + + /** + * Create a FlatfieldImageInfo with URI, using TIF format and root dataset. + */ + public FlatfieldImageInfo(final URI uri) { + this(uri, null, null); + } + + /** + * Create a FlatfieldImageInfo with URI and format, using root dataset. + */ + public FlatfieldImageInfo(final URI uri, final StorageFormat format) { + this(uri, format, null); + } + + public URI getUri() { + return uri; + } + + /** + * Get the storage format. + * @return the format, or null for TIF format + */ + public StorageFormat getFormat() { + return format; + } + + /** + * Get the dataset path within the container. + * @return the dataset path, or null/empty for root + */ + public String getDataset() { + return dataset; + } + + /** + * Get the effective dataset path (empty string if null). + */ + public String getEffectiveDataset() { + return dataset == null ? "" : dataset; + } + + /** + * Check if this is a TIF format (format is null). + */ + public boolean isTif() { + return format == null; + } + + @Override + public String toString() { + return "FlatfieldImageInfo{uri=" + uri + ", format=" + format + ", dataset=" + dataset + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FlatfieldImageInfo that = (FlatfieldImageInfo) o; + if (!Objects.equals(uri, that.uri)) return false; + if (format != that.format) return false; + return Objects.equals(dataset, that.dataset); + } + + @Override + public int hashCode() { + int result = uri != null ? uri.hashCode() : 0; + result = 31 * result + (format != null ? format.hashCode() : 0); + result = 31 * result + (dataset != null ? dataset.hashCode() : 0); + return result; + } +} From 87c7dea8e0fa334634554cf97c8c4a3e2ea54f07 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Mon, 2 Feb 2026 11:12:14 -0500 Subject: [PATCH 20/31] Use FlatfieldImageInfo in loaders --- ...ltFlatfieldCorrectionWrappedImgLoader.java | 2 +- .../FlatfieldCorrectionWrappedImgLoader.java | 43 ++--- .../flatfield/FlatfieldImageLoader.java | 173 ++++++++++-------- ...onFlatfieldCorrectionWrappedImgLoader.java | 9 +- ...erFlatfieldCorrectionWrappedImgLoader.java | 32 ++-- 5 files changed, 128 insertions(+), 131 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java index 5d4d23e1f..15c5aa8ce 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/DefaultFlatfieldCorrectionWrappedImgLoader.java @@ -285,7 +285,7 @@ public static void main(String[] args) ImgLoader il = data.getSequenceDescription().getImgLoader(); DefaultFlatfieldCorrectionWrappedImgLoader ffcil = new DefaultFlatfieldCorrectionWrappedImgLoader( il ); - ffcil.setDarkImage( new ViewId( 0, 0 ), new File( "/Users/david/desktop/ff.tif" ) ); + ffcil.setDarkImage(new ViewId(0, 0), new FlatfieldImageInfo(new File("/Users/david/desktop/ff.tif").toURI())); data.getSequenceDescription().setImgLoader( ffcil ); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java index be80c7f53..14e0f0464 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionWrappedImgLoader.java @@ -9,12 +9,12 @@ * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -22,49 +22,28 @@ */ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; -import java.io.File; -import java.net.URI; - import mpicbg.spim.data.sequence.ImgLoader; import mpicbg.spim.data.sequence.ViewId; public interface FlatfieldCorrectionWrappedImgLoader extends ImgLoader { - public IL getWrappedImgLoder(); - public void setActive(boolean active); - public boolean isActive(); - public void setCached(boolean cached); - public boolean isCached(); + IL getWrappedImgLoder(); + void setActive(boolean active); + boolean isActive(); + void setCached(boolean cached); + boolean isCached(); /** * Set the bright (flatfield) image for a view. * @param vId view id - * @param imgUri URI to the bright image (supports file://, s3://, gs://, local paths) + * @param info flatfield image info containing URI, format, and optional dataset path */ - public void setBrightImage(ViewId vId, URI imgUri); + void setBrightImage(ViewId vId, FlatfieldImageInfo info); /** * Set the dark (darkfield) image for a view. * @param vId view id - * @param imgUri URI to the dark image (supports file://, s3://, gs://, local paths) - */ - public void setDarkImage(ViewId vId, URI imgUri); - - /** - * Set the bright (flatfield) image for a view from a local file. - * @param vId view id - * @param imgFile local file path to the bright image - */ - default void setBrightImage(ViewId vId, File imgFile) { - setBrightImage(vId, imgFile == null ? null : imgFile.toURI()); - } - - /** - * Set the dark (darkfield) image for a view from a local file. - * @param vId view id - * @param imgFile local file path to the dark image + * @param info flatfield image info containing URI, format, and optional dataset path */ - default void setDarkImage(ViewId vId, File imgFile) { - setDarkImage(vId, imgFile == null ? null : imgFile.toURI()); - } + void setDarkImage(ViewId vId, FlatfieldImageInfo info); } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java index 32cd3d3e2..6704a545a 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldImageLoader.java @@ -48,39 +48,31 @@ * Helper class for loading flatfield correction images from various sources. * * This class handles: - * - URI-based storage of bright/dark image paths per view + * - Storage of bright/dark image info per view (URI, format, dataset path) * - Lazy loading and caching of images - * - Auto-detection of format (TIFF vs N5/Zarr) - * - Support for cloud storage (S3, GCS) + * - Support for TIF, N5, Zarr (v2/v3), and HDF5 formats + * - Support for cloud storage (S3, GCS) for N5/Zarr formats */ public class FlatfieldImageLoader { - protected final Map> raiMap; - protected final Map> uriMap; + protected final Map> raiMap; + protected final Map> infoMap; - private static final Pair NULL_PAIR = new ValuePair<>(null, null); + private static final Pair NULL_PAIR = new ValuePair<>(null, null); public FlatfieldImageLoader() { raiMap = new HashMap<>(); - uriMap = new HashMap<>(); + infoMap = new HashMap<>(); } - public void setBrightImage(ViewId vId, URI imgUri) { - final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); - uriMap.put(vId, new ValuePair<>(imgUri, oldPair.getB())); + public void setBrightImage(ViewId vId, FlatfieldImageInfo info) { + final Pair oldPair = infoMap.getOrDefault(vId, NULL_PAIR); + infoMap.put(vId, new ValuePair<>(info, oldPair.getB())); } - public void setDarkImage(ViewId vId, URI imgUri) { - final Pair oldPair = uriMap.getOrDefault(vId, NULL_PAIR); - uriMap.put(vId, new ValuePair<>(oldPair.getA(), imgUri)); - } - - public void setBrightImage(ViewId vId, File imgFile) { - setBrightImage(vId, imgFile == null ? null : imgFile.toURI()); - } - - public void setDarkImage(ViewId vId, File imgFile) { - setDarkImage(vId, imgFile == null ? null : imgFile.toURI()); + public void setDarkImage(ViewId vId, FlatfieldImageInfo info) { + final Pair oldPair = infoMap.getOrDefault(vId, NULL_PAIR); + infoMap.put(vId, new ValuePair<>(oldPair.getA(), info)); } public RandomAccessibleInterval getBrightImg(ViewId vId) { @@ -94,88 +86,125 @@ public RandomAccessibleInterval getDarkImg(ViewId vId) { /** * Get image for view id; the brightfield is stored in the A element of the pair, the darkfield in B * @param vId view id - * @param uriSelector function to select URI from pair + * @param infoSelector function to select info from pair * @return image, or null if not set */ - private RandomAccessibleInterval getImg(ViewId vId, Function, URI> uriSelector) { - if (!uriMap.containsKey(vId)) + private RandomAccessibleInterval getImg(ViewId vId, Function, FlatfieldImageInfo> infoSelector) { + if (!infoMap.containsKey(vId)) return null; - final URI uriToLoad = uriSelector.apply(uriMap.get(vId)); - if (uriToLoad == null) + final FlatfieldImageInfo info = infoSelector.apply(infoMap.get(vId)); + if (info == null) return null; - return loadImageIfNecessary(uriToLoad); + return loadImageIfNecessary(info); } /** - * Load an image from a URI. Supports: - * - Local TIFF files (via ImageJ) - * - Local/cloud Zarr v3 containers (via N5 API) - * - Local/cloud N5 containers (via N5 API) + * Load an image using the specified format. Supports: + * - TIF files (local only, via ImageJ) + * - N5 containers (local + cloud) + * - Zarr v2/v3 containers (local + cloud) + * - HDF5 files (local only) * - * @param uri URI to the image + * @param info the flatfield image info containing URI, format, and dataset path * @return the loaded image as FloatType */ - public RandomAccessibleInterval loadImageIfNecessary(URI uri) { - if (!raiMap.containsKey(uri)) { + public RandomAccessibleInterval loadImageIfNecessary(FlatfieldImageInfo info) { + if (!raiMap.containsKey(info)) { RandomAccessibleInterval img; - if (isChunkedFormat(uri)) { - // Use N5/Zarr API for chunked formats - final StorageFormat format = detectStorageFormat(uri); - final N5Reader reader = URITools.instantiateN5Reader(format, uri); - final RandomAccessibleInterval raw = N5Utils.open(reader, ""); - img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); - } else { - // Legacy TIFF path via ImageJ - final File file = new File(uri); + if (info.isTif()) { + // TIF format via ImageJ + final File file = new File(info.getUri()); final ImagePlus imp = IJ.openImage(file.getAbsolutePath()); if (imp == null) - throw new RuntimeException("Failed to load image from: " + uri); + throw new RuntimeException("Failed to load TIF image from: " + info.getUri()); img = ImageJFunctions.convertFloat(imp).copy(); + } else { + // N5, Zarr, or HDF5 via N5 API + final StorageFormat format = info.getFormat(); + final URI uri = info.getUri(); + + // Validate HDF5 is not used with cloud storage + if (format == StorageFormat.HDF5) { + final String scheme = uri.getScheme(); + if ("s3".equals(scheme) || "gs".equals(scheme)) { + throw new RuntimeException("HDF5 format does not support cloud storage (s3/gs). URI: " + uri); + } + } + + final N5Reader reader = URITools.instantiateN5Reader(format, uri); + final String dataset = info.getEffectiveDataset(); + final RandomAccessibleInterval raw = N5Utils.open(reader, dataset); + img = RealTypeConverters.convert(Cast.unchecked(raw), new FloatType()); } - raiMap.put(uri, img); + raiMap.put(info, img); } - return raiMap.get(uri); + return raiMap.get(info); } /** - * Determine if the URI points to a chunked format (N5/Zarr) vs TIFF. + * Get the info map for bright/dark images per view. + * @return map from ViewId to (brightInfo, darkInfo) pair */ - public static boolean isChunkedFormat(URI uri) { - final String scheme = uri.getScheme(); - // Cloud URIs are always chunked format - if ("s3".equals(scheme) || "gs".equals(scheme)) - return true; - - // Check path for .zarr or .n5 extension - final String path = uri.getPath(); - if (path == null) - return false; - - final String lowerPath = path.toLowerCase(); - return lowerPath.endsWith(".zarr") || lowerPath.endsWith(".n5") - || lowerPath.contains(".zarr/") || lowerPath.contains(".n5/"); + public Map> getInfoMap() { + return infoMap; } + // ==================== Format Parsing Utilities ==================== + /** - * Detect the storage format from the URI. + * Parse a format string to StorageFormat. + * @param formatStr the format string (tif, n5, zarr, zarr2, hdf5) + * @return StorageFormat, or null for TIF format + * @throws IllegalArgumentException if format string is unknown */ - public static StorageFormat detectStorageFormat(URI uri) { - final String path = uri.getPath(); - if (path != null && path.toLowerCase().contains(".n5")) - return StorageFormat.N5; - // Default to Zarr v3 for cloud and .zarr paths - return StorageFormat.ZARR; + public static StorageFormat parseFormat(String formatStr) { + if (formatStr == null || formatStr.isEmpty()) + throw new IllegalArgumentException("Format attribute is required"); + + switch (formatStr.toLowerCase()) { + case "tif": + case "tiff": + return null; // null indicates TIF format + case "n5": + return StorageFormat.N5; + case "zarr": + case "zarr3": + return StorageFormat.ZARR; + case "zarr2": + return StorageFormat.ZARR2; + case "hdf5": + case "h5": + return StorageFormat.HDF5; + default: + throw new IllegalArgumentException("Unknown flatfield format: " + formatStr + + ". Supported formats: tif, n5, zarr, zarr2, hdf5"); + } } /** - * Get the URI map for bright/dark images per view. - * @return map from ViewId to (brightUri, darkUri) pair + * Convert StorageFormat to format string for XML serialization. + * @param format the StorageFormat (null for TIF) + * @return format string */ - public Map> getUriMap() { - return uriMap; + public static String formatToString(StorageFormat format) { + if (format == null) + return "tif"; + + switch (format) { + case N5: + return "n5"; + case ZARR: + return "zarr"; + case ZARR2: + return "zarr2"; + case HDF5: + return "hdf5"; + default: + throw new IllegalArgumentException("Unsupported format for flatfield: " + format); + } } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java index f6a84a326..55c435434 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/MultiResolutionFlatfieldCorrectionWrappedImgLoader.java @@ -23,7 +23,6 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; -import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -72,7 +71,7 @@ public class MultiResolutionFlatfieldCorrectionWrappedImgLoader private boolean cacheResult; /* downsampled bright/dark images */ - private final Map>, RandomAccessibleInterval> dsRaiMap; + private final Map>, RandomAccessibleInterval> dsRaiMap; public MultiResolutionFlatfieldCorrectionWrappedImgLoader(MultiResolutionImgLoader wrappedImgLoader) { @@ -105,12 +104,12 @@ protected RandomAccessibleInterval< FloatType > getOrCreateDarkImgDownsampled(Vi private RandomAccessibleInterval getOrCreateDownsampledImg( ViewId vId, int[] downsamplingFactors, - Function, URI> uriSelector, + Function, FlatfieldImageInfo> infoSelector, Function> imgGetter ) { // Convert to a list here to have a proper hash code for the map key List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); - final ValuePair> key = new ValuePair<>(uriSelector.apply(getUriMap().get(vId)), dsFactorList); + final ValuePair> key = new ValuePair<>(infoSelector.apply(getInfoMap().get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { final RandomAccessibleInterval img = imgGetter.apply(vId); @@ -456,7 +455,7 @@ public static void main(String[] args) MultiResolutionImgLoader il = (MultiResolutionImgLoader) data.getSequenceDescription().getImgLoader(); MultiResolutionFlatfieldCorrectionWrappedImgLoader ffcil = new MultiResolutionFlatfieldCorrectionWrappedImgLoader( il ); - ffcil.setDarkImage( new ViewId( 0, 0 ), new File( "/Users/david/desktop/ff.tif" ) ); + ffcil.setDarkImage( new ViewId( 0, 0 ), new FlatfieldImageInfo( new File( "/Users/david/desktop/ff.tif" ).toURI(), null ) ); data.getSequenceDescription().setImgLoader( ffcil ); diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java index 7c8a58321..6d9709213 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -22,8 +22,6 @@ */ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; -import java.io.File; -import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -83,7 +81,7 @@ public class ViewerFlatfieldCorrectionWrappedImgLoader private final FlatfieldImageLoader imageLoader; /** Downsampled bright/dark images for each mipmap level */ - private final Map>, RandomAccessibleInterval> dsRaiMap; + private final Map>, RandomAccessibleInterval> dsRaiMap; public ViewerFlatfieldCorrectionWrappedImgLoader(final ViewerImgLoader wrappedImgLoader) { this(wrappedImgLoader, true); @@ -119,28 +117,20 @@ public void setCached(final boolean cached) { this.cacheResult = cached; } - public void setBrightImage(final ViewId vId, final URI imgUri) { - imageLoader.setBrightImage(vId, imgUri); + public void setBrightImage(final ViewId vId, final FlatfieldImageInfo info) { + imageLoader.setBrightImage(vId, info); } - public void setDarkImage(final ViewId vId, final URI imgUri) { - imageLoader.setDarkImage(vId, imgUri); - } - - public void setBrightImage(final ViewId vId, final File imgFile) { - imageLoader.setBrightImage(vId, imgFile); - } - - public void setDarkImage(final ViewId vId, final File imgFile) { - imageLoader.setDarkImage(vId, imgFile); + public void setDarkImage(final ViewId vId, final FlatfieldImageInfo info) { + imageLoader.setDarkImage(vId, info); } /** - * Get the URI map for bright/dark images per view. - * @return map from ViewId to (brightUri, darkUri) pair + * Get the info map for bright/dark images per view. + * @return map from ViewId to (brightInfo, darkInfo) pair */ - public Map> getUriMap() { - return imageLoader.getUriMap(); + public Map> getInfoMap() { + return imageLoader.getInfoMap(); } // ========== ViewerImgLoader interface ========== @@ -191,12 +181,12 @@ protected RandomAccessibleInterval getOrCreateDarkImgDownsampled( private RandomAccessibleInterval getOrCreateDownsampledImg( ViewId vId, int[] downsamplingFactors, - Function, URI> uriSelector, + Function, FlatfieldImageInfo> infoSelector, Function> imgGetter ) { // Convert to a list here to have a proper hash code for the map key List dsFactorList = Arrays.stream(downsamplingFactors).boxed().collect(Collectors.toList()); - final ValuePair> key = new ValuePair<>(uriSelector.apply(imageLoader.getUriMap().get(vId)), dsFactorList); + final ValuePair> key = new ValuePair<>(infoSelector.apply(imageLoader.getInfoMap().get(vId)), dsFactorList); if (!dsRaiMap.containsKey(key)) { final RandomAccessibleInterval img = imgGetter.apply(vId); From b530aec9106d681633d2bfc174e548d4adf76c8b Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Mon, 2 Feb 2026 11:19:35 -0500 Subject: [PATCH 21/31] Add xml support for new image info format --- .../popup/FlatFieldCorrectionPopup.java | 28 ++++---- .../LazyLoadingFlatFieldCorrectionMap.java | 19 +++-- ...lIoFlatfieldCorrectedWrappedImgLoader.java | 35 +++++---- ...erFlatfieldCorrectionWrappedImgLoader.java | 72 +++++++++++++++---- .../SplitMultiResolutionSetupImgLoader.java | 6 +- 5 files changed, 112 insertions(+), 48 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java index 1f683da7a..1d821ad7a 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/explorer/popup/FlatFieldCorrectionPopup.java @@ -27,7 +27,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; -import java.net.URI; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -46,6 +45,7 @@ import net.preibisch.mvrecon.fiji.spimdata.explorer.ExplorerWindow; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.DefaultFlatfieldCorrectionWrappedImgLoader; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldCorrectionWrappedImgLoader; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldImageInfo; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.LazyLoadingFlatFieldCorrectionMap; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.MultiResolutionFlatfieldCorrectionWrappedImgLoader; @@ -102,10 +102,10 @@ public void actionPerformed(ActionEvent e) .isCached() : true ); - Map> uriMap = null; + Map> infoMap = null; if ( alreadyFF ) - uriMap = ((LazyLoadingFlatFieldCorrectionMap) data.getSequenceDescription() - .getImgLoader()).getUriMap(); + infoMap = ((LazyLoadingFlatFieldCorrectionMap) data.getSequenceDescription() + .getImgLoader()).getInfoMap(); for ( Channel c : channels ) for ( Illumination ill : illums ) @@ -121,12 +121,12 @@ public void actionPerformed(ActionEvent e) } ).findAny().orElseGet( null ); if ( anyViewId != null ) - if (uriMap.containsKey(anyViewId)) { - Pair uris = uriMap.get(anyViewId); - if (uris.getA() != null) - bright = new File(uris.getA()).getAbsolutePath(); - if (uris.getB() != null) - dark = new File(uris.getB()).getAbsolutePath(); + if (infoMap.containsKey(anyViewId)) { + Pair infos = infoMap.get(anyViewId); + if (infos.getA() != null) + bright = new File(infos.getA().getUri()).getAbsolutePath(); + if (infos.getB() != null) + dark = new File(infos.getB().getUri()).getAbsolutePath(); } } gdp.addMessage( "Channel: " + c.getName() + ", Illumination: " + ill.getName() + ":" ); @@ -160,12 +160,16 @@ else if ( data.getSequenceDescription().getImgLoader() instanceof MultiResolutio File lightFile = !lightPath.equals( "" ) ? new File( lightPath ) : null; File darkFile = !darkPath.equals( "" ) ? new File( darkPath ) : null; + final File finalLightFile = lightFile; + final File finalDarkFile = darkFile; data.getSequenceDescription().getViewDescriptions().entrySet().forEach( el -> { if ( el.getValue().getViewSetup().getChannel() == c && el.getValue().getViewSetup().getIllumination() == ill ) { - ffIL.setBrightImage( el.getKey(), lightFile ); - ffIL.setDarkImage( el.getKey(), darkFile ); + if (finalLightFile != null) + ffIL.setBrightImage( el.getKey(), new FlatfieldImageInfo(finalLightFile.toURI(), null) ); + if (finalDarkFile != null) + ffIL.setDarkImage( el.getKey(), new FlatfieldImageInfo(finalDarkFile.toURI(), null) ); } } ); } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java index eda2b2a59..8e43f2cdd 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/LazyLoadingFlatFieldCorrectionMap.java @@ -23,7 +23,6 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; -import java.net.URI; import java.util.Map; import mpicbg.spim.data.sequence.ImgLoader; @@ -43,13 +42,13 @@ public LazyLoadingFlatFieldCorrectionMap() } @Override - public void setBrightImage(ViewId vId, URI imgUri) { - imageLoader.setBrightImage(vId, imgUri); + public void setBrightImage(ViewId vId, FlatfieldImageInfo info) { + imageLoader.setBrightImage(vId, info); } @Override - public void setDarkImage(ViewId vId, URI imgUri) { - imageLoader.setDarkImage(vId, imgUri); + public void setDarkImage(ViewId vId, FlatfieldImageInfo info) { + imageLoader.setDarkImage(vId, info); } protected RandomAccessibleInterval getBrightImg(ViewId vId) { @@ -63,18 +62,18 @@ protected RandomAccessibleInterval getDarkImg(ViewId vId) { public static void main(String[] args) { DefaultFlatfieldCorrectionWrappedImgLoader testImgLoader = new DefaultFlatfieldCorrectionWrappedImgLoader(null); - testImgLoader.setBrightImage(new ViewId(0, 0), new File("/Users/David/Desktop/ell2.tif")); + testImgLoader.setBrightImage(new ViewId(0, 0), new FlatfieldImageInfo(new File("/Users/David/Desktop/ell2.tif").toURI())); RandomAccessibleInterval brightImg = testImgLoader.getBrightImg(new ViewId(0, 0)); ImageJFunctions.show(brightImg); } /** - * Get the URI map for bright/dark images per view. - * @return map from ViewId to (brightUri, darkUri) pair + * Get the info map for bright/dark images per view. + * @return map from ViewId to (brightInfo, darkInfo) pair */ - public Map> getUriMap() + public Map> getInfoMap() { - return imageLoader.getUriMap(); + return imageLoader.getInfoMap(); } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java index f3d3c0e49..6d7b2edd4 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoFlatfieldCorrectedWrappedImgLoader.java @@ -26,6 +26,10 @@ import static mpicbg.spim.data.XmlKeys.IMGLOADER_TAG; import static mpicbg.spim.data.XmlKeys.TIMEPOINTS_TIMEPOINT_TAG; import static mpicbg.spim.data.XmlKeys.VIEWSETUP_TAG; +import static net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.XmlIoViewerFlatfieldCorrectionWrappedImgLoader.FORMAT_ATTR; +import static net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.XmlIoViewerFlatfieldCorrectionWrappedImgLoader.DATASET_ATTR; +import static net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.XmlIoViewerFlatfieldCorrectionWrappedImgLoader.parseFlatfieldImageInfo; +import static net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.XmlIoViewerFlatfieldCorrectionWrappedImgLoader.createFlatfieldImageElement; import java.io.File; import java.net.URI; @@ -35,7 +39,6 @@ import org.jdom2.Element; import mpicbg.spim.data.SpimDataInstantiationException; -import mpicbg.spim.data.XmlHelpers; import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription; import mpicbg.spim.data.generic.sequence.BasicImgLoader; import mpicbg.spim.data.generic.sequence.ImgLoaderIo; @@ -112,10 +115,15 @@ else if (ImgLoader.class.isInstance(wrappedImgLoader)) { int tp = Integer.parseInt(flatfield.getAttributeValue(TIMEPOINTS_TIMEPOINT_TAG)); int vs = Integer.parseInt(flatfield.getAttributeValue(VIEWSETUP_TAG)); - URI brightImg = XmlHelpers.loadPathURI(flatfield, BRIGHTIMG_TAG, basePathURI); - URI darkImg = XmlHelpers.loadPathURI(flatfield, DARKIMG_TAG, basePathURI); - res.setBrightImage(new ViewId(tp, vs), brightImg); - res.setDarkImage(new ViewId(tp, vs), darkImg); + ViewId viewId = new ViewId(tp, vs); + + FlatfieldImageInfo brightInfo = parseFlatfieldImageInfo(flatfield, BRIGHTIMG_TAG, basePathURI); + FlatfieldImageInfo darkInfo = parseFlatfieldImageInfo(flatfield, DARKIMG_TAG, basePathURI); + + if (brightInfo != null) + res.setBrightImage(viewId, brightInfo); + if (darkInfo != null) + res.setDarkImage(viewId, darkInfo); } res.setActive(active); @@ -131,7 +139,8 @@ public Element toXml(FlatfieldCorrectionWrappedImgLoader im @Override public Element toXml(FlatfieldCorrectionWrappedImgLoader imgLoader, URI basePathURI) { - final Map> uriMap = ((LazyLoadingFlatFieldCorrectionMap) imgLoader).getUriMap(); + final Map> infoMap = + ((LazyLoadingFlatFieldCorrectionMap) imgLoader).getInfoMap(); final Element wholeElem = new Element(IMGLOADER_TAG); wholeElem.setAttribute(IMGLOADER_FORMAT_ATTRIBUTE_NAME, @@ -157,20 +166,20 @@ public Element toXml(FlatfieldCorrectionWrappedImgLoader im final Element elFlatfields = new Element(FLATFIELDS_TAG); - for (ViewId vid : uriMap.keySet()) + for (ViewId vid : infoMap.keySet()) { - final Pair uris = uriMap.get(vid); - if (uris == null || (uris.getA() == null && uris.getB() == null)) + final Pair infos = infoMap.get(vid); + if (infos == null || (infos.getA() == null && infos.getB() == null)) continue; final Element elFlatfield = new Element(FLATFIELD_TAG); elFlatfield.setAttribute(TIMEPOINTS_TIMEPOINT_TAG, Integer.toString(vid.getTimePointId())); elFlatfield.setAttribute(VIEWSETUP_TAG, Integer.toString(vid.getViewSetupId())); - if (uris.getA() != null) - elFlatfield.addContent(XmlHelpers.pathElementURI(BRIGHTIMG_TAG, uris.getA(), basePathURI)); - if (uris.getB() != null) - elFlatfield.addContent(XmlHelpers.pathElementURI(DARKIMG_TAG, uris.getB(), basePathURI)); + if (infos.getA() != null) + elFlatfield.addContent(createFlatfieldImageElement(BRIGHTIMG_TAG, infos.getA(), basePathURI)); + if (infos.getB() != null) + elFlatfield.addContent(createFlatfieldImageElement(DARKIMG_TAG, infos.getB(), basePathURI)); elFlatfields.addContent(elFlatfield); } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java index 230d75551..fb6e56b4b 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java @@ -31,6 +31,7 @@ import java.net.URI; import java.util.Map; +import org.janelia.saalfeldlab.n5.universe.StorageFormat; import org.jdom2.DataConversionException; import org.jdom2.Element; @@ -61,6 +62,8 @@ public class XmlIoViewerFlatfieldCorrectionWrappedImgLoader public final static String DARKIMG_TAG = "DarkImg"; public final static String ACTIVE_TAG = "Active"; public final static String CACHED_TAG = "Cached"; + public final static String FORMAT_ATTR = "format"; + public final static String DATASET_ATTR = "dataset"; @Override public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, File basePath, @@ -106,10 +109,15 @@ public ViewerFlatfieldCorrectionWrappedImgLoader fromXml(Element elem, URI baseP for (Element flatfield : flatfields.getChildren()) { int tp = Integer.parseInt(flatfield.getAttributeValue(TIMEPOINTS_TIMEPOINT_TAG)); int vs = Integer.parseInt(flatfield.getAttributeValue(VIEWSETUP_TAG)); - URI brightImg = XmlHelpers.loadPathURI(flatfield, BRIGHTIMG_TAG, basePathURI); - URI darkImg = XmlHelpers.loadPathURI(flatfield, DARKIMG_TAG, basePathURI); - res.setBrightImage(new ViewId(tp, vs), brightImg); - res.setDarkImage(new ViewId(tp, vs), darkImg); + ViewId viewId = new ViewId(tp, vs); + + FlatfieldImageInfo brightInfo = parseFlatfieldImageInfo(flatfield, BRIGHTIMG_TAG, basePathURI); + FlatfieldImageInfo darkInfo = parseFlatfieldImageInfo(flatfield, DARKIMG_TAG, basePathURI); + + if (brightInfo != null) + res.setBrightImage(viewId, brightInfo); + if (darkInfo != null) + res.setDarkImage(viewId, darkInfo); } res.setActive(active); @@ -123,7 +131,7 @@ public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File b @Override public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, URI basePathURI) { - final Map> uriMap = imgLoader.getUriMap(); + final Map> infoMap = imgLoader.getInfoMap(); final Element wholeElem = new Element(IMGLOADER_TAG); wholeElem.setAttribute(IMGLOADER_FORMAT_ATTRIBUTE_NAME, @@ -147,19 +155,19 @@ public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, URI ba final Element elFlatfields = new Element(FLATFIELDS_TAG); - for (ViewId vid : uriMap.keySet()) { - final Pair uris = uriMap.get(vid); - if (uris == null || (uris.getA() == null && uris.getB() == null)) + for (ViewId vid : infoMap.keySet()) { + final Pair infos = infoMap.get(vid); + if (infos == null || (infos.getA() == null && infos.getB() == null)) continue; final Element elFlatfield = new Element(FLATFIELD_TAG); elFlatfield.setAttribute(TIMEPOINTS_TIMEPOINT_TAG, Integer.toString(vid.getTimePointId())); elFlatfield.setAttribute(VIEWSETUP_TAG, Integer.toString(vid.getViewSetupId())); - if (uris.getA() != null) - elFlatfield.addContent(XmlHelpers.pathElementURI(BRIGHTIMG_TAG, uris.getA(), basePathURI)); - if (uris.getB() != null) - elFlatfield.addContent(XmlHelpers.pathElementURI(DARKIMG_TAG, uris.getB(), basePathURI)); + if (infos.getA() != null) + elFlatfield.addContent(createFlatfieldImageElement(BRIGHTIMG_TAG, infos.getA(), basePathURI)); + if (infos.getB() != null) + elFlatfield.addContent(createFlatfieldImageElement(DARKIMG_TAG, infos.getB(), basePathURI)); elFlatfields.addContent(elFlatfield); } @@ -168,4 +176,44 @@ public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, URI ba wholeElem.addContent(elFlatfields); return wholeElem; } + + /** + * Parse a flatfield image element (BrightImg or DarkImg) into a FlatfieldImageInfo. + * + * @param parent parent element containing the image element + * @param tag the tag name (BRIGHTIMG_TAG or DARKIMG_TAG) + * @param basePathURI base path for resolving relative URIs + * @return FlatfieldImageInfo, or null if element doesn't exist + */ + protected static FlatfieldImageInfo parseFlatfieldImageInfo(Element parent, String tag, URI basePathURI) { + Element imgElement = parent.getChild(tag); + if (imgElement == null) + return null; + + URI uri = XmlHelpers.loadPathURI(parent, tag, basePathURI); + if (uri == null) + return null; + + String formatStr = imgElement.getAttributeValue(FORMAT_ATTR); + String dataset = imgElement.getAttributeValue(DATASET_ATTR); + + StorageFormat format = FlatfieldImageLoader.parseFormat(formatStr); + return new FlatfieldImageInfo(uri, format, dataset); + } + + /** + * Create an XML element for a flatfield image with format and dataset attributes. + * + * @param tag the tag name (BRIGHTIMG_TAG or DARKIMG_TAG) + * @param info the flatfield image info + * @param basePathURI base path for creating relative URIs + * @return the XML element + */ + protected static Element createFlatfieldImageElement(String tag, FlatfieldImageInfo info, URI basePathURI) { + Element el = XmlHelpers.pathElementURI(tag, info.getUri(), basePathURI); + el.setAttribute(FORMAT_ATTR, FlatfieldImageLoader.formatToString(info.getFormat())); + if (info.getDataset() != null && !info.getDataset().isEmpty()) + el.setAttribute(DATASET_ATTR, info.getDataset()); + return el; + } } diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/splitting/SplitMultiResolutionSetupImgLoader.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/splitting/SplitMultiResolutionSetupImgLoader.java index 0d925d273..7a7724e71 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/splitting/SplitMultiResolutionSetupImgLoader.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/splitting/SplitMultiResolutionSetupImgLoader.java @@ -170,7 +170,11 @@ public int numMipmapLevels() public RandomAccessibleInterval< FloatType > getFloatImage( int timepointId, int level, boolean normalize, ImgLoaderHint... hints ) { - throw new RuntimeException( "not supported." ); + final RandomAccessibleInterval< FloatType > full = underlyingSetupImgLoader.getFloatImage( timepointId, level, normalize, hints ); + + updateScaledIntervals( this.scaledIntervals, level, n, full ); + + return Views.zeroMin( Views.interval( full, scaledIntervals[ level ] ) ); } @Override From 9851d7e8493ef007c9949f4f9a9520d21040a615 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Mon, 2 Feb 2026 11:20:17 -0500 Subject: [PATCH 22/31] Update xml files and tests to new format --- data/dataset_corrected.xml | 36 +++++++++---------- data/dataset_corrected_viewer.xml | 36 +++++++++---------- .../flatfield/TestDecoratorChain.java | 4 +-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/data/dataset_corrected.xml b/data/dataset_corrected.xml index f3cdd3221..265e2fea3 100644 --- a/data/dataset_corrected.xml +++ b/data/dataset_corrected.xml @@ -10,40 +10,40 @@ - dark_and_flatfields/setup187-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup187-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup187-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup187-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup188-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup188-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup188-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup188-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup189-flatfield.tif - dark_and_flatfields/setup189-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup189-flatfield.tif + dark_and_flatfields/setup189-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup200-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup200-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup200-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup200-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup201-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup201-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup201-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup201-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup202-flatfield.tif - dark_and_flatfields/setup202-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup202-flatfield.tif + dark_and_flatfields/setup202-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup213-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup213-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup213-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup213-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup214-flatfield.tif - dark_and_flatfields/setup214-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup214-flatfield.tif + dark_and_flatfields/setup214-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup215-flatfield.tif - dark_and_flatfields/setup215-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup215-flatfield.tif + dark_and_flatfields/setup215-AVG_darkfield-fromdata.tif diff --git a/data/dataset_corrected_viewer.xml b/data/dataset_corrected_viewer.xml index 4bd50659d..7593a7917 100644 --- a/data/dataset_corrected_viewer.xml +++ b/data/dataset_corrected_viewer.xml @@ -10,40 +10,40 @@ - dark_and_flatfields/setup187-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup187-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup187-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup187-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup188-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup188-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup188-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup188-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup189-flatfield.tif - dark_and_flatfields/setup189-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup189-flatfield.tif + dark_and_flatfields/setup189-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup200-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup200-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup200-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup200-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup201-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup201-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup201-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup201-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup202-flatfield.tif - dark_and_flatfields/setup202-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup202-flatfield.tif + dark_and_flatfields/setup202-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup213-flatfield (fixed by mirroring).tif - dark_and_flatfields/setup213-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup213-flatfield (fixed by mirroring).tif + dark_and_flatfields/setup213-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup214-flatfield.tif - dark_and_flatfields/setup214-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup214-flatfield.tif + dark_and_flatfields/setup214-AVG_darkfield-fromdata.tif - dark_and_flatfields/setup215-flatfield.tif - dark_and_flatfields/setup215-AVG_darkfield-fromdata.tif + dark_and_flatfields/setup215-flatfield.tif + dark_and_flatfields/setup215-AVG_darkfield-fromdata.tif diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java index 178f28e1f..4fe5b8662 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java @@ -109,12 +109,12 @@ public static void main(String[] args) throws SpimDataException { final ViewId viewId = new ViewId(timepoint, setupId); if (darkfield.exists()) { - correctedLoader.setDarkImage(viewId, darkfield); + correctedLoader.setDarkImage(viewId, new FlatfieldImageInfo(darkfield.toURI(), null)); System.out.println(" Setup " + setupId + ": darkfield = " + darkfield.getName()); } if (flatfield.exists()) { - correctedLoader.setBrightImage(viewId, flatfield); + correctedLoader.setBrightImage(viewId, new FlatfieldImageInfo(flatfield.toURI(), null)); System.out.println(" Setup " + setupId + ": flatfield = " + flatfield.getName()); } } From d5705bc2bd46370b66538f87f015e3b23c18a444 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Mon, 2 Feb 2026 16:57:42 -0500 Subject: [PATCH 23/31] Add new test xml for loading from s3 --- data/dataset_corrected_viewer_s3.xml | 121 ++++++++++++++++++ .../TestViewerFlatfieldCorrection.java | 2 +- 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 data/dataset_corrected_viewer_s3.xml diff --git a/data/dataset_corrected_viewer_s3.xml b/data/dataset_corrected_viewer_s3.xml new file mode 100644 index 000000000..a0cea32a1 --- /dev/null +++ b/data/dataset_corrected_viewer_s3.xml @@ -0,0 +1,121 @@ + + + . + + + + + https://s3.janelia.org/keller-lab/s12a/samples_for_stitching/fly_brain_3/basic_corrected/dataset.n5 + + + + + https://s3.janelia.org/keller-lab/s12a/samples_for_stitching/fly_brain_3/kittisopikulm/camera1/dark_field/s0/ + https://s3.janelia.org/keller-lab/s12a/samples_for_stitching/fly_brain_3/kittisopikulm/camera1/flat_field/s0/ + + + + + + 0 + 0 + 512 320 825 + + pixels + 1.0 1.0 3.0 + + + 0 + 0 + 187 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 187 + 187 + + + 188 + 188 + + + 189 + 189 + + + 200 + 200 + + + 201 + 201 + + + 202 + 202 + + + 213 + 213 + + + 214 + 214 + + + 215 + 215 + + + + + 0 + 0 + + + + + 0 + + + + + + + AffineModel3D regularized with an RigidModel3D, lambda = 0.1 + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 0.0 + + + Stitching Transform + 1.0 0.0 0.0 0.4554158176323426 0.0 1.0 0.0 -0.13328548693164066 0.0 0.0 1.0 1.039028825990859 + + + Translation to Regular Grid + 1.0 0.0 0.0 199.68 0.0 1.0 0.0 102.40000000000003 0.0 0.0 1.0 -1236.0 + + + calibration + 1.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 3.0 0.0 + + + + + + + + + diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java index 661ace0da..4910a12be 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java @@ -60,7 +60,7 @@ public class TestViewerFlatfieldCorrection { public static void main(String[] args) throws SpimDataException { // Paths final String basePath = "/Users/innerbergerm/Projects/janelia/multiview-reconstruction/"; - final String correctedXmlPath = basePath + "data/dataset_corrected_zarr.xml"; + final String correctedXmlPath = basePath + "data/dataset_corrected_viewer_s3.xml"; final String uncorrectedXmlPath = basePath + "data/dataset.xml"; // Which setup to demonstrate (0-8) From 274068d466b7a526bd6a19aefff4a744ec21fa50 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 11:32:27 -0500 Subject: [PATCH 24/31] Update alpha versions of some N5 dependencies --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3fe7e5bec..f88fa300d 100644 --- a/pom.xml +++ b/pom.xml @@ -113,12 +113,13 @@ like Selective Plane Illumination Microscopy (SPIM) Data. - 4.0.0-alpha-6 - 7.1.0-alpha-6 + 4.0.0-alpha-7 + 7.1.0-alpha-7 2.4.0-alpha-6 2.0.0-alpha-4 2.0.0-alpha-4 2.0.0-alpha-4 + 4.4.0-alpha-5 From b88cfda107f82c6e28c7cd67a4ceecfc45390345 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 13:44:38 -0500 Subject: [PATCH 25/31] Add benchmark for fusion with and without correction --- .../tests/BenchmarkFlatfieldFusion.java | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java diff --git a/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java b/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java new file mode 100644 index 000000000..1627007fe --- /dev/null +++ b/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java @@ -0,0 +1,291 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.tests; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.ViewId; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.algorithm.blocks.BlockSupplier; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.preibisch.mvrecon.fiji.plugin.fusion.FusionGUI.FusionType; +import net.preibisch.mvrecon.fiji.spimdata.SpimData2; +import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; +import net.preibisch.mvrecon.fiji.spimdata.boundingbox.BoundingBox; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldCorrectionWrappedImgLoader; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.ViewerFlatfieldCorrectionWrappedImgLoader; +import net.preibisch.mvrecon.process.boundingbox.BoundingBoxMaximal; +import net.preibisch.mvrecon.process.fusion.FusionTools; +import net.preibisch.mvrecon.process.fusion.blk.BlkAffineFusion; +import net.preibisch.mvrecon.process.fusion.transformed.TransformVirtual; +import util.BlockSupplierUtils; + +/** + * Benchmark to measure the performance overhead of lazy flatfield correction during multi-view fusion. + * + * This benchmark: + * 1. Loads a dataset with flatfield correction configured (data/dataset_corrected_viewer.xml) + * 2. Toggles flatfield correction on/off using setActive() + * 3. Runs fusion with and without correction + * 4. Measures and reports timing comparison + * + * Run with: mvn compile exec:java -Dexec.mainClass="net.preibisch.mvrecon.tests.BenchmarkFlatfieldFusion" + * Or run from IDE as a Java application. + */ +public class BenchmarkFlatfieldFusion { + // Benchmark configuration + private static final double DOWNSAMPLING = 4.0; + private static final double ANISOTROPY_FACTOR = 3.0; // matches dataset's 1x1x3 voxel size + private static final int WARMUP_ITERATIONS = 10; + private static final int BENCHMARK_ITERATIONS = 50; + private static final FusionType FUSION_TYPE = FusionType.AVG_BLEND; + private static final int[] BLOCK_SIZE = new int[] {64, 64, 64}; + + public static void main(String[] args) { + // Path to dataset with flatfield correction + final File xmlFile = new File("data/dataset_corrected_viewer.xml"); + + if (!xmlFile.exists()) { + System.err.println("Dataset not found: " + xmlFile.getAbsolutePath()); + System.err.println("Please ensure data/dataset_corrected_viewer.xml exists."); + System.exit(1); + } + + try { + runBenchmark(xmlFile); + } catch (Exception e) { + System.err.println("Benchmark failed: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static void runBenchmark(final File xmlFile) throws SpimDataException { + System.out.println("============================================================"); + System.out.println("Flatfield Correction Benchmark During Fusion"); + System.out.println("============================================================"); + System.out.println(); + + // Load the dataset + System.out.println("Loading dataset: " + xmlFile.getAbsolutePath()); + final SpimData2 spimData = new XmlIoSpimData2().load(xmlFile.toURI()); + + // Get the flatfield-wrapped image loader + // Support both FlatfieldCorrectionWrappedImgLoader (interface) and ViewerFlatfieldCorrectionWrappedImgLoader (class) + final FlatfieldCorrectionWrappedImgLoader ffLoader; + final ViewerFlatfieldCorrectionWrappedImgLoader viewerFfLoader; + + if (spimData.getSequenceDescription().getImgLoader() instanceof FlatfieldCorrectionWrappedImgLoader) { + ffLoader = (FlatfieldCorrectionWrappedImgLoader) spimData.getSequenceDescription().getImgLoader(); + viewerFfLoader = null; + } else if (spimData.getSequenceDescription().getImgLoader() instanceof ViewerFlatfieldCorrectionWrappedImgLoader) { + ffLoader = null; + viewerFfLoader = (ViewerFlatfieldCorrectionWrappedImgLoader) spimData.getSequenceDescription().getImgLoader(); + } else { + System.err.println("Dataset does not have a flatfield correction image loader."); + System.err.println("ImgLoader type: " + spimData.getSequenceDescription().getImgLoader().getClass().getName()); + return; + } + + // Create a lambda to toggle flatfield correction for either loader type + final Consumer setActive = (active) -> { + if (ffLoader != null) + ffLoader.setActive(active); + else + viewerFfLoader.setActive(active); + }; + + // Get all views + final List viewIds = new ArrayList<>(); + viewIds.addAll(spimData.getSequenceDescription().getViewDescriptions().values()); + SpimData2.filterMissingViews(spimData, viewIds); + + System.out.println(); + System.out.println("Dataset Information:"); + System.out.println(" Views: " + viewIds.size()); + System.out.println(" Downsampling: " + DOWNSAMPLING + ", Anisotropy: " + ANISOTROPY_FACTOR); + System.out.println(" Fusion type: " + FUSION_TYPE); + System.out.println(" Block size: " + BLOCK_SIZE[0] + "x" + BLOCK_SIZE[1] + "x" + BLOCK_SIZE[2]); + System.out.println(" Warmup iterations: " + WARMUP_ITERATIONS + ", Benchmark iterations: " + BENCHMARK_ITERATIONS); + + // Compute bounding box + final BoundingBox bb = new BoundingBoxMaximal(viewIds, spimData).estimate("benchmark"); + System.out.println(" Bounding box: [" + bb.getMin()[0] + ", " + bb.getMin()[1] + ", " + bb.getMin()[2] + + "] to [" + bb.getMax()[0] + ", " + bb.getMax()[1] + ", " + bb.getMax()[2] + "]"); + + // Apply anisotropy and downsampling to bounding box + Interval boundingBoxFusion = FusionTools.createAnisotropicBoundingBox(bb, ANISOTROPY_FACTOR).getA(); + boundingBoxFusion = FusionTools.createDownsampledBoundingBox(boundingBoxFusion, DOWNSAMPLING).getA(); + + final long[] dims = boundingBoxFusion.dimensionsAsLongArray(); + System.out.println(" Fusion dimensions: " + dims[0] + " x " + dims[1] + " x " + dims[2]); + System.out.println(); + + // Adjust view registrations for anisotropy and downsampling + final HashMap registrations = + TransformVirtual.adjustAllTransforms( + viewIds, + spimData.getViewRegistrations().getViewRegistrations(), + ANISOTROPY_FACTOR, + DOWNSAMPLING); + + // Warmup runs + System.out.println("--- Warmup Phase ---"); + + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + System.out.print(" Warmup WITHOUT FF (" + (i + 1) + "/" + WARMUP_ITERATIONS + ")... "); + setActive.accept(false); + runFusion(spimData, viewIds, registrations, boundingBoxFusion); + System.out.println("done"); + } + + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + System.out.print(" Warmup WITH FF (" + (i + 1) + "/" + WARMUP_ITERATIONS + ")... "); + setActive.accept(true); + runFusion(spimData, viewIds, registrations, boundingBoxFusion); + System.out.println("done"); + } + + System.out.println(); + + // Benchmark WITHOUT flatfield correction + System.out.println("--- Benchmark Phase ---"); + System.out.println("Running WITHOUT flatfield correction..."); + setActive.accept(false); + final long[] timesWithout = new long[BENCHMARK_ITERATIONS]; + + for (int i = 0; i < BENCHMARK_ITERATIONS; i++) { + System.out.print(" Iteration " + (i + 1) + "/" + BENCHMARK_ITERATIONS + ": "); + final long start = System.currentTimeMillis(); + runFusion(spimData, viewIds, registrations, boundingBoxFusion); + timesWithout[i] = System.currentTimeMillis() - start; + System.out.println(timesWithout[i] + " ms"); + } + + System.out.println(); + + // Benchmark WITH flatfield correction + System.out.println("Running WITH flatfield correction..."); + setActive.accept(true); + final long[] timesWith = new long[BENCHMARK_ITERATIONS]; + + for (int i = 0; i < BENCHMARK_ITERATIONS; i++) { + System.out.print(" Iteration " + (i + 1) + "/" + BENCHMARK_ITERATIONS + ": "); + final long start = System.currentTimeMillis(); + runFusion(spimData, viewIds, registrations, boundingBoxFusion); + timesWith[i] = System.currentTimeMillis() - start; + System.out.println(timesWith[i] + " ms"); + } + + System.out.println(); + + // Calculate and report statistics + final Stats statsWithout = calculateStats(timesWithout); + final Stats statsWith = calculateStats(timesWith); + + System.out.println("--- Benchmark Results ---"); + System.out.printf("Without FF: avg %d ms (min %d, max %d, stddev %.1f)%n", + statsWithout.avg, statsWithout.min, statsWithout.max, statsWithout.stddev); + System.out.printf("With FF: avg %d ms (min %d, max %d, stddev %.1f)%n", + statsWith.avg, statsWith.min, statsWith.max, statsWith.stddev); + + final long overhead = statsWith.avg - statsWithout.avg; + final double overheadPercent = 100.0 * overhead / statsWithout.avg; + + System.out.println(); + System.out.printf("Overhead: %+d ms (%+.1f%%)%n", overhead, overheadPercent); + System.out.println("============================================================"); + } + + /** + * Run fusion and force full computation by copying to ArrayImg. + */ + private static void runFusion( + final SpimData2 spimData, + final List viewIds, + final HashMap registrations, + final Interval boundingBoxFusion) { + // Create fusion BlockSupplier + final BlockSupplier blocks = BlkAffineFusion.init( + (i, o) -> o.set(Math.round(i.get())), + spimData.getSequenceDescription().getImgLoader(), + viewIds, + registrations, + spimData.getSequenceDescription().getViewDescriptions(), + FUSION_TYPE, + ANISOTROPY_FACTOR, + 1, // interpolation: linear + null, // no intensity adjustments + boundingBoxFusion, + new UnsignedShortType(), + BLOCK_SIZE); + + // Force full computation by copying to ArrayImg (not lazy CellImg) + // This ensures all flatfield calculations are performed + BlockSupplierUtils.arrayImg(blocks, new FinalInterval(boundingBoxFusion.dimensionsAsLongArray())); + } + + private static Stats calculateStats(final long[] times) { + long sum = 0; + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + + for (final long t : times) { + sum += t; + min = Math.min(min, t); + max = Math.max(max, t); + } + + final long avg = sum / times.length; + + double variance = 0; + for (final long t : times) { + variance += (t - avg) * (t - avg); + } + variance /= times.length; + final double stddev = Math.sqrt(variance); + + return new Stats(avg, min, max, stddev); + } + + private static class Stats { + final long avg; + final long min; + final long max; + final double stddev; + + Stats(long avg, long min, long max, double stddev) { + this.avg = avg; + this.min = min; + this.max = max; + this.stddev = stddev; + } + } +} From 1df6f099cbdf82470c4b4fd1edc27ce07d54aa2d Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 14:20:09 -0500 Subject: [PATCH 26/31] Implement move methods of FlatFieldCorrectedRandomAccessibleInterval --- ...ieldCorrectedRandomAccessibleInterval.java | 159 +++++++++++++++--- 1 file changed, 137 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java index 161801803..f6c51c3c8 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java @@ -25,6 +25,7 @@ import net.imglib2.AbstractInterval; import net.imglib2.Cursor; import net.imglib2.Interval; +import net.imglib2.Localizable; import net.imglib2.Point; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; @@ -80,15 +81,11 @@ public O getType() return type; } - private class FlatFieldCorrectedRandomAccess extends Point implements RandomAccess< O > + private class FlatFieldCorrectedRandomAccess extends Point implements RandomAccess { - /* - * TODO: manually implement move methods - */ - - private final RandomAccess< T > sourceRA; - private final RandomAccess< S > brightRA; - private final RandomAccess< R > darkRA; + private final RandomAccess sourceRA; + private final RandomAccess brightRA; + private final RandomAccess darkRA; private final O value; private final int nDimBright; @@ -96,7 +93,7 @@ private class FlatFieldCorrectedRandomAccess extends Point implements RandomAcce public FlatFieldCorrectedRandomAccess() { - super( sourceImg.numDimensions() ); + super(sourceImg.numDimensions()); sourceRA = sourceImg.randomAccess(); brightRA = brightImg.randomAccess(); darkRA = darkImg.randomAccess(); @@ -108,35 +105,153 @@ public FlatFieldCorrectedRandomAccess() @Override public O get() { - // Use the fact that bright and dark must be of dimensionality <= source, - // and that coordinates outside the dimensionality are ignored - sourceRA.setPosition(position); - brightRA.setPosition(position); - darkRA.setPosition(position); - final double darkValue = darkRA.get().getRealDouble(); final double corrBright = brightRA.get().getRealDouble() - darkValue; final double corrImg = sourceRA.get().getRealDouble() - darkValue; if (corrBright == 0) - value.setReal( 0.0 ); + value.setReal(0.0); else { - final double corr = Math.min( Math.max( corrImg * meanBrightCorrected / corrBright, value.getMinValue() ), value.getMaxValue() ); - value.setReal( corr ); + final double corr = Math.min(Math.max(corrImg * meanBrightCorrected / corrBright, value.getMinValue()), value.getMaxValue()); + value.setReal(corr); } return value; } @Override - public RandomAccess< O > copy() + public void fwd(int d) + { + position[d]++; + sourceRA.fwd(d); + if (d < nDimBright) brightRA.fwd(d); + if (d < nDimDark) darkRA.fwd(d); + } + + @Override + public void bck(int d) + { + position[d]--; + sourceRA.bck(d); + if (d < nDimBright) brightRA.bck(d); + if (d < nDimDark) darkRA.bck(d); + } + + @Override + public void move(int distance, int d) + { + position[d] += distance; + sourceRA.move(distance, d); + if (d < nDimBright) brightRA.move(distance, d); + if (d < nDimDark) darkRA.move(distance, d); + } + + @Override + public void move(long distance, int d) + { + position[d] += distance; + sourceRA.move(distance, d); + if (d < nDimBright) brightRA.move(distance, d); + if (d < nDimDark) darkRA.move(distance, d); + } + + @Override + public void move(Localizable distance) + { + for (int d = 0; d < n; d++) + position[d] += distance.getLongPosition(d); + sourceRA.move(distance); + for (int d = 0; d < nDimBright; d++) + brightRA.move(distance.getLongPosition(d), d); + for (int d = 0; d < nDimDark; d++) + darkRA.move(distance.getLongPosition(d), d); + } + + @Override + public void move(int[] distance) + { + for (int d = 0; d < n; d++) + position[d] += distance[d]; + sourceRA.move(distance); + for (int d = 0; d < nDimBright; d++) + brightRA.move(distance[d], d); + for (int d = 0; d < nDimDark; d++) + darkRA.move(distance[d], d); + } + + @Override + public void move(long[] distance) { - final FlatFieldCorrectedRandomAccessibleInterval.FlatFieldCorrectedRandomAccess copy = new FlatFieldCorrectedRandomAccess(); - copy.setPosition( this ); + for (int d = 0; d < n; d++) + position[d] += distance[d]; + sourceRA.move(distance); + for (int d = 0; d < nDimBright; d++) + brightRA.move(distance[d], d); + for (int d = 0; d < nDimDark; d++) + darkRA.move(distance[d], d); + } + + @Override + public void setPosition(Localizable position) + { + for (int d = 0; d < n; d++) + this.position[d] = position.getLongPosition(d); + sourceRA.setPosition(position); + for (int d = 0; d < nDimBright; d++) + brightRA.setPosition(position.getLongPosition(d), d); + for (int d = 0; d < nDimDark; d++) + darkRA.setPosition(position.getLongPosition(d), d); + } + + @Override + public void setPosition(int[] position) + { + for (int d = 0; d < n; d++) + this.position[d] = position[d]; + sourceRA.setPosition(position); + for (int d = 0; d < nDimBright; d++) + brightRA.setPosition(position[d], d); + for (int d = 0; d < nDimDark; d++) + darkRA.setPosition(position[d], d); + } + + @Override + public void setPosition(long[] position) + { + for (int d = 0; d < n; d++) + this.position[d] = position[d]; + sourceRA.setPosition(position); + for (int d = 0; d < nDimBright; d++) + brightRA.setPosition(position[d], d); + for (int d = 0; d < nDimDark; d++) + darkRA.setPosition(position[d], d); + } + + @Override + public void setPosition(int position, int d) + { + this.position[d] = position; + sourceRA.setPosition(position, d); + if (d < nDimBright) brightRA.setPosition(position, d); + if (d < nDimDark) darkRA.setPosition(position, d); + } + + @Override + public void setPosition(long position, int d) + { + this.position[d] = position; + sourceRA.setPosition(position, d); + if (d < nDimBright) brightRA.setPosition(position, d); + if (d < nDimDark) darkRA.setPosition(position, d); + } + + @Override + public RandomAccess copy() { + final FlatFieldCorrectedRandomAccessibleInterval.FlatFieldCorrectedRandomAccess copy = new FlatFieldCorrectedRandomAccess(); + copy.setPosition(this); return copy; } - } public static

, Q extends RealType< Q >> double getMeanCorrected(RandomAccessibleInterval< P > brightImg, RandomAccessibleInterval< Q > darkImg) From 46fda1356cc368a2b87032c0656f7b132f043db8 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 20:02:12 -0500 Subject: [PATCH 27/31] Simplify FlatFieldCorrectedRandomAccessibleInterval logic --- ...ieldCorrectedRandomAccessibleInterval.java | 93 ++++++++++++++----- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java index f6c51c3c8..021875215 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleInterval.java @@ -26,7 +26,6 @@ import net.imglib2.Cursor; import net.imglib2.Interval; import net.imglib2.Localizable; -import net.imglib2.Point; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.RealType; @@ -81,25 +80,32 @@ public O getType() return type; } - private class FlatFieldCorrectedRandomAccess extends Point implements RandomAccess + private class FlatFieldCorrectedRandomAccess implements RandomAccess { private final RandomAccess sourceRA; private final RandomAccess brightRA; private final RandomAccess darkRA; private final O value; + private final int nDimSource; private final int nDimBright; private final int nDimDark; + // Cached min/max values to avoid virtual method calls in get() + private final double minValue; + private final double maxValue; + public FlatFieldCorrectedRandomAccess() { - super(sourceImg.numDimensions()); sourceRA = sourceImg.randomAccess(); brightRA = brightImg.randomAccess(); darkRA = darkImg.randomAccess(); value = type.createVariable(); + nDimSource = sourceImg.numDimensions(); nDimBright = brightImg.numDimensions(); nDimDark = darkImg.numDimensions(); + minValue = value.getMinValue(); + maxValue = value.getMaxValue(); } @Override @@ -113,7 +119,7 @@ public O get() value.setReal(0.0); else { - final double corr = Math.min(Math.max(corrImg * meanBrightCorrected / corrBright, value.getMinValue()), value.getMaxValue()); + final double corr = Math.min(Math.max(corrImg * meanBrightCorrected / corrBright, minValue), maxValue); value.setReal(corr); } @@ -123,7 +129,6 @@ public O get() @Override public void fwd(int d) { - position[d]++; sourceRA.fwd(d); if (d < nDimBright) brightRA.fwd(d); if (d < nDimDark) darkRA.fwd(d); @@ -132,7 +137,6 @@ public void fwd(int d) @Override public void bck(int d) { - position[d]--; sourceRA.bck(d); if (d < nDimBright) brightRA.bck(d); if (d < nDimDark) darkRA.bck(d); @@ -141,7 +145,6 @@ public void bck(int d) @Override public void move(int distance, int d) { - position[d] += distance; sourceRA.move(distance, d); if (d < nDimBright) brightRA.move(distance, d); if (d < nDimDark) darkRA.move(distance, d); @@ -150,7 +153,6 @@ public void move(int distance, int d) @Override public void move(long distance, int d) { - position[d] += distance; sourceRA.move(distance, d); if (d < nDimBright) brightRA.move(distance, d); if (d < nDimDark) darkRA.move(distance, d); @@ -159,8 +161,6 @@ public void move(long distance, int d) @Override public void move(Localizable distance) { - for (int d = 0; d < n; d++) - position[d] += distance.getLongPosition(d); sourceRA.move(distance); for (int d = 0; d < nDimBright; d++) brightRA.move(distance.getLongPosition(d), d); @@ -171,8 +171,6 @@ public void move(Localizable distance) @Override public void move(int[] distance) { - for (int d = 0; d < n; d++) - position[d] += distance[d]; sourceRA.move(distance); for (int d = 0; d < nDimBright; d++) brightRA.move(distance[d], d); @@ -183,8 +181,6 @@ public void move(int[] distance) @Override public void move(long[] distance) { - for (int d = 0; d < n; d++) - position[d] += distance[d]; sourceRA.move(distance); for (int d = 0; d < nDimBright; d++) brightRA.move(distance[d], d); @@ -195,8 +191,6 @@ public void move(long[] distance) @Override public void setPosition(Localizable position) { - for (int d = 0; d < n; d++) - this.position[d] = position.getLongPosition(d); sourceRA.setPosition(position); for (int d = 0; d < nDimBright; d++) brightRA.setPosition(position.getLongPosition(d), d); @@ -207,8 +201,6 @@ public void setPosition(Localizable position) @Override public void setPosition(int[] position) { - for (int d = 0; d < n; d++) - this.position[d] = position[d]; sourceRA.setPosition(position); for (int d = 0; d < nDimBright; d++) brightRA.setPosition(position[d], d); @@ -219,8 +211,6 @@ public void setPosition(int[] position) @Override public void setPosition(long[] position) { - for (int d = 0; d < n; d++) - this.position[d] = position[d]; sourceRA.setPosition(position); for (int d = 0; d < nDimBright; d++) brightRA.setPosition(position[d], d); @@ -231,7 +221,6 @@ public void setPosition(long[] position) @Override public void setPosition(int position, int d) { - this.position[d] = position; sourceRA.setPosition(position, d); if (d < nDimBright) brightRA.setPosition(position, d); if (d < nDimDark) darkRA.setPosition(position, d); @@ -240,16 +229,72 @@ public void setPosition(int position, int d) @Override public void setPosition(long position, int d) { - this.position[d] = position; sourceRA.setPosition(position, d); if (d < nDimBright) brightRA.setPosition(position, d); if (d < nDimDark) darkRA.setPosition(position, d); } + // Localizable - delegate to sourceRA + + @Override + public int numDimensions() + { + return nDimSource; + } + + @Override + public void localize(int[] position) + { + sourceRA.localize(position); + } + @Override - public RandomAccess copy() { + public void localize(long[] position) + { + sourceRA.localize(position); + } + + @Override + public int getIntPosition(int d) + { + return sourceRA.getIntPosition(d); + } + + @Override + public long getLongPosition(int d) + { + return sourceRA.getLongPosition(d); + } + + @Override + public void localize(float[] position) + { + sourceRA.localize(position); + } + + @Override + public void localize(double[] position) + { + sourceRA.localize(position); + } + + @Override + public float getFloatPosition(int d) + { + return sourceRA.getFloatPosition(d); + } + + @Override + public double getDoublePosition(int d) + { + return sourceRA.getDoublePosition(d); + } + + @Override + public RandomAccess copy() + { final FlatFieldCorrectedRandomAccessibleInterval.FlatFieldCorrectedRandomAccess copy = new FlatFieldCorrectedRandomAccess(); - copy.setPosition(this); + copy.setPosition(sourceRA); return copy; } } From 702b8ab87c7a9ff0770c8a1bff1759bbc80d03ae Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 20:04:15 -0500 Subject: [PATCH 28/31] Add block supplier for flat field correction --- .../FlatfieldCorrectionBlockSupplier.java | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionBlockSupplier.java diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionBlockSupplier.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionBlockSupplier.java new file mode 100644 index 000000000..abb68b330 --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatfieldCorrectionBlockSupplier.java @@ -0,0 +1,288 @@ +/*- + * #%L + * Software for the reconstruction of multi-view microscopic acquisitions + * like Selective Plane Illumination Microscopy (SPIM) Data. + * %% + * Copyright (C) 2012 - 2025 Multiview Reconstruction developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; + +import static net.imglib2.type.PrimitiveType.FLOAT; +import static net.imglib2.util.Util.safeInt; + +import bdv.util.ConstantRandomAccessible; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.blocks.BlockSupplier; +import net.imglib2.algorithm.blocks.convert.Convert; +import net.imglib2.blocks.BlockInterval; +import net.imglib2.blocks.TempArray; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Cast; +import net.imglib2.util.Intervals; +import net.imglib2.view.Views; + +/** + * Block-based flatfield correction for efficient fusion processing. + * + * Applies the correction formula: + * corrected = (source - dark) * meanBrightCorrected / (bright - dark) + * + * The bright and dark images are 2D (XY only), while the source may be 3D. + * For 3D blocks, the same 2D bright/dark data is reused for all Z slices, + * making this much more efficient than per-pixel RandomAccess. + */ +public class FlatfieldCorrectionBlockSupplier implements BlockSupplier { + private final BlockSupplier source; + private final BlockSupplier bright; + private final BlockSupplier dark; + private final double meanBrightCorrected; + private final int numDimensions; + + // Temp arrays for block data + private final TempArray srcTemp; + private final TempArray brightTemp; + private final TempArray darkTemp; + + /** + * Create a flatfield correction BlockSupplier. + * + * @param source source image BlockSupplier (can be 2D or 3D) + * @param bright bright (flatfield) image as BlockSupplier (2D) + * @param dark dark image as BlockSupplier (2D) + * @param meanBrightCorrected pre-computed mean of (bright - dark) + */ + public FlatfieldCorrectionBlockSupplier( + final BlockSupplier source, + final BlockSupplier bright, + final BlockSupplier dark, + final double meanBrightCorrected) + { + this.source = source; + this.bright = bright; + this.dark = dark; + this.meanBrightCorrected = meanBrightCorrected; + this.numDimensions = source.numDimensions(); + + this.srcTemp = TempArray.forPrimitiveType(FLOAT); + this.brightTemp = TempArray.forPrimitiveType(FLOAT); + this.darkTemp = TempArray.forPrimitiveType(FLOAT); + } + + private FlatfieldCorrectionBlockSupplier(final FlatfieldCorrectionBlockSupplier s) { + this.source = s.source.independentCopy(); + this.bright = s.bright.independentCopy(); + this.dark = s.dark.independentCopy(); + this.meanBrightCorrected = s.meanBrightCorrected; + this.numDimensions = s.numDimensions; + + this.srcTemp = TempArray.forPrimitiveType(FLOAT); + this.brightTemp = TempArray.forPrimitiveType(FLOAT); + this.darkTemp = TempArray.forPrimitiveType(FLOAT); + } + + /** + * Factory method to create a flatfield correction BlockSupplier from an existing FloatType source. + */ + public static BlockSupplier of( + final BlockSupplier source, + final RandomAccessibleInterval bright, + final RandomAccessibleInterval dark, + final double meanBrightCorrected) + { + return new FlatfieldCorrectionBlockSupplier( + source, + BlockSupplier.of(bright), + BlockSupplier.of(dark), + meanBrightCorrected); + } + + /** + * Factory method to create a flatfield correction BlockSupplier from RAIs. + * Handles interval adjustment for 2D bright/dark with 3D source, similar to + * {@link FlatFieldCorrectedRandomAccessibleIntervals#create}. + * + * @param sourceImg source image (can be any RealType, will be converted to FloatType) + * @param brightImg bright (flatfield) image, can be null + * @param darkImg dark image, can be null + * @return BlockSupplier that applies flatfield correction + */ + public static & NativeType, S extends RealType, R extends RealType> + BlockSupplier of( + final RandomAccessibleInterval sourceImg, + final RandomAccessibleInterval brightImg, + final RandomAccessibleInterval darkImg) + { + // Create source BlockSupplier and convert to float + final BlockSupplier source = BlockSupplier + .of(Views.extendBorder(sourceImg)) + .andThen(Convert.convert(new FloatType())); + + // Handle null bright/dark images + final RandomAccessibleInterval bright; + final RandomAccessibleInterval dark; + + if (brightImg == null && darkImg == null) { + // No correction needed, return source as-is + return source; + } else if (brightImg == null) { + // Assume bright == constant 1.0 + final ConstantRandomAccessible constantBright = + new ConstantRandomAccessible<>(new FloatType(1.0f), darkImg.numDimensions()); + bright = Views.interval(constantBright, createInterval(sourceImg, darkImg.numDimensions())); + dark = Views.interval(Views.extendBorder(convertToFloat(darkImg)), + createInterval(sourceImg, darkImg.numDimensions())); + } else if (darkImg == null) { + // Assume dark == constant 0.0 + final ConstantRandomAccessible constantDark = + new ConstantRandomAccessible<>(new FloatType(0.0f), brightImg.numDimensions()); + bright = Views.interval(Views.extendBorder(convertToFloat(brightImg)), + createInterval(sourceImg, brightImg.numDimensions())); + dark = Views.interval(constantDark, createInterval(sourceImg, brightImg.numDimensions())); + } else { + bright = Views.interval(Views.extendBorder(convertToFloat(brightImg)), + createInterval(sourceImg, brightImg.numDimensions())); + dark = Views.interval(Views.extendBorder(convertToFloat(darkImg)), + createInterval(sourceImg, darkImg.numDimensions())); + } + + final double meanBrightCorrected = FlatFieldCorrectedRandomAccessibleInterval.getMeanCorrected(bright, dark); + + return new FlatfieldCorrectionBlockSupplier( + source, + BlockSupplier.of(bright), + BlockSupplier.of(dark), + meanBrightCorrected); + } + + /** + * Create an interval matching the source's first N dimensions. + */ + private static FinalInterval createInterval(final Interval source, final int numDimensions) { + final long[] mins = new long[numDimensions]; + final long[] maxs = new long[numDimensions]; + for (int d = 0; d < numDimensions; d++) { + mins[d] = source.min(d); + maxs[d] = source.max(d); + } + return new FinalInterval(mins, maxs); + } + + /** + * Convert a RealType RAI to FloatType. + */ + @SuppressWarnings("unchecked") + private static > RandomAccessibleInterval convertToFloat( + final RandomAccessibleInterval img) + { + if (img.getType() instanceof FloatType) { + return (RandomAccessibleInterval) img; + } + + return net.imglib2.converter.RealTypeConverters.convert(img, new FloatType()); + } + + @Override + public void copy(final Interval interval, final Object dest) { + final BlockInterval blockInterval = BlockInterval.asBlockInterval(interval); + final int[] size = blockInterval.size(); + + final int len = safeInt(Intervals.numElements(size)); + final float[] srcArray = srcTemp.get(len); + + // Copy source block (full 3D or 2D) + source.copy(interval, srcArray); + + // Determine XY size for bright/dark + final int xyLen; + final int zSize; + if (numDimensions >= 3) { + xyLen = size[0] * size[1]; + zSize = size[2]; + } else { + xyLen = len; + zSize = 1; + } + + final float[] brightArray = brightTemp.get(xyLen); + final float[] darkArray = darkTemp.get(xyLen); + + // Create 2D interval for bright/dark (XY only) + final Interval interval2D; + if (numDimensions >= 3) { + interval2D = new FinalInterval( + new long[]{interval.min(0), interval.min(1)}, + new long[]{interval.max(0), interval.max(1)}); + } else { + interval2D = interval; + } + + // Copy bright/dark blocks (2D) + bright.copy(interval2D, brightArray); + dark.copy(interval2D, darkArray); + + // Apply correction + final float[] fdest = Cast.unchecked(dest); + final float meanBC = (float) meanBrightCorrected; + + for (int z = 0; z < zSize; z++) { + final int zOffset = z * xyLen; + for (int i = 0; i < xyLen; i++) { + final float darkVal = darkArray[i]; + final float corrBright = brightArray[i] - darkVal; + final float srcVal = srcArray[zOffset + i]; + final float corrImg = srcVal - darkVal; + + if (corrBright == 0) { + fdest[zOffset + i] = 0; + } else { + fdest[zOffset + i] = corrImg * meanBC / corrBright; + } + } + } + } + + @Override + public BlockSupplier independentCopy() { + return new FlatfieldCorrectionBlockSupplier(this); + } + + @Override + public BlockSupplier threadSafe() { + return new FlatfieldCorrectionBlockSupplier( + source.threadSafe(), + bright.threadSafe(), + dark.threadSafe(), + meanBrightCorrected); + } + + @Override + public int numDimensions() { + return numDimensions; + } + + private static final FloatType type = new FloatType(); + + @Override + public FloatType getType() { + return type; + } +} From 0dcd4e2a173390b6bbc8fe8076addd7d2294d4d0 Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 20:16:57 -0500 Subject: [PATCH 29/31] Add block supplier to benchmark --- .../tests/BenchmarkFlatfieldFusion.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java b/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java index 1627007fe..a649ad453 100644 --- a/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java +++ b/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java @@ -26,20 +26,27 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import mpicbg.spim.data.SpimDataException; import mpicbg.spim.data.sequence.ViewId; import net.imglib2.FinalInterval; import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.blocks.BlockSupplier; import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.numeric.real.FloatType; import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.util.Pair; import net.preibisch.mvrecon.fiji.plugin.fusion.FusionGUI.FusionType; import net.preibisch.mvrecon.fiji.spimdata.SpimData2; import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; import net.preibisch.mvrecon.fiji.spimdata.boundingbox.BoundingBox; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldCorrectionWrappedImgLoader; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldCorrectionBlockSupplier; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldImageInfo; +import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldImageLoader; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.ViewerFlatfieldCorrectionWrappedImgLoader; import net.preibisch.mvrecon.process.boundingbox.BoundingBoxMaximal; import net.preibisch.mvrecon.process.fusion.FusionTools; @@ -222,6 +229,129 @@ private static void runBenchmark(final File xmlFile) throws SpimDataException { System.out.println(); System.out.printf("Overhead: %+d ms (%+.1f%%)%n", overhead, overheadPercent); System.out.println("============================================================"); + + // Run BlockSupplier comparison if we have a ViewerFlatfieldCorrectionWrappedImgLoader + if (viewerFfLoader != null) { + System.out.println(); + runBlockSupplierBenchmark(spimData, viewerFfLoader, viewIds.get(0), boundingBoxFusion); + } + } + + /** + * Benchmark comparing RandomAccess-based vs BlockSupplier-based flatfield correction. + */ + private static void runBlockSupplierBenchmark( + final SpimData2 spimData, + final ViewerFlatfieldCorrectionWrappedImgLoader viewerFfLoader, + final ViewId viewId, + final Interval boundingBox) { + System.out.println("============================================================"); + System.out.println("BlockSupplier vs RandomAccess Flatfield Correction Benchmark"); + System.out.println("============================================================"); + System.out.println(); + + // Get bright/dark info for this view + final Map> infoMap = viewerFfLoader.getInfoMap(); + final Pair ffInfo = infoMap.get(viewId); + + if (ffInfo == null) { + System.out.println("No flatfield info for view " + viewId + ", skipping BlockSupplier benchmark."); + return; + } + + // Load bright/dark images + final FlatfieldImageLoader loader = new FlatfieldImageLoader(); + loader.setBrightImage(viewId, ffInfo.getA()); + loader.setDarkImage(viewId, ffInfo.getB()); + + final RandomAccessibleInterval brightImg = loader.getBrightImg(viewId); + final RandomAccessibleInterval darkImg = loader.getDarkImg(viewId); + + if (brightImg == null || darkImg == null) { + System.out.println("Could not load bright/dark images, skipping BlockSupplier benchmark."); + return; + } + + // Get raw source image (without flatfield correction) + viewerFfLoader.setActive(false); + @SuppressWarnings("unchecked") + final RandomAccessibleInterval sourceImg = + (RandomAccessibleInterval) viewerFfLoader.getSetupImgLoader(viewId.getViewSetupId()) + .getImage(viewId.getTimePointId()); + + System.out.println("Testing single view: " + viewId); + System.out.println("Source dimensions: " + sourceImg.dimension(0) + " x " + sourceImg.dimension(1) + " x " + sourceImg.dimension(2)); + System.out.println("Bright dimensions: " + brightImg.dimension(0) + " x " + brightImg.dimension(1)); + System.out.println(); + + // Create interval to test (use full XY, limited Z for faster testing) + final long[] min = new long[] {sourceImg.min(0), sourceImg.min(1), sourceImg.min(2)}; + final long[] max = new long[] {sourceImg.max(0), sourceImg.max(1), Math.min(sourceImg.min(2) + 31, sourceImg.max(2))}; + final FinalInterval testInterval = new FinalInterval(min, max); + + System.out.println("Test interval: [" + min[0] + "-" + max[0] + ", " + min[1] + "-" + max[1] + ", " + min[2] + "-" + max[2] + "]"); + System.out.println(); + + // Create BlockSupplier for comparison + final BlockSupplier blockSupplier = FlatfieldCorrectionBlockSupplier.of(sourceImg, brightImg, darkImg); + + // Allocate output array + final int len = (int) ((max[0] - min[0] + 1) * (max[1] - min[1] + 1) * (max[2] - min[2] + 1)); + final float[] output = new float[len]; + + // Warmup + System.out.println("--- Warmup ---"); + for (int i = 0; i < 5; i++) { + blockSupplier.copy(testInterval, output); + } + + // Benchmark BlockSupplier + System.out.println("--- BlockSupplier Benchmark ---"); + final long[] timesBlock = new long[20]; + for (int i = 0; i < timesBlock.length; i++) { + final long start = System.currentTimeMillis(); + blockSupplier.copy(testInterval, output); + timesBlock[i] = System.currentTimeMillis() - start; + System.out.println(" Iteration " + (i + 1) + ": " + timesBlock[i] + " ms"); + } + + // Benchmark RandomAccess-based (via fusion with single view) + System.out.println(); + System.out.println("--- RandomAccess Benchmark (via single-view fusion) ---"); + viewerFfLoader.setActive(true); + + final long[] timesRA = new long[20]; + for (int i = 0; i < timesRA.length; i++) { + // Re-get the image with correction enabled + @SuppressWarnings("unchecked") + final RandomAccessibleInterval correctedImg = + (RandomAccessibleInterval) viewerFfLoader.getSetupImgLoader(viewId.getViewSetupId()) + .getImage(viewId.getTimePointId()); + + final long start = System.currentTimeMillis(); + // Force computation by copying to array via BlockSupplier + final BlockSupplier raBlocks = BlockSupplier + .of(net.imglib2.view.Views.extendBorder(correctedImg)) + .andThen(net.imglib2.algorithm.blocks.convert.Convert.convert(new FloatType())); + raBlocks.copy(testInterval, output); + timesRA[i] = System.currentTimeMillis() - start; + System.out.println(" Iteration " + (i + 1) + ": " + timesRA[i] + " ms"); + } + + // Calculate and report statistics + final Stats statsBlock = calculateStats(timesBlock); + final Stats statsRA = calculateStats(timesRA); + + System.out.println(); + System.out.println("--- Results ---"); + System.out.printf("BlockSupplier: avg %d ms (min %d, max %d, stddev %.1f)%n", + statsBlock.avg, statsBlock.min, statsBlock.max, statsBlock.stddev); + System.out.printf("RandomAccess: avg %d ms (min %d, max %d, stddev %.1f)%n", + statsRA.avg, statsRA.min, statsRA.max, statsRA.stddev); + + final double speedup = (double) statsRA.avg / statsBlock.avg; + System.out.printf("%nSpeedup: %.2fx%n", speedup); + System.out.println("============================================================"); } /** From 3aa66a4d7c125fceb82ac6172f273f09f58a158a Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 21:13:14 -0500 Subject: [PATCH 30/31] Implement flat field corrected image on top of block supplier --- ...eldCorrectedRandomAccessibleIntervals.java | 190 ++++++++++++------ 1 file changed, 125 insertions(+), 65 deletions(-) diff --git a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java index 535ac9ffa..a2f1c2a31 100644 --- a/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/FlatFieldCorrectedRandomAccessibleIntervals.java @@ -22,85 +22,145 @@ */ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; -import java.util.Arrays; - -import bdv.util.ConstantRandomAccessible; -import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.blocks.BlockAlgoUtils; +import net.imglib2.algorithm.blocks.BlockSupplier; +import net.imglib2.cache.img.CachedCellImg; +import net.imglib2.converter.RealTypeConverters; +import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.view.Views; +/** + * Factory methods for creating flatfield-corrected images. + * + * All methods use efficient block-based processing internally via + * {@link FlatfieldCorrectionBlockSupplier}, backed by a {@link CachedCellImg}. + */ public class FlatFieldCorrectedRandomAccessibleIntervals { - public static < R extends RealType< R >, S extends RealType< S >, T extends RealType< T >> RandomAccessibleInterval< R > create( - RandomAccessibleInterval< R > sourceImg, - RandomAccessibleInterval< S > brightImg, - RandomAccessibleInterval< T > darkImg ) + /** Default cell size for block-based flatfield correction */ + private static final int[] DEFAULT_CELL_SIZE = new int[] {64, 64, 64}; + + /** + * Create a flatfield-corrected image with the same type as the source. + * Uses efficient block-based processing internally. + * + * @param sourceImg source image + * @param brightImg bright (flatfield) image, can be null + * @param darkImg dark image, can be null + * @return corrected image with same type as source + */ + public static & NativeType, S extends RealType, T extends RealType> + RandomAccessibleInterval create( + final RandomAccessibleInterval sourceImg, + final RandomAccessibleInterval brightImg, + final RandomAccessibleInterval darkImg) { - R type = sourceImg.firstElement().createVariable(); - return create( sourceImg, brightImg, darkImg, type ); + final R type = sourceImg.getType().createVariable(); + return create(sourceImg, brightImg, darkImg, type); } - public static , R extends RealType< R >, S extends RealType< S >, T extends RealType< T >> RandomAccessibleInterval< O > create( - RandomAccessibleInterval< R > sourceImg, - RandomAccessibleInterval< S > brightImg, - RandomAccessibleInterval< T > darkImg, - O outputType) + + /** + * Create a flatfield-corrected image with a specified output type. + * Uses efficient block-based processing internally. + * + * @param sourceImg source image + * @param brightImg bright (flatfield) image, can be null + * @param darkImg dark image, can be null + * @param outputType the desired output type + * @return corrected image with specified output type + */ + @SuppressWarnings("unchecked") + public static , R extends RealType & NativeType, S extends RealType, T extends RealType> + RandomAccessibleInterval create( + final RandomAccessibleInterval sourceImg, + final RandomAccessibleInterval brightImg, + final RandomAccessibleInterval darkImg, + final O outputType) { + // Create block-based corrected image (always FloatType internally) + final RandomAccessibleInterval correctedFloat = createBlockBased( + sourceImg, brightImg, darkImg, DEFAULT_CELL_SIZE); - // get intervals for bright and/or dark imgs: interval of source img, but only for dimensionality of bright/dark - final long[] minsBright; - final long[] maxsBright; - FinalInterval intervalBright = null; - if (brightImg != null) - { - minsBright = new long[brightImg.numDimensions()]; - maxsBright = new long[brightImg.numDimensions()]; - Arrays.fill( maxsBright, 1 ); - for (int d = 0; d < brightImg.numDimensions(); ++d) - { - minsBright[d] = sourceImg.min( d ); - maxsBright[d] = sourceImg.max( d ); - } - intervalBright = new FinalInterval( minsBright, maxsBright ); - } - final long[] minsDark; - final long[] maxsDark; - FinalInterval intervalDark = null; - if (darkImg != null) - { - minsDark = new long[darkImg.numDimensions()]; - maxsDark = new long[darkImg.numDimensions()]; - Arrays.fill( maxsDark, 1 ); - for (int d = 0; d < darkImg.numDimensions(); ++d) - { - minsDark[d] = sourceImg.min( d ); - maxsDark[d] = sourceImg.max( d ); - } - intervalDark = new FinalInterval( minsDark, maxsDark ); - } + // If output type is FloatType, return directly + if (outputType instanceof FloatType) + return (RandomAccessibleInterval) correctedFloat; + + // Otherwise, convert to the requested output type + return RealTypeConverters.convert(correctedFloat, outputType); + } + + /** + * Create a flatfield-corrected FloatType image using efficient block-based processing. + * The result is backed by a CachedCellImg that computes correction on-demand. + * + * @param sourceImg source image (can be any RealType) + * @param brightImg bright (flatfield) image, can be null + * @param darkImg dark image, can be null + * @return FloatType RandomAccessibleInterval with flatfield correction applied + */ + public static & NativeType, S extends RealType, T extends RealType> + RandomAccessibleInterval createBlockBased( + final RandomAccessibleInterval sourceImg, + final RandomAccessibleInterval brightImg, + final RandomAccessibleInterval darkImg) + { + return createBlockBased(sourceImg, brightImg, darkImg, DEFAULT_CELL_SIZE); + } + /** + * Create a flatfield-corrected FloatType image using efficient block-based processing. + * The result is backed by a CachedCellImg that computes correction on-demand. + * + * @param sourceImg source image (can be any RealType) + * @param brightImg bright (flatfield) image, can be null + * @param darkImg dark image, can be null + * @param cellSize cell size for the cached image + * @return FloatType RandomAccessibleInterval with flatfield correction applied + */ + public static & NativeType, S extends RealType, T extends RealType> + RandomAccessibleInterval createBlockBased( + final RandomAccessibleInterval sourceImg, + final RandomAccessibleInterval brightImg, + final RandomAccessibleInterval darkImg, + final int[] cellSize) + { + // Handle null bright/dark - if both null, just convert source to float if (brightImg == null && darkImg == null) { - // assume bright and dark images constant -> should return original - // TODO: 'optimize' by really returning sourceImg? - final ConstantRandomAccessible< FloatType > constantBright = new ConstantRandomAccessible<>( new FloatType(1.0f), sourceImg.numDimensions() ); - final ConstantRandomAccessible< FloatType > constantDark = new ConstantRandomAccessible<>( new FloatType(0.0f), sourceImg.numDimensions() ); - return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( constantBright, sourceImg ), Views.interval( constantDark, sourceImg ) ); - } - else if (brightImg == null) - { - // assume bright image == constant - final ConstantRandomAccessible< FloatType > constantBright = new ConstantRandomAccessible<>( new FloatType(1.0f), sourceImg.numDimensions() ); - return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( constantBright, sourceImg ), Views.interval( Views.extendBorder( darkImg ), intervalDark ) ); - } - else if (darkImg == null) - { - // assume dark image == constant == 0; - final ConstantRandomAccessible< FloatType > constantDark = new ConstantRandomAccessible<>( new FloatType(0.0f), sourceImg.numDimensions() ); - return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( Views.extendBorder( brightImg ), intervalBright ), Views.interval( constantDark, sourceImg ) ); + final BlockSupplier blocks = BlockSupplier + .of(Views.extendBorder(sourceImg)) + .andThen(net.imglib2.algorithm.blocks.convert.Convert.convert(new FloatType())); + return wrapWithOffset(blocks, sourceImg, cellSize); } - - return new FlatFieldCorrectedRandomAccessibleInterval<>(outputType, sourceImg, Views.interval( Views.extendBorder( brightImg ), intervalBright ), Views.interval( Views.extendBorder( darkImg ), intervalDark ) ); + + // Create the block supplier with flatfield correction + final BlockSupplier blocks = FlatfieldCorrectionBlockSupplier.of( + sourceImg, brightImg, darkImg); + + return wrapWithOffset(blocks, sourceImg, cellSize); + } + + /** + * Wrap a BlockSupplier in a CachedCellImg and translate to match source interval. + */ + private static > RandomAccessibleInterval wrapWithOffset( + final BlockSupplier blocks, + final RandomAccessibleInterval sourceImg, + final int[] cellSize) + { + // Create CachedCellImg (zero-min) + final CachedCellImg cellImg = BlockAlgoUtils.cellImg( + blocks.threadSafe(), + sourceImg.dimensionsAsLongArray(), + cellSize); + + // Translate to match source interval if not zero-min + if (Views.isZeroMin(sourceImg)) + return cellImg; + else + return Views.translate(cellImg, sourceImg.minAsLongArray()); } } From 76b6ddc1443745e5ef40af0e0cee4b96d072c3fb Mon Sep 17 00:00:00 2001 From: Michael Innerberger Date: Tue, 3 Feb 2026 21:13:52 -0500 Subject: [PATCH 31/31] Simplify benchmark --- .../tests/BenchmarkFlatfieldFusion.java | 130 ------------------ 1 file changed, 130 deletions(-) diff --git a/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java b/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java index a649ad453..1627007fe 100644 --- a/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java +++ b/src/test/java/net/preibisch/mvrecon/tests/BenchmarkFlatfieldFusion.java @@ -26,27 +26,20 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import mpicbg.spim.data.SpimDataException; import mpicbg.spim.data.sequence.ViewId; import net.imglib2.FinalInterval; import net.imglib2.Interval; -import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.blocks.BlockSupplier; import net.imglib2.realtransform.AffineTransform3D; -import net.imglib2.type.numeric.real.FloatType; import net.imglib2.type.numeric.integer.UnsignedShortType; -import net.imglib2.util.Pair; import net.preibisch.mvrecon.fiji.plugin.fusion.FusionGUI.FusionType; import net.preibisch.mvrecon.fiji.spimdata.SpimData2; import net.preibisch.mvrecon.fiji.spimdata.XmlIoSpimData2; import net.preibisch.mvrecon.fiji.spimdata.boundingbox.BoundingBox; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldCorrectionWrappedImgLoader; -import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldCorrectionBlockSupplier; -import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldImageInfo; -import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.FlatfieldImageLoader; import net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield.ViewerFlatfieldCorrectionWrappedImgLoader; import net.preibisch.mvrecon.process.boundingbox.BoundingBoxMaximal; import net.preibisch.mvrecon.process.fusion.FusionTools; @@ -229,129 +222,6 @@ private static void runBenchmark(final File xmlFile) throws SpimDataException { System.out.println(); System.out.printf("Overhead: %+d ms (%+.1f%%)%n", overhead, overheadPercent); System.out.println("============================================================"); - - // Run BlockSupplier comparison if we have a ViewerFlatfieldCorrectionWrappedImgLoader - if (viewerFfLoader != null) { - System.out.println(); - runBlockSupplierBenchmark(spimData, viewerFfLoader, viewIds.get(0), boundingBoxFusion); - } - } - - /** - * Benchmark comparing RandomAccess-based vs BlockSupplier-based flatfield correction. - */ - private static void runBlockSupplierBenchmark( - final SpimData2 spimData, - final ViewerFlatfieldCorrectionWrappedImgLoader viewerFfLoader, - final ViewId viewId, - final Interval boundingBox) { - System.out.println("============================================================"); - System.out.println("BlockSupplier vs RandomAccess Flatfield Correction Benchmark"); - System.out.println("============================================================"); - System.out.println(); - - // Get bright/dark info for this view - final Map> infoMap = viewerFfLoader.getInfoMap(); - final Pair ffInfo = infoMap.get(viewId); - - if (ffInfo == null) { - System.out.println("No flatfield info for view " + viewId + ", skipping BlockSupplier benchmark."); - return; - } - - // Load bright/dark images - final FlatfieldImageLoader loader = new FlatfieldImageLoader(); - loader.setBrightImage(viewId, ffInfo.getA()); - loader.setDarkImage(viewId, ffInfo.getB()); - - final RandomAccessibleInterval brightImg = loader.getBrightImg(viewId); - final RandomAccessibleInterval darkImg = loader.getDarkImg(viewId); - - if (brightImg == null || darkImg == null) { - System.out.println("Could not load bright/dark images, skipping BlockSupplier benchmark."); - return; - } - - // Get raw source image (without flatfield correction) - viewerFfLoader.setActive(false); - @SuppressWarnings("unchecked") - final RandomAccessibleInterval sourceImg = - (RandomAccessibleInterval) viewerFfLoader.getSetupImgLoader(viewId.getViewSetupId()) - .getImage(viewId.getTimePointId()); - - System.out.println("Testing single view: " + viewId); - System.out.println("Source dimensions: " + sourceImg.dimension(0) + " x " + sourceImg.dimension(1) + " x " + sourceImg.dimension(2)); - System.out.println("Bright dimensions: " + brightImg.dimension(0) + " x " + brightImg.dimension(1)); - System.out.println(); - - // Create interval to test (use full XY, limited Z for faster testing) - final long[] min = new long[] {sourceImg.min(0), sourceImg.min(1), sourceImg.min(2)}; - final long[] max = new long[] {sourceImg.max(0), sourceImg.max(1), Math.min(sourceImg.min(2) + 31, sourceImg.max(2))}; - final FinalInterval testInterval = new FinalInterval(min, max); - - System.out.println("Test interval: [" + min[0] + "-" + max[0] + ", " + min[1] + "-" + max[1] + ", " + min[2] + "-" + max[2] + "]"); - System.out.println(); - - // Create BlockSupplier for comparison - final BlockSupplier blockSupplier = FlatfieldCorrectionBlockSupplier.of(sourceImg, brightImg, darkImg); - - // Allocate output array - final int len = (int) ((max[0] - min[0] + 1) * (max[1] - min[1] + 1) * (max[2] - min[2] + 1)); - final float[] output = new float[len]; - - // Warmup - System.out.println("--- Warmup ---"); - for (int i = 0; i < 5; i++) { - blockSupplier.copy(testInterval, output); - } - - // Benchmark BlockSupplier - System.out.println("--- BlockSupplier Benchmark ---"); - final long[] timesBlock = new long[20]; - for (int i = 0; i < timesBlock.length; i++) { - final long start = System.currentTimeMillis(); - blockSupplier.copy(testInterval, output); - timesBlock[i] = System.currentTimeMillis() - start; - System.out.println(" Iteration " + (i + 1) + ": " + timesBlock[i] + " ms"); - } - - // Benchmark RandomAccess-based (via fusion with single view) - System.out.println(); - System.out.println("--- RandomAccess Benchmark (via single-view fusion) ---"); - viewerFfLoader.setActive(true); - - final long[] timesRA = new long[20]; - for (int i = 0; i < timesRA.length; i++) { - // Re-get the image with correction enabled - @SuppressWarnings("unchecked") - final RandomAccessibleInterval correctedImg = - (RandomAccessibleInterval) viewerFfLoader.getSetupImgLoader(viewId.getViewSetupId()) - .getImage(viewId.getTimePointId()); - - final long start = System.currentTimeMillis(); - // Force computation by copying to array via BlockSupplier - final BlockSupplier raBlocks = BlockSupplier - .of(net.imglib2.view.Views.extendBorder(correctedImg)) - .andThen(net.imglib2.algorithm.blocks.convert.Convert.convert(new FloatType())); - raBlocks.copy(testInterval, output); - timesRA[i] = System.currentTimeMillis() - start; - System.out.println(" Iteration " + (i + 1) + ": " + timesRA[i] + " ms"); - } - - // Calculate and report statistics - final Stats statsBlock = calculateStats(timesBlock); - final Stats statsRA = calculateStats(timesRA); - - System.out.println(); - System.out.println("--- Results ---"); - System.out.printf("BlockSupplier: avg %d ms (min %d, max %d, stddev %.1f)%n", - statsBlock.avg, statsBlock.min, statsBlock.max, statsBlock.stddev); - System.out.printf("RandomAccess: avg %d ms (min %d, max %d, stddev %.1f)%n", - statsRA.avg, statsRA.min, statsRA.max, statsRA.stddev); - - final double speedup = (double) statsRA.avg / statsBlock.avg; - System.out.printf("%nSpeedup: %.2fx%n", speedup); - System.out.println("============================================================"); } /**