Skip to content

Configure and Connect

Updated Examples

For the most up-to-date examples, please see the public repository on GitHub at https://github.com/JoeScan-Inc/pinchot-c-api/tree/master/examples or the "examples" folder in the software distribution.

/**
 * Copyright (c) JoeScan Inc. All Rights Reserved.
 *
 * Licensed under the BSD 3 Clause License. See LICENSE.txt in the project
 * root for license information.
 */

/**
 * @file configure_and_connect.cpp
 * @brief Example showing how to configure and connect to a single JS-50 WX
 * scan head.
 *
 * This example application provides a basic example of setting up a scan for
 * scanning using functions and data structures from the Pinchot API. In the
 * following order, the application will:
 *
 *    1. Create scan system and scan head
 *    2. Print out the scan head's capabilities
 *    3. Set the configuration for the scan head
 *    4. Build a basic phase table
 *    5. Connect to the scan head
 *    6. Print out the scan head's current status
 *    7. Disconnect from the scan head.
 *
 * Further information regarding  features demonstrated in this application can
 * be found online:
 *
 *    http://api.joescan.com/doc/v16/articles/js50-configuration.html
 *    http://api.joescan.com/doc/v16/articles/phase-table.html
 */

#include <iostream>
#include <stdexcept>
#include "joescan_pinchot.h"

class ApiError : public std::runtime_error {
 private:
  jsError m_return_code;

 public:
  ApiError(const char* what, int32_t return_code) : std::runtime_error(what)
  {
    if ((0 < return_code) || (JS_ERROR_UNKNOWN > return_code)) {
      m_return_code = JS_ERROR_UNKNOWN;
    } else {
      m_return_code = (jsError) return_code;
    }
  }

  jsError return_code() const
  {
    return m_return_code;
  }
};

/**
 * @brief Display the API version to console output for visual confirmation as
 * to the version being used for this example.
 */
void PrintApiVersion()
{
  uint32_t major, minor, patch;
  jsGetAPISemanticVersion(&major, &minor, &patch);
  std::cout << "Joescan API version " << major << "." << minor << "." << patch
            << std::endl;
}

/**
 * @brief Display serial number, type, and firmware version of the scan head
 * to console output.
 *
 * @param c Struct holding the scan head capabilities.
 */
void PrintScanHeadInfo(jsScanHead scan_head)
{
  int32_t r = 0;

  jsScanHeadType type = jsScanHeadGetType(scan_head);
  if (JS_SCAN_HEAD_INVALID_TYPE == type) {
    throw ApiError("invalid type", r);
  }

  switch (type) {
    case (JS_SCAN_HEAD_JS50WX):
      std::cout << "JS-50 WX";
      break;
    case(JS_SCAN_HEAD_JS50WSC):
      std::cout << "JS-50 WSC";
      break;
    case (JS_SCAN_HEAD_JS50X6B30):
      std::cout << "JS-50 X6B30";
      break;
    case (JS_SCAN_HEAD_JS50X6B20):
      std::cout << "JS-50 X6B20";
      break;
    case (JS_SCAN_HEAD_JS50MX):
      std::cout << "JS-50 MX";
      break;
    case (JS_SCAN_HEAD_JS50Z820):
      std::cout << "JS-50 Z820";
      break;
    case (JS_SCAN_HEAD_JS50Z830):
      std::cout << "JS-50 Z830";
      break;
    case (JS_SCAN_HEAD_INVALID_TYPE):
    default:
      std::cout << "Invalid";
      (void) type;
  }

  uint32_t serial = jsScanHeadGetSerial(scan_head);
  std::cout << " serial " << serial;

  uint32_t major, minor, patch;
  r = jsScanHeadGetFirmwareVersion(scan_head, &major, &minor, &patch);
  if (0 > r) {
    throw ApiError("failed to get firmware version", r);
  }

  std::cout << " firmware version " << major << "." << minor << "." << patch
            << std::endl;
}

/**
 * @brief Display the capabilities of a scan head to console output.
 *
 * @param c Struct holding the scan head capabilities.
 */
