As far as i know, the minimal required permission set solution in this case is to create a store procedure thats executed within an impersonated context. Do this by:
create procedure [schema].[get_idx_info] with execute as [user with view server state perms and select perms or OWNER] begin select db_name() as DBName, object_schema_name(i.object_id) as SchemaName, object_name(i.object_id) as TableName, isnull(i.name,'(HEAP)') as IndexName, ps.SizeInPages from sys.indexes i inner join ( select ps.object_id,ps.index_id,sum(ps.row_count) as IndexRows, sum(ps.used_page_count) as SizeInPages, count(*) as PartitionCount from sys.dm_db_partition_stats ps group by ps.object_id,ps.index_id ) ps on ps.object_id = i.object_id and ps.index_id = i.index_id where object_schema_name(i.object_id) not in ('cdc','sys') end see: (https://learn.microsoft.com/en-us/sql/t-sql/statements/execute-as-clause-transact-sql)
Then grant users execute on that stored procedure. This will allow the user to view that info w/o additional permissions (aside from EXECUTE on the SP).
By doing this, the stored procedure will execute with the permissions of either the designated user or (if set to OWNER) the creator/owner of the stored procedure, instead of the permissions of the user calling EXECUTE (this is default behavior, which is why sys.indexes is filtering by select permissions in your case).