diff --git a/ift/encoder/closure_glyph_segmenter.cc b/ift/encoder/closure_glyph_segmenter.cc index fad3b60..5b6251f 100644 --- a/ift/encoder/closure_glyph_segmenter.cc +++ b/ift/encoder/closure_glyph_segmenter.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "absl/container/btree_map.h" @@ -193,11 +194,10 @@ struct SegmentOrdering { }; static std::vector PreGroupSegments( - const btree_map& merge_groups, - const std::vector& ordering, - const std::vector& subset_definitions, - std::vector& segment_index_map -) { + const btree_map& merge_groups, + const std::vector& ordering, + const std::vector& subset_definitions, + std::vector& segment_index_map) { segment_index_map.resize(subset_definitions.size()); std::vector segments; @@ -206,9 +206,10 @@ static std::vector PreGroupSegments( auto merge_group_it = merge_groups.begin(); auto ordering_it = ordering.begin(); - while (ordering_it != ordering.end()) { + while (ordering_it != ordering.end()) { const auto& o = *ordering_it; - if (o.group_index != last_group_index && merge_group_it != merge_groups.end()) { + if (o.group_index != last_group_index && + merge_group_it != merge_groups.end()) { merge_group_it++; } @@ -217,11 +218,11 @@ static std::vector PreGroupSegments( strategy = &(merge_group_it->second); } - Segment segment = Segment{subset_definitions[o.original_index], o.probability}; + Segment segment = + Segment{subset_definitions[o.original_index], o.probability}; ordering_it++; - if (strategy == nullptr || - strategy->PreClosureGroupSize() <= 1 || + if (strategy == nullptr || strategy->PreClosureGroupSize() <= 1 || o.probability.Max() > strategy->PreClosureProbabilityThreshold()) { segment_index_map[o.original_index] = i; } else { @@ -232,7 +233,8 @@ static std::vector PreGroupSegments( break; } - segment.Definition().Union(subset_definitions[ordering_it->original_index]); + segment.Definition().Union( + subset_definitions[ordering_it->original_index]); segment_index_map[ordering_it->original_index] = i; ordering_it++; @@ -240,7 +242,9 @@ static std::vector PreGroupSegments( } if (strategy->UseCosts()) { - segment.SetProbability(strategy->ProbabilityCalculator()->ComputeProbability(segment.Definition())); + segment.SetProbability( + strategy->ProbabilityCalculator()->ComputeProbability( + segment.Definition())); } } @@ -328,7 +332,8 @@ static StatusOr> ToOrderedSegments( // maps from index in subset_definitions to the new ordering. std::vector segment_index_map; - std::vector segments = PreGroupSegments(merge_groups, ordering, subset_definitions, segment_index_map); + std::vector segments = PreGroupSegments( + merge_groups, ordering, subset_definitions, segment_index_map); VLOG(0) << segments.size() << " segments after pregrouping."; btree_map new_merge_groups; @@ -344,8 +349,13 @@ static StatusOr> ToOrderedSegments( remapped_full.insert(s_prime); } + std::string name = std::to_string(group_index); + if (strategy.Name().has_value()) { + name = *strategy.Name(); + } - VLOG(0) << " Merge group " << group_index << " has " << remapped.size() << " segments."; + VLOG(0) << " Merge group " << name << " has " << remapped.size() + << " segments."; group_index++; if (!new_merge_groups.insert(std::make_pair(remapped, std::move(strategy))) @@ -392,6 +402,7 @@ StatusOr ClosureGlyphSegmenter::CodepointToGlyphSegments( const std::vector& subset_definitions, btree_map merge_groups, bool place_fallback_in_init) const { + for (const auto& [segments, strategy] : merge_groups) { if (strategy.UseCosts()) { TRYV(CheckForDisjointCodepoints(subset_definitions, segments)); @@ -448,8 +459,13 @@ StatusOr ClosureGlyphSegmenter::CodepointToGlyphSegments( // ### Iteratively merge segments and incrementally reprocess affected data. size_t merger_index = 0; + std::string merger_name = std::to_string(merger_index); + if (mergers[merger_index].Strategy().Name().has_value()) { + merger_name = *mergers[merger_index].Strategy().Name(); + } + segment_index_t last_merged_segment_index = 0; - VLOG(0) << "Starting merge selection for merge group " << merger_index + VLOG(0) << "Starting merge selection for merge group " << merger_name << std::endl << " " << mergers[merger_index].NumInscopeSegments() << " inscope segments, " << mergers[merger_index].NumCutoffSegments() @@ -461,8 +477,13 @@ StatusOr ClosureGlyphSegmenter::CodepointToGlyphSegments( if (!merged.has_value()) { merger_index++; + if (merger_index < mergers.size()) { - VLOG(0) << "Merge group finished, starting next group " << merger_index + std::string merger_name = std::to_string(merger_index); + if (mergers[merger_index].Strategy().Name().has_value()) { + merger_name = *mergers[merger_index].Strategy().Name(); + } + VLOG(0) << "Merge group finished, starting next group " << merger_name << std::endl << " " << mergers[merger_index].NumInscopeSegments() << " inscope segments, " diff --git a/ift/encoder/closure_glyph_segmenter_test.cc b/ift/encoder/closure_glyph_segmenter_test.cc index 41b7d49..508427d 100644 --- a/ift/encoder/closure_glyph_segmenter_test.cc +++ b/ift/encoder/closure_glyph_segmenter_test.cc @@ -1427,19 +1427,11 @@ if (s0 AND s2) then p2 )"); } - TEST_F(ClosureGlyphSegmenterTest, MultipleMergeGroups_PreGrouping) { UnicodeFrequencies freq{ - {{' ', ' '}, 100}, - {{'d', 'd'}, 100}, - {{'a', 'a'}, 60}, - {{'e', 'e'}, 30}, - {{'b', 'b'}, 29}, - {{'f', 'f'}, 28}, - {{'c', 'c'}, 10}, - {{'g', 'g'}, 9}, - {{'h', 'h'}, 5}, - {{'i', 'i'}, 1}, // 8 + {{' ', ' '}, 100}, {{'d', 'd'}, 100}, {{'a', 'a'}, 60}, {{'e', 'e'}, 30}, + {{'b', 'b'}, 29}, {{'f', 'f'}, 28}, {{'c', 'c'}, 10}, {{'g', 'g'}, 9}, + {{'h', 'h'}, 5}, {{'i', 'i'}, 1}, // 8 }; MergeStrategy costs = *MergeStrategy::CostBased(std::move(freq), 0, 1); @@ -1473,8 +1465,8 @@ TEST_F(ClosureGlyphSegmenterTest, MultipleMergeGroups_PreGrouping) { // Group 1 {'d'}, {'a'}, - {'e', 'b', 'f'}, // pre merge - {'c', 'g'}, // pre merge + {'e', 'b', 'f'}, // pre merge + {'c', 'g'}, // pre merge // Shared {'h'}, {'i'}, diff --git a/ift/encoder/estimated_patch_size_cache.cc b/ift/encoder/estimated_patch_size_cache.cc index 7ef33f9..94020b4 100644 --- a/ift/encoder/estimated_patch_size_cache.cc +++ b/ift/encoder/estimated_patch_size_cache.cc @@ -3,8 +3,8 @@ #include "common/int_set.h" #include "common/try.h" -using absl::StatusOr; using absl::flat_hash_set; +using absl::StatusOr; using common::GlyphSet; namespace ift::encoder { diff --git a/ift/encoder/estimated_patch_size_cache.h b/ift/encoder/estimated_patch_size_cache.h index 5349a69..6002aa5 100644 --- a/ift/encoder/estimated_patch_size_cache.h +++ b/ift/encoder/estimated_patch_size_cache.h @@ -2,6 +2,7 @@ #define IFT_ENCODER_ESTIMATED_PATCH_SIZE_CACHE_H_ #include + #include "absl/status/statusor.h" #include "common/font_data.h" #include "common/int_set.h" @@ -18,14 +19,13 @@ class EstimatedPatchSizeCache : public PatchSizeCache { public: static absl::StatusOr> New(hb_face_t* face) { double compression_ratio = TRY(EstimateCompressionRatio(face)); - return std::unique_ptr(new EstimatedPatchSizeCache(face, compression_ratio)); + return std::unique_ptr( + new EstimatedPatchSizeCache(face, compression_ratio)); } absl::StatusOr GetPatchSize(const common::GlyphSet& gids) override; - double CompressionRatio() const { - return compression_ratio_; - } + double CompressionRatio() const { return compression_ratio_; } private: explicit EstimatedPatchSizeCache(hb_face_t* original_face, diff --git a/ift/encoder/estimated_patch_size_cache_test.cc b/ift/encoder/estimated_patch_size_cache_test.cc index c72e5ba..24df11d 100644 --- a/ift/encoder/estimated_patch_size_cache_test.cc +++ b/ift/encoder/estimated_patch_size_cache_test.cc @@ -1,17 +1,16 @@ #include "ift/encoder/estimated_patch_size_cache.h" +#include "common/font_data.h" #include "common/font_helper.h" #include "common/int_set.h" #include "gtest/gtest.h" -#include "common/font_data.h" - -using common::hb_face_unique_ptr; -using common::make_hb_face; -using common::hb_blob_unique_ptr; -using common::make_hb_blob; using common::FontHelper; using common::GlyphSet; +using common::hb_blob_unique_ptr; +using common::hb_face_unique_ptr; +using common::make_hb_blob; +using common::make_hb_face; namespace ift::encoder { @@ -23,15 +22,14 @@ class EstimatedPatchSizeCacheTest : public ::testing::Test { roboto = make_hb_face(hb_face_create(blob.get(), 0)); } - double CompressionRatio(GlyphSet gids, double expected_compression_ratio) { - uint32_t raw_outline_size = - *FontHelper::TotalGlyphData(roboto.get(), gids); - double fixed_size = 1 + 7 * 4; // header - fixed_size += (double) (5 + gids.size() * 2 + 4 + (gids.size() + 1)*4) * expected_compression_ratio; // glyph patches header + uint32_t raw_outline_size = *FontHelper::TotalGlyphData(roboto.get(), gids); + double fixed_size = 1 + 7 * 4; // header + fixed_size += (double)(5 + gids.size() * 2 + 4 + (gids.size() + 1) * 4) * + expected_compression_ratio; // glyph patches header auto estimated = *EstimatedPatchSizeCache::New(roboto.get()); uint32_t compressed_size = *estimated->GetPatchSize(gids); - return (double) (compressed_size - fixed_size) / (double) raw_outline_size; + return (double)(compressed_size - fixed_size) / (double)raw_outline_size; } hb_face_unique_ptr roboto; @@ -39,8 +37,9 @@ class EstimatedPatchSizeCacheTest : public ::testing::Test { TEST_F(EstimatedPatchSizeCacheTest, PatchSize) { // There should be a consistent compression ratio between patches. - ASSERT_NEAR(this->CompressionRatio(GlyphSet {44, 47, 49}, 0.457), 0.46, 0.01); - ASSERT_NEAR(CompressionRatio(GlyphSet {45, 48, 50, 51, 52, 53}, 0.457), 0.46, 0.01); + ASSERT_NEAR(this->CompressionRatio(GlyphSet{44, 47, 49}, 0.457), 0.46, 0.01); + ASSERT_NEAR(CompressionRatio(GlyphSet{45, 48, 50, 51, 52, 53}, 0.457), 0.46, + 0.01); } } // namespace ift::encoder \ No newline at end of file diff --git a/ift/encoder/glyph_segmentation.cc b/ift/encoder/glyph_segmentation.cc index 0e695b5..a833419 100644 --- a/ift/encoder/glyph_segmentation.cc +++ b/ift/encoder/glyph_segmentation.cc @@ -2,13 +2,11 @@ #include #include -#include #include #include "absl/container/btree_map.h" #include "absl/container/btree_set.h" #include "absl/container/flat_hash_map.h" -#include "absl/container/flat_hash_set.h" #include "absl/status/statusor.h" #include "common/font_helper.h" #include "common/int_set.h" @@ -144,6 +142,14 @@ ProtoType TagsToSetProto(const btree_set& set) { return values; } +void GlyphSegmentation::SubsetDefinitionToSegment(const SubsetDefinition& def, + SegmentProto& segment_proto) { + (*segment_proto.mutable_codepoints()) = + ToSetProto(def.codepoints); + (*segment_proto.mutable_features()) = + TagsToSetProto(def.feature_tags); +} + SegmentationPlan GlyphSegmentation::ToSegmentationPlanProto() const { SegmentationPlan config; @@ -151,10 +157,7 @@ SegmentationPlan GlyphSegmentation::ToSegmentationPlanProto() const { for (const auto& s : Segments()) { if (!s.Empty()) { SegmentProto segment_proto; - (*segment_proto.mutable_codepoints()) = - ToSetProto(s.codepoints); - (*segment_proto.mutable_features()) = - TagsToSetProto(s.feature_tags); + SubsetDefinitionToSegment(s, segment_proto); (*config.mutable_segments())[set_index++] = segment_proto; } else { set_index++; diff --git a/ift/encoder/glyph_segmentation.h b/ift/encoder/glyph_segmentation.h index 20c733a..06b3fd5 100644 --- a/ift/encoder/glyph_segmentation.h +++ b/ift/encoder/glyph_segmentation.h @@ -89,6 +89,9 @@ class GlyphSegmentation { return init_font_segment_; }; + static void SubsetDefinitionToSegment(const SubsetDefinition& def, + SegmentProto& segment_proto); + SegmentationPlan ToSegmentationPlanProto() const; static absl::Status GroupsToSegmentation( diff --git a/ift/encoder/merge_strategy.h b/ift/encoder/merge_strategy.h index c532057..08fa9a8 100644 --- a/ift/encoder/merge_strategy.h +++ b/ift/encoder/merge_strategy.h @@ -114,6 +114,18 @@ class MergeStrategy { bool UseCosts() const { return use_costs_; } bool UsePatchMerges() const { return use_patch_merges_; } + std::optional Name() const { + if (name_.has_value()) { + return name_; + } else { + return std::nullopt; + } + } + + void SetName(std::string name) { + name_ = name; + } + uint32_t NetworkOverheadCost() const { return network_overhead_cost_; } uint32_t MinimumGroupSize() const { return min_group_size_; } uint32_t PatchSizeMinBytes() const { return patch_size_min_bytes_; } @@ -145,9 +157,7 @@ class MergeStrategy { return init_font_merge_probability_threshold_; } - uint32_t PreClosureGroupSize() const { - return pre_closure_group_size_; - } + uint32_t PreClosureGroupSize() const { return pre_closure_group_size_; } double PreClosureProbabilityThreshold() const { return pre_closure_probability_threshold_; @@ -193,6 +203,7 @@ class MergeStrategy { patch_size_max_bytes_(patch_size_max_bytes), probability_calculator_(nullptr) {} + std::optional name_ = std::nullopt; bool use_costs_; uint32_t network_overhead_cost_; uint32_t min_group_size_; diff --git a/util/BUILD b/util/BUILD index e1d9f89..16f9316 100644 --- a/util/BUILD +++ b/util/BUILD @@ -42,6 +42,7 @@ proto_library( "//visibility:public", ], deps = [ + ":segmentation_plan_proto", ":common_proto", ], ) diff --git a/util/closure_glyph_keyed_segmenter_util.cc b/util/closure_glyph_keyed_segmenter_util.cc index 7c6dc63..82ba299 100644 --- a/util/closure_glyph_keyed_segmenter_util.cc +++ b/util/closure_glyph_keyed_segmenter_util.cc @@ -127,6 +127,39 @@ static Status Analysis(hb_face_t* font, return absl::OkStatus(); } +static void AddTableKeyedSegments( + SegmentationPlan& plan, + const btree_map& merge_groups, + const std::vector& segments, + const SubsetDefinition& init_segment) { + std::vector table_keyed_segments; + for (const auto& [segment_ids, _] : merge_groups) { + SubsetDefinition new_segment; + for (uint32_t s : segment_ids) { + new_segment.Union(segments.at(s)); + } + new_segment.Subtract(init_segment); + table_keyed_segments.push_back(new_segment); + } + + uint32_t max_id = 0; + for (const auto& [id, _] : plan.segments()) { + if (id > max_id) { + max_id = id; + } + } + + uint32_t next_id = max_id + 1; + auto* plan_segments = plan.mutable_segments(); + for (const SubsetDefinition& def : table_keyed_segments) { + GlyphSegmentation::SubsetDefinitionToSegment(def, + (*plan_segments)[next_id]); + SegmentsProto* segment_ids = plan.add_non_glyph_segments(); + segment_ids->add_values(next_id); + next_id++; + } +} + static Status Main(const std::vector args) { hb_face_unique_ptr font = TRY(LoadFont(absl::GetFlag(FLAGS_input_font).c_str())); @@ -137,12 +170,14 @@ static Status Main(const std::vector args) { CodepointSet font_codepoints = FontHelper::ToCodepointsSet(font.get()); SubsetDefinition init_segment = config_util.SegmentProtoToSubsetDefinition(config.initial_segment()); + std::vector segments; btree_map merge_groups = TRY(config_util.ConfigToMergeGroups(config, font_codepoints, segments)); - ClosureGlyphSegmenter segmenter(config.brotli_quality(), - config.brotli_quality_for_initial_font_merging()); + ClosureGlyphSegmenter segmenter( + config.brotli_quality(), + config.brotli_quality_for_initial_font_merging()); GlyphSegmentation segmentation = TRY(segmenter.CodepointToGlyphSegments( font.get(), init_segment, segments, merge_groups, config.move_fallback_glyphs_into_initial_font())); @@ -154,12 +189,19 @@ static Status Main(const std::vector args) { plan.clear_initial_codepoints(); } + if (config.generate_table_keyed_segments()) { + AddTableKeyedSegments(plan, merge_groups, segments, init_segment); + } + + SegmentationPlan combined = config.base_segmentation_plan(); + combined.MergeFrom(plan); + // TODO(garretrieger): assign a basic (single segment) table keyed config. // Later on the input to this util should include information on how the // segments should be grouped together for the table keyed portion of the // font. std::string config_string; - TextFormat::PrintToString(plan, &config_string); + TextFormat::PrintToString(combined, &config_string); std::cout << config_string; } else { // No config requested, just output a simplified plain text representation diff --git a/util/segmenter_config.proto b/util/segmenter_config.proto index 81d734d..5dfb51e 100644 --- a/util/segmenter_config.proto +++ b/util/segmenter_config.proto @@ -1,5 +1,6 @@ edition = "2023"; +import "util/segmentation_plan.proto"; import "util/common.proto"; // This messages provides the configuration details for closure_glyph_keyed_segmenter_util @@ -43,18 +44,23 @@ message SegmenterConfig { // are instead moved into the initial font. bool move_fallback_glyphs_into_initial_font = 2 [default = true]; + // If enabled then the generated segmentation plan will include a set of table keyed segments. + // One table keyed segment will be generated per merge group, including the auto generated + // ungrouped one. + bool generate_table_keyed_segments = 3 [default = false]; + // The set of segments the font is initially broken up into. The key in the map is an ID // used to refer to the segment in other parts of the config. Segments must be disjoint. // // If this is not specified then a segmentation where each codepoint in the font is placed // into it's own segment will be used. In this case each segment id is the value of // the codepoint in that segment - map segments = 3; + map segments = 4; // When utilizing automated segment generation ('segments' is unspecified), this provides // a list of feature groups that should be added to the automated segment list. Each group // will be one segment. These use their own id space separate from segments. - map feature_segments = 4; + map feature_segments = 5; // When generating compressed patches (to evaluate their size) this is the brotli quality // level used. Merge selection is typically bottle necked on brotli compression so higher @@ -65,65 +71,71 @@ message SegmenterConfig { // If quality is set to '0' this disables brotli compression and instead estimates the // effect of compression using a fixed compression ratio calculated based on how well // the glyph data in the input font compresses. - uint32 brotli_quality = 5 [default = 8]; + uint32 brotli_quality = 6 [default = 8]; // During processing to determine which segments to move into the initial font this is // the brotli quality used for generating compressed patches (to evaluate their size). // The init font merge is more sensitive to lower brotli qualities, so this allows a higher // brotli quality to be used in this phase if desired. - uint32 brotli_quality_for_initial_font_merging = 6 [default = 11]; + uint32 brotli_quality_for_initial_font_merging = 7 [default = 11]; // These base configs define the common config setings used by the merge groups. // Each individual merge group's config is created by starting with the base config of the // matching type and then overiding any fields specified in the merge group config. - HeuristicConfiguration base_heuristic_config = 7; - CostConfiguration base_cost_config = 8; + HeuristicConfiguration base_heuristic_config = 8; + CostConfiguration base_cost_config = 9; // If this is provided, then for any segments that are not covered by one of the merge groups // the heuristic merger will be used to group them according to this config. The heuristic // merger is used since for the ungrouped segments we do not have any available frequency data. // // Like the merge groups this config will be combined with base_heuristic_config. - HeuristicConfiguration ungrouped_config = 9; + HeuristicConfiguration ungrouped_config = 10; // This is the group size (number of segments merged together) used in preprocess merging // of any segments not covered by a merge group. Setting to 1 disables preprocess merging // of ungrouped segments. - uint32 preprocess_merging_group_size_for_ungrouped = 10 [default = 1]; + uint32 preprocess_merging_group_size_for_ungrouped = 11 [default = 1]; // Merge groups specify how merging will be performed for groups of segments. // Any segments that are not covered by any merge group will not be merged. // Merge groups are not required to be disjoint and may have overlapping segments. - repeated MergeGroup merge_groups = 11; + repeated MergeGroup merge_groups = 12; + + // When provide the generated segmentation plan will be merged onto this base plan. + SegmentationPlan base_segmentation_plan = 13; } // For a given set of segments this configures how merging will be performed. Each merge group // is processed independently. Merges will only be made between things within the same group. // Each merge group has it's own configuration and frequency data. message MergeGroup { + // Used to identify the group in logging. + string name = 1; + // The set of segments that this merge group covers. May overlap with other merge groups. // If this is not specified and a heuristic config is used, then it will default to all segments. // If this is not specified and a cost config is used, then this will default to the set of // segments the provided frequency data covers. - SegmentsProto segment_ids = 1; + SegmentsProto segment_ids = 2; // Adds in segments provided in the 'feature_segments' mapping. - SegmentsProto feature_segment_ids = 2; + SegmentsProto feature_segment_ids = 3; // This is the group size (number of segments merged together) used in preprocess merging // of any segments covered by this merge group. Setting to 1 disables preprocess merging // of ungrouped segments. - uint32 preprocess_merging_group_size = 3 [default = 1]; + uint32 preprocess_merging_group_size = 4 [default = 1]; // If frequency data is available only segments with probability less than this will // be included in the preprocess merging phase. Setting this to 1.0 will make preprocess // merging apply to all segments. Has no effect if this merge group is using heuristic // merging. - double preprocess_merging_probability_threshold = 4 [default = 1.0]; + double preprocess_merging_probability_threshold = 5 [default = 1.0]; oneof config { - HeuristicConfiguration heuristic_config = 5; - CostConfiguration cost_config = 6; + HeuristicConfiguration heuristic_config = 6; + CostConfiguration cost_config = 7; } } diff --git a/util/segmenter_config_util.cc b/util/segmenter_config_util.cc index 4ce7903..eb57dda 100644 --- a/util/segmenter_config_util.cc +++ b/util/segmenter_config_util.cc @@ -157,9 +157,11 @@ SegmenterConfigUtil::ProtoToMergeGroup( SegmentId{.feature = true, .id_value = feature_group_id})); } + MergeStrategy strategy = MergeStrategy::None(); + if (group.has_cost_config()) { CodepointSet covered_codepoints; - MergeStrategy strategy = TRY( + strategy = TRY( ProtoToStrategy(base_cost, group.cost_config(), covered_codepoints)); if (group.has_segment_ids()) { @@ -173,9 +175,8 @@ SegmenterConfigUtil::ProtoToMergeGroup( } strategy.SetPreClosureGroupSize(group.preprocess_merging_group_size()); - strategy.SetPreClosureProbabilityThreshold(group.preprocess_merging_probability_threshold()); - - return std::make_pair(segment_indices, strategy); + strategy.SetPreClosureProbabilityThreshold( + group.preprocess_merging_probability_threshold()); } else { if (group.has_segment_ids()) { segment_indices.union_set(MapToIndices(group.segment_ids(), id_to_index)); @@ -184,14 +185,18 @@ SegmenterConfigUtil::ProtoToMergeGroup( segment_indices.insert_range(0, id_to_index.size() - 1); } - MergeStrategy strategy = + strategy = ::util::ProtoToStrategy(base_heuristic, group.heuristic_config()); strategy.SetPreClosureGroupSize(group.preprocess_merging_group_size()); strategy.SetPreClosureProbabilityThreshold(1.0); + } - return std::make_pair(segment_indices, strategy); + if (group.has_name()) { + strategy.SetName(group.name()); } + + return std::make_pair(segment_indices, strategy); } StatusOr> @@ -233,7 +238,9 @@ SegmenterConfigUtil::ConfigToMergeGroups( MergeStrategy strategy = util::ProtoToStrategy(config.base_heuristic_config(), config.ungrouped_config()); - strategy.SetPreClosureGroupSize(config.preprocess_merging_group_size_for_ungrouped()); + strategy.SetName("Ungrouped"); + strategy.SetPreClosureGroupSize( + config.preprocess_merging_group_size_for_ungrouped()); strategy.SetPreClosureProbabilityThreshold(1.0); merge_groups.insert(std::make_pair(uncovered_segments, strategy));