What is FlowSDK?

FlowSDK is the perfect photogrammetry Software Development Kit: a powerful, fully customizable photogrammetry reconstruction engine written in C++ for Windows platforms.

With FlowSDK, you are free to develop your own 3D reconstruction application, powered by our world class, powerful state of the art technology. Our own flagship product, 3DF Zephyr, runs the exact same technology that powers our SDK.

With 3Dflow’s SDK, capturing reality using photogrammetry has never been easier.

How does it work?

FlowSDK comes as two Windows x64 only dynamic link libraries. The Structure from motion and Multiview stereo Interfaces are written in C++ and are very easy to use. You can control the full reconstruction pipeline, from the structure from motion phase to the mesh and texturing. Settings can be passed directly or can be read and written using XML files, which are also 100% compatible with 3DF Zephyr.

How does the licensing works? How much does it cost?

FlowSDK can be used in any scenario (internal server for research and development, component for a commercial application, …. ) and covers unlimited reconstructions (meaning that while your license valid, you don’t have to pay royalties fee or any other hidden price).

The FlowSDK license is per-installation and directly tied to the scenario in which it gets used. This means that we do not have a fixed price, but rather we discuss with you your scenario, your expected installations and tailor the best licensing cost for your company.

Where can i find the documentation? Even better, can i see an example?

Sure you can! Our SDK documentation is available at the following URL: https://www.3dflow.net/3DFSDKdoc/

A full Visual Studio 2015 example project is also included in our SDK. However, here is a brief sample to give you and idea:

