/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #ifndef itkVideoFileWriter_hxx #define itkVideoFileWriter_hxx #include "itkNumericTraits.h" #include "itkTemporalDataObject.h" #include "itkMath.h" namespace itk { template VideoFileWriter::VideoFileWriter() : m_FileName("") , m_VideoIO(nullptr) , m_FourCC("MP42") { // TemporalProcessObject inherited members this->TemporalProcessObject::m_UnitInputNumberOfFrames = 1; this->TemporalProcessObject::m_UnitOutputNumberOfFrames = 1; this->TemporalProcessObject::m_FrameSkipPerOutput = 1; this->TemporalProcessObject::m_InputStencilCurrentFrameIndex = 0; } template VideoFileWriter::~VideoFileWriter() { this->FinishWriting(); } template void VideoFileWriter::SetInput(const VideoStreamType * input) { this->ProcessObject::SetNthInput(0, const_cast(input)); } template auto VideoFileWriter::GetInput() -> const VideoStreamType * { if (this->GetNumberOfInputs() < 1) { return nullptr; } return static_cast(this->ProcessObject::GetInput(0)); } template void VideoFileWriter::SetVideoIO(IOBasePointer videoIO) { m_VideoIO = videoIO; } template void VideoFileWriter::Write() { // // Make sure everything is set correctly // // Make sure input is available const VideoStreamType * input = this->GetInput(); if (input == nullptr) { itkExceptionMacro("No input to writer"); } // Make sure FileName is specified if (m_FileName.empty()) { itkExceptionMacro("No FileName set for writer"); } // Make sure FramesPerSecond and FourCC have been set if (Math::ExactlyEquals(m_FramesPerSecond, TemporalRatioType{}) || m_FourCC.length() == 0) { itkExceptionMacro("Cannot write with FramesPerSecond or FourCC unset"); } // // Trigger the first phase of the pipeline. We don't need to propagate the // requested region because that will get redone during the data generation // phase using temporal streaming. // // Update the output information upstream auto * nonConstInput = const_cast(this->GetInput()); nonConstInput->UpdateOutputInformation(); // // Set up the writer using information propagated from upstream // // Initialize writing information if (!this->InitializeOutputParameters()) { itkExceptionMacro("Could not initialize output parameters for writing"); } // Initialize VideoIO if necessary if (m_VideoIO.IsNull() && !this->InitializeVideoIO()) { itkExceptionMacro("Could not create VideoIO"); } // Set output information m_VideoIO->SetWriterParameters( m_FramesPerSecond, m_Dimensions, m_FourCC.c_str(), m_NumberOfComponents, m_ComponentType); m_VideoIO->SetFileName(m_FileName); // If no OutputTemporalRegion specified, get the largest one possible and // use that. if (m_OutputTemporalRegion.GetFrameDuration() == 0) { m_OutputTemporalRegion = input->GetLargestPossibleTemporalRegion(); } // FIXME: For now we will always request the entire spatial region of each // frame as output SizeValueType frameStart = m_OutputTemporalRegion.GetFrameStart(); SizeValueType numFrames = m_OutputTemporalRegion.GetFrameDuration(); for (SizeValueType i = frameStart; i < frameStart + numFrames; ++i) { nonConstInput->SetFrameRequestedSpatialRegion(i, input->GetFrameLargestPossibleSpatialRegion(i)); } // Propagate the requested regions nonConstInput->PropagateRequestedRegion(); // Set the output's temporal regions to match the input's. The output object // doesn't actually get used, but its temporal regions are used by the // temporal streaming in TemporalProcessObject's GenerateData. We set the // buffered region to an empty region so that the entire requested region is // unbuffered and gets written. auto output = TemporalDataObject::New(); output->SetLargestPossibleTemporalRegion(m_OutputTemporalRegion); output->SetRequestedTemporalRegion(m_OutputTemporalRegion); output->SetBufferedTemporalRegion(TemporalRegion()); this->ProcessObject::SetNthOutput(0, output); // // Trigger the data generation phase of the pipeline // // Notify observers this->InvokeEvent(StartEvent()); // Write the data this->GenerateData(); // Notify observers this->InvokeEvent(EndEvent()); // Finish writing the file this->FinishWriting(); } template void VideoFileWriter::FinishWriting() { if (!m_VideoIO.IsNull()) { m_VideoIO->FinishReadingOrWriting(); } } template void VideoFileWriter::Update() { this->Write(); } template void VideoFileWriter::UpdateLargestPossibleRegion() { const VideoStreamType * input = this->GetInput(); if (input == nullptr) { itkExceptionMacro("No input to writer"); } m_OutputTemporalRegion = input->GetLargestPossibleTemporalRegion(); this->Write(); } template void VideoFileWriter::TemporalStreamingGenerateData() { // Get a non-const pointer to the input and output const VideoStreamType * const input = this->GetInput(); auto * output = dynamic_cast(this->GetOutput(0)); if (!output) { itkExceptionMacro("Could not cast output to TemporalDataObject"); } // Get the frame we're going to write SizeValueType frameNum = output->GetRequestedTemporalRegion().GetFrameStart(); const FrameType * frame = input->GetFrame(frameNum); if (!frame) { itkExceptionMacro("Could not get input frame " << frameNum << " for writing"); } // Write the frame out m_VideoIO->Write(static_cast(frame->GetBufferPointer())); } template bool VideoFileWriter::InitializeOutputParameters() { // InputImage and VideoIO must be valid if (this->GetInput() == nullptr) { return false; } // Get the frame number for the current frame SizeValueType frameNum = this->GetInput()->GetRequestedTemporalRegion().GetFrameStart(); // Get a non-const pointer so we can get spatial regions (VideoStream isn't const correct) const VideoStreamType * input = this->GetInput(); // Set dimensions m_Dimensions.clear(); typename FrameType::SizeType size = input->GetFrameLargestPossibleSpatialRegion(frameNum).GetSize(); for (unsigned int i = 0; i < FrameType::ImageDimension; ++i) { m_Dimensions.push_back(size[i]); } // Set NumberOfComponents. At this point, just handle RGB and RGBA // non-scalar pixels m_NumberOfComponents = itk::NumericTraits::MeasurementVectorType::Length; return true; } template bool VideoFileWriter::InitializeVideoIO() { if (m_FileName.length() != 0) { m_VideoIO = itk::VideoIOFactory::CreateVideoIO(itk::VideoIOFactory::IOModeEnum::WriteMode, m_FileName.c_str()); // Return true if a VideoIO was successfully created if (!m_VideoIO.IsNull()) { // Get the pixel type m_ComponentType = m_VideoIO->GetComponentType(); return true; } else { return false; } } else { return false; } } template void VideoFileWriter::PrintSelf(std::ostream & os, Indent indent) const { using namespace print_helper; Superclass::PrintSelf(os, indent); os << indent << "FileName: " << m_FileName << std::endl; itkPrintSelfObjectMacro(VideoIO); os << indent << "OutputTemporalRegion: " << m_OutputTemporalRegion << std::endl; os << indent << "FramesPerSecond: " << static_cast::PrintType>(m_FramesPerSecond) << std::endl; os << indent << "FourCC: " << m_FourCC << std::endl; os << indent << "Dimensions: " << m_Dimensions << std::endl; os << indent << "NumberOfComponents: " << static_cast::PrintType>(m_NumberOfComponents) << std::endl; os << indent << "ComponentType: " << m_ComponentType << std::endl; } } // end namespace itk #endif