[libcamera-devel,v7,09/13] py: add Transform
diff mbox series

Message ID 20220505104104.70841-10-tomi.valkeinen@ideasonboard.com
State Superseded
Headers show
Series
  • Python bindings
Related show

Commit Message

Tomi Valkeinen May 5, 2022, 10:41 a.m. UTC
Add binding for Transform.

C++'s Transform is an enum class, but we expose it as a Python class so
that it can be easily manipulated.

Original version from David Plowman <david.plowman@raspberrypi.com>.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 src/py/libcamera/pymain.cpp | 63 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

Comments

Laurent Pinchart May 5, 2022, 5:59 p.m. UTC | #1
Hi Tomi,

Thank you for the patch.

On Thu, May 05, 2022 at 01:41:00PM +0300, Tomi Valkeinen wrote:
> Add binding for Transform.
> 
> C++'s Transform is an enum class, but we expose it as a Python class so
> that it can be easily manipulated.
> 
> Original version from David Plowman <david.plowman@raspberrypi.com>.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  src/py/libcamera/pymain.cpp | 63 ++++++++++++++++++++++++++++++++++++-
>  1 file changed, 62 insertions(+), 1 deletion(-)
> 
> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
> index db9d90ab..3d2393ab 100644
> --- a/src/py/libcamera/pymain.cpp
> +++ b/src/py/libcamera/pymain.cpp
> @@ -152,6 +152,7 @@ PYBIND11_MODULE(_libcamera, m)
>  	auto pyControlId = py::class_<ControlId>(m, "ControlId");
>  	auto pyRequest = py::class_<Request>(m, "Request");
>  	auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
> +	auto pyTransform = py::class_<Transform>(m, "Transform");
>  
>  	/* Global functions */
>  	m.def("logSetLevel", &logSetLevel);
> @@ -342,7 +343,8 @@ PYBIND11_MODULE(_libcamera, m)
>  		.def("validate", &CameraConfiguration::validate)
>  		.def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at), py::return_value_policy::reference_internal)
>  		.def_property_readonly("size", &CameraConfiguration::size)
> -		.def_property_readonly("empty", &CameraConfiguration::empty);
> +		.def_property_readonly("empty", &CameraConfiguration::empty)
> +		.def_readwrite("transform", &CameraConfiguration::transform);
>  
>  	pyStreamConfiguration
>  		.def("toString", &StreamConfiguration::toString)
> @@ -462,4 +464,63 @@ PYBIND11_MODULE(_libcamera, m)
>  			transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
>  			return v;
>  		});
> +
> +	pyTransform
> +		.def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {

I'm not fond of the constructor, three bool arguments in a row are quite
error-prone. I'd rather expose the enum.

> +			bool ok;
> +
> +			Transform t = transformFromRotation(rotation, &ok);
> +			if (!ok)
> +				throw std::runtime_error("Unable to create the transform");

"Invalid rotation" would be a better message I think.

> +
> +			if (hflip)
> +				t ^= Transform::HFlip;
> +			if (vflip)
> +				t ^= Transform::VFlip;
> +			if (transpose)
> +				t ^= Transform::Transpose;
> +			return t;
> +		}), py::arg("rotation") = 0, py::arg("hflip") = false,
> +		    py::arg("vflip") = false, py::arg("transpose") = false)
> +		.def(py::init([](Transform &other) { return other; }))
> +		.def("__repr__", [](Transform &self) {
> +			return "<libcamera.Transform '" + std::string(transformToString(self)) + "'>";
> +		})
> +		.def_property("hflip",
> +			      [](Transform &self) {
> +				      return !!(self & Transform::HFlip);
> +			      },
> +			      [](Transform &self, bool hflip) {
> +				      if (hflip)
> +					      self |= Transform::HFlip;
> +				      else
> +					      self &= ~Transform::HFlip;
> +			      })
> +		.def_property("vflip",
> +			      [](Transform &self) {
> +				      return !!(self & Transform::VFlip);
> +			      },
> +			      [](Transform &self, bool vflip) {
> +				      if (vflip)
> +					      self |= Transform::VFlip;
> +				      else
> +					      self &= ~Transform::VFlip;
> +			      })
> +		.def_property("transpose",
> +			      [](Transform &self) {
> +				      return !!(self & Transform::Transpose);
> +			      },
> +			      [](Transform &self, bool transpose) {
> +				      if (transpose)
> +					      self |= Transform::Transpose;
> +				      else
> +					      self &= ~Transform::Transpose;
> +			      })
> +		.def("inverse", [](Transform &self) { return -self; })
> +		.def("invert", [](Transform &self) {
> +			self = -self;
> +		})
> +		.def("compose", [](Transform &self, Transform &other) {
> +			self = self * other;
> +		});
>  }
Tomi Valkeinen May 6, 2022, 11:25 a.m. UTC | #2
On 05/05/2022 20:59, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, May 05, 2022 at 01:41:00PM +0300, Tomi Valkeinen wrote:
>> Add binding for Transform.
>>
>> C++'s Transform is an enum class, but we expose it as a Python class so
>> that it can be easily manipulated.
>>
>> Original version from David Plowman <david.plowman@raspberrypi.com>.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   src/py/libcamera/pymain.cpp | 63 ++++++++++++++++++++++++++++++++++++-
>>   1 file changed, 62 insertions(+), 1 deletion(-)
>>
>> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
>> index db9d90ab..3d2393ab 100644
>> --- a/src/py/libcamera/pymain.cpp
>> +++ b/src/py/libcamera/pymain.cpp
>> @@ -152,6 +152,7 @@ PYBIND11_MODULE(_libcamera, m)
>>   	auto pyControlId = py::class_<ControlId>(m, "ControlId");
>>   	auto pyRequest = py::class_<Request>(m, "Request");
>>   	auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
>> +	auto pyTransform = py::class_<Transform>(m, "Transform");
>>   
>>   	/* Global functions */
>>   	m.def("logSetLevel", &logSetLevel);
>> @@ -342,7 +343,8 @@ PYBIND11_MODULE(_libcamera, m)
>>   		.def("validate", &CameraConfiguration::validate)
>>   		.def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at), py::return_value_policy::reference_internal)
>>   		.def_property_readonly("size", &CameraConfiguration::size)
>> -		.def_property_readonly("empty", &CameraConfiguration::empty);
>> +		.def_property_readonly("empty", &CameraConfiguration::empty)
>> +		.def_readwrite("transform", &CameraConfiguration::transform);
>>   
>>   	pyStreamConfiguration
>>   		.def("toString", &StreamConfiguration::toString)
>> @@ -462,4 +464,63 @@ PYBIND11_MODULE(_libcamera, m)
>>   			transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
>>   			return v;
>>   		});
>> +
>> +	pyTransform
>> +		.def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
> 
> I'm not fond of the constructor, three bool arguments in a row are quite
> error-prone. I'd rather expose the enum.

