diff --git a/doc/examples/bos_with_topology.py b/doc/examples/bos_with_topology.py index c6db24d5..be5a0457 100644 --- a/doc/examples/bos_with_topology.py +++ b/doc/examples/bos_with_topology.py @@ -4,7 +4,7 @@ params = TopologyBOS.create_default_params() params.images.path = get_path_image_samples() / "Karman/Images" -params.images.str_slice = "1:3" +params.images.str_subset = "1:3" params.piv0.shape_crop_im0 = 32 params.multipass.number = 2 diff --git a/doc/examples/experimental/image2image_await.py b/doc/examples/experimental/image2image_await.py index 1a07a10b..7c717516 100644 --- a/doc/examples/experimental/image2image_await.py +++ b/doc/examples/experimental/image2image_await.py @@ -16,7 +16,7 @@ params.saving.postfix = "await_im2im_recompute" -params.im2im = "fluidimage.preproc.image2image.im2im_func_example" +params.im2im = "fluidimage.image2image.im2im_func_example" topology = TopologyImage2Image(params, logging_level="info") image2image.complete_im2im_params_with_default(params) diff --git a/doc/examples/experimental/old_piv_recompute.py b/doc/examples/experimental/old_piv_recompute.py index a6233f73..50361e09 100644 --- a/doc/examples/experimental/old_piv_recompute.py +++ b/doc/examples/experimental/old_piv_recompute.py @@ -1,8 +1,8 @@ import os -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = "../../../image_samples/Karman/Images3" params.series.ind_start = 1 @@ -14,7 +14,7 @@ params.saving.how = "recompute" params.saving.postfix = "old_piv" -topology = TopologyPIV(params, logging_level="info") +topology = Topology(params, logging_level="info") # topology.make_code_graphviz('topo.dot') topology.compute() diff --git a/doc/examples/experimental/piv_complete.py b/doc/examples/experimental/piv_complete.py index f12c3ded..04f2b028 100644 --- a/doc/examples/experimental/piv_complete.py +++ b/doc/examples/experimental/piv_complete.py @@ -1,9 +1,9 @@ from fluidimage.experimental.executors.executor_await import ( ExecutorAwaitMultiprocs, ) -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = "../../../image_samples/Karman/Images2" params.series.ind_start = 1 @@ -18,7 +18,7 @@ params.saving.postfix = "piv_complete" -topology = TopologyPIV(params, logging_level="debug") +topology = Topology(params, logging_level="debug") # topology.make_code_graphviz('topo.dot') topology.compute(executer=ExecutorAwaitMultiprocs(topology), sequential=True) diff --git a/doc/examples/experimental/piv_trio.py b/doc/examples/experimental/piv_trio.py index 317b74e1..09103f43 100644 --- a/doc/examples/experimental/piv_trio.py +++ b/doc/examples/experimental/piv_trio.py @@ -2,10 +2,10 @@ from fluidimage.experimental.no_topology_computations.base_trio import BaseAsync from fluidimage.experimental.no_topology_computations.piv_trio import PivTrio -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology from fluidimage.works.piv import multipass -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = "../../../image_samples/Karman/Images3" diff --git a/doc/examples/my_example_im2im.py b/doc/examples/my_example_im2im.py index 5d9a764c..6316be07 100644 --- a/doc/examples/my_example_im2im.py +++ b/doc/examples/my_example_im2im.py @@ -4,6 +4,7 @@ """ -def im2im(im): +def im2im(im_path): + im, path = im_path print("in the function im2im...") - return 2 * im + return 2 * im, path diff --git a/doc/examples/my_example_im2im_class.py b/doc/examples/my_example_im2im_class.py index c42bd4a7..35ba41e8 100644 --- a/doc/examples/my_example_im2im_class.py +++ b/doc/examples/my_example_im2im_class.py @@ -9,6 +9,7 @@ def __init__(self, arg0, arg1): self.arg0 = arg0 self.arg1 = arg1 - def calcul(self, image): + def calcul(self, image_path): + image, path = image_path print(f"in the function Im2Im.calcul (arg0={self.arg0})...") - return 2 * image + return 2 * image, path diff --git a/doc/examples/piv_as_real/job_piv.py b/doc/examples/piv_as_real/job_piv.py index f190932d..3d2223d1 100755 --- a/doc/examples/piv_as_real/job_piv.py +++ b/doc/examples/piv_as_real/job_piv.py @@ -15,7 +15,7 @@ import params_piv -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology def main(args): @@ -26,7 +26,7 @@ def main(args): postfix_out=args.postfix_out, ) - topology = TopologyPIV(params, nb_max_workers=int(args.nb_cores)) + topology = Topology(params, nb_max_workers=int(args.nb_cores)) topology.compute(sequential=args.seq) diff --git a/doc/examples/piv_as_real/params_piv.py b/doc/examples/piv_as_real/params_piv.py index 8eec513f..d2cd9852 100644 --- a/doc/examples/piv_as_real/params_piv.py +++ b/doc/examples/piv_as_real/params_piv.py @@ -11,7 +11,7 @@ from glob import glob -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology def get_path(iexp): @@ -27,7 +27,7 @@ def make_params_piv( if postfix_in is not None and postfix_in != "": path += "." + postfix_in - params = TopologyPIV.create_default_params() + params = Topology.create_default_params() params.series.path = path print("path", path) @@ -38,9 +38,9 @@ def make_params_piv( pathim = paths[0] if pathim.endswith("a.png") or pathim.endswith("b.png"): - params.series.strcouple = "i, 0:2" + params.series.str_subset = "i, 0:2" else: - params.series.strcouple = "i:i+2" + params.series.str_subset = "i:i+2" params.series.ind_start = 60 params.series.ind_stop = None diff --git a/doc/examples/piv_as_real/params_pre.py b/doc/examples/piv_as_real/params_pre.py index 1825d983..c4838a40 100644 --- a/doc/examples/piv_as_real/params_pre.py +++ b/doc/examples/piv_as_real/params_pre.py @@ -41,9 +41,9 @@ def make_params_pre(iexp, savinghow="recompute", postfix_out="pre"): pathim = paths[0] double_frame = pathim.endswith("a.png") or pathim.endswith("b.png") if double_frame: - params.preproc.series.strcouple = "i:i+1, 0" + params.preproc.series.str_subset = "i:i+1, 0" else: - params.preproc.series.strcouple = "i:i+1" + params.preproc.series.str_subset = "i:i+1" params.preproc.series.ind_start = 60 params.preproc.series.ind_stop = 62 @@ -63,8 +63,8 @@ def make_params_pre(iexp, savinghow="recompute", postfix_out="pre"): if double_frame: # for 'b.png' images params2 = deepcopy(params) - params2.preproc.series.strcouple = ( - params.preproc.series.strcouple[:-1] + "1" + params2.preproc.series.str_subset = ( + params.preproc.series.str_subset[:-1] + "1" ) return [params, params2] else: diff --git a/doc/examples/piv_as_real/try_piv.py b/doc/examples/piv_as_real/try_piv.py index 11938895..6a9460d0 100644 --- a/doc/examples/piv_as_real/try_piv.py +++ b/doc/examples/piv_as_real/try_piv.py @@ -16,7 +16,7 @@ import params_piv from fluidimage import SeriesOfArrays -from fluidimage.works.piv import WorkPIV +from fluidimage.piv import WorkPIV try: reload @@ -32,16 +32,8 @@ work = WorkPIV(params=params) -pathin = params.series.path - -series = SeriesOfArrays( - pathin, params.series.strcouple, ind_start=params.series.ind_start -) - # c060a.png and c060b.png -serie = series.get_serie_from_index(params.series.ind_start) - -piv = work.calcul(serie) +piv = work.process_1_serie(params.series.ind_start) # piv.piv0.display(show_interp=True, scale=0.05, show_error=True) diff --git a/doc/examples/piv_try_params.py b/doc/examples/piv_try_params.py index 7d78107d..7d3a517a 100644 --- a/doc/examples/piv_try_params.py +++ b/doc/examples/piv_try_params.py @@ -1,7 +1,6 @@ """To be run in IPython to find a good set of parameters""" -from fluidimage import SeriesOfArrays -from fluidimage.works.piv import WorkPIV +from fluidimage.piv import WorkPIV params = WorkPIV.create_default_params() @@ -15,15 +14,14 @@ params.mask.strcrop = "30:250, 100:" -work = WorkPIV(params=params) - path = "../../image_samples/Oseen/Images" # path = '../../image_samples/Karman/Images' -series = SeriesOfArrays(path, "i+1:i+3") -# we select the serie corresponding to i = 0 -serie = series.get_serie_from_index(0) +params.series.path = path +params.series.str_subset = "i+1:i+3" + +work = WorkPIV(params=params) -piv = work.calcul(serie) +piv = work.process_1_serie() # piv.display(show_interp=True, scale=0.3, show_error=True) piv.display(show_interp=False, scale=1, show_error=True) diff --git a/doc/examples/piv_try_params_with_im2im_preproc.py b/doc/examples/piv_try_params_with_im2im_preproc.py index 51e9643b..3c922444 100644 --- a/doc/examples/piv_try_params_with_im2im_preproc.py +++ b/doc/examples/piv_try_params_with_im2im_preproc.py @@ -1,8 +1,7 @@ -from fluidimage import SeriesOfArrays -from fluidimage.preproc.image2image import apply_im2im_filter -from fluidimage.works.piv import WorkPIV +from fluidimage.image2image import apply_im2im_filter +from fluidimage.piv import Work -params = WorkPIV.create_default_params() +params = Work.create_default_params() params.multipass.number = 2 params.multipass.use_tps = True @@ -14,12 +13,14 @@ params.mask.strcrop = "30:250, 100:" -work = WorkPIV(params=params) - path = "../../image_samples/Oseen/Images" # path = '../../image_samples/Karman/Images' -series = SeriesOfArrays(path, "i+1:i+3") -serie = series.get_serie_from_index(0) +params.series.path = path +params.series.str_subset = "i+1:i+3" + +work = Work(params=params) + +serie = work.get_serie() # "image to image" filter serie = apply_im2im_filter(serie, im2im="my_example_im2im.im2im") diff --git a/doc/examples/piv_with_topo_and_preproc.py b/doc/examples/piv_with_topo_and_preproc.py index d2ca7b99..fb5c2ebb 100644 --- a/doc/examples/piv_with_topo_and_preproc.py +++ b/doc/examples/piv_with_topo_and_preproc.py @@ -1,7 +1,7 @@ from fluidimage import get_path_image_samples -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = get_path_image_samples() / "Karman/Images" params.series.ind_start = 1 @@ -22,7 +22,7 @@ # Here the "image to image" function will be imported with the statement # `from my_example_im2im import im2im` -topology = TopologyPIV(params, logging_level="info") +topology = Topology(params, logging_level="info") # To produce a graph of the topology topology.make_code_graphviz("topo.dot") diff --git a/doc/examples/piv_with_topo_and_preproc_class.py b/doc/examples/piv_with_topo_and_preproc_class.py index 8c3b0834..0b95225a 100644 --- a/doc/examples/piv_with_topo_and_preproc_class.py +++ b/doc/examples/piv_with_topo_and_preproc_class.py @@ -1,7 +1,7 @@ from fluidimage import get_path_image_samples -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = get_path_image_samples() / "Karman/Images" params.series.ind_start = 1 @@ -23,7 +23,7 @@ # Here, the class will be imported with the statement # `from my_example_im2im_class import Im2Im` -topology = TopologyPIV(params, logging_level="info") +topology = Topology(params, logging_level="info") # To produce a graph of the topology topology.make_code_graphviz("topo.dot") diff --git a/doc/examples/piv_complete.py b/doc/examples/piv_with_topo_complete.py similarity index 73% rename from doc/examples/piv_complete.py rename to doc/examples/piv_with_topo_complete.py index 49f86826..c04bd022 100644 --- a/doc/examples/piv_complete.py +++ b/doc/examples/piv_with_topo_complete.py @@ -1,8 +1,8 @@ import os -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = "../../image_samples/Karman/Images" params.series.ind_start = 1 @@ -16,7 +16,7 @@ params.saving.how = "complete" params.saving.postfix = "piv_complete" -topology = TopologyPIV(params, logging_level="info") +topology = Topology(params, logging_level="info") # topology.make_code_graphviz('topo.dot') topology.compute() diff --git a/doc/examples/piv_with_topology.py b/doc/examples/piv_with_topology.py index 43dac435..8de6e060 100644 --- a/doc/examples/piv_with_topology.py +++ b/doc/examples/piv_with_topology.py @@ -1,7 +1,7 @@ from fluidimage import get_path_image_samples -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = get_path_image_samples() / "Karman/Images" params.series.ind_start = 1 @@ -16,7 +16,7 @@ # params.saving.how = 'complete' params.saving.postfix = "piv_example" -topology = TopologyPIV(params, logging_level="info") +topology = Topology(params, logging_level="info") # To produce a graph of the topology # topology.make_code_graphviz('topo.dot') diff --git a/doc/examples/pivchallenge/bench_piv_2005C.py b/doc/examples/pivchallenge/bench_piv_2005C.py index 89db878d..eef73ad7 100644 --- a/doc/examples/pivchallenge/bench_piv_2005C.py +++ b/doc/examples/pivchallenge/bench_piv_2005C.py @@ -4,14 +4,14 @@ from path_images import get_path -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology path = os.path.join(get_path("2005C"), "c*.bmp") -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = path -params.series.strcouple = "i, 0:2" +params.series.str_subset = "i, 0:2" # params.series.ind_start = 48 params.series.ind_stop = 20 @@ -28,7 +28,7 @@ params.saving.how = "recompute" -topology = TopologyPIV(params) +topology = Topology(params) serie = topology.series.serie diff --git a/doc/examples/pivchallenge/piv_2001A_topology.py b/doc/examples/pivchallenge/piv_2001A_topology.py index c8c4a704..d702f061 100644 --- a/doc/examples/pivchallenge/piv_2001A_topology.py +++ b/doc/examples/pivchallenge/piv_2001A_topology.py @@ -2,14 +2,14 @@ from path_images import get_path -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() path = os.path.join(get_path("2001A"), "A*") params.series.path = path -params.series.strcouple = "i, 1:3" +params.series.str_subset = "i, 1:3" params.series.ind_start = 1 params.piv0.shape_crop_im0 = 32 @@ -19,7 +19,7 @@ params.saving.how = "recompute" -topology = TopologyPIV(params) +topology = Topology(params) serie = topology.series.serie diff --git a/doc/examples/pivchallenge/piv_2001A_work.py b/doc/examples/pivchallenge/piv_2001A_work.py index 792a1ead..525c82f0 100644 --- a/doc/examples/pivchallenge/piv_2001A_work.py +++ b/doc/examples/pivchallenge/piv_2001A_work.py @@ -3,7 +3,7 @@ from path_images import get_path from fluidimage import SeriesOfArrays -from fluidimage.works.piv import WorkPIV +from fluidimage.piv import WorkPIV params = WorkPIV.create_default_params() diff --git a/doc/examples/pivchallenge/piv_2005C_topology.py b/doc/examples/pivchallenge/piv_2005C_topology.py index 16a8b6e1..901a55ae 100644 --- a/doc/examples/pivchallenge/piv_2005C_topology.py +++ b/doc/examples/pivchallenge/piv_2005C_topology.py @@ -2,14 +2,14 @@ from path_images import get_path -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology path = os.path.join(get_path("2005C"), "c*.bmp") -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() params.series.path = path -params.series.strcouple = "i, 0:2" +params.series.str_subset = "i, 0:2" params.series.ind_start = 48 params.series.ind_stop = 52 @@ -26,7 +26,7 @@ params.saving.how = "complete" -topology = TopologyPIV(params) +topology = Topology(params) serie = topology.series.serie diff --git a/doc/examples/pivchallenge/piv_2005C_work.py b/doc/examples/pivchallenge/piv_2005C_work.py index dd16db00..453ad82e 100644 --- a/doc/examples/pivchallenge/piv_2005C_work.py +++ b/doc/examples/pivchallenge/piv_2005C_work.py @@ -4,7 +4,7 @@ from path_images import get_path from fluidimage import SeriesOfArrays -from fluidimage.works.piv import WorkPIV +from fluidimage.piv import WorkPIV path = os.path.join(get_path("2005C"), "c*.bmp") diff --git a/doc/examples/preproc.md b/doc/examples/preproc.md index f0531761..bab898e1 100644 --- a/doc/examples/preproc.md +++ b/doc/examples/preproc.md @@ -26,7 +26,7 @@ To find the good parameter, you can use the class {class}`fluidimage.preproc.base.PreprocBase` (see also {mod}`fluidimage.preproc`). -```{literalinclude} preproc_simple.py +```{literalinclude} preproc_try_params.py ``` ### Preprocessing large series of images diff --git a/doc/examples/preproc_sback1_filter.py b/doc/examples/preproc_sback1_filter.py index 30b45fb9..e1d28734 100644 --- a/doc/examples/preproc_sback1_filter.py +++ b/doc/examples/preproc_sback1_filter.py @@ -7,7 +7,7 @@ "Exp21_2016-06-22_N0.8_L6.0_V0.08_piv3d/PCO_top/level2" ) -params.preproc.series.strcouple = "i:i+23" +params.preproc.series.str_subset = "i:i+23" params.preproc.series.ind_start = 1200 params.preproc.series.ind_stop = 1202 diff --git a/doc/examples/preproc_sback2_rescale.py b/doc/examples/preproc_sback2_rescale.py index fa214f8c..805d2dbc 100644 --- a/doc/examples/preproc_sback2_rescale.py +++ b/doc/examples/preproc_sback2_rescale.py @@ -7,7 +7,7 @@ + "Exp21_2016-06-22_N0.8_L6.0_V0.08_piv3d/PCO_top/level2.tfilter" ) -params.preproc.series.strcouple = "i:i+1" +params.preproc.series.str_subset = "i:i+1" params.preproc.series.ind_start = 1200 params.preproc.series.ind_stop = 1224 diff --git a/doc/examples/preproc_simple.py b/doc/examples/preproc_try_params.py similarity index 79% rename from doc/examples/preproc_simple.py rename to doc/examples/preproc_try_params.py index f8b88e02..8d8aa44e 100644 --- a/doc/examples/preproc_simple.py +++ b/doc/examples/preproc_try_params.py @@ -1,6 +1,6 @@ -from fluidimage.preproc import PreprocBase +from fluidimage.preproc import Work -params = PreprocBase.create_default_params() +params = Work.create_default_params() params.preproc.series.path = "../../image_samples/Karman/Images" print("Available preprocessing tools: ", params.preproc.tools.available_tools) @@ -13,7 +13,6 @@ params.preproc.tools.global_threshold.minima = 0.0 params.preproc.tools.global_threshold.maxima = 255.0 -preproc = PreprocBase(params) -preproc() +preproc = Work(params) preproc.display(1, hist=False) diff --git a/doc/examples/preproc_simple_opencv.py b/doc/examples/preproc_try_params_opencv.py similarity index 65% rename from doc/examples/preproc_simple_opencv.py rename to doc/examples/preproc_try_params_opencv.py index e119869e..463d7749 100644 --- a/doc/examples/preproc_simple_opencv.py +++ b/doc/examples/preproc_try_params_opencv.py @@ -1,6 +1,6 @@ -from fluidimage.preproc.base import PreprocBase +from fluidimage.preproc import Work -params = PreprocBase.create_default_params(backend="opencv") +params = Work.create_default_params(backend="opencv") params.preproc.series.path = "../../image_samples/Karman/Images" print("Available preprocessing tools: ", params.preproc.tools.available_tools) @@ -8,7 +8,6 @@ params.preproc.tools.sliding_median.enable = True params.preproc.tools.sliding_median.window_size = 25 -preproc = PreprocBase(params) -preproc() +preproc = Work(params) preproc.display(1, hist=False) diff --git a/doc/examples/preproc_with_topology.py b/doc/examples/preproc_with_topology.py index 3c0efe07..4961def8 100644 --- a/doc/examples/preproc_with_topology.py +++ b/doc/examples/preproc_with_topology.py @@ -4,7 +4,7 @@ params = TopologyPreproc.create_default_params() params.preproc.series.path = get_path_image_samples() / "Jet/Images" -params.preproc.series.strcouple = "i+60:i+62, 0" +params.preproc.series.str_subset = "i+60:i+62, 0" print("Available preprocessing tools: ", params.preproc.tools.available_tools) params.preproc.tools.sequence = [ diff --git a/doc/examples/surface_tracking.py b/doc/examples/surface_tracking.py index 6cbacbf9..9f4555ba 100644 --- a/doc/examples/surface_tracking.py +++ b/doc/examples/surface_tracking.py @@ -19,5 +19,5 @@ topology.compute(sequential=seq) # not generating plots if seq mode is false -if seq == False: +if not seq: params.saving.plot = False diff --git a/doc/ipynbslides/fluidimage_legi_20170403.ipynb b/doc/ipynbslides/fluidimage_legi_20170403.ipynb index 10ab57aa..bbd9f888 100644 --- a/doc/ipynbslides/fluidimage_legi_20170403.ipynb +++ b/doc/ipynbslides/fluidimage_legi_20170403.ipynb @@ -133,7 +133,7 @@ } ], "source": [ - "from fluidimage.topologies.piv import TopologyPIV\n", + "from fluidimage.piv import Topology\n", "\n", "params = TopologyPIV.create_default_params()\n", "\n", @@ -187,7 +187,7 @@ " {\"program\": \"fluidimage\", \"class\": \"TopologyPIV\", \"module\":\n", " \"fluidimage.topologies.piv\"}\n", " \n", + " path=\"../../image_samples/Karman/Images\" str_subset=\"i:i+2\"/> \n", "\n", " \n", "\n", @@ -253,7 +253,7 @@ " String indicating the input images (can be a full path towards an image\n", " file or a string given to `glob`).\n", "\n", - "strcouple: 'i:i+2'\n", + "str_subset: 'i:i+2'\n", "\n", " String indicating as a Python slicing how couples of images are formed.\n", " There is one couple per value of `i`. The values of `i` are set with the\n", @@ -284,13 +284,13 @@ "\n", " For double-frame images (im1a, im1b, im2a, im2b, ...) you can write\n", "\n", - " >>> params.series.strcouple = 'i, 0:2'\n", + " >>> params.series.str_subset = 'i, 0:2'\n", "\n", " In this case, the first couple will be (im1a, im1b).\n", "\n", " To get the first couple (im1a, im1a), we would have to write\n", "\n", - " >>> params.series.strcouple = 'i:i+2, 0'\n", + " >>> params.series.str_subset = 'i:i+2, 0'\n", "\n", "ind_start: int, {0}\n", "\n", diff --git a/doc/tutorials/tuto_piv.md b/doc/tutorials/tuto_piv.md index 941a5d6b..849f66ff 100644 --- a/doc/tutorials/tuto_piv.md +++ b/doc/tutorials/tuto_piv.md @@ -34,13 +34,13 @@ We first import the class :class:`fluidimage.topologies.piv.TopologyPIV`. ``` ```{code-cell} ipython3 -from fluidimage.topologies.piv import TopologyPIV +from fluidimage.piv import Topology ``` We use a class function to create an object containing the parameters. ```{code-cell} ipython3 -params = TopologyPIV.create_default_params() +params = Topology.create_default_params() ``` The representation of this object is useful. In Ipython, just do: @@ -74,7 +74,7 @@ In order to run the PIV computation, we have to instanciate an object of the cla ``` ```{code-cell} ipython3 -topology = TopologyPIV(params) +topology = Topology(params) ``` We then launch the computation by running the function `topology.compute`. We use a sequential executor to get a simpler logging. diff --git a/pdm.lock b/pdm.lock index f290aabf..6580122a 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "build", "dev", "dev-doc", "doc", "opencv", "pims", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:6c6b653e0d09f67da1a3f263d8e9c9080d56c487cf454f49936e253043921210" +content_hash = "sha256:51dc6cfe002fdfe01a96f4d40513bf34c29747cd6d27afbf5b94deb88d777681" [[package]] name = "accessible-pygments" @@ -140,7 +140,7 @@ files = [ name = "asttokens" version = "2.4.1" summary = "Annotate AST trees with source code positions" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] dependencies = [ "six>=1.12.0", ] @@ -711,7 +711,7 @@ name = "decorator" version = "5.1.1" requires_python = ">=3.5" summary = "Decorators for Humans" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, @@ -766,7 +766,7 @@ name = "exceptiongroup" version = "1.2.0" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] marker = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, @@ -778,7 +778,7 @@ name = "executing" version = "2.0.1" requires_python = ">=3.5" summary = "Get the currently executing AST node of a frame, and other information" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, @@ -1106,6 +1106,24 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "ipdb" +version = "0.13.13" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "IPython-enabled pdb" +groups = ["dev"] +dependencies = [ + "decorator; python_version > \"3.6\" and python_version < \"3.11\"", + "decorator; python_version >= \"3.11\"", + "ipython>=7.31.1; python_version > \"3.6\" and python_version < \"3.11\"", + "ipython>=7.31.1; python_version >= \"3.11\"", + "tomli; python_version > \"3.6\" and python_version < \"3.11\"", +] +files = [ + {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, + {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, +] + [[package]] name = "ipykernel" version = "6.29.2" @@ -1137,7 +1155,7 @@ name = "ipython" version = "8.18.1" requires_python = ">=3.9" summary = "IPython: Productive Interactive Computing" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] dependencies = [ "colorama; sys_platform == \"win32\"", "decorator", @@ -1186,7 +1204,7 @@ name = "jedi" version = "0.19.1" requires_python = ">=3.6" summary = "An autocompletion tool for Python that can be used for text editors." -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] dependencies = [ "parso<0.9.0,>=0.8.3", ] @@ -1740,7 +1758,7 @@ name = "matplotlib-inline" version = "0.1.6" requires_python = ">=3.5" summary = "Inline Matplotlib backend for Jupyter" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] dependencies = [ "traitlets", ] @@ -2251,7 +2269,7 @@ name = "parso" version = "0.8.3" requires_python = ">=3.6" summary = "A Python Parser" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, @@ -2287,7 +2305,7 @@ files = [ name = "pexpect" version = "4.9.0" summary = "Pexpect allows easy control of interactive console applications." -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] marker = "sys_platform != \"win32\"" dependencies = [ "ptyprocess>=0.5", @@ -2438,7 +2456,7 @@ name = "prompt-toolkit" version = "3.0.43" requires_python = ">=3.7.0" summary = "Library for building powerful interactive command lines in Python" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] dependencies = [ "wcwidth", ] @@ -2467,7 +2485,7 @@ files = [ name = "ptyprocess" version = "0.7.0" summary = "Run a subprocess in a pseudo terminal" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] marker = "sys_platform != \"win32\" or os_name != \"nt\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -2478,7 +2496,7 @@ files = [ name = "pure-eval" version = "0.2.2" summary = "Safely evaluate AST nodes without side effects" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, @@ -2564,7 +2582,7 @@ name = "pygments" version = "2.17.2" requires_python = ">=3.7" summary = "Pygments is a syntax highlighting package written in Python." -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, @@ -3185,7 +3203,7 @@ name = "six" version = "1.16.0" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" summary = "Python 2 and 3 compatibility utilities" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -3404,7 +3422,7 @@ files = [ name = "stack-data" version = "0.6.3" summary = "Extract data from python stack frames and tracebacks for informative displays" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] dependencies = [ "asttokens>=2.1.0", "executing>=1.2.0", @@ -3540,7 +3558,7 @@ name = "traitlets" version = "5.14.1" requires_python = ">=3.8" summary = "Traitlets Python configuration system" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, @@ -3642,7 +3660,7 @@ files = [ name = "wcwidth" version = "0.2.13" summary = "Measures the displayed width of unicode strings in a terminal" -groups = ["default", "dev-doc", "doc", "test"] +groups = ["default", "dev", "dev-doc", "doc", "test"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, diff --git a/pylintrc b/pylintrc index 67fac493..f76bdf65 100644 --- a/pylintrc +++ b/pylintrc @@ -52,7 +52,9 @@ ignore=CVS # ignore-list. The regex matches against paths and can be in Posix or Windows # format. Because '\\' represents the directory delimiter on Windows systems, # it can't be used as an escape character. -ignore-paths= +ignore-paths=src/fluidimage/[a-z0-9/_]+__pythran__, + src/fluidimage/[a-z0-9/_]+__python__, + src/fluidimage/[a-z0-9/_]+__numba__ # Files or directories matching the regular expression patterns are skipped. # The regex matches against base names, not paths. The default value ignores @@ -265,7 +267,9 @@ defining-attr-methods=__init__, # List of member names, which should be excluded from the protected access # warning. -exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit, + _complete_params_with_default,_set_internal_attr,_set_child,_set_doc, + _set_attrib,_set_attribs,_get_formatted_docs # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls diff --git a/pyproject.toml b/pyproject.toml index 956b82b6..f1002f04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = 'mesonpy' [project] name = "fluidimage" -version = "0.2.0" +version = "0.3.0rc0" description = "Fluid image processing with Python." authors = [ {name = "Pierre Augier", email = "pierre.augier@legi.cnrs.fr"}, @@ -115,13 +115,14 @@ dev = [ "black", "pylint", "isort", + "ipdb>=0.13.13", ] [tool.pdm.scripts] black = "black src doc" isort = "isort --atomic --tc src bench doc/examples" black_check = "black --check src doc" -lint = {shell="pylint -rn --rcfile=pylintrc --jobs=$(nproc) src doc --exit-zero"} +lint = {shell="pylint -rn --rcfile=pylintrc --jobs=$(nproc) src --exit-zero"} validate_code = {composite = ["black_check", "lint"]} [tool.pdm.options] diff --git a/src/fluidimage/data_objects/piv.py b/src/fluidimage/data_objects/piv.py index 694bcefd..ab57dd29 100644 --- a/src/fluidimage/data_objects/piv.py +++ b/src/fluidimage/data_objects/piv.py @@ -93,6 +93,7 @@ def __init__( self, names=None, arrays=None, + paths=None, serie=None, str_path=None, hdf5_parent=None, @@ -121,11 +122,12 @@ def __init__( + str(paths) ) - self.paths = tuple(os.path.abspath(p) for p in paths) + self.paths = paths = tuple(os.path.abspath(p) for p in paths) if arrays is None: arrays = self.read_images() + self.paths = paths self.names = tuple(names) self.name = "-".join(self.names) self.arrays = self._mask_arrays(arrays) diff --git a/src/fluidimage/executors/__init__.py b/src/fluidimage/executors/__init__.py index 12e478d1..9a20007e 100644 --- a/src/fluidimage/executors/__init__.py +++ b/src/fluidimage/executors/__init__.py @@ -36,10 +36,9 @@ import sys if sys.version_info < (3, 10): - from importlib_metadata import entry_points, EntryPoint + from importlib_metadata import EntryPoint, entry_points else: - from importlib.metadata import entry_points, EntryPoint - + from importlib.metadata import EntryPoint, entry_points import trio @@ -53,7 +52,6 @@ def afterfork(): from .base import ExecutorBase - _entry_points = None diff --git a/src/fluidimage/executors/base.py b/src/fluidimage/executors/base.py index 6bc33ec8..76ea80c4 100644 --- a/src/fluidimage/executors/base.py +++ b/src/fluidimage/executors/base.py @@ -1,5 +1,5 @@ -"""Base class for executors (:mod:`fluidimage.executors.base`) -============================================================== +"""Base class for executors +=========================== .. autoclass:: ExecutorBase :members: diff --git a/src/fluidimage/executors/exec_async.py b/src/fluidimage/executors/exec_async.py index a8e3d970..49aa6939 100644 --- a/src/fluidimage/executors/exec_async.py +++ b/src/fluidimage/executors/exec_async.py @@ -1,5 +1,5 @@ -"""Executor async/await (:mod:`fluidimage.executors.exec_async`) -================================================================ +"""Executor async/await +======================= This executer uses await/async with trio library to put topology tasks in concurrency. diff --git a/src/fluidimage/executors/exec_async_multiproc.py b/src/fluidimage/executors/exec_async_multiproc.py index ccc18fcd..6fa15a01 100644 --- a/src/fluidimage/executors/exec_async_multiproc.py +++ b/src/fluidimage/executors/exec_async_multiproc.py @@ -1,5 +1,5 @@ -"""Executor async/await + multiprocessing (:mod:`fluidimage.executors.exec_async_multiproc`) -============================================================================================ +"""Executor async/await + multiprocessing +========================================= A executor using async for IO and multiprocessing for CPU bounded tasks. diff --git a/src/fluidimage/executors/exec_async_sequential.py b/src/fluidimage/executors/exec_async_sequential.py index 0808ef0d..cab05ed2 100644 --- a/src/fluidimage/executors/exec_async_sequential.py +++ b/src/fluidimage/executors/exec_async_sequential.py @@ -1,5 +1,5 @@ -"""Executor async/await sequential (:mod:`fluidimage.executors.exec_async_sequential`) -====================================================================================== +"""Executor async/await sequential +================================== A executor using async for IO but launching CPU-bounded tasks sequentially. diff --git a/src/fluidimage/executors/exec_async_servers.py b/src/fluidimage/executors/exec_async_servers.py index 05237e6f..ff24319d 100644 --- a/src/fluidimage/executors/exec_async_servers.py +++ b/src/fluidimage/executors/exec_async_servers.py @@ -1,5 +1,5 @@ -"""Executor async/await using servers (:mod:`fluidimage.executors.exec_async_servers`) -====================================================================================== +"""Executor async/await using servers +===================================== A executor using async for IO and servers for CPU-bounded tasks. diff --git a/src/fluidimage/executors/exec_async_servers_threading.py b/src/fluidimage/executors/exec_async_servers_threading.py index 78d3dbb7..4f01f406 100644 --- a/src/fluidimage/executors/exec_async_servers_threading.py +++ b/src/fluidimage/executors/exec_async_servers_threading.py @@ -1,3 +1,12 @@ +"""Executor async/await using servers +===================================== + +.. autoclass:: ExecutorAsyncServersThreading + :members: + :private-members: + +""" + from .exec_async_servers import ExecutorAsyncServers diff --git a/src/fluidimage/executors/exec_sequential.py b/src/fluidimage/executors/exec_sequential.py index fccc63a7..0c674d1b 100644 --- a/src/fluidimage/executors/exec_sequential.py +++ b/src/fluidimage/executors/exec_sequential.py @@ -1,5 +1,5 @@ -"""Execute a topology sequentially (:mod:`fluidimage.executors.exec_async_sequential`) -====================================================================================== +"""Execute a topology sequentially +================================== .. autoclass:: ExecutorBase :members: diff --git a/src/fluidimage/executors/multi_exec_async.py b/src/fluidimage/executors/multi_exec_async.py index bafc6fdf..00fa89e0 100644 --- a/src/fluidimage/executors/multi_exec_async.py +++ b/src/fluidimage/executors/multi_exec_async.py @@ -1,6 +1,6 @@ """ -Multi executors async (:mod:`fluidimage.executors.multi_exec_async`) -==================================================================== +Multi executors async +===================== .. autoclass:: MultiExecutorAsync :members: @@ -166,11 +166,11 @@ def handler_signals(signal_number, stack): if hasattr(self.topology, "series"): self.start_multiprocess_series() else: - self.start_mutiprocess_first_queue() + self.start_multiprocess_first_queue() self._finalize_compute() - def start_mutiprocess_first_queue(self): + def start_multiprocess_first_queue(self): """Start the processes spitting the work with the first queue""" first_work = self.topology.works[0] if first_work.input_queue is not None: diff --git a/src/fluidimage/executors/multi_exec_async_subproc.py b/src/fluidimage/executors/multi_exec_async_subproc.py new file mode 100644 index 00000000..00647da4 --- /dev/null +++ b/src/fluidimage/executors/multi_exec_async_subproc.py @@ -0,0 +1,84 @@ +""" +Multi executors async using subprocesses +======================================== + +.. autoclass:: ExecutorAsyncForMultiSubproc + :members: + :private-members: + +""" + +import sys +from pathlib import Path +from subprocess import Popen + +from fluidimage.util import logger + +from .multi_exec_async import ExecutorAsyncForMulti, MultiExecutorAsync + + +def main(): + """Create an executor and start it in a process""" + + # topology_this_process, path_input, path_output, log_path + + executor = ExecutorAsyncForMulti( + topology_this_process, + path_dir_result, + sleep_time=sleep_time, + log_path=log_path, + logging_level=logging_level, + ) + executor.t_start = t_start + executor.compute() + + +class MultiExecutorAsyncSubproc(MultiExecutorAsync): + + def launch_process(self, topology, ind_process): + """Launch one process""" + + log_path = Path( + str(self._log_path).split(".txt")[0] + f"_multi{ind_process:03}.txt" + ) + + self.log_paths.append(log_path) + + process = Popen( + [ + sys.executable, + "-m", + "fluidimage.executors.multi_exec_async_subproc", + ], + ) + + self.processes.append(process) + + def wait_for_all_processes(self): + """logging + wait for all processes to finish""" + logger.info( + f"logging files: {[log_path.name for log_path in self.log_paths]}" + ) + + # wait until end of all processes + + self.topology.results = results_all = [] + for index, process in enumerate(self.processes): + try: + results = process.connection.recv() + except EOFError: + logger.error(f"EOFError for process {index} ({process})") + results = None + + if results is not None: + results_all.extend(results) + + for process in self.processes: + process.join() + + +Executor = MultiExecutorAsync + + +if __name__ == "__main__": + main() diff --git a/src/fluidimage/preproc/image2image.py b/src/fluidimage/image2image.py similarity index 84% rename from src/fluidimage/preproc/image2image.py rename to src/fluidimage/image2image.py index b25e4487..2e0c3db2 100644 --- a/src/fluidimage/preproc/image2image.py +++ b/src/fluidimage/image2image.py @@ -1,5 +1,5 @@ -""""Image to image" processing (:mod:`fluidimage.preproc.image2image`) -====================================================================== +""""Image to image" processing +============================== .. autofunction:: init_im2im_function @@ -15,13 +15,12 @@ """ import types +from pathlib import Path import numpy as np from fluiddyn.util import import_class from fluiddyn.util.serieofarrays import SerieOfArraysFromFiles -from ..data_objects.piv import ArrayCouple - def im2im_func_example(tuple_image_path): """Process one image @@ -65,8 +64,7 @@ def calcul(self, tuple_image_path): def complete_im2im_params_with_default(params): """Complete params for image-to-image processing.""" - params._set_attrib("im2im", None) - params._set_attrib("args_init", tuple()) + params._set_attribs({"im2im": None, "args_init": tuple()}) params._set_doc( """ @@ -117,12 +115,20 @@ def apply_im2im_filter(serie, im2im=None, args_init=()): if im2im is None: return serie - obj, im2im_func = init_im2im_function(im2im, args_init) + _, im2im_func = init_im2im_function(im2im, args_init) if not isinstance(serie, SerieOfArraysFromFiles): raise NotImplementedError + result = {"names": [], "arrays": [], "paths": []} + arrays = serie.get_arrays() paths = serie.get_path_arrays() - return tuple(im2im_func(t) for t in zip(arrays, paths)) + for arr, path in zip(arrays, paths): + new_arr, path = im2im_func((arr, path)) + result["arrays"].append(new_arr) + result["paths"].append(path) + result["names"].append(Path(path).name) + + return result diff --git a/src/fluidimage/meson.build b/src/fluidimage/meson.build index 2767d738..7838c847 100644 --- a/src/fluidimage/meson.build +++ b/src/fluidimage/meson.build @@ -4,8 +4,11 @@ python_sources = [ '_opencv.py', '_version.py', 'config.py', + 'image2image.py', + 'piv.py', 'run_from_xml.py', 'synthetic.py', + 'test_image2image.py', 'test_run_from_xml.py', ] diff --git a/src/fluidimage/piv.py b/src/fluidimage/piv.py new file mode 100644 index 00000000..af7050ec --- /dev/null +++ b/src/fluidimage/piv.py @@ -0,0 +1,7 @@ +"""Helper module for PIV computations""" + +from .topologies.piv import TopologyPIV +from .works.piv import WorkPIV + +Work = WorkPIV +Topology = TopologyPIV diff --git a/src/fluidimage/preproc/__init__.py b/src/fluidimage/preproc/__init__.py index 022566d1..b3304eb3 100644 --- a/src/fluidimage/preproc/__init__.py +++ b/src/fluidimage/preproc/__init__.py @@ -10,12 +10,12 @@ .. autosummary:: :toctree: - base toolbox image2image """ -from .base import PreprocBase +from fluidimage.topologies.preproc import Topology, TopologyPreproc +from fluidimage.works.preproc import Work, WorkPreproc -__all__ = ["PreprocBase"] +__all__ = ["Work", "WorkPreproc", "TopologyPreproc", "Topology"] diff --git a/src/fluidimage/preproc/base.py b/src/fluidimage/preproc/base.py deleted file mode 100644 index ad80f635..00000000 --- a/src/fluidimage/preproc/base.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Preprocess base (:mod:`fluidimage.preproc.base`) -=================================================== - -To preprocess series of images. - -Provides: - -.. autoclass:: PreprocBase - :members: - :private-members: - -""" - -import os -import sys - -from fluidimage.data_objects.display_pre import DisplayPreProc - -from .. import ParamContainer, SerieOfArraysFromFiles - - -class PreprocBase: - """Preprocess series of images with various tools.""" - - @classmethod - def create_default_params(cls, backend="python"): - """Class method returning the default parameters. - - Parameters - ---------- - - backend: {'python', 'opencv'} - - Specifies which backend to use. - - """ - params = ParamContainer(tag="params") - params._set_child("preproc") - params.preproc._set_child("series", attribs={"path": ""}) - params.preproc.series._set_doc( - """ -Parameters indicating the input series of images. - -path : str, {''} - - String indicating the input images (can be a full path towards an image - file or a string given to `glob`). -""" - ) - if backend == "python": - from .toolbox import PreprocToolsPy - - cls._Tools = PreprocToolsPy - elif backend == "opencv": - from .toolbox import PreprocToolsCV - - cls._Tools = PreprocToolsCV - else: - raise ImportError("Unknown backend: %s" % backend) - - cls._Tools.create_default_params(params) - return params - - def __init__(self, params=None): - """Set path for results and loads images as SerieOfArraysFromFiles.""" - if params is None: - params = self.__class__.create_default_params() - - self.params = params.preproc - - path = params.preproc.series.path - if not os.path.exists(path): - path = params.preproc.series.path = os.path.expandvars(path) - - self.serie_arrays = SerieOfArraysFromFiles(path) - self.tools = self._Tools(params) - self.results = {} - - def __call__(self): - """Apply all enabled preprocessing tools on the series of arrays - and saves them in self.results. - - """ - name_files = self.serie_arrays.get_name_files() - for i, img in enumerate(self.serie_arrays.iter_arrays()): - name = name_files[i] - self.results[name] = self.tools(img) - - def display(self, ind=None, hist=False, results=None): - nimages = 2 - if not ind: - name_files = self.serie_arrays.get_name_files()[:nimages] - else: - name_files = self.serie_arrays.get_name_files()[ind : ind + nimages] - - before = {} - for fname in name_files: - before[fname] = self.serie_arrays.get_array_from_name(fname) - - if results is None: - results = self.results - - return DisplayPreProc( - before[name_files[0]], - before[name_files[1]], - results[name_files[0]], - results[name_files[1]], - hist=hist, - ) - - -def _make_doc_with_filtered_params_doc(cls): - params = cls.create_default_params() - strings = ("Parameters", "References", "----------") - return "\n".join( - line - for line in params._get_formatted_docs().split("\n") - if not any(line.endswith(string) for string in strings) - ) - - -if "sphinx" in sys.modules: - __doc__ += _make_doc_with_filtered_params_doc(PreprocBase) diff --git a/src/fluidimage/preproc/meson.build b/src/fluidimage/preproc/meson.build index 6f97f1f1..557923ca 100644 --- a/src/fluidimage/preproc/meson.build +++ b/src/fluidimage/preproc/meson.build @@ -3,11 +3,7 @@ python_sources = [ '__init__.py', '_toolbox_cv.py', '_toolbox_py.py', - 'base.py', - 'image2image.py', 'io.py', - 'test_base.py', - 'test_image2image.py', 'toolbox.py', ] diff --git a/src/fluidimage/preproc/toolbox.py b/src/fluidimage/preproc/toolbox.py index 328e1a1c..bb402e08 100644 --- a/src/fluidimage/preproc/toolbox.py +++ b/src/fluidimage/preproc/toolbox.py @@ -35,9 +35,9 @@ def create_default_params(cls, params): tools = cls._get_backend() available_tools = tools.__all__ - params.preproc._set_child("tools") - params = params.preproc.tools - params._set_attribs({"available_tools": tools.__all__, "sequence": None}) + params = params.preproc._set_child( + "tools", {"available_tools": available_tools, "sequence": None} + ) for tool in available_tools: func = tools.__dict__[tool] @@ -86,7 +86,7 @@ def _complete_class_with_tools(cls): def __init__(self, params): self.params = params.preproc.tools - def __call__(self, img): + def apply(self, img): """ Apply all preprocessing tools for which `enable` is `True`. Return the preprocessed image (numpy array). @@ -104,7 +104,7 @@ def __call__(self, img): for tool in sequence: tool_params = self.params[tool] if tool_params.enable: - logger.debug("Apply " + tool) + logger.debug("Apply %s", tool) kwargs = tool_params._make_dict_attribs() for k in list(kwargs.keys()): if k == "enable": diff --git a/src/fluidimage/run_from_xml.py b/src/fluidimage/run_from_xml.py index 5c6ebc74..6c55dc29 100644 --- a/src/fluidimage/run_from_xml.py +++ b/src/fluidimage/run_from_xml.py @@ -65,7 +65,7 @@ def tidy_uvmat_instructions(params): ir._set_attrib("last_i", None) if not hasattr(ir, "first_j"): - strcouple = f"i:i+{ir.incr_i + 1}:{ir.incr_i}" + str_subset = f"i:i+{ir.incr_i + 1}:{ir.incr_i}" ind_start = ir.first_i if ir.last_i is None: @@ -75,7 +75,7 @@ def tidy_uvmat_instructions(params): else: raise NotImplementedError - params._set_attrib("strcouple", strcouple) + params._set_attrib("str_subset", str_subset) params._set_attrib("ind_stop", ind_stop) params._set_attrib("ind_start", ind_start) @@ -136,7 +136,7 @@ def params_piv_from_uvmat_xml(instructions, args): params.series.path = instructions.path_file_input - params.series.strcouple = instructions.strcouple + params.series.str_subset = instructions.str_subset params.series.ind_stop = instructions.ind_stop params.series.ind_start = instructions.ind_start diff --git a/src/fluidimage/preproc/test_image2image.py b/src/fluidimage/test_image2image.py similarity index 100% rename from src/fluidimage/preproc/test_image2image.py rename to src/fluidimage/test_image2image.py diff --git a/src/fluidimage/topologies/base.py b/src/fluidimage/topologies/base.py index 3a65b3fa..1189f582 100644 --- a/src/fluidimage/topologies/base.py +++ b/src/fluidimage/topologies/base.py @@ -20,7 +20,7 @@ from fluidimage.util import cstring, logger -from ..executors import ExecutorBase, import_executor_class, get_executor_names +from ..executors import ExecutorBase, get_executor_names, import_executor_class class Work: @@ -82,17 +82,25 @@ def __repr__(self): return f'\nqueue "{self.name}": ' + super().__repr__() def __copy__(self): - newone = type(self)(self.name, kind=self.kind) - newone.__dict__.update(self.__dict__) + new_one = type(self)(self.name, kind=self.kind) + new_one.__dict__.update(self.__dict__) for key, values in self.items(): - newone[key] = values + new_one[key] = values - return newone + return new_one def pop_first_item(self): + """Pop the first item of the queue""" return self.popitem(last=False) + def is_name_in_values(self, image_name): + """Check if a name is in the queue""" + for names in self.values(): + if image_name in names: + return True + return False + class TopologyBase: """Base class for topologies of processing. @@ -110,6 +118,29 @@ class TopologyBase: """ + @classmethod + def _add_default_params_saving(cls, params): + + params._set_child( + "saving", + attribs={"path": None, "how": "ask", "postfix": "piv"}, + doc="""Saving of the results. + +path : None or str + + Path of the directory where the data will be saved. If None, the path is + obtained from the input path and the parameter `postfix`. + +how : str {'ask'} + + 'ask', 'new_dir', 'complete' or 'recompute'. + +postfix : str + + Postfix from which the output file is computed. +""", + ) + def __init__( self, path_dir_result=None, logging_level="info", nb_max_workers=None ): @@ -221,9 +252,7 @@ def make_text_at_exit(self, time_since_start): except AttributeError: nb_results = None if nb_results is not None and nb_results > 0: - txt += " ({} results, {:.2f} s/result).".format( - nb_results, time_since_start / nb_results - ) + txt += f" ({nb_results} results, {time_since_start / nb_results:.2f} s/result)." else: txt += "." @@ -336,7 +365,7 @@ def make_code_graphviz(self, name_file="tmp.dot"): code += "}\n" - with open(name_file + ".dot", "w") as file: + with open(name_file + ".dot", "w", encoding="utf-8") as file: file.write(code) print( diff --git a/src/fluidimage/topologies/bos.py b/src/fluidimage/topologies/bos.py index 5c5bda46..c08b1e1b 100644 --- a/src/fluidimage/topologies/bos.py +++ b/src/fluidimage/topologies/bos.py @@ -69,7 +69,7 @@ def create_default_params(cls): """ params = ParamContainer(tag="params") - params._set_child("images", attribs={"path": "", "str_slice": None}) + params._set_child("images", attribs={"path": "", "str_subset": None}) params.images._set_doc( """ @@ -80,7 +80,7 @@ def create_default_params(cls): String indicating the input images (can be a full path towards an image file or a string given to `glob`). -str_slice : None +str_subset : None String indicating as a Python slicing how to select images from the serie of images on the disk. If None, no selection so all images are going to be @@ -102,27 +102,8 @@ def create_default_params(cls): """ ) - params._set_child( - "saving", attribs={"path": None, "how": "ask", "postfix": "bos"} - ) - - params.saving._set_doc( - """Saving of the results. - -path : None or str - - Path of the directory where the data will be saved. If None, the path is - obtained from the input path and the parameter `postfix`. - -how : str {'ask'} - - 'ask', 'new_dir', 'complete' or 'recompute'. - -postfix : str - - Postfix from which the output file is computed. -""" - ) + TopologyBase._add_default_params_saving(params) + params.saving.postfix = "bos" WorkPIV._complete_params_with_default(params) @@ -146,7 +127,7 @@ def __init__(self, params, logging_level="info", nb_max_workers=None): self.params = params self.serie = SerieOfArraysFromFiles( - params.images.path, params.images.str_slice + params.images.path, params.images.str_subset ) path_dir = Path(self.serie.path_dir) @@ -289,10 +270,10 @@ def fill_queue_paths(self, input_queue, output_queue): pass nb_names = len(names) - logger.info(f"Add {nb_names} images to compute.") - logger.info(f"First files to process: {names[:4]}") + logger.info("Add %s images to compute.", nb_names) + logger.info("First files to process: %s", names[:4]) - logger.debug(f"All files: {names}") + logger.debug("All files: %s", names) def make_text_at_exit(self, time_since_start): """Make a text printed at exit""" @@ -312,25 +293,5 @@ def make_text_at_exit(self, time_since_start): if "sphinx" in sys.modules: - params = TopologyBOS.create_default_params() - - __doc__ += params._get_formatted_docs() - -if __name__ == "__main__": - params = TopologyBOS.create_default_params() - params.series.path = "../../../image_samples/Karman/Images" - params.series.ind_start = 1 - params.series.ind_step = 2 - - params.piv0.shape_crop_im0 = 32 - params.multipass.number = 2 - params.multipass.use_tps = False - - params.mask.strcrop = ":, 50:500" - - # params.saving.how = 'complete' - params.saving.postfix = "bos_example2" - - topo = TopologyBOS(params, logging_level="info") - - topo.make_code_graphviz("tmp.dot") + _params = TopologyBOS.create_default_params() + __doc__ += _params._get_formatted_docs() diff --git a/src/fluidimage/topologies/image2image.py b/src/fluidimage/topologies/image2image.py index f7ad5bb5..49a7e0fc 100644 --- a/src/fluidimage/topologies/image2image.py +++ b/src/fluidimage/topologies/image2image.py @@ -14,7 +14,7 @@ from fluiddyn.io.image import imsave from fluidimage import ParamContainer, SerieOfArraysFromFiles -from fluidimage.preproc.image2image import ( +from fluidimage.image2image import ( complete_im2im_params_with_default, init_im2im_function, ) @@ -65,9 +65,10 @@ def create_default_params(cls): """ params = ParamContainer(tag="params") + complete_im2im_params_with_default(params) - params._set_child("images", attribs={"path": "", "str_slice": None}) + params._set_child("images", attribs={"path": "", "str_subset": None}) params.images._set_doc( """ @@ -78,7 +79,7 @@ def create_default_params(cls): String indicating the input images (can be a full path towards an image file or a string given to `glob`). -str_slice : None +str_subset : None String indicating as a Python slicing how to select images from the serie of images on the disk. If None, no selection so all images are going to be @@ -129,7 +130,7 @@ def __init__(self, params, logging_level="info", nb_max_workers=None): raise ValueError("params.im2im has to be set.") self.serie = SerieOfArraysFromFiles( - params.images.path, params.images.str_slice + params.images.path, params.images.str_subset ) path_dir = self.serie.path_dir @@ -228,13 +229,13 @@ def fill_queue_paths(self, input_queue, output_queue): nb_names = len(names) - logger.info(f"Add {nb_names} images to compute.") - logger.info(f"First files to process: {names[:4]}") + logger.info("Add %s images to compute.", nb_names) + logger.info("First files to process: %s", names[:4]) - logger.debug(f"All files: {names}") + logger.debug("All files: %s", names) if "sphinx" in sys.modules: - params = TopologyImage2Image.create_default_params() + _params = TopologyImage2Image.create_default_params() - __doc__ += params._get_formatted_docs() + __doc__ += _params._get_formatted_docs() diff --git a/src/fluidimage/topologies/optical_flow.py b/src/fluidimage/topologies/optical_flow.py index ad40a3d4..c8071a86 100644 --- a/src/fluidimage/topologies/optical_flow.py +++ b/src/fluidimage/topologies/optical_flow.py @@ -5,7 +5,6 @@ :members: :private-members: - """ import sys @@ -42,27 +41,5 @@ def create_default_params(cls): Topology = TopologyOpticalFlow if "sphinx" in sys.modules: - params = Topology.create_default_params() - __doc__ += params._get_formatted_docs() - - -if __name__ == "__main__": - from fluidimage import get_path_image_samples - - params = Topology.create_default_params() - - params.series.path = str(get_path_image_samples() / "Karman/Images") - params.series.ind_start = 1 - params.series.ind_step = 2 - - params.mask.strcrop = ":, 50:500" - - # params.preproc.im2im = "numpy.ones_like" - - # params.saving.how = 'complete' - params.saving.postfix = "optical_flow_example" - - topo = Topology(params, logging_level="info") - - # topo.make_code_graphviz("tmp.dot") - topo.compute() + _params = Topology.create_default_params() + __doc__ += _params._get_formatted_docs() diff --git a/src/fluidimage/topologies/piv.py b/src/fluidimage/topologies/piv.py index a8f445f7..624c89b5 100644 --- a/src/fluidimage/topologies/piv.py +++ b/src/fluidimage/topologies/piv.py @@ -20,14 +20,6 @@ from . import image2image -def is_name_in_queue(image_name, queue): - """Check if a name is in a queue of series""" - for names in queue.values(): - if image_name in names: - return True - return False - - class TopologyPIV(TopologyBase): """Topology for PIV computation. @@ -72,95 +64,7 @@ def create_default_params(cls): """ params = ParamContainer(tag="params") - params._set_child( - "series", - attribs={ - "path": "", - "strcouple": "i:i+2", - "ind_start": 0, - "ind_stop": None, - "ind_step": 1, - }, - ) - - params.series._set_doc( - """ -Parameters indicating the input series of images. - -path : str, {''} - - String indicating the input images (can be a full path towards an image - file or a string given to `glob`). - -strcouple : 'i:i+2' - - String indicating as a Python slicing how couples of images are formed. - There is one couple per value of `i`. The values of `i` are set with the - other parameters `ind_start`, `ind_step` and `ind_stop` approximately with - the function range (`range(ind_start, ind_stop, ind_step)`). - - Python slicing is a very powerful notation to define subset from a - (possibly multidimensional) set of images. For a user, an alternative is to - understand how Python slicing works. See for example this page: - http://stackoverflow.com/questions/509211/explain-pythons-slice-notation. - - Another possibility is to follow simple examples: - - For single-frame images (im0, im1, im2, im3, ...), we keep the default - value 'i:i+2' to form the couples (im0, im1), (im1, im2), ... - - To see what it gives, one can use IPython and range: - - >>> i = 0 - >>> list(range(10))[i:i+2] - [0, 1] - - >>> list(range(10))[i:i+4:2] - [0, 2] - - We see that we can also use the value 'i:i+4:2' to form the couples (im0, - im2), (im1, im3), ... - - For double-frame images (im1a, im1b, im2a, im2b, ...) you can write - - >>> params.series.strcouple = 'i, 0:2' - - In this case, the first couple will be (im1a, im1b). - - To get the first couple (im1a, im1a), we would have to write - - >>> params.series.strcouple = 'i:i+2, 0' - -ind_start : int, {0} - -ind_step : int, {1} - -int_stop : None - -""" - ) - - params._set_child( - "saving", attribs={"path": None, "how": "ask", "postfix": "piv"} - ) - - params.saving._set_doc( - """Saving of the results. - -path : None or str - - Path of the directory where the data will be saved. If None, the path is - obtained from the input path and the parameter `postfix`. - -how : str {'ask'} - - 'ask', 'new_dir', 'complete' or 'recompute'. - -postfix : str - - Postfix from which the output file is computed. -""" - ) + TopologyBase._add_default_params_saving(params) cls.WorkVelocimetry._complete_params_with_default(params) @@ -185,7 +89,7 @@ def __init__(self, params, logging_level="info", nb_max_workers=None): self.series = SeriesOfArrays( params.series.path, - params.series.strcouple, + params.series.str_subset, ind_start=params.series.ind_start, ind_stop=params.series.ind_stop, ind_step=params.series.ind_step, @@ -298,14 +202,12 @@ def fill_couples_of_names_and_paths(self, input_queue, output_queues): logger.debug(repr([serie.get_name_arrays() for serie in series])) nb_series = len(series) - logger.info(f"Add {nb_series} PIV fields to compute.") + logger.info("Add %s PIV fields to compute.", nb_series) for iserie, serie in enumerate(series): if iserie > 1: break - logger.info( - "Files of serie {}: {}".format(iserie, serie.get_name_arrays()) - ) + logger.info("Files of serie %s: %s", iserie, serie.get_name_arrays()) for ind_serie, serie in series.items(): queue_couples_of_names[ind_serie] = serie.get_name_arrays() @@ -344,9 +246,9 @@ def make_couples(self, input_queues, output_queue): output_queue[key] = array_couple del queue_couples_of_names[key] # remove the image_array if it not will be used anymore - if not is_name_in_queue(couple[0], queue_couples_of_names): + if not queue_couples_of_names.is_name_in_values(couple[0]): del queue_arrays[couple[0]] - if not is_name_in_queue(couple[1], queue_couples_of_names): + if not queue_couples_of_names.is_name_in_values(couple[1]): del queue_arrays[couple[1]] def make_text_at_exit(self, time_since_start): @@ -370,30 +272,5 @@ def make_text_at_exit(self, time_since_start): Topology = TopologyPIV if "sphinx" in sys.modules: - params = Topology.create_default_params() - __doc__ += params._get_formatted_docs() - - -if __name__ == "__main__": - from fluidimage import get_path_image_samples - - params = Topology.create_default_params() - - params.series.path = str(get_path_image_samples() / "Karman/Images") - params.series.ind_start = 1 - params.series.ind_step = 2 - - params.piv0.shape_crop_im0 = 32 - params.multipass.number = 2 - params.multipass.use_tps = False - - params.mask.strcrop = ":, 50:500" - - params.preproc.im2im = "numpy.ones_like" - - # params.saving.how = 'complete' - params.saving.postfix = "piv_example" - - topo = Topology(params, logging_level="info") - - topo.make_code_graphviz("tmp.dot") + _params = Topology.create_default_params() + __doc__ += _params._get_formatted_docs() diff --git a/src/fluidimage/topologies/preproc.py b/src/fluidimage/topologies/preproc.py index d3fba92a..337bbee9 100644 --- a/src/fluidimage/topologies/preproc.py +++ b/src/fluidimage/topologies/preproc.py @@ -18,6 +18,7 @@ from fluidimage import SeriesOfArrays from fluidimage.data_objects.preproc import ArraySerie as ArraySubset from fluidimage.data_objects.preproc import get_name_preproc +from fluidimage.image2image import complete_im2im_params_with_default from fluidimage.topologies import TopologyBase, prepare_path_dir_result from fluidimage.util import DEBUG, imread, logger from fluidimage.works.preproc import ( @@ -26,7 +27,6 @@ ) from . import image2image -from .piv import is_name_in_queue class TopologyPreproc(TopologyBase): @@ -79,7 +79,7 @@ def create_default_params(cls, backend="python"): params = WorkPreproc.create_default_params(backend) params.preproc.series._set_attribs( { - "strcouple": "i:i+1", + "str_subset": "i:i+1", "ind_start": 0, "ind_stop": None, "ind_step": 1, @@ -90,24 +90,20 @@ def create_default_params(cls, backend="python"): """ Parameters describing image loading prior to preprocessing. -strcouple : str +str_subset : str Determines the subset from the whole series of images that should be loaded and preprocessed together. Particularly useful when temporal filtering requires multiple images. For example, for a series of images with just one index, - >>> strcouple = 'i:i+1' # load one image at a time - >>> strcouple = 'i-2:i+3' # loads 5 images at a time + >>> str_subset = 'i:i+1' # load one image at a time + >>> str_subset = 'i-2:i+3' # loads 5 images at a time Similarly for two indices, - >>> strcouple = 'i:i+1,0' # load one image at a time, with second index fixed - >>> strcouple = 'i-2:i+3,0' # loads 5 images at a time, with second index fixed - - .. todo:: - - Rename this parameter to strsubset / strslice + >>> str_subset = 'i:i+1,0' # load one image at a time, with second index fixed + >>> str_subset = 'i-2:i+3,0' # loads 5 images at a time, with second index fixed ind_start : int Start index for the whole series of images being loaded. @@ -124,14 +120,12 @@ def create_default_params(cls, backend="python"): """ ) - params.preproc._set_child( - "saving", - attribs={ - "path": None, - "strcouple": None, - "how": "ask", + TopologyBase._add_default_params_saving(params.preproc) + + params.preproc.saving._set_attribs( + { "format": "img", - "postfix": "pre", + "str_subset": None, }, ) @@ -142,6 +136,15 @@ def create_default_params(cls, backend="python"): path : str or None Path to which preprocessed images are saved. +how : str {'ask', 'new_dir', 'complete', 'recompute'} + How preprocessed images must be saved if it already exists or not. + +postfix : str + A suffix added to the new directory where preprocessed images are saved. + +format : str {'img', 'hdf5'} + Format in which preprocessed image data must be saved. + str_subset : str or None NotImplemented! Determines the sub-subset of images must be saved from subset of images that were loaded and preprocessed. When set as None, saves the @@ -150,16 +153,6 @@ def create_default_params(cls, backend="python"): .. todo:: Implement the option params.saving.str_subset... - -how : str {'ask', 'new_dir', 'complete', 'recompute'} - How preprocessed images must be saved if it already exists or not. - -format : str {'img', 'hdf5'} - Format in which preprocessed image data must be saved. - -postfix : str - A suffix added to the new directory where preprocessed images are saved. - """ ) @@ -175,7 +168,8 @@ def create_default_params(cls, backend="python"): ) params._set_child("im2im") - image2image.complete_im2im_params_with_default(params.im2im) + + complete_im2im_params_with_default(params.im2im) return params @@ -188,10 +182,9 @@ def __init__( self.results = [] self.display = self.preproc_work.display - serie_arrays = self.preproc_work.serie_arrays self.series = SeriesOfArrays( - serie_arrays, - params.preproc.series.strcouple, + params.preproc.series.path, + params.preproc.series.str_subset, ind_start=params.preproc.series.ind_start, ind_stop=params.preproc.series.ind_stop, ind_step=params.preproc.series.ind_step, @@ -323,7 +316,7 @@ def init_series(self) -> List[str]: else: plurial = "s" - logger.info(f"Add {nb_subsets} image serie{plurial} to compute.") + logger.info("Add %s image serie%s to compute.", nb_subsets, plurial) def fill_subsets_of_names_and_paths( self, input_queue: None, output_queues: Tuple[Dict] @@ -359,7 +352,7 @@ def make_subsets(self, input_queues: Tuple[Dict], output_queue: Dict) -> bool: key_arrays = list(queue_arrays.keys()) for key_array in key_arrays: - if not is_name_in_queue(key_array, queue_subsets_of_names): + if not queue_subsets_of_names.is_name_in_values(key_array): del queue_arrays[key_array] diff --git a/src/fluidimage/topologies/surface_tracking.py b/src/fluidimage/topologies/surface_tracking.py index 5d939d1f..b7f9d7e5 100644 --- a/src/fluidimage/topologies/surface_tracking.py +++ b/src/fluidimage/topologies/surface_tracking.py @@ -64,8 +64,8 @@ def create_default_params(cls): attribs={ "path": "", "path_ref": "", - "str_slice_ref": None, - "str_slice": None, + "str_subset_ref": None, + "str_subset": None, }, ) @@ -83,14 +83,14 @@ def create_default_params(cls): String indicating the reference input images (can be a full path towards an image file or a string given to `glob`). -str_slice_ref : None +str_subset_ref : None String indicating as a Python slicing how to select reference images from the serie of reference images on the disk (in order to compute k_x value necessary for gain and filter). If None, no selection so all images are going to be processed. -str_slice : None +str_subset : None String indicating as a Python slicing how to select images from the serie of images on the disk. If None, no selection so all images @@ -99,27 +99,7 @@ def create_default_params(cls): """ ) - params._set_child( - "saving", attribs={"path": None, "how": "ask", "postfix": "pre"} - ) - - params.saving._set_doc( - """Saving of the results. - -path : None or str - - Path of the directory where the data will be saved. If None, the path is - obtained from the input path and the parameter `postfix`. - -how : str {'ask'} - - 'ask', 'new_dir', 'complete' or 'recompute'. - -postfix : str - - Postfix from which the output file is computed. -""" - ) + TopologyBase._add_default_params_saving(params) params._set_internal_attr( "_value_text", @@ -144,7 +124,7 @@ def __init__(self, params, logging_level="info", nb_max_workers=None): raise ValueError("params.surface_tracking has to be set.") self.serie = SerieOfArraysFromFiles( - params.images.path, params.images.str_slice + params.images.path, params.images.str_subset ) self.series = SeriesOfArrays( params.images.path, diff --git a/src/fluidimage/topologies/test_bos.py b/src/fluidimage/topologies/test_bos.py index 2ad0dcf9..4f42c8e7 100644 --- a/src/fluidimage/topologies/test_bos.py +++ b/src/fluidimage/topologies/test_bos.py @@ -27,7 +27,7 @@ def test_bos_new_multiproc(self): params = TopologyBOS.create_default_params() params.images.path = str(self.path_input_files) - params.images.str_slice = "1:3" + params.images.str_subset = "1:3" params.piv0.shape_crop_im0 = 32 params.multipass.number = 2 diff --git a/src/fluidimage/topologies/test_image2image.py b/src/fluidimage/topologies/test_image2image.py index 64d02617..c5cc4cd2 100644 --- a/src/fluidimage/topologies/test_image2image.py +++ b/src/fluidimage/topologies/test_image2image.py @@ -27,7 +27,7 @@ def test_im2im(self): params.images.path = str(self.path_src) - params.im2im = "fluidimage.preproc.image2image.Im2ImExample" + params.im2im = "fluidimage.image2image.Im2ImExample" params.args_init = ((1024, 2048), "clip") params.saving.how = "recompute" diff --git a/src/fluidimage/topologies/test_piv.py b/src/fluidimage/topologies/test_piv.py index a383e615..a7b204c3 100644 --- a/src/fluidimage/topologies/test_piv.py +++ b/src/fluidimage/topologies/test_piv.py @@ -55,7 +55,7 @@ def test_piv_new_multiproc(self): params.series.path = str(self.path_Jet) params.series.ind_start = 60 params.series.ind_step = 1 - params.series.strcouple = "i, 0:2" + params.series.str_subset = "i, 0:2" params.piv0.shape_crop_im0 = 128 params.multipass.number = 2 diff --git a/src/fluidimage/topologies/test_preproc.py b/src/fluidimage/topologies/test_preproc.py index 98949761..28e69e72 100644 --- a/src/fluidimage/topologies/test_preproc.py +++ b/src/fluidimage/topologies/test_preproc.py @@ -35,7 +35,7 @@ def test_preproc(self): params = TopologyPreproc.create_default_params() params.preproc.series.path = self._work_dir - params.preproc.series.strcouple = "i:i+2,1" + params.preproc.series.str_subset = "i:i+2,1" params.preproc.series.ind_start = 60 for tool in params.preproc.tools.available_tools: diff --git a/src/fluidimage/topologies/test_surftracking.py b/src/fluidimage/topologies/test_surftracking.py index a3b699b9..4b1d3c32 100644 --- a/src/fluidimage/topologies/test_surftracking.py +++ b/src/fluidimage/topologies/test_surftracking.py @@ -46,8 +46,8 @@ def test_surftrack(self): params.images.path = str(self.path_src) params.images.path_ref = str(self.path_src) - params.images.str_slice = ":4:2" - params.images.str_slice_ref = ":3" + params.images.str_subset = ":4:2" + params.images.str_subset_ref = ":3" params.surface_tracking.xmin = 200 params.surface_tracking.xmax = 250 diff --git a/src/fluidimage/works/__init__.py b/src/fluidimage/works/__init__.py index 23f6b8d6..ae2c4b3d 100644 --- a/src/fluidimage/works/__init__.py +++ b/src/fluidimage/works/__init__.py @@ -19,6 +19,10 @@ """ +from copy import deepcopy + +from fluiddyn.util.serieofarrays import SeriesOfArrays + from .. import imread @@ -27,6 +31,91 @@ def __init__(self, params=None): self.params = params +class BaseWorkFromSerie(BaseWork): + """Base class for work taking as argument a SerieOfArraysFromFiles""" + + _series: SeriesOfArrays + + @classmethod + def _complete_params_with_default(cls, params): + + params._set_child( + "series", + attribs={ + "path": "", + "str_subset": "i:i+2", + "ind_start": 0, + "ind_stop": None, + "ind_step": 1, + }, + doc=""" +Parameters indicating the input series of images. + +path : str, {''} + + String indicating the input images (can be a full path towards an image + file or a string given to `glob`). + +str_subset : 'i:i+2' + + String indicating as a Python slicing how couples of images are formed. + There is one couple per value of `i`. The values of `i` are set with the + other parameters `ind_start`, `ind_step` and `ind_stop` approximately with + the function range (`range(ind_start, ind_stop, ind_step)`). + + Python slicing is a very powerful notation to define subset from a + (possibly multidimensional) set of images. For a user, an alternative is to + understand how Python slicing works. See for example this page: + http://stackoverflow.com/questions/509211/explain-pythons-slice-notation. + + Another possibility is to follow simple examples: + + For single-frame images (im0, im1, im2, im3, ...), we keep the default + value 'i:i+2' to form the couples (im0, im1), (im1, im2), ... + + To see what it gives, one can use IPython and range: + + >>> i = 0 + >>> list(range(10))[i:i+2] + [0, 1] + + >>> list(range(10))[i:i+4:2] + [0, 2] + + We see that we can also use the value 'i:i+4:2' to form the couples (im0, + im2), (im1, im3), ... + + For double-frame images (im1a, im1b, im2a, im2b, ...) you can write + + >>> params.series.str_subset = 'i, 0:2' + + In this case, the first couple will be (im1a, im1b). + + To get the first couple (im1a, im1a), we would have to write + + >>> params.series.str_subset = 'i:i+2, 0' + +ind_start : int, {0} + +ind_step : int, {1} + +int_stop : None + +""", + ) + + def get_serie(self, index_serie: int = 0): + """Get a serie as defined by params.series""" + if not hasattr(self, "_series"): + p_series = self.params.series + self._series = SeriesOfArrays(p_series.path, p_series.str_subset) + return deepcopy(self._series.get_serie_from_index(index_serie)) + + def process_1_serie(self, index_serie: int = 0): + """Process one serie and return the result""" + return self.calcul(self.get_serie(index_serie)) + + def load_image(path): im = imread(path) return im diff --git a/src/fluidimage/works/meson.build b/src/fluidimage/works/meson.build index 1bda9392..fcc3a081 100644 --- a/src/fluidimage/works/meson.build +++ b/src/fluidimage/works/meson.build @@ -5,6 +5,7 @@ python_sources = [ 'preproc.py', 'surface_tracking.py', 'with_mask.py', + 'test_preproc.py', ] py.install_sources( diff --git a/src/fluidimage/works/optical_flow.py b/src/fluidimage/works/optical_flow.py index 4be31171..f8907127 100644 --- a/src/fluidimage/works/optical_flow.py +++ b/src/fluidimage/works/optical_flow.py @@ -13,6 +13,7 @@ from fluidimage._opencv import cv2 from fluidimage.data_objects.piv import ArrayCouple, HeavyPIVResults +from . import BaseWorkFromSerie from .with_mask import BaseWorkWithMask @@ -62,7 +63,7 @@ def optical_flow( return positions, displacements -class WorkOpticalFlow(BaseWorkWithMask): +class WorkOpticalFlow(BaseWorkWithMask, BaseWorkFromSerie): @classmethod def create_default_params(cls): "Create an object containing the default parameters (class method)." @@ -72,6 +73,8 @@ def create_default_params(cls): @classmethod def _complete_params_with_default(cls, params): + BaseWorkFromSerie._complete_params_with_default(params) + params._set_child( "optical_flow", attribs=dict( @@ -177,7 +180,7 @@ def _complete_params_with_default(cls, params): ) def __init__(self, params): - self.params = params + super().__init__(params) self.dict_params_features = dict_from_params(self.params.features) self.dict_params_flow = dict_from_params(self.params.optical_flow) diff --git a/src/fluidimage/works/piv/_try_piv.py b/src/fluidimage/works/piv/_try_piv.py deleted file mode 100644 index a97f5d15..00000000 --- a/src/fluidimage/works/piv/_try_piv.py +++ /dev/null @@ -1,44 +0,0 @@ -# import h5py - -from fluidimage import SeriesOfArrays -from fluidimage.data_objects.piv import LightPIVResults -from fluidimage.works.piv import WorkPIV - -params = WorkPIV.create_default_params() - -# # for a very short computation -# params.piv0.shape_crop_im0 = 128 -# params.piv0.grid.overlap = 0. - -# params.piv0.method_subpix = 'centroid' -# params.piv0.method_correl = 'pythran' - -params.multipass.number = 1 -params.multipass.use_tps = False -# params.multipass.coeff_zoom = [2, 2] - -# bug params.piv0.shape_crop_im0 = 128 # !! -params.piv0.shape_crop_im0 = 64 # (80, 90) -# params.piv0.shape_crop_im1 = (38, 36) -params.fix.correl_min = 0.2 -params.fix.threshold_diff_neighbour = 4 -# params.piv0.grid.overlap = 10 - -piv = WorkPIV(params=params) - -series = SeriesOfArrays("../../../image_samples/Oseen/Images", "i+1:i+3") -serie = series.get_serie_from_index(0) - -result = piv.calcul(serie) - -result.display() - -result.save() - -# lightresult = result.make_light_result() -# lightresult.save() - -# lightresultload = LightPIVResults(str_path='piv_Oseen_center01-02_light.h5') - -# f=h5netcdf.File('piv_Oseen_center01-02.h5') -# f=h5py.File('piv_Oseen_center01-02.h5') diff --git a/src/fluidimage/works/piv/meson.build b/src/fluidimage/works/piv/meson.build index 5e3172c0..2ab78d6d 100644 --- a/src/fluidimage/works/piv/meson.build +++ b/src/fluidimage/works/piv/meson.build @@ -1,7 +1,6 @@ python_sources = [ '__init__.py', - '_try_piv.py', 'fix.py', 'multipass.py', 'singlepass.py', diff --git a/src/fluidimage/works/piv/multipass.py b/src/fluidimage/works/piv/multipass.py index f4829080..55d10284 100644 --- a/src/fluidimage/works/piv/multipass.py +++ b/src/fluidimage/works/piv/multipass.py @@ -12,12 +12,12 @@ from fluiddyn.util.paramcontainer import ParamContainer from ...data_objects.piv import MultipassPIVResults -from .. import BaseWork +from .. import BaseWorkFromSerie from .fix import WorkFIX from .singlepass import FirstWorkPIV, InterpError, WorkPIVFromDisplacement -class WorkPIV(BaseWork): +class WorkPIV(BaseWorkFromSerie): """Main work for PIV with multipass. Parameters @@ -58,6 +58,7 @@ def _complete_params_with_default(cls, params): """Complete the default parameters (class method).""" FirstWorkPIV._complete_params_with_default(params) WorkFIX._complete_params_with_default(params) + BaseWorkFromSerie._complete_params_with_default(params) params._set_child( "multipass", @@ -86,7 +87,7 @@ def _complete_params_with_default(cls, params): use_tps : bool or 'last' If it is True, the interpolation is done using the Thin Plate Spline method - (computationnally heavy but sometimes interesting). If it is 'last', the + (computationally heavy but sometimes interesting). If it is 'last', the TPS method is used only for the last pass. subdom_size : int @@ -108,7 +109,7 @@ def _complete_params_with_default(cls, params): ) def __init__(self, params=None): - self.params = params + super().__init__(params) self.works_piv = [] self.works_fix = [] @@ -205,5 +206,5 @@ def _prepare_with_image(self, im=None, imshape=None): work_piv._prepare_with_image(imshape=imshape) -params = WorkPIV.create_default_params() -__doc__ += params._get_formatted_docs() +_params = WorkPIV.create_default_params() +__doc__ += _params._get_formatted_docs() diff --git a/src/fluidimage/works/piv/singlepass.py b/src/fluidimage/works/piv/singlepass.py index 6d440c7d..88f0706a 100644 --- a/src/fluidimage/works/piv/singlepass.py +++ b/src/fluidimage/works/piv/singlepass.py @@ -152,9 +152,13 @@ def calcul(self, couple): """Calcul the PIV (one pass) from a couple of images.""" if isinstance(couple, SerieOfArraysFromFiles): couple = ArrayCouple(serie=couple) + elif isinstance(couple, dict): + couple = ArrayCouple(**couple) if not isinstance(couple, ArrayCouple): - raise ValueError + raise ValueError( + f"not isinstance(couple, ArrayCouple), {type(couple) = }" + ) couple.apply_mask(self.params.mask) diff --git a/src/fluidimage/works/piv/test_piv.py b/src/fluidimage/works/piv/test_piv.py index f7e97732..39e8f1dd 100644 --- a/src/fluidimage/works/piv/test_piv.py +++ b/src/fluidimage/works/piv/test_piv.py @@ -1,9 +1,7 @@ import unittest from shutil import rmtree -from fluiddyn.io import stdout_redirected - -from fluidimage import SeriesOfArrays, get_path_image_samples +from fluidimage import get_path_image_samples from fluidimage.data_objects.display_piv import DisplayPIV from fluidimage.data_objects.piv import LightPIVResults, MultipassPIVResults from fluidimage.works.piv import WorkPIV @@ -16,15 +14,12 @@ class MyObj: class TestPIV(unittest.TestCase): @classmethod def setUpClass(cls): - path_images = get_path_image_samples() / "Oseen/Images" - series = SeriesOfArrays(str(path_images / "Oseen*"), "i+1:i+3") - cls.path_tmp = path_images.parent / "tmp_test_work_piv" + cls.path_images = get_path_image_samples() / "Oseen/Images" + cls.path_tmp = cls.path_images.parent / "tmp_test_work_piv" if not cls.path_tmp.exists(): cls.path_tmp.mkdir() - cls.serie = series.get_serie_from_index(0) - @classmethod def tearDownClass(cls): if cls.path_tmp.exists(): @@ -45,10 +40,12 @@ def test_minimal_piv(self): params.fix.displacement_max = 2 params.fix.threshold_diff_neighbour = 2 + params.series.path = str(self.path_images / "Oseen*") + params.series.str_subset = "i+1:i+3" + piv = WorkPIV(params=params) - with stdout_redirected(): - result = piv.calcul(self.serie) + result = piv.process_1_serie() result.piv0.save(self.path_tmp) result.save(self.path_tmp) @@ -75,16 +72,17 @@ def test_piv_list(self): params.multipass.use_tps = False + params.series.path = str(self.path_images / "Oseen*") + params.series.str_subset = "i+1:i+3" + piv = WorkPIV(params=params) - with stdout_redirected(): - result = piv.calcul(self.serie) + result = piv.process_1_serie() piv0 = result.piv0 im0, im1 = piv0.get_images() - with stdout_redirected(): - DisplayPIV(im0, im1, piv0) - d = DisplayPIV(im0, im1, piv0, show_interp=True, hist=True) + DisplayPIV(im0, im1, piv0) + d = DisplayPIV(im0, im1, piv0, show_interp=True, hist=True) d.switch() d.select_arrow([0], artist=d.q) diff --git a/src/fluidimage/works/preproc.py b/src/fluidimage/works/preproc.py index 2078ff9d..9e608bbf 100644 --- a/src/fluidimage/works/preproc.py +++ b/src/fluidimage/works/preproc.py @@ -16,14 +16,72 @@ import numpy as np from fluiddyn.util.serieofarrays import SerieOfArraysFromFiles -from ..data_objects.preproc import ArraySerie, PreprocResults, get_ind_middle -from ..preproc.base import PreprocBase, _make_doc_with_filtered_params_doc -from ..util import print_memory_usage +from fluidimage import ParamContainer +from fluidimage.data_objects.display_pre import DisplayPreProc +from fluidimage.data_objects.preproc import ( + ArraySerie, + PreprocResults, + get_ind_middle, +) +from fluidimage.util import print_memory_usage +from . import BaseWorkFromSerie -class WorkPreproc(PreprocBase): + +def _make_doc_with_filtered_params_doc(cls): + params = cls.create_default_params() + strings = ("Parameters", "References", "----------") + return "\n".join( + line + for line in params._get_formatted_docs().split("\n") + if not any(line.endswith(string) for string in strings) + ) + + +class WorkPreproc(BaseWorkFromSerie): """Work for preprocessing.""" + """Preprocess series of images with various tools.""" + + @classmethod + def create_default_params(cls, backend="python"): + """Class method returning the default parameters. + + Parameters + ---------- + + backend: {'python', 'opencv'} + + Specifies which backend to use. + + """ + params = ParamContainer(tag="params") + params._set_child("preproc") + BaseWorkFromSerie._complete_params_with_default(params.preproc) + params.preproc.series.str_subset = "i+1:i+2" + + if backend == "python": + from fluidimage.preproc.toolbox import PreprocToolsPy + + cls._Tools = PreprocToolsPy + elif backend == "opencv": + from fluidimage.preproc.toolbox import PreprocToolsCV + + cls._Tools = PreprocToolsCV + else: + raise ImportError("Unknown backend: %s" % backend) + + cls._Tools.create_default_params(params) + return params + + def __init__(self, params=None): + """Set path for results and loads images as SerieOfArraysFromFiles.""" + if params is None: + params = type(self).create_default_params() + super().__init__(params) + self.params = params.preproc + self.tools = self._Tools(params) + def calcul(self, serie): """Apply all enabled preprocessing tools on the series of arrays and returns the result as a data object. @@ -37,10 +95,9 @@ def calcul(self, serie): result = PreprocResults(self.params) images = np.array(serie.get_arrays()) - images = self.tools(images) + images = self.tools.apply(images) serie._clear_data() - dico = self._make_dict_to_save(serie, images) - result.data.update(dico) + result.data.update(self._make_dict_to_save(serie, images)) print_memory_usage( "Memory usage after preprocessing {}/{} series".format( serie.ind_serie + 1, serie.nb_series @@ -66,15 +123,25 @@ def _make_dict_to_save(self, array_serie, images): return dict(zip(name_files[s], images[s])) def display(self, ind=0, hist=False): - nb_images = 2 - name_files = self.serie_arrays.get_name_files()[ind : ind + nb_images] - results_series = SerieOfArraysFromFiles(self.params.saving.path) - results = { - name: results_series.get_array_from_name(name) for name in name_files - } + serie0 = self.get_serie(ind) + serie1 = self.get_serie(ind + 1) - return super().display(ind, hist, results) + result0 = self.calcul(serie0) + result1 = self.calcul(serie1) + + key0 = serie0.get_name_arrays()[0] + key1 = serie1.get_name_arrays()[0] + + arr_input0 = serie0.get_array_from_name(key0) + arr_input1 = serie0.get_array_from_name(key1) + + arr_output0 = result0.data[key0] + arr_output1 = result1.data[key1] + + return DisplayPreProc( + arr_input0, arr_input1, arr_output0, arr_output1, hist=hist + ) Work = WorkPreproc diff --git a/src/fluidimage/works/surface_tracking.py b/src/fluidimage/works/surface_tracking.py index 6b80c292..d5d4e44e 100644 --- a/src/fluidimage/works/surface_tracking.py +++ b/src/fluidimage/works/surface_tracking.py @@ -211,7 +211,7 @@ def __init__(self, params): self.ky = np.arange(-self.l_y / 2, self.l_y / 2) / self.l_y self.refserie = SerieOfArraysFromFiles( - params.images.path_ref, params.images.str_slice_ref + params.images.path_ref, params.images.str_subset_ref ) k_x = self.compute_kx(self.refserie) logger.info("Value of kx computed = " + str(k_x)) diff --git a/src/fluidimage/preproc/test_base.py b/src/fluidimage/works/test_preproc.py similarity index 62% rename from src/fluidimage/preproc/test_base.py rename to src/fluidimage/works/test_preproc.py index 94b4f7d2..0aa6e9f3 100644 --- a/src/fluidimage/preproc/test_base.py +++ b/src/fluidimage/works/test_preproc.py @@ -1,13 +1,12 @@ import os import unittest -from glob import glob +from pathlib import Path from shutil import rmtree -from fluiddyn.io import stdout_redirected from fluiddyn.io.image import imread, imsave from fluidimage import get_path_image_samples -from fluidimage.preproc.base import PreprocBase +from fluidimage.works.preproc import Work class TestPreprocKarman(unittest.TestCase): @@ -15,21 +14,20 @@ class TestPreprocKarman(unittest.TestCase): @classmethod def setUpClass(cls): - path_in = str(get_path_image_samples() / cls.name / "Images") + path_in = get_path_image_samples() / cls.name / "Images" - cls._work_dir = os.path.join( - "test_fluidimage_topo_preproc_" + cls.name, "Images" + cls._work_dir = ( + Path("test_fluidimage_topo_preproc_" + cls.name) / "Images" ) - if not os.path.exists(cls._work_dir): - os.makedirs(cls._work_dir) - paths = glob(path_in + "/*") + if not cls._work_dir.exists(): + cls._work_dir.mkdir(parents=True) - for path in paths: - name = os.path.split(path)[-1] + for path in sorted(path_in.glob("*")): + name = path.name im = imread(path) im = im[::6, ::6] - imsave(os.path.join(cls._work_dir, name), im, as_int=True) + imsave(cls._work_dir / name, im, as_int=True) @classmethod def tearDownClass(cls): @@ -37,7 +35,7 @@ def tearDownClass(cls): def test_preproc(self): """Test preproc subpackage on image sample Karman with one index.""" - params = PreprocBase.create_default_params() + params = Work.create_default_params() params.preproc.series.path = self._work_dir @@ -46,8 +44,7 @@ def test_preproc(self): tool = params.preproc.tools.__getitem__(tool) tool.enable = True - preproc = PreprocBase(params) - preproc() + preproc = Work(params) preproc.display(1, hist=True) @@ -56,18 +53,19 @@ class TestPreprocTime(TestPreprocKarman): def test_preproc(self): """Test preproc subpackage on image sample Jet with two indices.""" - params = PreprocBase.create_default_params() + params = Work.create_default_params() params.preproc.series.path = self._work_dir + params.preproc.series.str_subset = "i,0" + params.preproc.series.ind_start = "60" for tool in params.preproc.tools.available_tools: if "sliding" in tool: tool = params.preproc.tools.__getitem__(tool) tool.enable = True - preproc = PreprocBase(params) - preproc() - preproc.display(1, hist=False) + preproc = Work(params) + preproc.display(60, hist=False) if __name__ == "__main__":