void PrintScanHeadCapabilities(jsScanHeadCapabilities &c)
{
  std::cout << "jsScanHeadCapabilities" << std::endl;
  std::cout << "\tcamera_brightness_bit_depth=" <<
    c.camera_brightness_bit_depth << std::endl;
  std::cout << "\tmax_camera_image_height=" << c.max_camera_image_height <<
    std::endl;
  std::cout << "\tmax_camera_image_width=" << c.max_camera_image_width <<
    std::endl;
  std::cout << "\tmin_scan_period=" << c.min_scan_period_us << std::endl;
  std::cout << "\tnum_cameras=" << c.num_cameras << std::endl;
  std::cout << "\tnum_encoders=" << c.num_encoders << std::endl;
  std::cout << "\tnum_lasers=" << c.num_lasers << std::endl;
}

/**
 * @brief Prints the contents of a `jsScanHeadStatus` data type to standard out.
 *
 * @param stat Reference to scan head status to print.
 */
void PrintScanHeadStatus(jsScanHeadStatus &stat)
{
  std::cout << "jsScanHeadStatus" << std::endl;
  std::cout << "\tglobal_time_ns=" << stat.global_time_ns << std::endl;
  std::cout << "\tnum_encoder_values=" << stat.num_encoder_values << std::endl;

  std::cout << "\tencoder_values=";
  for (uint32_t n = 0; n < stat.num_encoder_values; n++) {
    std::cout << stat.encoder_values[n];
    if (n != (stat.num_encoder_values - 1)) {
      std::cout << ",";
    } else {
      std::cout << std::endl;
    }
  }

  std::cout << "\tcamera_a_pixels_in_window="
            << stat.camera_a_pixels_in_window << std::endl;
  std::cout << "\tcamera_a_temp=" << stat.camera_a_temp << std::endl;


  std::cout << "\tcamera_b_pixels_in_window="
            << stat.camera_b_pixels_in_window << std::endl;
  std::cout << "\tcamera_b_temp=" << stat.camera_b_temp << std::endl;
}