It's not that bad with python, e.g.:

Transform(transpose=True, vflip=True)

I think that's more pythonic than or'ing enums.

>> +			bool ok;
>> +
>> +			Transform t = transformFromRotation(rotation, &ok);
>> +			if (!ok)
>> +				throw std::runtime_error("Unable to create the transform");
> 
> "Invalid rotation" would be a better message I think.

And std::invalid_argument.

  Tomi
Laurent Pinchart May 6, 2022, 3:24 p.m. UTC | #3
Hi Tomi,

On Fri, May 06, 2022 at 02:25:50PM +0300, Tomi Valkeinen wrote:
> On 05/05/2022 20:59, Laurent Pinchart wrote:
> > On Thu, May 05, 2022 at 01:41:00PM +0300, Tomi Valkeinen wrote:
> >> Add binding for Transform.
> >>
> >> C++'s Transform is an enum class, but we expose it as a Python class so
> >> that it can be easily manipulated.
> >>
> >> Original version from David Plowman <david.plowman@raspberrypi.com>.
> >>
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >> ---
> >>   src/py/libcamera/pymain.cpp | 63 ++++++++++++++++++++++++++++++++++++-
> >>   1 file changed, 62 insertions(+), 1 deletion(-)
> >>
> >> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
> >> index db9d90ab..3d2393ab 100644
> >> --- a/src/py/libcamera/pymain.cpp
> >> +++ b/src/py/libcamera/pymain.cpp
> >> @@ -152,6 +152,7 @@ PYBIND11_MODULE(_libcamera, m)
> >>   	auto pyControlId = py::class_<ControlId>(m, "ControlId");
> >>   	auto pyRequest = py::class_<Request>(m, "Request");
> >>   	auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
> >> +	auto pyTransform = py::class_<Transform>(m, "Transform");
> >>   
> >>   	/* Global functions */
> >>   	m.def("logSetLevel", &logSetLevel);
> >> @@ -342,7 +343,8 @@ PYBIND11_MODULE(_libcamera, m)
> >>   		.def("validate", &CameraConfiguration::validate)
> >>   		.def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at), py::return_value_policy::reference_internal)
> >>   		.def_property_readonly("size", &CameraConfiguration::size)
> >> -		.def_property_readonly("empty", &CameraConfiguration::empty);
> >> +		.def_property_readonly("empty", &CameraConfiguration::empty)
> >> +		.def_readwrite("transform", &CameraConfiguration::transform);
> >>   
> >>   	pyStreamConfiguration
> >>   		.def("toString", &StreamConfiguration::toString)
> >> @@ -462,4 +464,63 @@ PYBIND11_MODULE(_libcamera, m)
> >>   			transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
> >>   			return v;
> >>   		});
> >> +
> >> +	pyTransform
> >> +		.def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
> > 
> > I'm not fond of the constructor, three bool arguments in a row are quite
> > error-prone. I'd rather expose the enum.
> 
> It's not that bad with python, e.g.:
> 
> Transform(transpose=True, vflip=True)
> 
> I think that's more pythonic than or'ing enums.

