Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[software] panoramaPostProcessing: export downscaled panorama levels #1496

Merged
merged 8 commits into from
Jul 31, 2023
234 changes: 182 additions & 52 deletions src/software/pipeline/main_panoramaPostProcessing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

// These constants define the current software version.
// They must be updated when the command line is changed.
#define ALICEVISION_SOFTWARE_VERSION_MAJOR 1
#define ALICEVISION_SOFTWARE_VERSION_MAJOR 2
#define ALICEVISION_SOFTWARE_VERSION_MINOR 0

using namespace aliceVision;
Expand Down Expand Up @@ -87,68 +87,117 @@
return true;
}

bool readFullTile(image::Image<image::RGBAfColor> & output, std::unique_ptr<oiio::ImageInput> & input, int tx, int ty)
{
const oiio::ImageSpec &inputSpec = input->spec();
int tileSize = inputSpec.tile_width;
int width = inputSpec.width;
int height = inputSpec.height;
const int tileSize = inputSpec.tile_width;
const int width = inputSpec.width;
const int height = inputSpec.height;

int countWidth = std::ceil(double(width) / double(tileSize));
int countHeight = std::ceil(double(height) / double(tileSize));
const int countWidth = std::ceil(double(width) / double(tileSize));
const int countHeight = std::ceil(double(height) / double(tileSize));

if (tx < 0 || tx >= countWidth || ty < 0 || ty >= countHeight)
// Utility lambda to load only part of a tile (before of after a given split position)
auto readTilePartial = [&](
image::Image<image::RGBAfColor> & buf,
int txLeft,
int xOutput, int xBuf, int sliceWidth) -> bool
{
output.fill(image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f));
if (!input->read_tile(txLeft * tileSize, ty * tileSize, 0, oiio::TypeDesc::FLOAT, buf.data()))
{
return false;
}

output.block(0, xOutput, tileSize, sliceWidth) = buf.block(0, xBuf, tileSize, sliceWidth);

return true;
}
};

if (tx < 0)
// Default filling
output.fill(image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f));

// Make sure tile y-coordinate is in the right range
if (ty < 0 || ty >= countHeight)
{
tx = countWidth + tx;
return true;
}

if (tx >= countWidth)
if (tx == -1)
{
tx = tx - countWidth;
}
// Wrap tile x-coordinate
const int offset = width - 1 - tileSize;
const int txLeft = offset / tileSize;

/*int available = width - (tx * tileSize);
// Size of the left and right part of the tile
const int leftside = (txLeft + 1) * tileSize - offset;
const int rightside = tileSize - leftside;

if (available < tileSize)
{
image::Image<image::RGBAfColor> buf(tileSize, tileSize);
if (!input->read_tile(tx * tileSize, ty * tileSize, 0, oiio::TypeDesc::FLOAT, buf.data()))

// Load left part tile
if (!readTilePartial(buf, txLeft, 0, rightside, leftside)) return false;

// Load right part tile to complete filling output
if (rightside > 0)
{
return false;
if (!readTilePartial(buf, txLeft + 1, leftside, 0, rightside)) return false;
}
}
else if (tx == countWidth - 1)
{
// Size of the left and right part of the tile
const int leftside = width - tx * tileSize;
const int rightside = tileSize - leftside;

image::Image<image::RGBAfColor> buf(tileSize, tileSize);

output.block(0, tileSize - available, tileSize, available) = buf.block(0, 0, tileSize, available);
// Load last tile (which may be incomplete)
if (!readTilePartial(buf, tx, 0, 0, leftside)) return false;

if (!input->read_tile((tx - 1) * tileSize, ty * tileSize, 0, oiio::TypeDesc::FLOAT, buf.data()))
// Load first tile to complete filling output
if (rightside > 0)
{
return false;
if (!readTilePartial(buf, 0, leftside, 0, rightside)) return false;
}
}
else if (tx == countWidth)
{
// Wrap tile x-coordinate
const int offset = countWidth * tileSize - width;
const int txLeft = offset / tileSize;

output.block(0, 0, tileSize, tileSize - available) = buf.block(0, available, tileSize, tileSize - available);
// Size of the left and right part of the tile
const int leftside = (txLeft + 1) * tileSize - offset;
const int rightside = tileSize - leftside;

return true;
}*/
image::Image<image::RGBAfColor> buf(tileSize, tileSize);

output.fill(image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f));
if (!input->read_tile(tx * tileSize, ty * tileSize, 0, oiio::TypeDesc::FLOAT, output.data()))
// Load left part tile
if (!readTilePartial(buf, txLeft, 0, rightside, leftside)) return false;

// Load right part tile to complete filling output
if (rightside > 0)
{
if (!readTilePartial(buf, txLeft + 1, leftside, 0, rightside)) return false;
}
}
else if (tx >= 0 && tx < countWidth - 1)
{
return false;
// Load tile data directly into output
if (!input->read_tile(tx * tileSize, ty * tileSize, 0, oiio::TypeDesc::FLOAT, output.data()))
{
return false;
}
}

return true;
}

