
https://svn.lrde.epita.fr/svn/oln/prototypes/proto-1.0 ChangeLog | 24 ++++ oln/core/abstract/image_with_extension.hh | 2 oln/core/gen/image_with_nbh.hh | 6 - oln/makefile.src | 2 oln/morpho/closing.hh | 166 ++++++++++++++++++++++++++++++ oln/morpho/geodesic_dilation.hh | 11 + oln/morpho/geodesic_erosion.hh | 13 +- oln/morpho/opening.hh | 166 ++++++++++++++++++++++++++++++ tests/core/tests/readwrite_image | 1 tests/core/tests/setget | 11 + tests/morpho/tests/closing | 27 ++++ tests/morpho/tests/dilation | 1 tests/morpho/tests/erosion | 1 tests/morpho/tests/geodesic_dilation | 76 +++++++------ tests/morpho/tests/geodesic_erosion | 75 ++++++------- tests/morpho/tests/opening | 27 ++++ 16 files changed, 519 insertions(+), 90 deletions(-) Index: olena/ChangeLog from Roland Levillain <roland@lrde.epita.fr> Add generic morphological opening and closing. Repair geodesic erosion and dilation. * oln/morpho/closing.hh, oln/morpho/opening.hh: New files. * oln/makefile.src (OLN_DEP): Add morpho/closing.hh and morpho/opening.hh. * tests/morpho/tests/closing, tests/morpho/tests/opening: New tests. * tests/core/tests/readwrite_image, tests/core/tests/setget: Update tests. * oln/morpho/geodesic_dilation.hh (impl_run): Fix access to window. * oln/morpho/geodesic_erosion.hh (impl_run): Likewise. Fix precondition. * oln/core/abstract/image_with_extension.hh (image_with_extension::image_with_extension): Remove const before reference of the first argument. * oln/core/gen/image_with_nbh.hh (image_with_nbh::image_with_nbh, join): Likewise. (nbh_): Make it non const. Index: olena/tests/morpho/tests/opening --- olena/tests/morpho/tests/opening (revision 0) +++ olena/tests/morpho/tests/opening (revision 0) @@ -0,0 +1,27 @@ + // -*- C++ -*- +#include "data.hh" +#include <oln/utils/md5.hh> + +#include <oln/io/read_image.hh> +#include <oln/basics2d.hh> +#include <oln/morpho/opening.hh> +#include <oln/level/compare.hh> +#include <ntg/all.hh> + +bool check() +{ + // MD5 sum of object.pbm's geodesic dilation result. + oln::utils::key::value_type data_key[16] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + oln::utils::key key(data_key); + + typedef oln::image2d<ntg::bin> im_type; + im_type ima; + ima = oln::io::read(rdata("16x16.pbm")); + + if (oln::utils::md5(oln::morpho::opening(ima, oln::win_c8p())) != key) + return true; + + return false; +} Index: olena/tests/morpho/tests/geodesic_erosion --- olena/tests/morpho/tests/geodesic_erosion (revision 157) +++ olena/tests/morpho/tests/geodesic_erosion (working copy) @@ -1,45 +1,46 @@ + // -*- C++ -*- #include "data.hh" -//#include <oln/utils/md5.hh> +#include <oln/utils/md5.hh> #include <iostream> -//#include <oln/io/read_image.hh> -//#include <oln/basics2d.hh> -//#include <oln/core/abstract/image_with_nbh.hh> -//#include <oln/morpho/opening.hh> -//#include <oln/morpho/geodesic_erosion.hh> -//#include <ntg/all.hh> +#include <oln/io/read_image.hh> +#include <oln/basics2d.hh> +#include <oln/core/gen/image_with_nbh.hh> +#include <oln/morpho/closing.hh> +#include <oln/morpho/geodesic_erosion.hh> +#include <ntg/all.hh> +using namespace oln; bool check() { - // FIXME : really test this algorithm - // FAKE test below - std::cout << "FIXME : md5 does not math with olena-0.10 reference" << std::endl; - return false; - // FIXME : end fake test + // MD5 sum of object.pbm's geodesic erosion result. + utils::key::value_type data_key[16] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + utils::key key(data_key); + + typedef image2d<ntg::bin> im_type; + + neighborhood2d nbh(neighb_c4()); + + im_type marker; + im_type mask; + + mask = io::read(rdata("object.pbm")); + marker = morpho::closing(mask, win_c4p()).exact(); + + // Classical procedure. + // FIXME: Re-enable when proc::geodesic_erosion is fixed. +#if 0 + if (utils::md5(morpho::proc::geodesic_erosion(join(marker, nbh), + mask).exact()) != key) + return true; +#endif + + // Facade to object-algorithm. + if (utils::md5(morpho::geodesic_erosion(join(marker, nbh), + mask).exact()) != key) + return true; -// oln::utils::key::value_type data_key[16] = -// {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // md5 of object.pbm's geodesic_erosion result -// oln::utils::key key(data_key); - -// typedef oln::image2d<ntg::bin> im_type; - -// oln::neighborhood2d nbh(oln::neighb_c4()); - -// im_type marker; -// im_type mask; - -// marker = oln::io::read(rdata("object.pbm")); -// mask = oln::morpho::opening(mask, oln::win_c4p()).exact(); - -// if (oln::utils::md5(oln::morpho::geodesic_erosion(join(marker, nbh), mask).exact()) == key) -// { -// std::cout << "OK" << std::endl; -// return false; -// } -// else -// { -// std::cout << "FAIL" << std::endl; -// return true; -// } + return false; } Index: olena/tests/morpho/tests/dilation --- olena/tests/morpho/tests/dilation (revision 157) +++ olena/tests/morpho/tests/dilation (working copy) @@ -1,3 +1,4 @@ + // -*- C++ -*- #include "data.hh" #include <oln/utils/md5.hh> Index: olena/tests/morpho/tests/geodesic_dilation --- olena/tests/morpho/tests/geodesic_dilation (revision 157) +++ olena/tests/morpho/tests/geodesic_dilation (working copy) @@ -1,44 +1,46 @@ + // -*- C++ -*- #include "data.hh" -//#include <oln/utils/md5.hh> +#include <oln/utils/md5.hh> #include <iostream> -//#include <oln/io/read_image.hh> -//#include <oln/basics2d.hh> -//#include <oln/core/abstract/image_with_nbh.hh> -//#include <oln/morpho/opening.hh> -//#include <oln/morpho/geodesic_erosion.hh> -//#include <ntg/all.hh> +#include <oln/io/read_image.hh> +#include <oln/basics2d.hh> +#include <oln/core/gen/image_with_nbh.hh> +#include <oln/morpho/opening.hh> +#include <oln/morpho/geodesic_dilation.hh> +#include <ntg/all.hh> + +using namespace oln; bool check() { - // FIXME : really test this algorithm - // FAKE test below - std::cout << "FIXME : md5 does not math with olena-0.10 reference" << std::endl; - return false; - // FIXME : end fake test + // MD5 sum of object.pbm's geodesic dilation result. + utils::key::value_type data_key[16] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + utils::key key(data_key); + + typedef image2d<ntg::bin> im_type; + + neighborhood2d nbh(neighb_c4()); + + im_type marker; + im_type mask; + + mask = io::read(rdata("object.pbm")); + marker = morpho::opening(mask, win_c4p()).exact(); + + // Classical procedure. + // FIXME: Re-enable when proc::geodesic_dilation is fixed. +#if 0 + if (utils::md5(morpho::proc::geodesic_dilation(join(marker, nbh), + mask).exact()) != key) + return true; +#endif + + // Facade to object-algorithm. + if (utils::md5(morpho::geodesic_dilation(join(marker, nbh), + mask).exact()) != key) + return true; -// oln::utils::key::value_type data_key[16] = -// {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // md5 of object.pbm's geodesic_dilation result -// oln::utils::key key(data_key); - -// typedef oln::image2d<ntg::bin> im_type; - -// oln::neighborhood2d nbh(oln::neighb_c4()); - -// im_type marker; -// im_type mask; - -// mask = oln::io::read(rdata("object.pbm")); -// marker = oln::morpho::opening(mask, oln::win_c4p()).exact(); - -// if (oln::utils::md5(oln::morpho::geodesic_dilation(join(marker, nbh), mask).exact()) == key) -// { -// std::cout << "OK" << std::endl; -// return false; -// } -// else -// { -// std::cout << "FAIL" << std::endl; -// return true; -// } + return false; } Index: olena/tests/morpho/tests/erosion --- olena/tests/morpho/tests/erosion (revision 157) +++ olena/tests/morpho/tests/erosion (working copy) @@ -1,3 +1,4 @@ + // -*- C++ -*- #include "data.hh" #include <oln/utils/md5.hh> Index: olena/tests/morpho/tests/closing --- olena/tests/morpho/tests/closing (revision 0) +++ olena/tests/morpho/tests/closing (revision 0) @@ -0,0 +1,27 @@ + // -*- C++ -*- +#include "data.hh" +#include <oln/utils/md5.hh> + +#include <oln/io/read_image.hh> +#include <oln/basics2d.hh> +#include <oln/morpho/closing.hh> +#include <oln/level/compare.hh> +#include <ntg/all.hh> + +bool check() +{ + // MD5 sum of object.pbm's geodesic dilation result. + oln::utils::key::value_type data_key[16] = + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + oln::utils::key key(data_key); + + typedef oln::image2d<ntg::bin> im_type; + im_type ima; + ima = oln::io::read(rdata("16x16.pbm")); + + if (oln::utils::md5(oln::morpho::closing(ima, oln::win_c8p())) != key) + return true; + + return false; +} Index: olena/tests/core/tests/readwrite_image --- olena/tests/core/tests/readwrite_image (revision 157) +++ olena/tests/core/tests/readwrite_image (working copy) @@ -22,6 +22,7 @@ template <> struct set_props <category::image, dummy_image> { + typedef grid2d grid_type; typedef is_a<abstract::readwrite_image> image_constness; typedef is_a<abstract::image2d> image_dimension_type; Index: olena/tests/core/tests/setget --- olena/tests/core/tests/setget (revision 157) +++ olena/tests/core/tests/setget (working copy) @@ -15,17 +15,18 @@ template <> struct set_props < category::image, dummy_image > { - typedef int concrete_type; - typedef int image_dimension_type; + typedef mlc::no_type grid_type; + typedef mlc::no_type concrete_type; + typedef mlc::no_type image_dimension_type; typedef int size_type; typedef int point_type; typedef int value_type; typedef mlc::no_type delegated_type; - typedef fwd_piter2d piter_type; - typedef fwd_piter2d fwd_piter_type; - typedef bkd_piter2d bkd_piter_type; + typedef mlc::no_type piter_type; + typedef mlc::no_type fwd_piter_type; + typedef mlc::no_type bkd_piter_type; }; struct dummy_image : public abstract::readwrite_image< dummy_image > Index: olena/oln/core/abstract/image_with_extension.hh --- olena/oln/core/abstract/image_with_extension.hh (revision 157) +++ olena/oln/core/abstract/image_with_extension.hh (working copy) @@ -68,7 +68,7 @@ { } - image_with_extension(const abstract::image<I>& image) : + image_with_extension(abstract::image<I>& image) : super_type(image) { } Index: olena/oln/core/gen/image_with_nbh.hh --- olena/oln/core/gen/image_with_nbh.hh (revision 157) +++ olena/oln/core/gen/image_with_nbh.hh (working copy) @@ -58,7 +58,7 @@ public: - image_with_nbh(const abstract::image<I>& image, + image_with_nbh(abstract::image<I>& image, const abstract::neighborhood<N>& nbh) : super_type(image), nbh_(nbh.exact()) @@ -72,7 +72,7 @@ protected: - N& nbh_; + const N& nbh_; }; @@ -80,7 +80,7 @@ template <typename I, typename N> image_with_nbh<I, N> - join(const abstract::image<I>& image, + join(abstract::image<I>& image, const abstract::neighborhood<N>& nbh) { image_with_nbh<I, N> tmp(image, nbh); Index: olena/oln/makefile.src --- olena/oln/makefile.src (revision 157) +++ olena/oln/makefile.src (working copy) @@ -137,8 +137,10 @@ level/fill.hh \ \ morpho/cc_tarjan.hh \ + morpho/closing.hh \ morpho/dilation.hh \ morpho/erosion.hh \ + morpho/opening.hh \ morpho/reconstruction.hh \ morpho/splitse.hh \ morpho/stat.hh \ Index: olena/oln/morpho/geodesic_dilation.hh --- olena/oln/morpho/geodesic_dilation.hh (revision 157) +++ olena/oln/morpho/geodesic_dilation.hh (working copy) @@ -57,10 +57,12 @@ precondition(marker.size() == mask.size()); precondition(level::is_greater_or_equal(mask, marker)); oln_type_of(I1, concrete) output(marker.size()); - marker.border_adapt_copy(marker.nbh_get().delta()); + // FIXME: Useless? + // marker.border_adapt_copy(marker.nbh_get().delta()); oln_type_of(I1, piter) p(marker); for_all_p (p) - output[p] = arith::min(morpho::max(marker, p, convert::nbh_to_cse(marker.nbh_get())), + output[p] = + arith::min(morpho::max(marker, p, marker.nbh_get().get_win()), mask[p]); return output; } @@ -110,7 +112,10 @@ mlc::eq<oln_type_of(I1, size), oln_type_of(I2, size)>::ensure(); precondition((this->input1).size() == (this->input2).size()); precondition(level::is_greater_or_equal(this->input2, this->input1)); - this->output = arith::min(dilation(this->input1.unbox(), this->input1.unbox().nbh_get()).output.unbox(), this->input2.unbox()).output; + this->output = + arith::min(dilation(this->input1.unbox(), + this->input1.unbox().nbh_get().get_win()).output.unbox(), + this->input2.unbox()).output; } }; Index: olena/oln/morpho/closing.hh --- olena/oln/morpho/closing.hh (revision 0) +++ olena/oln/morpho/closing.hh (revision 0) @@ -0,0 +1,166 @@ +// Copyright (C) 2001, 2002, 2003, 2004, 2005 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library 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 library; see the file COPYING. If not, write to +// the Free Software Foundation, 59 Temple Place - Suite 330, Boston, +// MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef OLENA_MORPHO_CLOSING_HH +# define OLENA_MORPHO_CLOSING_HH + +# include <mlc/cmp.hh> +# include <mlc/to_string.hh> + +# include <oln/core/abstract/images.hh> +# include <oln/core/abstract/image_operator.hh> + +# include <oln/morpho/dilation.hh> +# include <oln/morpho/erosion.hh> +# include <oln/core/2d/window2d.hh> + + +namespace oln { + + + // fwd decl + + namespace morpho { + template <typename I> struct closing_ret; + } + + // super_type + + template <typename I> + struct set_super_type< morpho::closing_ret<I> > + { + typedef oln_type_of(I, concrete) output_type; + + typedef morpho::closing_ret<I> self_type; + typedef abstract::image_unary_operator<output_type, I, self_type > ret; + }; + + + + + namespace morpho { + + + + // Closing as a 'classical' procedure returning an image (do not + // use it; prefer morpho::closing). + + namespace proc { + + template<typename I, typename W> + oln_type_of(I, concrete) closing(const abstract::image<I>& input, + const abstract::window<W>& win) + { + mlc::eq<oln_type_of(I, grid), oln_wn_type_of(W, grid)>::ensure(); + oln_type_of(I, concrete) output(input.size()); + output = morpho::erosion(morpho::dilation(input, win), win); + return output; + } + + } // end of namespace oln::morpho::proc + + + + /// Closing return type. + + template <typename I> + struct closing_ret : public oln_super_of_(closing_ret<I>) + { + typedef oln_super_of(closing_ret<I>) super_type; + + closing_ret(const abstract::image<I>& input) : + super_type(input) + { + } + + }; + + + // Various implementation. + + namespace impl { + + + /// Generic implementation of closing (type). + + template <typename I, typename W> + struct generic_closing : public closing_ret<I> + { + typedef closing_ret<I> super_type; + // FIXME: typedef oln_type_of(super_type, output) output_type; + + const W& win; + + generic_closing(const abstract::image<I>& input, + const abstract::window<W>& win) : + super_type(input), + win(win.exact()) + { + } + + void impl_run() + { + oln_type_of(super_type, output) tmp(input.size()); + tmp = morpho::erosion(morpho::dilation(input, win), win); + output = tmp; + } + }; + + // Generic implementation of closing (routine). + + template<typename I, typename W> + closing_ret<I> closing(const abstract::image<I>& input, + const abstract::window<W>& win) + { + impl::generic_closing<I,W> tmp(input, win); + tmp.run(); + return tmp; + } + + // FIXME: Add specialized implementations. + + } // end of namespace oln::morpho::impl + + + /// Generic closing (facade). + + template<typename I, typename W> + closing_ret<I> closing(const abstract::image<I>& input, + const abstract::window<W>& win) + { + mlc::eq<oln_type_of(I, grid), oln_wn_type_of(W, grid)>::ensure(); + return impl::closing(input.exact(), win.exact()); + } + + + } // end of namespace oln::morpho + + +} // end of namespace oln + + +#endif // ! OLENA_MORPHO_CLOSING_HH Index: olena/oln/morpho/opening.hh --- olena/oln/morpho/opening.hh (revision 0) +++ olena/oln/morpho/opening.hh (revision 0) @@ -0,0 +1,166 @@ +// Copyright (C) 2001, 2002, 2003, 2004, 2005 EPITA Research and Development Laboratory +// +// This file is part of the Olena Library. This library is free +// software; you can redistribute it and/or modify it under the terms +// of the GNU General Public License version 2 as published by the +// Free Software Foundation. +// +// This library 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 library; see the file COPYING. If not, write to +// the Free Software Foundation, 59 Temple Place - Suite 330, Boston, +// MA 02111-1307, USA. +// +// As a special exception, you may use this file as part of a free +// software library without restriction. Specifically, if other files +// instantiate templates or use macros or inline functions from this +// file, or you compile this file and link it with other files to +// produce an executable, this file does not by itself cause the +// resulting executable to be covered by the GNU General Public +// License. This exception does not however invalidate any other +// reasons why the executable file might be covered by the GNU General +// Public License. + +#ifndef OLENA_MORPHO_OPENING_HH +# define OLENA_MORPHO_OPENING_HH + +# include <mlc/cmp.hh> +# include <mlc/to_string.hh> + +# include <oln/core/abstract/images.hh> +# include <oln/core/abstract/image_operator.hh> + +# include <oln/morpho/dilation.hh> +# include <oln/morpho/erosion.hh> +# include <oln/core/2d/window2d.hh> + + +namespace oln { + + + // fwd decl + + namespace morpho { + template <typename I> struct opening_ret; + } + + // super_type + + template <typename I> + struct set_super_type< morpho::opening_ret<I> > + { + typedef oln_type_of(I, concrete) output_type; + + typedef morpho::opening_ret<I> self_type; + typedef abstract::image_unary_operator<output_type, I, self_type > ret; + }; + + + + + namespace morpho { + + + + // Opening as a 'classical' procedure returning an image (do not + // use it; prefer morpho::opening). + + namespace proc { + + template<typename I, typename W> + oln_type_of(I, concrete) opening(const abstract::image<I>& input, + const abstract::window<W>& win) + { + mlc::eq<oln_type_of(I, grid), oln_wn_type_of(W, grid)>::ensure(); + oln_type_of(I, concrete) output(input.size()); + output = morpho::dilation(morpho::erosion(input, win), win); + return output; + } + + } // end of namespace oln::morpho::proc + + + + /// Opening return type. + + template <typename I> + struct opening_ret : public oln_super_of_(opening_ret<I>) + { + typedef oln_super_of(opening_ret<I>) super_type; + + opening_ret(const abstract::image<I>& input) : + super_type(input) + { + } + + }; + + + // Various implementation. + + namespace impl { + + + /// Generic implementation of opening (type). + + template <typename I, typename W> + struct generic_opening : public opening_ret<I> + { + typedef opening_ret<I> super_type; + // FIXME: typedef oln_type_of(super_type, output) output_type; + + const W& win; + + generic_opening(const abstract::image<I>& input, + const abstract::window<W>& win) : + super_type(input), + win(win.exact()) + { + } + + void impl_run() + { + oln_type_of(super_type, output) tmp(input.size()); + tmp = morpho::dilation(morpho::erosion(input, win), win); + output = tmp; + } + }; + + // Generic implementation of opening (routine). + + template<typename I, typename W> + opening_ret<I> opening(const abstract::image<I>& input, + const abstract::window<W>& win) + { + impl::generic_opening<I,W> tmp(input, win); + tmp.run(); + return tmp; + } + + // FIXME: Add specialized implementations. + + } // end of namespace oln::morpho::impl + + + /// Generic opening (facade). + + template<typename I, typename W> + opening_ret<I> opening(const abstract::image<I>& input, + const abstract::window<W>& win) + { + mlc::eq<oln_type_of(I, grid), oln_wn_type_of(W, grid)>::ensure(); + return impl::opening(input.exact(), win.exact()); + } + + + } // end of namespace oln::morpho + + +} // end of namespace oln + + +#endif // ! OLENA_MORPHO_OPENING_HH Index: olena/oln/morpho/geodesic_erosion.hh --- olena/oln/morpho/geodesic_erosion.hh (revision 157) +++ olena/oln/morpho/geodesic_erosion.hh (working copy) @@ -57,10 +57,12 @@ precondition(marker.size() == mask.size()); precondition(level::is_greater_or_equal(marker, mask)); oln_type_of(I1, concrete) output(marker.size()); - marker.border_adapt_copy(marker.nbh_get().delta()); + // FIXME: Useless? + // marker.border_adapt_copy(marker.nbh_get().delta()); oln_type_of(I1, piter) p(marker); for_all_p (p) - output[p] = arith::max(morpho::min(marker, p, convert::nbh_to_cse(marker.nbh_get())), + output[p] = + arith::max(morpho::min(marker, p, marker.nbh_get().get_win()), mask[p]); return output; } @@ -108,8 +110,11 @@ { mlc::eq<oln_type_of(I1, size), oln_type_of(I2, size)>::ensure(); precondition((this->input1).size() == (this->input2).size()); - precondition(level::is_greater_or_equal(this->input2, this->input1)); - this->output = arith::max(erosion(this->input1.unbox(), this->input1.unbox().nbh_get()).output.unbox(), this->input2.unbox()).output; + precondition(level::is_greater_or_equal(this->input1, this->input2)); + this->output = + arith::max(erosion(this->input1.unbox(), + this->input1.unbox().nbh_get().get_win()).output.unbox(), + this->input2.unbox()).output; } };