One could also write

    Transform(0, true, false, true)

which isn't nice. We can address that out later if needed, either
merging this patch already or postponing it. David, do you have any
opinion ?

> >> +			bool ok;
> >> +
> >> +			Transform t = transformFromRotation(rotation, &ok);
> >> +			if (!ok)
> >> +				throw std::runtime_error("Unable to create the transform");
> > 
> > "Invalid rotation" would be a better message I think.
> 
> And std::invalid_argument.

Good point.
David Plowman May 6, 2022, 3:40 p.m. UTC | #4
Hi Laurent, Tomi

On Fri, 6 May 2022 at 16:24, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Tomi,
>
> On Fri, May 06, 2022 at 02:25:50PM +0300, Tomi Valkeinen wrote:
> > On 05/05/2022 20:59, Laurent Pinchart wrote:
> > > On Thu, May 05, 2022 at 01:41:00PM +0300, Tomi Valkeinen wrote:
> > >> Add binding for Transform.
> > >>
> > >> C++'s Transform is an enum class, but we expose it as a Python class so
> > >> that it can be easily manipulated.
> > >>
> > >> Original version from David Plowman <david.plowman@raspberrypi.com>.
> > >>
> > >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> > >> ---
> > >>   src/py/libcamera/pymain.cpp | 63 ++++++++++++++++++++++++++++++++++++-
> > >>   1 file changed, 62 insertions(+), 1 deletion(-)
> > >>
> > >> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
> > >> index db9d90ab..3d2393ab 100644
> > >> --- a/src/py/libcamera/pymain.cpp
> > >> +++ b/src/py/libcamera/pymain.cpp
> > >> @@ -152,6 +152,7 @@ PYBIND11_MODULE(_libcamera, m)
> > >>    auto pyControlId = py::class_<ControlId>(m, "ControlId");
> > >>    auto pyRequest = py::class_<Request>(m, "Request");
> > >>    auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
> > >> +  auto pyTransform = py::class_<Transform>(m, "Transform");
> > >>
> > >>    /* Global functions */
> > >>    m.def("logSetLevel", &logSetLevel);
> > >> @@ -342,7 +343,8 @@ PYBIND11_MODULE(_libcamera, m)
> > >>            .def("validate", &CameraConfiguration::validate)
> > >>            .def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at), py::return_value_policy::reference_internal)
> > >>            .def_property_readonly("size", &CameraConfiguration::size)
> > >> -          .def_property_readonly("empty", &CameraConfiguration::empty);
> > >> +          .def_property_readonly("empty", &CameraConfiguration::empty)
> > >> +          .def_readwrite("transform", &CameraConfiguration::transform);
> > >>
> > >>    pyStreamConfiguration
> > >>            .def("toString", &StreamConfiguration::toString)
> > >> @@ -462,4 +464,63 @@ PYBIND11_MODULE(_libcamera, m)
> > >>                    transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
> > >>                    return v;
> > >>            });
> > >> +
> > >> +  pyTransform
> > >> +          .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
> > >
> > > I'm not fond of the constructor, three bool arguments in a row are quite
> > > error-prone. I'd rather expose the enum.
> >
> > It's not that bad with python, e.g.:
> >
> > Transform(transpose=True, vflip=True)
> >
> > I think that's more pythonic than or'ing enums.
>
> One could also write
>
>     Transform(0, true, false, true)
>
> which isn't nice. We can address that out later if needed, either
> merging this patch already or postponing it. David, do you have any
> opinion ?