void colorSpaceTransform(image::Image<image::RGBAfColor>& inputImage, image::EImageColorSpace fromColorSpace, image::EImageColorSpace toColorSpace, image::DCPProfile dcpProf, image::DCPProfile::Triple neutral)
{
const int width = inputImage.Width();
const int tileSize = inputImage.Height();
oiio::ImageBuf inBuf = oiio::ImageBuf(oiio::ImageSpec(width, tileSize, 4, oiio::TypeDesc::FLOAT), const_cast<image::RGBAfColor*>(inputImage.data()));

Check notice on line 200 in src/software/pipeline/main_panoramaPostProcessing.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/software/pipeline/main_panoramaPostProcessing.cpp#L90-L200

Complex Method
oiio::ImageBuf* outBuf = &inBuf;

if (fromColorSpace == image::EImageColorSpace::NO_CONVERSION)
Expand Down Expand Up @@ -200,7 +249,9 @@
int compressionLevel = 0;
image::EImageColorSpace outputColorSpace = image::EImageColorSpace::LINEAR;
size_t previewSize = 1000;
bool fillHoles = false;
bool fillHoles = false;
bool exportLevels = false;
int lastLevelMaxSize = 3840;

// Description of mandatory parameters
po::options_description requiredParams("Required parameters");
Expand All @@ -221,6 +272,8 @@
"Only dwaa, dwab, zip and zips compression methods are concerned.")

("fillHoles", po::value<bool>(&fillHoles)->default_value(fillHoles), "Execute fill holes algorithm")
("exportLevels", po::value<bool>(&exportLevels)->default_value(exportLevels), "Export downscaled panorama levels")
("lastLevelMaxSize", po::value<int>(&lastLevelMaxSize)->default_value(lastLevelMaxSize), "Maximum width of smallest downscaled panorama level.")
("previewSize", po::value<size_t>(&previewSize)->default_value(previewSize), "Preview image width")
("outputColorSpace", po::value<image::EImageColorSpace>(&outputColorSpace)->default_value(outputColorSpace), "Color space for the output panorama.")
("outputPanoramaPreview,p", po::value<std::string>(&outputPanoramaPreviewPath)->default_value(outputPanoramaPreviewPath), "Path of the output panorama preview.");
Expand All @@ -241,9 +294,6 @@
return EXIT_FAILURE;
}


fs::path previewPath = fs::path(outputPanoramaPath).parent_path() / "preview.jpg";

//Get information about input panorama
const oiio::ImageSpec &inputSpec = panoramaInput->spec();
const int tileWidth = inputSpec.tile_width;
Expand Down Expand Up @@ -340,6 +390,35 @@
image::Image<image::RGBAfColor> previewImage(previewSize, previewSize / 2);
int previewCurrentRow = 0;

// Create image outputs for downscaled panorama levels
const int nbLevels = exportLevels ?
static_cast<int>(std::min(
std::floor(std::log2(tileSize)),
std::ceil(std::log2(width) - std::log2(lastLevelMaxSize))))
: 0;
std::vector<std::unique_ptr<oiio::ImageOutput>> levelOutputs;

ALICEVISION_LOG_INFO("Number of downscaled panorama levels to generate: " << nbLevels);

for (int levelIdx = 1; levelIdx <= nbLevels; ++levelIdx)
{
const int levelWidth = width / (1 << levelIdx);
fs::path levelPath = fs::path(outputPanoramaPath).parent_path() / ("level_" + std::to_string(levelWidth) + ".exr");
levelOutputs.push_back(std::move(oiio::ImageOutput::create(levelPath.string())));

oiio::ImageSpec levelSpec(outputSpec);
levelSpec.width /= (1 << levelIdx);
levelSpec.height /= (1 << levelIdx);
levelSpec.full_width /= (1 << levelIdx);
levelSpec.full_height /= (1 << levelIdx);

if (!levelOutputs[levelIdx-1]->open(levelPath.string(), levelSpec))
{
ALICEVISION_LOG_ERROR("Impossible to write downscaled panorama at level " << levelIdx);
return EXIT_FAILURE;
}
}

if (fillHoles)
{
ALICEVISION_LOG_INFO("Reduce image (" << width << "x" << height << ")");
Expand Down Expand Up @@ -422,7 +501,7 @@
int yend = (ty + 1) * tileSize - 1;

//Build subimage
image::Image<image::RGBAfColor> region(tileSize * rowSize, tileSize * 3);
image::Image<image::RGBAfColor> region(tileSize * rowSize, tileSize * 3);
image::Image<image::RGBAfColor> subFiled(rowSize, 3, true, image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f));
image::Image<image::RGBAfColor> final(width, tileSize, true, image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f));

Expand Down Expand Up @@ -544,7 +623,6 @@
}

const image::Image<image::RGBAfColor> & finalTile = pyramid[0];

final.block(0, 0, tileSize, width) = finalTile.block(tileSize, tileSize, tileSize, width);

//Fill preview image
Expand All @@ -566,41 +644,77 @@
previewCurrentRow++;
}

