文件 AWS 開發套件範例 GitHub 儲存庫中有更多可用的 AWS SDK 範例
本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
使用 AWS SDK 的 HealthImaging 影像集和影像影格入門
下列程式碼範例示範如何在 HealthImaging 中匯入 DICOM 檔案和下載影像影格。
實作會以命令列應用程式的形式建構。
設定 DICOM 匯入的資源。
將 DICOM 檔案匯入資料存放區。
擷取匯入任務的影像集 IDs。
擷取影像集的影像影格 IDs。
下載、解碼和驗證影像影格。
清除資源。
- SDK for C++
-
建立具有必要資源的 AWS CloudFormation 堆疊。
Aws::String inputBucketName; Aws::String outputBucketName; Aws::String dataStoreId; Aws::String roleArn; Aws::String stackName; if (askYesNoQuestion( "Would you like to let this workflow create the resources for you? (y/n) ")) { stackName = askQuestion( "Enter a name for the AWS CloudFormation stack to create. "); Aws::String dataStoreName = askQuestion( "Enter a name for the HealthImaging datastore to create. "); Aws::Map<Aws::String, Aws::String> outputs = createCloudFormationStack( stackName, dataStoreName, clientConfiguration); if (!retrieveOutputs(outputs, dataStoreId, inputBucketName, outputBucketName, roleArn)) { return false; } std::cout << "The following resources have been created." << std::endl; std::cout << "A HealthImaging datastore with ID: " << dataStoreId << "." << std::endl; std::cout << "An Amazon S3 input bucket named: " << inputBucketName << "." << std::endl; std::cout << "An Amazon S3 output bucket named: " << outputBucketName << "." << std::endl; std::cout << "An IAM role with the ARN: " << roleArn << "." << std::endl; askQuestion("Enter return to continue.", alwaysTrueTest); } else { std::cout << "You have chosen to use preexisting resources:" << std::endl; dataStoreId = askQuestion( "Enter the data store ID of the HealthImaging datastore you wish to use: "); inputBucketName = askQuestion( "Enter the name of the S3 input bucket you wish to use: "); outputBucketName = askQuestion( "Enter the name of the S3 output bucket you wish to use: "); roleArn = askQuestion( "Enter the ARN for the IAM role with the proper permissions to import a DICOM series: "); }
將 DICOM 檔案複製到 Amazon S3 匯入儲存貯體。
std::cout << "This workflow uses DICOM files from the National Cancer Institute Imaging Data\n" << "Commons (IDC) Collections." << std::endl; std::cout << "Here is the link to their website." << std::endl; std::cout << "https://registry.opendata.aws/nci-imaging-data-commons/" << std::endl; std::cout << "We will use DICOM files stored in an S3 bucket managed by the IDC." << std::endl; std::cout << "First one of the DICOM folders in the IDC collection must be copied to your\n" "input S3 bucket." << std::endl; std::cout << "You have the choice of one of the following " << IDC_ImageChoices.size() << " folders to copy." << std::endl; int index = 1; for (auto &idcChoice: IDC_ImageChoices) { std::cout << index << " - " << idcChoice.mDescription << std::endl; index++; } int choice = askQuestionForIntRange("Choose DICOM files to import: ", 1, 4); Aws::String fromDirectory = IDC_ImageChoices[choice - 1].mDirectory; Aws::String inputDirectory = "input"; std::cout << "The files in the directory '" << fromDirectory << "' in the bucket '" << IDC_S3_BucketName << "' will be copied " << std::endl; std::cout << "to the folder '" << inputDirectory << "/" << fromDirectory << "' in the bucket '" << inputBucketName << "'." << std::endl; askQuestion("Enter return to start the copy.", alwaysTrueTest); if (!AwsDoc::Medical_Imaging::copySeriesBetweenBuckets( IDC_S3_BucketName, fromDirectory, inputBucketName, inputDirectory, clientConfiguration)) { std::cerr << "This workflow will exit because of an error." << std::endl; cleanup(stackName, dataStoreId, clientConfiguration); return false; }
將 DICOM 檔案匯入 Amazon S3 資料存放區。
bool AwsDoc::Medical_Imaging::startDicomImport(const Aws::String &dataStoreID, const Aws::String &inputBucketName, const Aws::String &inputDirectory, const Aws::String &outputBucketName, const Aws::String &outputDirectory, const Aws::String &roleArn, Aws::String &importJobId, const Aws::Client::ClientConfiguration &clientConfiguration) { bool result = false; if (startDICOMImportJob(dataStoreID, inputBucketName, inputDirectory, outputBucketName, outputDirectory, roleArn, importJobId, clientConfiguration)) { std::cout << "DICOM import job started with job ID " << importJobId << "." << std::endl; result = waitImportJobCompleted(dataStoreID, importJobId, clientConfiguration); if (result) { std::cout << "DICOM import job completed." << std::endl; } } return result; } //! Routine which starts a HealthImaging import job. /*! \param dataStoreID: The HealthImaging data store ID. \param inputBucketName: The name of the Amazon S3 bucket containing the DICOM files. \param inputDirectory: The directory in the S3 bucket containing the DICOM files. \param outputBucketName: The name of the S3 bucket for the output. \param outputDirectory: The directory in the S3 bucket to store the output. \param roleArn: The ARN of the IAM role with permissions for the import. \param importJobId: A string to receive the import job ID. \param clientConfig: Aws client configuration. \return bool: Function succeeded. */ bool AwsDoc::Medical_Imaging::startDICOMImportJob( const Aws::String &dataStoreID, const Aws::String &inputBucketName, const Aws::String &inputDirectory, const Aws::String &outputBucketName, const Aws::String &outputDirectory, const Aws::String &roleArn, Aws::String &importJobId, const Aws::Client::ClientConfiguration &clientConfig) { Aws::MedicalImaging::MedicalImagingClient medicalImagingClient(clientConfig); Aws::String inputURI = "s3://" + inputBucketName + "/" + inputDirectory + "/"; Aws::String outputURI = "s3://" + outputBucketName + "/" + outputDirectory + "/"; Aws::MedicalImaging::Model::StartDICOMImportJobRequest startDICOMImportJobRequest; startDICOMImportJobRequest.SetDatastoreId(dataStoreID); startDICOMImportJobRequest.SetDataAccessRoleArn(roleArn); startDICOMImportJobRequest.SetInputS3Uri(inputURI); startDICOMImportJobRequest.SetOutputS3Uri(outputURI); Aws::MedicalImaging::Model::StartDICOMImportJobOutcome startDICOMImportJobOutcome = medicalImagingClient.StartDICOMImportJob( startDICOMImportJobRequest); if (startDICOMImportJobOutcome.IsSuccess()) { importJobId = startDICOMImportJobOutcome.GetResult().GetJobId(); } else { std::cerr << "Failed to start DICOM import job because " << startDICOMImportJobOutcome.GetError().GetMessage() << std::endl; } return startDICOMImportJobOutcome.IsSuccess(); } //! Routine which waits for a DICOM import job to complete. /*! * @param dataStoreID: The HealthImaging data store ID. * @param importJobId: The import job ID. * @param clientConfiguration : Aws client configuration. * @return bool: Function succeeded. */ bool AwsDoc::Medical_Imaging::waitImportJobCompleted(const Aws::String &datastoreID, const Aws::String &importJobId, const Aws::Client::ClientConfiguration &clientConfiguration) { Aws::MedicalImaging::Model::JobStatus jobStatus = Aws::MedicalImaging::Model::JobStatus::IN_PROGRESS; while (jobStatus == Aws::MedicalImaging::Model::JobStatus::IN_PROGRESS) { std::this_thread::sleep_for(std::chrono::seconds(1)); Aws::MedicalImaging::Model::GetDICOMImportJobOutcome getDicomImportJobOutcome = getDICOMImportJob( datastoreID, importJobId, clientConfiguration); if (getDicomImportJobOutcome.IsSuccess()) { jobStatus = getDicomImportJobOutcome.GetResult().GetJobProperties().GetJobStatus(); std::cout << "DICOM import job status: " << Aws::MedicalImaging::Model::JobStatusMapper::GetNameForJobStatus( jobStatus) << std::endl; } else { std::cerr << "Failed to get import job status because " << getDicomImportJobOutcome.GetError().GetMessage() << std::endl; return false; } } return jobStatus == Aws::MedicalImaging::Model::JobStatus::COMPLETED; } //! Routine which gets a HealthImaging DICOM import job's properties. /*! \param dataStoreID: The HealthImaging data store ID. \param importJobID: The DICOM import job ID \param clientConfig: Aws client configuration. \return GetDICOMImportJobOutcome: The import job outcome. */ Aws::MedicalImaging::Model::GetDICOMImportJobOutcome AwsDoc::Medical_Imaging::getDICOMImportJob(const Aws::String &dataStoreID, const Aws::String &importJobID, const Aws::Client::ClientConfiguration &clientConfig) { Aws::MedicalImaging::MedicalImagingClient client(clientConfig); Aws::MedicalImaging::Model::GetDICOMImportJobRequest request; request.SetDatastoreId(dataStoreID); request.SetJobId(importJobID); Aws::MedicalImaging::Model::GetDICOMImportJobOutcome outcome = client.GetDICOMImportJob( request); if (!outcome.IsSuccess()) { std::cerr << "GetDICOMImportJob error: " << outcome.GetError().GetMessage() << std::endl; } return outcome; }
取得 DICOM 匯入任務建立的影像集。
bool AwsDoc::Medical_Imaging::getImageSetsForDicomImportJob(const Aws::String &datastoreID, const Aws::String &importJobId, Aws::Vector<Aws::String> &imageSets, const Aws::Client::ClientConfiguration &clientConfiguration) { Aws::MedicalImaging::Model::GetDICOMImportJobOutcome getDicomImportJobOutcome = getDICOMImportJob( datastoreID, importJobId, clientConfiguration); bool result = false; if (getDicomImportJobOutcome.IsSuccess()) { auto outputURI = getDicomImportJobOutcome.GetResult().GetJobProperties().GetOutputS3Uri(); Aws::Http::URI uri(outputURI); const Aws::String &bucket = uri.GetAuthority(); Aws::String key = uri.GetPath(); Aws::S3::S3Client s3Client(clientConfiguration); Aws::S3::Model::GetObjectRequest objectRequest; objectRequest.SetBucket(bucket); objectRequest.SetKey(key + "/" + IMPORT_JOB_MANIFEST_FILE_NAME); auto getObjectOutcome = s3Client.GetObject(objectRequest); if (getObjectOutcome.IsSuccess()) { auto &data = getObjectOutcome.GetResult().GetBody(); std::stringstream stringStream; stringStream << data.rdbuf(); try { // Use JMESPath to extract the image set IDs. // https://jmespath.org/specification.html std::string jmesPathExpression = "jobSummary.imageSetsSummary[].imageSetId"; jsoncons::json doc = jsoncons::json::parse(stringStream.str()); jsoncons::json imageSetsJson = jsoncons::jmespath::search(doc, jmesPathExpression);\ for (auto &imageSet: imageSetsJson.array_range()) { imageSets.push_back(imageSet.as_string()); } result = true; } catch (const std::exception &e) { std::cerr << e.what() << '\n'; } } else { std::cerr << "Failed to get object because " << getObjectOutcome.GetError().GetMessage() << std::endl; } } else { std::cerr << "Failed to get import job status because " << getDicomImportJobOutcome.GetError().GetMessage() << std::endl; } return result; }
取得影像集的影像影格資訊。
bool AwsDoc::Medical_Imaging::getImageFramesForImageSet(const Aws::String &dataStoreID, const Aws::String &imageSetID, const Aws::String &outDirectory, Aws::Vector<ImageFrameInfo> &imageFrames, const Aws::Client::ClientConfiguration &clientConfiguration) { Aws::String fileName = outDirectory + "/" + imageSetID + "_metadata.json.gzip"; bool result = false; if (getImageSetMetadata(dataStoreID, imageSetID, "", // Empty string for version ID. fileName, clientConfiguration)) { try { std::string metadataGZip; { std::ifstream inFileStream(fileName.c_str(), std::ios::binary); if (!inFileStream) { throw std::runtime_error("Failed to open file " + fileName); } std::stringstream stringStream; stringStream << inFileStream.rdbuf(); metadataGZip = stringStream.str(); } std::string metadataJson = gzip::decompress(metadataGZip.data(), metadataGZip.size()); // Use JMESPath to extract the image set IDs. // https://jmespath.org/specification.html jsoncons::json doc = jsoncons::json::parse(metadataJson); std::string jmesPathExpression = "Study.Series.*.Instances[].*[]"; jsoncons::json instances = jsoncons::jmespath::search(doc, jmesPathExpression); for (auto &instance: instances.array_range()) { jmesPathExpression = "DICOM.RescaleSlope"; std::string rescaleSlope = jsoncons::jmespath::search(instance, jmesPathExpression).to_string(); jmesPathExpression = "DICOM.RescaleIntercept"; std::string rescaleIntercept = jsoncons::jmespath::search(instance, jmesPathExpression).to_string(); jmesPathExpression = "ImageFrames[][]"; jsoncons::json imageFramesJson = jsoncons::jmespath::search(instance, jmesPathExpression); for (auto &imageFrame: imageFramesJson.array_range()) { ImageFrameInfo imageFrameIDs; imageFrameIDs.mImageSetId = imageSetID; imageFrameIDs.mImageFrameId = imageFrame.find( "ID")->value().as_string(); imageFrameIDs.mRescaleIntercept = rescaleIntercept; imageFrameIDs.mRescaleSlope = rescaleSlope; imageFrameIDs.MinPixelValue = imageFrame.find( "MinPixelValue")->value().as_string(); imageFrameIDs.MaxPixelValue = imageFrame.find( "MaxPixelValue")->value().as_string(); jmesPathExpression = "max_by(PixelDataChecksumFromBaseToFullResolution, &Width).Checksum"; jsoncons::json checksumJson = jsoncons::jmespath::search(imageFrame, jmesPathExpression); imageFrameIDs.mFullResolutionChecksum = checksumJson.as_integer<uint32_t>(); imageFrames.emplace_back(imageFrameIDs); } } result = true; } catch (const std::exception &e) { std::cerr << "getImageFramesForImageSet failed because " << e.what() << std::endl; } } return result; } //! Routine which gets a HealthImaging image set's metadata. /*! \param dataStoreID: The HealthImaging data store ID. \param imageSetID: The HealthImaging image set ID. \param versionID: The HealthImaging image set version ID, ignored if empty. \param outputFilePath: The path where the metadata will be stored as gzipped json. \param clientConfig: Aws client configuration. \\return bool: Function succeeded. */ bool AwsDoc::Medical_Imaging::getImageSetMetadata(const Aws::String &dataStoreID, const Aws::String &imageSetID, const Aws::String &versionID, const Aws::String &outputFilePath, const Aws::Client::ClientConfiguration &clientConfig) { Aws::MedicalImaging::Model::GetImageSetMetadataRequest request; request.SetDatastoreId(dataStoreID); request.SetImageSetId(imageSetID); if (!versionID.empty()) { request.SetVersionId(versionID); } Aws::MedicalImaging::MedicalImagingClient client(clientConfig); Aws::MedicalImaging::Model::GetImageSetMetadataOutcome outcome = client.GetImageSetMetadata( request); if (outcome.IsSuccess()) { std::ofstream file(outputFilePath, std::ios::binary); auto &metadata = outcome.GetResult().GetImageSetMetadataBlob(); file << metadata.rdbuf(); } else { std::cerr << "Failed to get image set metadata: " << outcome.GetError().GetMessage() << std::endl; } return outcome.IsSuccess(); }
下載、解碼和驗證影像影格。
bool AwsDoc::Medical_Imaging::downloadDecodeAndCheckImageFrames( const Aws::String &dataStoreID, const Aws::Vector<ImageFrameInfo> &imageFrames, const Aws::String &outDirectory, const Aws::Client::ClientConfiguration &clientConfiguration) { Aws::Client::ClientConfiguration clientConfiguration1(clientConfiguration); clientConfiguration1.executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>( "executor", 25); Aws::MedicalImaging::MedicalImagingClient medicalImagingClient( clientConfiguration1); Aws::Utils::Threading::Semaphore semaphore(0, 1); std::atomic<size_t> count(imageFrames.size()); bool result = true; for (auto &imageFrame: imageFrames) { Aws::MedicalImaging::Model::GetImageFrameRequest getImageFrameRequest; getImageFrameRequest.SetDatastoreId(dataStoreID); getImageFrameRequest.SetImageSetId(imageFrame.mImageSetId); Aws::MedicalImaging::Model::ImageFrameInformation imageFrameInformation; imageFrameInformation.SetImageFrameId(imageFrame.mImageFrameId); getImageFrameRequest.SetImageFrameInformation(imageFrameInformation); auto getImageFrameAsyncLambda = [&semaphore, &result, &count, imageFrame, outDirectory]( const Aws::MedicalImaging::MedicalImagingClient *client, const Aws::MedicalImaging::Model::GetImageFrameRequest &request, Aws::MedicalImaging::Model::GetImageFrameOutcome outcome, const std::shared_ptr<const Aws::Client::AsyncCallerContext> &context) { if (!handleGetImageFrameResult(outcome, outDirectory, imageFrame)) { std::cerr << "Failed to download and convert image frame: " << imageFrame.mImageFrameId << " from image set: " << imageFrame.mImageSetId << std::endl; result = false; } count--; if (count <= 0) { semaphore.ReleaseAll(); } }; // End of 'getImageFrameAsyncLambda' lambda. medicalImagingClient.GetImageFrameAsync(getImageFrameRequest, getImageFrameAsyncLambda); } if (count > 0) { semaphore.WaitOne(); } if (result) { std::cout << imageFrames.size() << " image files were downloaded." << std::endl; } return result; } bool AwsDoc::Medical_Imaging::decodeJPHFileAndValidateWithChecksum( const Aws::String &jphFile, uint32_t crc32Checksum) { opj_image_t *outputImage = jphImageToOpjBitmap(jphFile); if (!outputImage) { return false; } bool result = true; if (!verifyChecksumForImage(outputImage, crc32Checksum)) { std::cerr << "The checksum for the image does not match the expected value." << std::endl; std::cerr << "File :" << jphFile << std::endl; result = false; } opj_image_destroy(outputImage); return result; } opj_image * AwsDoc::Medical_Imaging::jphImageToOpjBitmap(const Aws::String &jphFile) { opj_stream_t *inFileStream = nullptr; opj_codec_t *decompressorCodec = nullptr; opj_image_t *outputImage = nullptr; try { std::shared_ptr<opj_dparameters> decodeParameters = std::make_shared<opj_dparameters>(); memset(decodeParameters.get(), 0, sizeof(opj_dparameters)); opj_set_default_decoder_parameters(decodeParameters.get()); decodeParameters->decod_format = 1; // JP2 image format. decodeParameters->cod_format = 2; // BMP image format. std::strncpy(decodeParameters->infile, jphFile.c_str(), OPJ_PATH_LEN); inFileStream = opj_stream_create_default_file_stream( decodeParameters->infile, true); if (!inFileStream) { throw std::runtime_error( "Unable to create input file stream for file '" + jphFile + "'."); } decompressorCodec = opj_create_decompress(OPJ_CODEC_JP2); if (!decompressorCodec) { throw std::runtime_error("Failed to create decompression codec."); } int decodeMessageLevel = 1; if (!setupCodecLogging(decompressorCodec, &decodeMessageLevel)) { std::cerr << "Failed to setup codec logging." << std::endl; } if (!opj_setup_decoder(decompressorCodec, decodeParameters.get())) { throw std::runtime_error("Failed to setup decompression codec."); } if (!opj_codec_set_threads(decompressorCodec, 4)) { throw std::runtime_error("Failed to set decompression codec threads."); } if (!opj_read_header(inFileStream, decompressorCodec, &outputImage)) { throw std::runtime_error("Failed to read header."); } if (!opj_decode(decompressorCodec, inFileStream, outputImage)) { throw std::runtime_error("Failed to decode."); } if (DEBUGGING) { std::cout << "image width : " << outputImage->x1 - outputImage->x0 << std::endl; std::cout << "image height : " << outputImage->y1 - outputImage->y0 << std::endl; std::cout << "number of channels: " << outputImage->numcomps << std::endl; std::cout << "colorspace : " << outputImage->color_space << std::endl; } } catch (const std::exception &e) { std::cerr << e.what() << std::endl; if (outputImage) { opj_image_destroy(outputImage); outputImage = nullptr; } } if (inFileStream) { opj_stream_destroy(inFileStream); } if (decompressorCodec) { opj_destroy_codec(decompressorCodec); } return outputImage; } //! Template function which converts a planar image bitmap to an interleaved image bitmap and //! then verifies the checksum of the bitmap. /*! * @param image: The OpenJPEG image struct. * @param crc32Checksum: The CRC32 checksum. * @return bool: Function succeeded. */ template<class myType> bool verifyChecksumForImageForType(opj_image_t *image, uint32_t crc32Checksum) { uint32_t width = image->x1 - image->x0; uint32_t height = image->y1 - image->y0; uint32_t numOfChannels = image->numcomps; // Buffer for interleaved bitmap. std::vector<myType> buffer(width * height * numOfChannels); // Convert planar bitmap to interleaved bitmap. for (uint32_t channel = 0; channel < numOfChannels; channel++) { for (uint32_t row = 0; row < height; row++) { uint32_t fromRowStart = row / image->comps[channel].dy * width / image->comps[channel].dx; uint32_t toIndex = (row * width) * numOfChannels + channel; for (uint32_t col = 0; col < width; col++) { uint32_t fromIndex = fromRowStart + col / image->comps[channel].dx; buffer[toIndex] = static_cast<myType>(image->comps[channel].data[fromIndex]); toIndex += numOfChannels; } } } // Verify checksum. boost::crc_32_type crc32; crc32.process_bytes(reinterpret_cast<char *>(buffer.data()), buffer.size() * sizeof(myType)); bool result = crc32.checksum() == crc32Checksum; if (!result) { std::cerr << "verifyChecksumForImage, checksum mismatch, expected - " << crc32Checksum << ", actual - " << crc32.checksum() << std::endl; } return result; } //! Routine which verifies the checksum of an OpenJPEG image struct. /*! * @param image: The OpenJPEG image struct. * @param crc32Checksum: The CRC32 checksum. * @return bool: Function succeeded. */ bool AwsDoc::Medical_Imaging::verifyChecksumForImage(opj_image_t *image, uint32_t crc32Checksum) { uint32_t channels = image->numcomps; bool result = false; if (0 < channels) { // Assume the precision is the same for all channels. uint32_t precision = image->comps[0].prec; bool signedData = image->comps[0].sgnd; uint32_t bytes = (precision + 7) / 8; if (signedData) { switch (bytes) { case 1 : result = verifyChecksumForImageForType<int8_t>(image, crc32Checksum); break; case 2 : result = verifyChecksumForImageForType<int16_t>(image, crc32Checksum); break; case 4 : result = verifyChecksumForImageForType<int32_t>(image, crc32Checksum); break; default: std::cerr << "verifyChecksumForImage, unsupported data type, signed bytes - " << bytes << std::endl; break; } } else { switch (bytes) { case 1 : result = verifyChecksumForImageForType<uint8_t>(image, crc32Checksum); break; case 2 : result = verifyChecksumForImageForType<uint16_t>(image, crc32Checksum); break; case 4 : result = verifyChecksumForImageForType<uint32_t>(image, crc32Checksum); break; default: std::cerr << "verifyChecksumForImage, unsupported data type, unsigned bytes - " << bytes << std::endl; break; } } if (!result) { std::cerr << "verifyChecksumForImage, error bytes " << bytes << " signed " << signedData << std::endl; } } else { std::cerr << "'verifyChecksumForImage', no channels in the image." << std::endl; } return result; }
清除資源。
bool AwsDoc::Medical_Imaging::cleanup(const Aws::String &stackName, const Aws::String &dataStoreId, const Aws::Client::ClientConfiguration &clientConfiguration) { bool result = true; if (!stackName.empty() && askYesNoQuestion( "Would you like to delete the stack " + stackName + "? (y/n)")) { std::cout << "Deleting the image sets in the stack." << std::endl; result &= emptyDatastore(dataStoreId, clientConfiguration); printAsterisksLine(); std::cout << "Deleting the stack." << std::endl; result &= deleteStack(stackName, clientConfiguration); } return result; } bool AwsDoc::Medical_Imaging::emptyDatastore(const Aws::String &datastoreID, const Aws::Client::ClientConfiguration &clientConfiguration) { Aws::MedicalImaging::Model::SearchCriteria emptyCriteria; Aws::Vector<Aws::String> imageSetIDs; bool result = false; if (searchImageSets(datastoreID, emptyCriteria, imageSetIDs, clientConfiguration)) { result = true; for (auto &imageSetID: imageSetIDs) { result &= deleteImageSet(datastoreID, imageSetID, clientConfiguration); } } return result; }
-
如需 API 詳細資訊,請參閱《AWS SDK for C++ API 參考》中的下列主題。
注意
GitHub 上提供更多範例。尋找完整範例,並了解如何在 AWS 程式碼範例儲存庫
中設定和執行。 -