Yes, what Tomi put is exactly how I am expecting to use it. I don't
feel terribly strongly, but it did seem both handy and Python-ish. You
can do

t = Transform(180)

t = Transform(hflip=True, vflip=True)

t = Transform()
t.hflip = t.vflip =1

I agree that it can be misused by the determined, but I definitely
went for convenience...

David

>
> > >> +                  bool ok;
> > >> +
> > >> +                  Transform t = transformFromRotation(rotation, &ok);
> > >> +                  if (!ok)
> > >> +                          throw std::runtime_error("Unable to create the transform");
> > >
> > > "Invalid rotation" would be a better message I think.
> >
> > And std::invalid_argument.
>
> Good point.
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart May 6, 2022, 3:46 p.m. UTC | #5
On Fri, May 06, 2022 at 04:40:01PM +0100, David Plowman wrote:
> On Fri, 6 May 2022 at 16:24, Laurent Pinchart wrote:
> > On Fri, May 06, 2022 at 02:25:50PM +0300, Tomi Valkeinen wrote:
> > > On 05/05/2022 20:59, Laurent Pinchart wrote:
> > > > On Thu, May 05, 2022 at 01:41:00PM +0300, Tomi Valkeinen wrote:
> > > >> Add binding for Transform.
> > > >>
> > > >> C++'s Transform is an enum class, but we expose it as a Python class so
> > > >> that it can be easily manipulated.
> > > >>
> > > >> Original version from David Plowman <david.plowman@raspberrypi.com>.
> > > >>
> > > >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> > > >> ---
> > > >>   src/py/libcamera/pymain.cpp | 63 ++++++++++++++++++++++++++++++++++++-
> > > >>   1 file changed, 62 insertions(+), 1 deletion(-)
> > > >>
> > > >> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
> > > >> index db9d90ab..3d2393ab 100644
> > > >> --- a/src/py/libcamera/pymain.cpp
> > > >> +++ b/src/py/libcamera/pymain.cpp
> > > >> @@ -152,6 +152,7 @@ PYBIND11_MODULE(_libcamera, m)
> > > >>    auto pyControlId = py::class_<ControlId>(m, "ControlId");
> > > >>    auto pyRequest = py::class_<Request>(m, "Request");
> > > >>    auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
> > > >> +  auto pyTransform = py::class_<Transform>(m, "Transform");
> > > >>
> > > >>    /* Global functions */
> > > >>    m.def("logSetLevel", &logSetLevel);
> > > >> @@ -342,7 +343,8 @@ PYBIND11_MODULE(_libcamera, m)
> > > >>            .def("validate", &CameraConfiguration::validate)
> > > >>            .def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at), py::return_value_policy::reference_internal)
> > > >>            .def_property_readonly("size", &CameraConfiguration::size)
> > > >> -          .def_property_readonly("empty", &CameraConfiguration::empty);
> > > >> +          .def_property_readonly("empty", &CameraConfiguration::empty)
> > > >> +          .def_readwrite("transform", &CameraConfiguration::transform);
> > > >>
> > > >>    pyStreamConfiguration
> > > >>            .def("toString", &StreamConfiguration::toString)
> > > >> @@ -462,4 +464,63 @@ PYBIND11_MODULE(_libcamera, m)
> > > >>                    transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
> > > >>                    return v;
> > > >>            });
> > > >> +
> > > >> +  pyTransform
> > > >> +          .def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
> > > >
> > > > I'm not fond of the constructor, three bool arguments in a row are quite
> > > > error-prone. I'd rather expose the enum.
> > >
> > > It's not that bad with python, e.g.:
> > >
> > > Transform(transpose=True, vflip=True)
> > >
> > > I think that's more pythonic than or'ing enums.
> >
> > One could also write
> >
> >     Transform(0, true, false, true)
> >
> > which isn't nice. We can address that out later if needed, either
> > merging this patch already or postponing it. David, do you have any
> > opinion ?
> 
> Yes, what Tomi put is exactly how I am expecting to use it. I don't
> feel terribly strongly, but it did seem both handy and Python-ish. You
> can do
> 
> t = Transform(180)
> 
> t = Transform(hflip=True, vflip=True)
> 
> t = Transform()
> t.hflip = t.vflip =1
> 
> I agree that it can be misused by the determined, but I definitely
> went for convenience...

