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/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/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/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/ConvertFlatfieldsToZarr.java b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java new file mode 100644 index 000000000..66fa3377b --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ConvertFlatfieldsToZarr.java @@ -0,0 +1,258 @@ +/*- + * #%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.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.DataType; +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 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; + +/** + * 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 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()); + } + + /** + * 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 by copying + * an existing working XML and updating the flatfield paths to point to Zarr files. + * + * @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 sourceXmlPath, + final String outputXmlPath, + final File zarrDir, + final Map convertedFiles) throws IOException { + + System.out.println("\n=== Generating Test XML ==="); + System.out.println("Source XML: " + sourceXmlPath); + System.out.println("Output XML: " + outputXmlPath); + + // 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"); + } + } + + String xml = content.toString(); + + // Pattern to match BrightImg and DarkImg paths + final Pattern brightPattern = Pattern.compile("()([^<]+)()"); + final Pattern darkPattern = Pattern.compile("()([^<]+)()"); + + // Replace TIFF paths with Zarr paths + xml = replaceFlatfieldPaths(xml, brightPattern, zarrDir, convertedFiles); + xml = replaceFlatfieldPaths(xml, darkPattern, zarrDir, convertedFiles); + + // Write output XML + try (FileWriter writer = new FileWriter(outputXmlPath)) { + 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); + + return result.toString(); + } + + /** + * 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 (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()) { + convertedFiles = convertDirectory(tiffDir, zarrDir); + } else { + throw new IOException("Input TIFF directory does not exist: " + tiffDir.getAbsolutePath()); + } + + // Step 2: Generate test XML by copying from working TIFF-based XML + System.out.println("\n=== Step 2: Generating Test XML ===\n"); + + // 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(sourceXml).exists()) { + generateTestXml( + sourceXml, + outputXml, + zarrDir, + convertedFiles + ); + } else { + System.out.println("Source XML not found: " + sourceXml); + System.out.println("Skipping XML generation."); + } + + System.out.println("\n=== Done! ==="); + System.out.println("To test, load: " + outputXml); + } +} 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); } } 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..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 @@ -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; /* * @@ -57,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; } @@ -107,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 ); + // 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 corrBright = brightRA.get().getRealDouble() - darkRA.get().getRealDouble(); - final double corrImg = sourceRA.get().getRealDouble() - darkRA.get().getRealDouble(); + 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 ); @@ -148,7 +144,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 +168,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 +179,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 ) ); } 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/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 1c98182e6..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 @@ -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,104 +23,58 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; -import java.util.HashMap; +import java.net.URI; import java.util.Map; -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.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Pair; -import net.imglib2.util.ValuePair; -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 FlatfieldImageLoader imageLoader; + public LazyLoadingFlatFieldCorrectionMap() { - raiMap = new HashMap<>(); - fileMap = new HashMap<>(); + imageLoader = new FlatfieldImageLoader(); } - - @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() ) ); + @Override + public void setBrightImage(ViewId vId, URI imgUri) { + imageLoader.setBrightImage(vId, imgUri); } @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, URI imgUri) { + imageLoader.setDarkImage(vId, imgUri); } - - protected RandomAccessibleInterval< FloatType > getBrightImg(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 getBrightImg(ViewId vId) { + return imageLoader.getBrightImg(vId); } - protected RandomAccessibleInterval< FloatType > getDarkImg(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 RandomAccessibleInterval getDarkImg(ViewId vId) { + return imageLoader.getDarkImg(vId); } - - 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 ); - } - + 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 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 1edc09e1d..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 @@ -23,13 +23,14 @@ package net.preibisch.mvrecon.fiji.spimdata.imgloaders.flatfield; import java.io.File; -import java.util.ArrayList; +import java.net.URI; import java.util.Arrays; 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; import mpicbg.spim.data.generic.sequence.ImgLoaderHint; import mpicbg.spim.data.generic.sequence.ImgLoaderHints; @@ -37,16 +38,17 @@ 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; 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; @@ -58,7 +60,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; @@ -66,12 +67,12 @@ public class MultiResolutionFlatfieldCorrectionWrappedImgLoader extends LazyLoadingFlatFieldCorrectionMap< MultiResolutionImgLoader > implements MultiResolutionImgLoader { - private MultiResolutionImgLoader wrappedImgLoader; + private final MultiResolutionImgLoader wrappedImgLoader; private boolean active; 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) { @@ -89,55 +90,39 @@ 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, 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<>(uriSelector.apply(getUriMap().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 ); + final RandomAccessibleInterval downsampled = downsampleHDF5(img, downsamplingFactors); + dsRaiMap.put(key, downsampled); } - return dsRaiMap.get( key ); + return dsRaiMap.get(key); } @Override @@ -185,8 +170,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(); @@ -205,9 +193,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) { @@ -216,13 +207,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; } @@ -232,8 +224,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; } @@ -268,9 +260,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) { @@ -279,13 +274,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; } @@ -295,17 +291,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) { @@ -315,12 +313,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; } @@ -330,8 +328,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); } } @@ -398,84 +396,54 @@ 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. + *

+ * 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) + * @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]; - - final int sx = ox * dsFactor[0]; - final int sy = oy * dsFactor[1]; - final int sz = oz * dsFactor[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; + } - 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; + // Return input unchanged if all factors are 1 + if (!needsDownsampling) + return input; + + final long[] outDim = new long[n]; + for (int d = 0; d < n; d++) + outDim[d] = Math.max(input.dimension(d) / effectiveFactors[d], 1); + + // Determine output cell size - use input chunk size if available + final int[] cellSize = new int[n]; + if (input instanceof AbstractCellImg) { + @SuppressWarnings("rawtypes") + final CellGrid grid = ((AbstractCellImg) input).getCellGrid(); + grid.cellDimensions(cellSize); + } else { + // Default fallback for non-chunked images + Arrays.fill(cellSize, 128); } - 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, cellSize); } public static void main(String[] args) 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..178f28e1f --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestDecoratorChain.java @@ -0,0 +1,217 @@ +/*- + * #%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 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} + )); + + // 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 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 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 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 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..cb996338b --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestFlatfieldCorrection.java @@ -0,0 +1,116 @@ +/*- + * #%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 ij.ImageJ; +import mpicbg.spim.data.SpimDataException; +import mpicbg.spim.data.sequence.ImgLoader; +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 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! + * + * The XML wraps the N5 loader with MultiResolutionFlatfieldCorrectionWrappedImgLoader + * and specifies bright/dark images for each view setup. + */ +public class TestFlatfieldCorrection { + + 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"; + final String uncorrectedXmlPath = basePath + "data/dataset.xml"; + + // Which setup to display (0-8) + final int setupToShow = 0; + final int timepoint = 0; + + // ========== Load CORRECTED dataset (from XML with flatfield config) ========== + System.out.println("=== Loading CORRECTED dataset ==="); + System.out.println("XML path: " + correctedXmlPath); + + final SpimData2 correctedData = new XmlIoSpimData2().load(correctedXmlPath); + final ImgLoader correctedImgLoader = correctedData.getSequenceDescription().getImgLoader(); + + 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()); + } + + // ========== 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 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..."); + 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 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)"); + } +} 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..661ace0da --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/TestViewerFlatfieldCorrection.java @@ -0,0 +1,231 @@ +/*- + * #%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.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.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 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 + * - Multi-resolution mipmap levels + */ +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 uncorrectedXmlPath = basePath + "data/dataset.xml"; + + // Which setup to demonstrate (0-8) + final int setupToShow = 0; + 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); + + 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()); + 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")); + + // ========== STEP 2: Load UNCORRECTED dataset (original XML) ========== + System.out.println("\n=== STEP 2: Loading UNCORRECTED dataset ==="); + System.out.println("XML path: " + 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()); + return; + } + + final ViewerImgLoader uncorrectedLoader = (ViewerImgLoader) uncorrectedSeqDesc.getImgLoader(); + 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 ==="); + + // 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]); + + // Create a simple 2x1 split in X dimension + final long splitX = dims[0] / 2; + + // Define the mappings for split regions + 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} + )); + + // 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, + correctedSeqDesc + ); + + 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(); + + // 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)..."); + @SuppressWarnings("unchecked") + 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 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 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 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)"); + } +} 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..7c8a58321 --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/ViewerFlatfieldCorrectionWrappedImgLoader.java @@ -0,0 +1,441 @@ +/*- + * #%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.Arrays; +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; +import bdv.cache.CacheControl; +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.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; + + /** Helper for loading flatfield images */ + private final FlatfieldImageLoader imageLoader; + + /** Downsampled bright/dark images for each mipmap level */ + private final Map>, RandomAccessibleInterval> 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.imageLoader = new FlatfieldImageLoader(); + this.dsRaiMap = new HashMap<>(); + } + + // ========== 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 URI imgUri) { + imageLoader.setBrightImage(vId, imgUri); + } + + 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); + } + + /** + * Get the URI map for bright/dark images per view. + * @return map from ViewId to (brightUri, darkUri) pair + */ + public Map> getUriMap() { + return imageLoader.getUriMap(); + } + + // ========== 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); + } + + // ========== Image loading helpers ========== + + protected RandomAccessibleInterval getBrightImg(final ViewId vId) { + return imageLoader.getBrightImg(vId); + } + + protected RandomAccessibleInterval getDarkImg(final ViewId vId) { + return imageLoader.getDarkImg(vId); + } + + protected RandomAccessibleInterval getOrCreateBrightImgDownsampled( + final ViewId vId, + final int[] downsamplingFactors + ) { + return getOrCreateDownsampledImg(vId, downsamplingFactors, Pair::getA, this::getBrightImg); + } + + protected RandomAccessibleInterval getOrCreateDarkImgDownsampled( + final ViewId vId, + final int[] downsamplingFactors + ) { + return getOrCreateDownsampledImg(vId, downsamplingFactors, Pair::getB, this::getDarkImg); + } + + /** + * 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, 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<>(uriSelector.apply(imageLoader.getUriMap().get(vId)), dsFactorList); + + if (!dsRaiMap.containsKey(key)) { + final RandomAccessibleInterval img = imgGetter.apply(vId); + + if (img == null) + return null; + + final RandomAccessibleInterval downsampled = + MultiResolutionFlatfieldCorrectionWrappedImgLoader.downsampleHDF5(img, downsamplingFactors); + dsRaiMap.put(key, downsampled); + } + + return dsRaiMap.get(key); + } + + // ========== Inner class: ViewerSetupImgLoader implementation ========== + + public class ViewerFlatfieldCorrectionWrappedSetupImgLoader & NativeType, V extends Volatile & RealType & NativeType> + implements ViewerSetupImgLoader, MultiResolutionSetupImgLoader { + private final int setupId; + + ViewerFlatfieldCorrectionWrappedSetupImgLoader(final int setupId) { + this.setupId = setupId; + } + + @SuppressWarnings("unchecked") + private ViewerSetupImgLoader getUnderlyingViewerSetupImgLoader() { + return (ViewerSetupImgLoader) wrappedImgLoader.getSetupImgLoader(setupId); + } + + @SuppressWarnings("unchecked") + private MultiResolutionSetupImgLoader getUnderlyingMultiResSetupImgLoader() { + // The wrapped ViewerImgLoader should also be a MultiResolutionImgLoader + return (MultiResolutionSetupImgLoader) ((MultiResolutionImgLoader) wrappedImgLoader).getSetupImgLoader(setupId); + } + + // ========== Regular image access ========== + + @Override + public RandomAccessibleInterval getImage(final int timepointId, final ImgLoaderHint... hints) { + return getImage(timepointId, 0, hints); + } + + @Override + 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); + + 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; + + 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; + break; + } + + if (loadCompletelyRequested) { + long numPx = 1; + for (int d = 0; d < rai.numDimensions(); d++) + numPx *= rai.dimension(d); + + final ImgFactory imgFactory; + if (Math.log(numPx) / Math.log(2) < 31) { + imgFactory = new ArrayImgFactory<>(getImageType()); + } else { + imgFactory = new CellImgFactory<>(getImageType()); + } + + final Img loadedImg = imgFactory.create(rai); + 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, rai.firstElement().createVariable(), cellSize); + } + + return rai; + } + + // ========== Volatile image access ========== + + @Override + 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); + + 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 + 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 getFloatImage(final int timepointId, final boolean normalize, final ImgLoaderHint... hints) { + return getFloatImage(timepointId, 0, normalize, hints); + } + + @Override + 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; + break; + } + + if (loadCompletelyRequested) { + long numPx = 1; + for (int d = 0; d < rai.numDimensions(); d++) + numPx *= rai.dimension(d); + + final ImgFactory imgFactory; + if (Math.log(numPx) / Math.log(2) < 31) + imgFactory = new ArrayImgFactory<>(new FloatType()); + else + imgFactory = new CellImgFactory<>(new FloatType()); + + final Img loadedImg = imgFactory.create(rai); + 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); + } + } +} 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 new file mode 100644 index 000000000..230d75551 --- /dev/null +++ b/src/main/java/net/preibisch/mvrecon/fiji/spimdata/imgloaders/flatfield/XmlIoViewerFlatfieldCorrectionWrappedImgLoader.java @@ -0,0 +1,171 @@ +/*- + * #%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.net.URI; +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.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 { + 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) { + 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 { + 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, basePathURI, 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)); + 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); + return res; + } + + @Override + public Element toXml(ViewerFlatfieldCorrectionWrappedImgLoader imgLoader, File basePath) { + 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, + 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({"rawtypes"}) + XmlIoBasicImgLoader loaderIO = ImgLoaders + .createXmlIoForImgLoaderClass(imgLoader.getWrappedImgLoader().getClass()); + @SuppressWarnings("unchecked") + Element wrappedInner = loaderIO.toXml(imgLoader.getWrappedImgLoader(), basePathURI); + wrappedIL.addContent(wrappedInner); + } catch (SpimDataInstantiationException e) { + e.printStackTrace(); + return null; + } + + 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)) + 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)); + + elFlatfields.addContent(elFlatfield); + } + + wholeElem.addContent(wrappedIL); + wholeElem.addContent(elFlatfields); + return wholeElem; + } +}