int main(int argc, char *argv[])
{
  jsScanSystem scan_system = 0;
  jsScanHead scan_head = 0;
  uint32_t serial_number = 0;
  int32_t r = 0;

  if (2 > argc) {
    std::cout << "Usage: " << argv[0] << " SERIAL" << std::endl;
    return 1;
  }

  // Grab the serial number of the scan head from the command line.
  serial_number = strtoul(argv[1], nullptr, 0);

  try {
    PrintApiVersion();

    // One of the first calls to the API should be to create a scan manager
    // software object. This object will be used to manage groupings of scan
    // heads, telling them when to start and stop scanning.
    scan_system = jsScanSystemCreate(JS_UNITS_INCHES);
    if (0 > scan_system) {
      throw ApiError("failed to create scan system", r);
    }

    // Create a scan head software object for the user's specified serial
    // number and associate it with the scan manager we just created. We'll
    // also assign it a user defined ID that can be used within the application
    // as an optional identifier if prefered over the serial number. Note that
    // at this point, we haven't connected with the physical scan head yet.
    int32_t id = 0;
    scan_head = jsScanSystemCreateScanHead(scan_system, serial_number, id);
    if (0 > scan_head) {
      r = (int32_t) scan_head;
      throw ApiError("failed to create scan head", r);
    }

    // The scan head has been created. We can query it for information and also
    // begin configuring it.
    PrintScanHeadInfo(scan_head);

    jsScanHeadCapabilities cap;
    r = jsScanHeadGetCapabilities(scan_head, &cap);
    if (0 > r) {
      throw ApiError("failed to get capabilities", r);
    }

    PrintScanHeadCapabilities(cap);

    // Many of the settings directly related to the operation of the cameras
    // and lasers can be found in the `jsScanHeadConfiguration` struct. Refer
    // to the API documentation for specific details regarding each field. For
    // this example, we'll use some generic values not specifically set for any
    // particular scenario.
    jsScanHeadConfiguration config;
    config.laser_on_time_min_us = 100;
    config.laser_on_time_def_us = 100;
    config.laser_on_time_max_us = 1000;
    config.laser_detection_threshold = 120;
    config.saturation_threshold = 800;
    config.saturation_percentage = 30;
    r = jsScanHeadSetConfiguration(scan_head, &config);
    if (0 > r) {
      throw ApiError("failed to set scan head configuration", r);
    }

    // Proper window selection can be crucial to successful scanning as it
    // allows users to limit the region of interest for scanning; filtering out
    // other sources of light that could complicate scanning. It is worth
    // noting that there is an inverse relationship with the scan window and
    // the overall scan rate a system can run at. Using larger scan windows
    // will reduce the maximum scan rate of a system, whereas using a smaller
    // scan window will increase the maximum scan rate.
    r = jsScanHeadSetWindowRectangular(scan_head, 30.0, -30.0, -30.0, 30.0);
    if (0 > r) {
      throw ApiError("failed to set window", r);
    }

    // Setting the alignment through the following function can help to
    // correct for any mounting issues with a scan head that could affect
    // the 3D measurement. For this example, we'll assume that the scan head
    // is mounted perfectly such that the laser is pointed directly at the scan
    // target.
    r = jsScanHeadSetAlignment(scan_head, 0.0, 0.0, 0.0);
    if (0 > r) {
      throw ApiError("failed to set alignment", r);
    }

    // For this example we will create a basic phase table that utilizes all of
    // the phasable elements of the scan head. Depending on the type of scan
    // head, we will need to either schedule its cameras or its lasers. The
    // bellow `switch` statement shows this process for each type of scan head.
    jsScanHeadType type = jsScanHeadGetType(scan_head);
    switch (type) {
    case (JS_SCAN_HEAD_JS50X6B20):
    case (JS_SCAN_HEAD_JS50X6B30):
      // Phase | Laser | Camera
      //   1   |   1   |   B
      //   2   |   4   |   A
      //   3   |   2   |   B
      //   4   |   5   |   A
      //   5   |   3   |   B
      //   6   |   6   |   A

      for (int n = 0; n < 3; n++) {
        jsLaser laser = JS_LASER_INVALID;

        // Lasers associated with Camera B
        r = jsScanSystemPhaseCreate(scan_system);
        if (0 != r) {
          throw ApiError("failed to create phase", r);
        }

        laser = (jsLaser) (JS_LASER_1 + n);
        r = jsScanSystemPhaseInsertLaser(scan_system, scan_head, laser);
        if (0 != r) {
          throw ApiError("failed to insert into phase", r);
        }

        // Lasers associated with Camera A
        r = jsScanSystemPhaseCreate(scan_system);
        if (0 != r) {
          throw ApiError("failed to create phase", r);
        }

        laser = (jsLaser) (JS_LASER_4 + n);
        r = jsScanSystemPhaseInsertLaser(scan_system, scan_head, laser);
        if (0 != r) {
          throw ApiError("failed to insert into phase", r);
        }
      }
      break;

    case (JS_SCAN_HEAD_JS50Z820):
    case (JS_SCAN_HEAD_JS50Z830):
      // Phase | Laser | Camera
      //   1   |   1   |   B
      //   2   |   5   |   A
      //   3   |   2   |   B
      //   4   |   6   |   A
      //   5   |   3   |   B
      //   6   |   7   |   A
      //   7   |   4   |   B
      //   8   |   8   |   A

      for (int n = 0; n < 4; n++) {
        jsLaser laser = JS_LASER_INVALID;

        // Lasers associated with Camera B
        r = jsScanSystemPhaseCreate(scan_system);
        if (0 != r) {
          throw ApiError("failed to create phase", r);
        }

        laser = (jsLaser) (JS_LASER_1 + n);
        r = jsScanSystemPhaseInsertLaser(scan_system, scan_head, laser);
        if (0 != r) {
          throw ApiError("failed to insert into phase", r);
        }

        // Lasers associated with Camera A
        r = jsScanSystemPhaseCreate(scan_system);
        if (0 != r) {
          throw ApiError("failed to create phase", r);
        }

        laser = (jsLaser) (JS_LASER_5 + n);
        r = jsScanSystemPhaseInsertLaser(scan_system, scan_head, laser);
        if (0 != r) {
          throw ApiError("failed to insert into phase", r);
        }
      }
      break;

    case (JS_SCAN_HEAD_JS50WSC):
    case (JS_SCAN_HEAD_JS50MX):
      // Phase | Laser | Camera
      //   1   |   1   |   A

      r = jsScanSystemPhaseCreate(scan_system);
      if (0 != r) {
        throw ApiError("failed to create phase", r);
      }

      r = jsScanSystemPhaseInsertCamera(scan_system, scan_head, JS_CAMERA_A);
      if (0 != r) {
        throw ApiError("failed to insert into phase", r);
      }
      break;

    case (JS_SCAN_HEAD_JS50WX):
      // Phase | Laser | Camera
      //   1   |   1   |   A
      //   2   |   1   |   B

      r = jsScanSystemPhaseCreate(scan_system);
      if (0 != r) {
        throw ApiError("failed to create phase", r);
      }

      r = jsScanSystemPhaseInsertCamera(scan_system, scan_head, JS_CAMERA_A);
      if (0 != r) {
        throw ApiError("failed to insert into phase", r);
      }

      r = jsScanSystemPhaseCreate(scan_system);
      if (0 != r) {
        throw ApiError("failed to create phase", r);
      }

      r = jsScanSystemPhaseInsertCamera(scan_system, scan_head, JS_CAMERA_B);
      if (0 != r) {
        throw ApiError("failed to insert into phase", r);
      }
      break;

    case (JS_SCAN_HEAD_INVALID_TYPE):
    default:
      throw ApiError("invalid scan head type", 0);
    }

    // We've now successfully configured the scan head. Now comes the time to
    // connect to the physical scanner and transmit the configuration values
    // we previously set up.
    r = jsScanSystemConnect(scan_system, 10);
    if (jsScanSystemGetNumberScanHeads(scan_system) != r) {
      throw ApiError("failed to connect", r);
    }

    // Now that we are connected, we can query the scan head to get it's
    // current status. Note that the status will be updated periodically by the
    // scan head and calling this function multiple times will provide the
    // last reported status of the scan head.
    jsScanHeadStatus status;
    r = jsScanHeadGetStatus(scan_head, &status);
    if (0 > r) {
      throw ApiError("failed to get scan head status", r);
    }

    PrintScanHeadStatus(status);

    // The minimum scan period indicates the fastest that the scan system can
    // obtain profiles. This value is dependent upon the laser on time, the
    // size of the scan window, and the phase table. For this example with only
    // one scan head, only its configuration affects the minimum scan period.
    // With more scan heads in the scan system, they will collectively affect
    // this value.
    int32_t min_period_us = jsScanSystemGetMinScanPeriod(scan_system);
    if (0 >= min_period_us) {
      throw ApiError("failed to read min scan period", min_period_us);
    }
    std::cout << "min scan period is " << min_period_us << " us" << std::endl;

    // Once connected, this is the point where we could command the scan system
    // to start scanning; obtaining profile data from the scan heads associated
    // with it. This will be the focus of a later example.

    // We've accomplished what we set out to do for this example; now it's time
    // to bring down our system.
    r = jsScanSystemDisconnect(scan_system);
    if (0 > r) {
      throw ApiError("failed to disconnect", r);
    }
  } catch (ApiError &e) {
    std::cout << "ERROR: " << e.what() << std::endl;
    r = 1;

    const char *err_str = nullptr;
    jsError err = e.return_code();
    if (JS_ERROR_NONE != err) {
      jsGetError(err, &err_str);
      std::cout << "jsError (" << err << "): " << err_str << std::endl;
    }
  }

  // Clean up data allocated by the scan manager.
  jsScanSystemFree(scan_system);

  return r;
}

Comments