Time to document the Python bindings then ? :-)

> > > >> +                  bool ok;
> > > >> +
> > > >> +                  Transform t = transformFromRotation(rotation, &ok);
> > > >> +                  if (!ok)
> > > >> +                          throw std::runtime_error("Unable to create the transform");
> > > >
> > > > "Invalid rotation" would be a better message I think.
> > >
> > > And std::invalid_argument.
> >
> > Good point.

Patch
diff mbox series

diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
index db9d90ab..3d2393ab 100644
--- a/src/py/libcamera/pymain.cpp
+++ b/src/py/libcamera/pymain.cpp
@@ -152,6 +152,7 @@  PYBIND11_MODULE(_libcamera, m)
 	auto pyControlId = py::class_<ControlId>(m, "ControlId");
 	auto pyRequest = py::class_<Request>(m, "Request");
 	auto pyFrameMetadata = py::class_<FrameMetadata>(m, "FrameMetadata");
+	auto pyTransform = py::class_<Transform>(m, "Transform");
 
 	/* Global functions */
 	m.def("logSetLevel", &logSetLevel);
@@ -342,7 +343,8 @@  PYBIND11_MODULE(_libcamera, m)
 		.def("validate", &CameraConfiguration::validate)
 		.def("at", py::overload_cast<unsigned int>(&CameraConfiguration::at), py::return_value_policy::reference_internal)
 		.def_property_readonly("size", &CameraConfiguration::size)
-		.def_property_readonly("empty", &CameraConfiguration::empty);
+		.def_property_readonly("empty", &CameraConfiguration::empty)
+		.def_readwrite("transform", &CameraConfiguration::transform);
 
 	pyStreamConfiguration
 		.def("toString", &StreamConfiguration::toString)
@@ -462,4 +464,63 @@  PYBIND11_MODULE(_libcamera, m)
 			transform(self.planes().begin(), self.planes().end(), v.begin(), [](const auto &p) { return p.bytesused; });
 			return v;
 		});
+
+	pyTransform
+		.def(py::init([](int rotation, bool hflip, bool vflip, bool transpose) {
+			bool ok;
+
+			Transform t = transformFromRotation(rotation, &ok);
+			if (!ok)
+				throw std::runtime_error("Unable to create the transform");
+
+			if (hflip)
+				t ^= Transform::HFlip;
+			if (vflip)
+				t ^= Transform::VFlip;
+			if (transpose)
+				t ^= Transform::Transpose;
+			return t;
+		}), py::arg("rotation") = 0, py::arg("hflip") = false,
+		    py::arg("vflip") = false, py::arg("transpose") = false)
+		.def(py::init([](Transform &other) { return other; }))
+		.def("__repr__", [](Transform &self) {
+			return "<libcamera.Transform '" + std::string(transformToString(self)) + "'>";
+		})
+		.def_property("hflip",
+			      [](Transform &self) {
+				      return !!(self & Transform::HFlip);
+			      },
+			      [](Transform &self, bool hflip) {
+				      if (hflip)
+					      self |= Transform::HFlip;
+				      else
+					      self &= ~Transform::HFlip;
+			      })
+		.def_property("vflip",
+			      [](Transform &self) {
+				      return !!(self & Transform::VFlip);
+			      },
+			      [](Transform &self, bool vflip) {
+				      if (vflip)
+					      self |= Transform::VFlip;
+				      else
+					      self &= ~Transform::VFlip;
+			      })
+		.def_property("transpose",
+			      [](Transform &self) {
+				      return !!(self & Transform::Transpose);
+			      },
+			      [](Transform &self, bool transpose) {
+				      if (transpose)
+					      self |= Transform::Transpose;
+				      else
+					      self &= ~Transform::Transpose;
+			      })
+		.def("inverse", [](Transform &self) { return -self; })
+		.def("invert", [](Transform &self) {
+			self = -self;
+		})
+		.def("compose", [](Transform &self, Transform &other) {
+			self = self * other;
+		});
 }