341 const torch::Tensor &
input,
343 const torch::Device device(torch::kCUDA,
cuda_device);
344 const torch::Tensor mean =
346 const torch::Tensor standard_deviation =
348 const torch::Tensor normalized_source =
349 ((
input - mean) / standard_deviation).detach();
350 const torch::Tensor minimum = (torch::zeros_like(mean) - mean) /
352 const torch::Tensor maximum = (torch::ones_like(mean) - mean) /
355 const auto resize_tensor = [](
const torch::Tensor &tensor,
357 std::int64_t width) {
358 if (tensor.size(2) == height && tensor.size(3) == width) {
361 return torch::nn::functional::interpolate(
363 torch::nn::functional::InterpolateFuncOptions()
364 .size(std::vector<std::int64_t>{height, width})
365 .mode(torch::kBilinear)
366 .align_corners(
false));
369 const std::int64_t input_height =
input.size(2);
370 const std::int64_t input_width =
input.size(3);
371 std::vector<std::pair<std::int64_t, std::int64_t>> octave_sizes;
372 octave_sizes.reserve(
static_cast<std::size_t
>(options.
octaves));
373 const double minimum_scale = std::min(
378 for (
int octave = options.
octaves - 1; octave >= 0; --octave) {
379 const double scale = std::max(
381 1.0 / std::pow(
static_cast<double>(options.
octave_scale),
383 const std::int64_t height = std::max<std::int64_t>(
385 static_cast<std::int64_t
>(std::lround(input_height * scale)));
386 const std::int64_t width = std::max<std::int64_t>(
388 static_cast<std::int64_t
>(std::lround(input_width * scale)));
389 const std::pair<std::int64_t, std::int64_t> size{height, width};
390 if (octave_sizes.empty() || octave_sizes.back() != size) {
391 octave_sizes.push_back(size);
400 torch::Tensor dream_input;
401 torch::Tensor previous_source;
402 for (std::size_t octave_index = 0;
403 octave_index < octave_sizes.size(); ++octave_index) {
404 const auto [octave_height, octave_width] =
405 octave_sizes[octave_index];
406 const torch::Tensor octave_source = resize_tensor(
407 normalized_source, octave_height, octave_width);
408 if (!dream_input.defined()) {
409 dream_input = octave_source.detach();
411 const torch::Tensor restored_detail =
412 octave_source - resize_tensor(previous_source,
416 (resize_tensor(dream_input.detach(), octave_height,
419 .clamp(minimum, maximum)
422 previous_source = octave_source;
423 dream_input.requires_grad_(
true);
425 for (
int iteration = 0; iteration < options.
iterations;
427 torch::Tensor model_input = dream_input;
429 const std::uint64_t span =
430 static_cast<std::uint64_t
>(options.
jitter * 2 + 1);
431 const std::uint64_t phase =
432 current_frame * 1315423911ULL +
433 octave_index * 2654435761ULL +
434 static_cast<std::uint64_t
>(iteration) * 2246822519ULL;
435 const std::int64_t shift_x =
436 static_cast<std::int64_t
>(phase % span) -
438 const std::int64_t shift_y =
439 static_cast<std::int64_t
>((phase / span) % span) -
441 model_input = torch::roll(
442 dream_input, {shift_y, shift_x}, {2, 3});
444 const std::vector<torch::Tensor> outputs =
445 feature_outputs(
module.forward({model_input}));
447 throw std::runtime_error(
448 "Deep Dream model output count changed during gradient ascent");
451 torch::Tensor target_activation = activation;
454 throw std::runtime_error(
455 "Deep Dream target channel is outside the selected layer's range");
460 const torch::Tensor loss =
461 target_activation.to(torch::kFloat32).square().mean();
462 if (!torch::isfinite(loss).item<bool>()) {
463 throw std::runtime_error(
464 "Deep Dream activation loss is not finite");
468 torch::Tensor gradient = dream_input.grad();
469 if (!gradient.defined() ||
470 !torch::isfinite(gradient).all().item<
bool>()) {
471 throw std::runtime_error(
472 "Deep Dream produced an invalid input gradient");
475 const std::int64_t kernel_size =
477 gradient = torch::nn::functional::avg_pool2d(
479 torch::nn::functional::AvgPool2dFuncOptions(
480 {kernel_size, kernel_size})
484 .count_include_pad(
false));
486 const torch::Tensor mean_gradient =
487 gradient.to(torch::kFloat32).abs().mean();
488 const float gradient_value = mean_gradient.item<
float>();
489 if (!std::isfinite(gradient_value)) {
490 throw std::runtime_error(
491 "Deep Dream produced a non-finite input gradient magnitude");
495 if (gradient_value <= 0.0F) {
496 dream_input.grad().zero_();
501 torch::NoGradGuard no_grad;
505 (mean_gradient + GRADIENT_EPSILON)));
506 dream_input.copy_(torch::maximum(
507 torch::minimum(dream_input, maximum), minimum));
509 dream_input.grad().zero_();
513 torch::Tensor output =
514 (dream_input.detach() * standard_deviation + mean)
517 (output.to(torch::kFloat32) -
input.to(torch::kFloat32))
521 return TensorAscentResult{std::move(output), result};
532 std::string_view layer,
bool use_half) {
534 "Deep Dream model path");
535 const std::filesystem::path model_path =
536 std::filesystem::absolute(filename).lexically_normal();
537 if (!std::filesystem::is_regular_file(model_path)) {
538 throw std::runtime_error(
"Deep Dream model is not a regular file: " +
539 model_path.string());
544 const c10::DeviceIndex device_count = torch::cuda::device_count();
545 if (!torch::cuda::is_available() || device_count == 0) {
546 throw std::runtime_error(
547 "Deep Dream model loading requires an available CUDA device");
549 if (cuda_device < 0 || cuda_device >= device_count) {
550 throw std::runtime_error(
"Deep Dream CUDA device index is outside "
551 "the available range");
554 const torch::Device device(torch::kCUDA, cuda_device);
555 torch::jit::Module module = torch::jit::load(model_path.string(), device);
556 const torch::ScalarType scalar_type =
557 use_half ? torch::kFloat16 : torch::kFloat32;
558 module.to(device, scalar_type);
560 for (torch::Tensor parameter : module.parameters()) {
561 parameter.set_requires_grad(
false);
573 const std::int64_t input_size =
574 std::max<std::int64_t>(64,
implementation->metadata.minimum_input_size);
575 torch::NoGradGuard no_grad;
576 const torch::Tensor
input = torch::zeros(
577 {1,
implementation->metadata.input_channels, input_size, input_size},
578 torch::TensorOptions().dtype(scalar_type).device(device));
579 const std::vector<torch::Tensor> outputs = feature_outputs(
582 throw std::runtime_error(
583 "Deep Dream model output count does not match its metadata");
586 for (
const torch::Tensor &output : outputs) {
587 if (!output.defined() || output.dim() != 4 || output.size(0) != 1 ||
588 !output.is_floating_point() || !output.device().is_cuda() ||
589 output.get_device() != cuda_device || output.size(2) <= 0 ||
590 output.size(3) <= 0) {
591 throw std::runtime_error(
592 "Deep Dream model returned an invalid feature tensor");
596 torch::cuda::synchronize(cuda_device);
615 validate_gradient_options(options);
616 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols < 1 ||
618 throw std::runtime_error(
619 "Deep Dream input must be a non-empty RGBA8 image");
622 cv::Mat working_rgba;
623 const int source_max_dimension = std::max(rgba.cols, rgba.rows);
624 double resize_scale = 1.0;
628 source_max_dimension;
630 if (resize_scale < 1.0) {
631 const cv::Size working_size(
632 std::max(1,
static_cast<int>(std::lround(rgba.cols * resize_scale))),
633 std::max(1,
static_cast<int>(std::lround(rgba.rows * resize_scale))));
634 cv::resize(rgba, working_rgba, working_size, 0.0, 0.0,
639 if (working_rgba.cols <
implementation->metadata.minimum_input_size ||
640 working_rgba.rows <
implementation->metadata.minimum_input_size) {
641 throw std::runtime_error(
642 "Deep Dream working image is smaller than the model minimum");
645 cv::Mat dream_source = working_rgba;
646 cv::Mat transformed_feedback;
647 cv::Mat blended_source;
652 const cv::Point2f center(
653 static_cast<float>(working_rgba.cols - 1) * 0.5F,
654 static_cast<float>(working_rgba.rows - 1) * 0.5F);
655 const cv::Mat transform = cv::getRotationMatrix2D(
658 transformed_feedback, transform,
660 cv::INTER_LINEAR, cv::BORDER_REFLECT_101);
661 cv::addWeighted(transformed_feedback, options.
feedback,
662 working_rgba, 1.0F - options.
feedback, 0.0,
664 dream_source = blended_source;
668 cv::cvtColor(dream_source, rgb, cv::COLOR_RGBA2RGB);
670 rgb.convertTo(rgb_float, CV_32FC3, 1.0 / 255.0);
672 const torch::Device device(torch::kCUDA,
674 const torch::Tensor
input =
675 torch::from_blob(rgb_float.data, {rgb_float.rows, rgb_float.cols, 3},
676 torch::TensorOptions().dtype(torch::kFloat32))
681 TensorAscentResult tensor_result =
684 torch::Tensor output = std::move(tensor_result.output);
686 output = output.squeeze(0)
695 cv::Mat dreamed_rgb(rgb.rows, rgb.cols, CV_8UC3,
696 output.data_ptr<std::uint8_t>());
697 cv::Mat dreamed_rgba;
698 cv::cvtColor(dreamed_rgb, dreamed_rgba, cv::COLOR_RGB2RGBA);
700 if (dreamed_rgba.size() != rgba.size()) {
702 cv::resize(dreamed_rgba, restored, rgba.size(), 0.0, 0.0,
704 dreamed_rgba = restored;
706 std::vector<cv::Mat> original_channels;
707 std::vector<cv::Mat> dreamed_channels;
708 cv::split(rgba, original_channels);
709 cv::split(dreamed_rgba, dreamed_channels);
710 dreamed_channels[3] = original_channels[3];
711 cv::merge(dreamed_channels, rgba);
716 const cv::cuda::GpuMat &rgba, cv::cuda::GpuMat &output,
718 validate_gradient_options(options);
719 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols < 1 ||
721 throw std::runtime_error(
722 "Deep Dream CUDA input must be a non-empty RGBA8 image");
725 const int source_max_dimension = std::max(rgba.cols, rgba.rows);
726 double resize_scale = 1.0;
730 source_max_dimension;
733 const cv::cuda::GpuMat *working_rgba = &rgba;
734 if (resize_scale < 1.0) {
735 const cv::Size working_size(
736 std::max(1,
static_cast<int>(std::lround(rgba.cols * resize_scale))),
737 std::max(1,
static_cast<int>(std::lround(rgba.rows * resize_scale))));
739 working_size, 0.0, 0.0, cv::INTER_AREA, stream);
742 if (working_rgba->cols <
746 throw std::runtime_error(
747 "Deep Dream CUDA working image is smaller than the model minimum");
750 const cv::cuda::GpuMat *dream_source = working_rgba;
755 working_rgba->size()) {
756 const cv::Point2f center(
757 static_cast<float>(working_rgba->cols - 1) * 0.5F,
758 static_cast<float>(working_rgba->rows - 1) * 0.5F);
759 const cv::Mat transform = cv::getRotationMatrix2D(
761 cv::cuda::warpAffine(
764 working_rgba->size(), cv::INTER_LINEAR,
765 cv::BORDER_REFLECT_101, cv::Scalar(), stream);
766 cv::cuda::addWeighted(
768 *working_rgba, 1.0F - options.
feedback, 0.0,
772 stream.waitForCompletion();
774 const torch::Device device(torch::kCUDA,
776 const torch::TensorOptions byte_options =
777 torch::TensorOptions().dtype(torch::kUInt8).device(device);
778 const torch::Tensor source_rgba = torch::from_blob(
779 rgba.data, {rgba.rows, rgba.cols, 4},
780 {static_cast<std::int64_t>(rgba.step), 4, 1}, byte_options);
781 const torch::Tensor working_rgba_tensor = torch::from_blob(
783 {dream_source->rows, dream_source->cols, 4},
784 {static_cast<std::int64_t>(dream_source->step), 4, 1},
786 const torch::Tensor
input =
787 working_rgba_tensor.narrow(2, 0, 3)
794 TensorAscentResult tensor_result =
796 const auto rgba_tensor = [](
const torch::Tensor &rgb,
797 const torch::Tensor &alpha) {
798 const torch::Tensor rgb_bytes =
804 return torch::cat({rgb_bytes, alpha}, 2).contiguous();
807 const torch::Tensor working_output = rgba_tensor(
808 tensor_result.output,
809 working_rgba_tensor.narrow(2, 3, 1));
811 dream_source->rows, dream_source->cols, CV_8UC4);
816 working_output.data_ptr<std::uint8_t>(),
817 static_cast<std::size_t
>(dream_source->cols) * 4U,
818 static_cast<std::size_t
>(dream_source->cols) * 4U,
819 dream_source->rows, cudaMemcpyDeviceToDevice),
820 "Deep Dream could not preserve its CUDA feedback frame");
822 torch::Tensor final_rgb = tensor_result.output;
823 if (dream_source->cols != rgba.cols ||
824 dream_source->rows != rgba.rows) {
825 final_rgb = torch::nn::functional::interpolate(
827 torch::nn::functional::InterpolateFuncOptions()
828 .size(std::vector<std::int64_t>{rgba.rows, rgba.cols})
829 .mode(torch::kBilinear)
830 .align_corners(
false));
832 const torch::Tensor final_output =
833 rgba_tensor(final_rgb, source_rgba.narrow(2, 3, 1));
835 output.create(rgba.rows, rgba.cols, CV_8UC4);
837 cudaMemcpy2D(output.data, output.step,
838 final_output.data_ptr<std::uint8_t>(),
839 static_cast<std::size_t
>(rgba.cols) * 4U,
840 static_cast<std::size_t
>(rgba.cols) * 4U, rgba.rows,
841 cudaMemcpyDeviceToDevice),
842 "Deep Dream could not publish its CUDA output frame");
843 return tensor_result.metrics;