// Application main loop
void run( int argc, char **argv )
{
    // Prepare the log listener to let the application write the log to file
    std::string   logFileName( "log.txt" );
    std::ofstream myfile;

    myfile.open( logFileName );

    FlowCore::LogListenerOStream *stdOutLogListener = NULL;

    if ( myfile.good() )
    {
        stdOutLogListener = new FlowCore::LogListenerOStream( &myfile );
    }
    else
    {
        // Write log on the std::out only
        stdOutLogListener = new FlowCore::LogListenerOStream();
        std::cout << "Cannot write log to file" << std::endl;
    }

    // Declare data needed by Samantha. This will be filled by Samantha during computation.
    // List of cameras and a point cloud
    std::vector< FlowSaM::CameraInterface * >  cameraVector;
    FlowSaM::PointCloudInterface              *pointCloud = new FlowSaM::PointCloudInterface();

    // Get all the files in the ./Images folder
    WIN32_FIND_DATA data;
    std::wstring    path( L"./Images/*.*" );
    HANDLE          hFile = FindFirstFile( path.c_str(), &data );

    if ( hFile == INVALID_HANDLE_VALUE )
    {
        std::cout << "No file to load" << std::endl;
        return;
    }

    while ( FindNextFile( hFile, &data ) != 0 || GetLastError() != ERROR_NO_MORE_FILES )
    {
        if ( std::wstring( L".." ).compare( data.cFileName ) == 0 || std::wstring( L"." ).compare( data.cFileName ) == 0 )
            continue;

        // Push a new camera object on the vector. Samantha just need the camera filename
        // Supported format are jpg, png, bmp, tiff.
        FlowSaM::CameraInterface *cam = new FlowSaM::CameraInterface();
        std::wstring              ws  = std::wstring( L"./Images/" ) + data.cFileName;
        int                       i   = 0;

        for ( std::wstring::iterator it = ws.begin(); it != ws.end(); ++it, ++i )
            cam->mFilePath[ i ] = ( *it );

        cam->mFilePath[ i ] = '\0';


        cameraVector.push_back( cam );
    }

    // create output directory
    CreateDirectory( L".\\out\\", NULL );

    // Create the samantha object
    FlowSaM::SamanthaInterface *sammy = dynamic_cast< FlowSaM::SamanthaInterface * >( FlowSaM::CreateSamanthaInterfaceObject() );

    // Feed samantha - required input
    for ( size_t i = 0; i < cameraVector.size(); ++i )
        sammy->addCamera( cameraVector[ i ] );

    sammy->setPointCloud( pointCloud );

    // optional stuff
    sammy->setLogListener( stdOutLogListener );

    // run samantha
    int errCode = sammy->compute();

    // Test code for triangulation a 3D point
    if ( pointCloud->mNumberOfPoints > 0 )
    {
        // Read the first point image coords
        std::vector< unsigned int > camViewingTheFirstPoint;                                                 // #tracklength
        std::vector< double >       camCoordsViewingTheFirstPoint;                                           // #2 * tracklength

        for ( unsigned int i = 0; i < static_cast< unsigned int >( cameraVector.size() ); ++i )
        {
            if ( cameraVector[ i ]->mVisiblePointsCount > 0 && cameraVector[ i ]->mVisiblePoints[ 0 ] == 0 ) // First point viewed by this camera
            {
                camViewingTheFirstPoint.push_back( i );
                camCoordsViewingTheFirstPoint.push_back( cameraVector[ i ]->mVisiblePointsCoords[ 0 ] );
                camCoordsViewingTheFirstPoint.push_back( cameraVector[ i ]->mVisiblePointsCoords[ 1 ] );
            }
        }

        // .. and triangulate it
        double triangulatePt[ 3 ];
        sammy->triangulatePoint( static_cast< unsigned int >( camViewingTheFirstPoint.size() ), &camViewingTheFirstPoint[ 0 ], &camCoordsViewingTheFirstPoint[ 0 ], triangulatePt );

        // position can be (slightly) different because the points coming from the SaMInterface are triangulated with a different procedure
        double dx = pointCloud->mPositions[ 0 ] - triangulatePt[ 0 ];
        double dy = pointCloud->mPositions[ 1 ] - triangulatePt[ 1 ];
        double dz = pointCloud->mPositions[ 2 ] - triangulatePt[ 2 ];
    }

    // You can safely delete the samantha object after computation
    delete sammy;

    if ( errCode == 1 )
    {
        // Success - level up
        // Count number of reconstructed cameras and remove invalid one
        std::vector< FlowSaM::CameraInterface * >::iterator it = cameraVector.begin();

        while ( it != cameraVector.end() )
        {
            if ( ( *it )->mVisiblePointsCount == 0 )
            {
                delete ( *it );
                it = cameraVector.erase( it );
            }
            else
            {
                ++it;
            }
        }

        // Setup Stereo data - Stesha will fill this mesh
        FlowStesha::StereoMeshInterface *stereoMesh = new FlowStesha::StereoMeshInterface();

        // Create the stasia object
        FlowStesha::SteshaInterface *stesha = dynamic_cast< FlowStesha::SteshaInterface * >( FlowStesha::CreateSteshaInterfaceObject() );

        // Feed Stesha - required input
        for ( size_t i = 0; i < cameraVector.size(); ++i )
            stesha->addCamera( cameraVector[ i ] );

        stesha->setPointCloud( pointCloud ); // Samantha output
        stesha->setStereoMesh( stereoMesh ); // Samantha output

        // optional stuff
        stesha->setLogListener( stdOutLogListener );

        stesha->setSettingValue( "Workspace", "ExportPath", "./Out/" );
        stesha->setSettingValue( "Texture", "TextureSize", "4096" );

        // Compute and generate both a colored mesh and a texture.
        bool generateMesh     = true;
        bool generateTextures = true;
        errCode = stesha->compute( generateMesh, generateTextures );

        if ( errCode == 1 )
        {
            // Export as 3DK, a file that can be opened by 3DF Zephyr
            stesha->save( "./Out/out.3dk" );

            // Export also the final mesh as ply or obj+texture
            if ( !generateTextures )
                exportPly( stereoMesh, "./Out/mesh.ply" );
            else
                exportObj( stereoMesh, "./Out/", "TexturedMesh" );

            std::cout << "All done." << std::endl;
        }
        else
        {
            std::cout << "Stasia computation failed. Error code: " << errCode << std::endl;
        }

        // You can safely delete the stesha object after computation
        delete stesha;

        // Delete stereo mesh
        delete stereoMesh;
    }
    else
    {
        std::cout << "Samantha computation failed. Error code: " << errCode << std::endl;
    }

    // Clean up
    if ( stdOutLogListener )
        delete stdOutLogListener;

    delete pointCloud;

    for ( size_t i = 0; i < cameraVector.size(); ++i )
        delete cameraVector[ i ];

    cameraVector.clear();
}

What are its input and output?

The SDK is completetly independent: you can simply input any image (JPG, PNG…) and you can export directly into our native export format (.3dk) which can be then loaded into Zephyr, or you can write your own exporter to specific file formats. You will always be able to access all information generated by our reconstruction pipeline.

Great! How do i start?

Request a quotation for our SDK usage