// Write panorama output
colorSpaceTransform(final, fromColorSpace, outputColorSpace, dcpProf, neutral);

panoramaOutput->write_scanlines(ty * tileSize, (ty + 1) * tileSize, 0, oiio::TypeDesc::FLOAT, final.data());

// Write downscaled panorama levels
for (int levelIdx = 1; levelIdx <= nbLevels; ++levelIdx)
{
image::Image<image::RGBAfColor> & levelTile = pyramid[levelIdx];
const int levelTileSize = tileSize / (1 << levelIdx);
const int levelWidth = width / (1 << levelIdx);
image::Image<image::RGBAfColor> level(levelWidth, levelTileSize);
level.block(0, 0, levelTileSize, levelWidth) = levelTile.block(levelTileSize, levelTileSize, levelTileSize, levelWidth);
colorSpaceTransform(level, fromColorSpace, outputColorSpace, dcpProf, neutral);
levelOutputs[levelIdx-1]->write_scanlines(ty * levelTileSize, (ty + 1) * levelTileSize, 0, oiio::TypeDesc::FLOAT, level.data());
}
}
}
else
{
//Process one full row of tiles each iteration
//Process one full row of tiles each iteration
for (int ty = 0; ty < countHeight; ty++)
{
int ybegin = ty * tileSize;
int yend = (ty + 1) * tileSize - 1;

// Build subimage
image::Image<image::RGBAfColor> region(tileSize * rowSize, tileSize * 3);
image::Image<image::RGBAfColor> final(width, tileSize, true, image::RGBAfColor(0.0f, 0.0f, 0.0f, 0.0f));
#pragma omp parallel for
for (int tx = 0; tx < countWidth; tx++)

//Build a region
for (int ry = 0; ry < 3; ry++)
{
image::Image<image::RGBAfColor> tile(tileSize, tileSize);
if (!panoramaInput->read_tile(tx * tileSize, ybegin, 0, oiio::TypeDesc::FLOAT, tile.data()))
int dy = ry - 1;
int cy = ty + dy;

#pragma omp parallel for
for (int rx = 0; rx < rowSize; rx++)
{
ALICEVISION_LOG_ERROR("Error reading from image");
}
int dx = rx - 1;
int cx = dx;

int available = width - tx*tileSize;
if (available < tileSize)
{
final.block(0, tx * tileSize, tileSize, available) = tile.block(0, 0, tileSize, available);
}
else
{
final.block(0, tx * tileSize, tileSize, tileSize) = tile;
image::Image<image::RGBAfColor> tile(tileSize, tileSize);
if (!readFullTile(tile, panoramaInput, cx, cy))
{
ALICEVISION_LOG_ERROR("Invalid tile");
continue;
}

region.block(ry * tileSize, rx * tileSize, tileSize, tileSize) = tile;
}
}

//First level is original image
std::vector<image::Image<image::RGBAfColor>> pyramid;
pyramid.push_back(region);

//Build pyramid for tile
int cs = tileSize * rowSize;
while (cs != rowSize)
{
int ns = cs / 2;
image::Image<image::RGBAfColor> smaller;
downscaleTriangle(smaller, region);
pyramid.push_back(smaller);
region = smaller;
cs = ns;
}

const image::Image<image::RGBAfColor> & finalTile = pyramid[0];
final.block(0, 0, tileSize, width) = finalTile.block(tileSize, tileSize, tileSize, width);

//Fill preview image
while (previewCurrentRow < previewImage.rows())
{
Expand All @@ -620,14 +734,30 @@
previewCurrentRow++;
}

// Write panorama output
colorSpaceTransform(final, fromColorSpace, outputColorSpace, dcpProf, neutral);
panoramaOutput->write_scanlines(ty * tileSize, (ty + 1) * tileSize, 0, oiio::TypeDesc::FLOAT, final.data());

panoramaOutput->write_scanlines(ybegin, yend, 0, oiio::TypeDesc::FLOAT, final.data());
// Write downscaled panorama levels
for (int levelIdx = 1; levelIdx <= nbLevels; ++levelIdx)
{
image::Image<image::RGBAfColor> & levelTile = pyramid[levelIdx];
const int levelTileSize = tileSize / (1 << levelIdx);
const int levelWidth = width / (1 << levelIdx);
image::Image<image::RGBAfColor> level(levelWidth, levelTileSize);
level.block(0, 0, levelTileSize, levelWidth) = levelTile.block(levelTileSize, levelTileSize, levelTileSize, levelWidth);
colorSpaceTransform(level, fromColorSpace, outputColorSpace, dcpProf, neutral);
levelOutputs[levelIdx-1]->write_scanlines(ty * levelTileSize, (ty + 1) * levelTileSize, 0, oiio::TypeDesc::FLOAT, level.data());
}
}
}

panoramaInput->close();
panoramaOutput->close();
for (int levelIdx = 1; levelIdx <= nbLevels; ++levelIdx)
{
levelOutputs[levelIdx-1]->close();
}

if (outputPanoramaPreviewPath != "")
{
Expand Down
Loading