diff --git a/IRaCIS.Core.Application/Helper/OSSService.cs b/IRaCIS.Core.Application/Helper/OSSService.cs index 3e56770cc..34efdf74c 100644 --- a/IRaCIS.Core.Application/Helper/OSSService.cs +++ b/IRaCIS.Core.Application/Helper/OSSService.cs @@ -204,6 +204,8 @@ public interface IOSSService public Task GetObjectSizeAsync(string sourcePath); public Task SyncFileAsync(string objectKey, ObjectStoreUse source, ObjectStoreUse destination, CancellationToken ct = default); + + public void ConvertPrefixToStandard(string prefix); } @@ -215,6 +217,7 @@ public class OSSService(IOptionsMonitor options, private AliyunOSSTempToken AliyunOSSTempToken { get; set; } private AWSTempToken AWSTempToken { get; set; } + public object result { get; private set; } @@ -652,6 +655,90 @@ public class OSSService(IOptionsMonitor options, } + /// + /// 将某个路径下的归档的文件 转为标准存储 + /// + /// + public void ConvertPrefixToStandard(string prefix) + { + + BackBatchGetToken(); + + if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS") + { + + var aliConfig = ObjectStoreServiceOptions.AliyunOSS; + var _ossClient = new OssClient( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? aliConfig.EndPoint : aliConfig.InternalEndpoint, + AliyunOSSTempToken.AccessKeyId, + AliyunOSSTempToken.AccessKeySecret, + AliyunOSSTempToken.SecurityToken + ); + var bucketName = aliConfig.BucketName; + + try + { + ObjectListing objectListing = null; + string nextMarker = null; + do + { + // 使用 prefix 模拟目录结构,设置 MaxKeys 和 NextMarker + objectListing = _ossClient.ListObjects(new Aliyun.OSS.ListObjectsRequest(bucketName) + { + Prefix = prefix, + MaxKeys = 1000, + Marker = nextMarker + }); + + + foreach (var obj in objectListing.ObjectSummaries) + { + try + { + // 👇 跳过已经是标准存储的(优化) + if (obj.StorageClass == StorageClass.Standard.ToString()) + continue; + + var metadata = new ObjectMetadata(); + + // 👇 关键:手动加 Header + metadata.AddHeader("x-oss-storage-class", "Standard"); + + var copyRequest = new Aliyun.OSS.CopyObjectRequest(bucketName, obj.Key, bucketName, obj.Key) { NewObjectMetadata = metadata }; + + + _ossClient.CopyObject(copyRequest); + + } + catch (Exception ex) + { + Log.Logger.Error($"❌ 失败: {obj.Key}, 错误: {ex.Message}"); + } + } + + + + // 设置 NextMarker 以获取下一页的数据 + nextMarker = objectListing.NextMarker; + + } while (objectListing.IsTruncated); + } + catch (Exception ex) + { + Log.Logger.Error($"Error: {ex.Message}"); + } + + + } + else if (ObjectStoreServiceOptions.ObjectStoreUse == "AWS") + { + throw new BusinessValidationFailedException("未定义的存储介质类型"); + } + + + } + + /// /// 坑方法,会清空之前的规则 /// @@ -1507,20 +1594,20 @@ public class OSSService(IOptionsMonitor options, GetObjectStoreTempToken(objectUse: config.Primary); - await DeleteFromPrefixInternal(config.Primary,prefix, isCache); + await DeleteFromPrefixInternal(config.Primary, prefix, isCache); } } else { GetObjectStoreTempToken(); - await DeleteFromPrefixInternal(ObjectStoreServiceOptions.ObjectStoreUse,prefix, isCache); + await DeleteFromPrefixInternal(ObjectStoreServiceOptions.ObjectStoreUse, prefix, isCache); } } - private async Task DeleteFromPrefixInternal(string objectUse ,string prefix, bool isCache = false) + private async Task DeleteFromPrefixInternal(string objectUse, string prefix, bool isCache = false) { if (objectUse == "AliyunOSS") { diff --git a/IRaCIS.Core.Application/Helper/OtherTool/DicomSortHelper.cs b/IRaCIS.Core.Application/Helper/OtherTool/DicomSortHelper.cs index ab0d67063..9bb5e2816 100644 --- a/IRaCIS.Core.Application/Helper/OtherTool/DicomSortHelper.cs +++ b/IRaCIS.Core.Application/Helper/OtherTool/DicomSortHelper.cs @@ -6,6 +6,7 @@ using FellowOakDicom.IO.Buffer; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; +using System.Data; namespace IRaCIS.Core.Application.Helper; @@ -158,8 +159,16 @@ public sealed class DicomMaskOptions /// /// 当 AutoSelectGrayscaleMaskValue=false 时,使用该值。 /// 当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。 + /// 灰度图像的遮挡像素值,默认 0。 + /// 注意:这表示写入的原始像素值,不保证视觉上一定为黑色。 /// public int? GrayscaleMaskValue { get; init; } + + /// + /// PALETTE COLOR 图像使用的遮盖索引。 + /// null 表示自动从调色板中选择最暗的一个索引。 + /// + public int? PaletteColorMaskIndex { get; init; } } @@ -213,38 +222,46 @@ public static class DicomPixelMasker var planarConfiguration = originalDataset.GetSingleValueOrDefault(DicomTag.PlanarConfiguration, (ushort)0); Console.WriteLine($"Rows={rows}, Cols={cols}, BitsAllocated={bitsAllocated}, BitsStored={bitsStored}, SamplesPerPixel={samplesPerPixel}, PixelRepresentation={pixelRepresentation}, PlanarConfiguration={planarConfiguration}"); - EnsureSupportedPhotometric(originalPhotometric, samplesPerPixel); + var isSupport = IsSupportedPhotometric(originalPhotometric, samplesPerPixel); - // 1. 转为工作用的未压缩 DICOM - var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false); - // 2. 修改像素 - MaskPixelDataInPlace(workingFile.Dataset, regionList, options); - // 3. 保持原 PhotometricInterpretation - if (!string.IsNullOrWhiteSpace(originalPhotometric)) + if (isSupport) { - workingFile.Dataset.AddOrUpdate(DicomTag.PhotometricInterpretation, originalPhotometric); + // 1. 转为工作用的未压缩 DICOM + var workingFile = await EnsureUncompressedAsync(originalFile, cancellationToken).ConfigureAwait(false); + // 2. 修改像素 + MaskPixelDataInPlace(workingFile.Dataset, regionList, options); + // 3. 保持原 PhotometricInterpretation + if (!string.IsNullOrWhiteSpace(originalPhotometric)) + { + workingFile.Dataset.AddOrUpdate(DicomTag.PhotometricInterpretation, originalPhotometric); + } + // 4. 可选更新 BurnedInAnnotation + if (options.UpdateBurnedInAnnotationToNo) + { + workingFile.Dataset.AddOrUpdate(DicomTag.BurnedInAnnotation, "NO"); + } + // 5. 编码回原始传输语法 + var finalFile = await ReEncodeToOriginalTransferSyntaxAsync( + workingFile, + originalTs, + options.StrictKeepTransferSyntax, + cancellationToken).ConfigureAwait(false); + + finalFile.FileMetaInfo.TransferSyntax = originalTs; + + if (output.CanSeek) + output.SetLength(0); + + await finalFile.SaveAsync(output).ConfigureAwait(false); + + if (output.CanSeek) + output.Position = 0; } - // 4. 可选更新 BurnedInAnnotation - if (options.UpdateBurnedInAnnotationToNo) + else { - workingFile.Dataset.AddOrUpdate(DicomTag.BurnedInAnnotation, "NO"); + } - // 5. 编码回原始传输语法 - var finalFile = await ReEncodeToOriginalTransferSyntaxAsync( - workingFile, - originalTs, - options.StrictKeepTransferSyntax, - cancellationToken).ConfigureAwait(false); - - finalFile.FileMetaInfo.TransferSyntax = originalTs; - - if (output.CanSeek) - output.SetLength(0); - - await finalFile.SaveAsync(output).ConfigureAwait(false); - - if (output.CanSeek) - output.Position = 0; + } @@ -264,23 +281,24 @@ public static class DicomPixelMasker _ = dataset.GetSingleValue(DicomTag.SamplesPerPixel); } - private static void EnsureSupportedPhotometric(string photometric, int samplesPerPixel, Action? log = null) + private static bool IsSupportedPhotometric(string photometric, int samplesPerPixel) { if (samplesPerPixel == 1) { if (string.Equals(photometric, "MONOCHROME1", StringComparison.OrdinalIgnoreCase) || - string.Equals(photometric, "MONOCHROME2", StringComparison.OrdinalIgnoreCase)) + string.Equals(photometric, "MONOCHROME2", StringComparison.OrdinalIgnoreCase) || + string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase)) { - return; + return true; } - throw new NotSupportedException($"Unsupported grayscale PhotometricInterpretation: {photometric}"); + return false; } if (samplesPerPixel == 3) { if (string.Equals(photometric, "RGB", StringComparison.OrdinalIgnoreCase) || string.Equals(photometric, "YBR_FULL", StringComparison.OrdinalIgnoreCase)) { - return; + return true; } // 对 YBR_FULL_422 不建议直接按当前布局改 if (string.Equals(photometric, "YBR_FULL_422", StringComparison.OrdinalIgnoreCase) || @@ -290,9 +308,12 @@ public static class DicomPixelMasker throw new NotSupportedException( $"PhotometricInterpretation={photometric} uses subsampling layout and is not safely supported by this pixel masking implementation."); } - throw new NotSupportedException($"Unsupported color PhotometricInterpretation: {photometric}"); + + return false; + //throw new NotSupportedException($"Unsupported color PhotometricInterpretation: {photometric}"); } - throw new NotSupportedException($"Unsupported SamplesPerPixel={samplesPerPixel}"); + return false; + //throw new NotSupportedException($"Unsupported SamplesPerPixel={samplesPerPixel}"); } /// @@ -402,7 +423,7 @@ public static class DicomPixelMasker photometric: photometric, planarConfiguration: planarConfiguration, regions: regions, - options: options); + options: options, dataset); } replacementFrames.Add(new MemoryByteBuffer(bytes)); } @@ -455,28 +476,56 @@ public static class DicomPixelMasker string photometric, ushort planarConfiguration, IReadOnlyList regions, - DicomMaskOptions options) + DicomMaskOptions options, + DicomDataset dataset) { if (samplesPerPixel == 1) { - if (bitsAllocated == 8) + + if (string.Equals(photometric, "PALETTE COLOR", StringComparison.OrdinalIgnoreCase)) { - byte maskValue8 = ResolveGrayscaleMaskValue8(photometric, options); - ApplyMask_Grayscale8(frameData, rows, cols, regions, maskValue8); - return; + if (pixelRepresentation != 0) + { + throw new NotSupportedException("PALETTE COLOR with signed pixel representation is not supported."); + } + if (bitsAllocated == 8) + { + byte maskIndex = (byte)ResolvePaletteColorMaskIndex(dataset, bitsStored, options); + ApplyMask_Grayscale8(frameData, rows, cols, regions, maskIndex); + return; + } + if (bitsAllocated == 16) + { + ushort maskIndex = ResolvePaletteColorMaskIndex(dataset, bitsStored, options); + ApplyMask_Grayscale16(frameData, rows, cols, regions, maskIndex); + return; + } + throw new NotSupportedException( + $"Unsupported PALETTE COLOR image: BitsAllocated={bitsAllocated}"); } - if (bitsAllocated == 16) + else { - ushort maskValue16 = ResolveGrayscaleMaskValue16( - photometric, - bitsStored, - pixelRepresentation, - options); - ApplyMask_Grayscale16(frameData, rows, cols, regions, maskValue16); - return; + if (bitsAllocated == 8) + { + byte maskValue8 = ResolveGrayscaleMaskValue8(photometric, options); + ApplyMask_Grayscale8(frameData, rows, cols, regions, maskValue8); + return; + } + if (bitsAllocated == 16) + { + ushort maskValue16 = ResolveGrayscaleMaskValue16( + photometric, + bitsStored, + pixelRepresentation, + options); + ApplyMask_Grayscale16(frameData, rows, cols, regions, maskValue16); + return; + } + throw new NotSupportedException( + $"Unsupported grayscale image: BitsAllocated={bitsAllocated}, Photometric={photometric}"); } - throw new NotSupportedException( - $"Unsupported grayscale image: BitsAllocated={bitsAllocated}, Photometric={photometric}"); + + } if (samplesPerPixel == 3) { @@ -501,6 +550,276 @@ public static class DicomPixelMasker throw new NotSupportedException( $"Unsupported format: SamplesPerPixel={samplesPerPixel}, BitsAllocated={bitsAllocated}, Photometric={photometric}"); } + private static ushort ResolvePaletteColorMaskIndex( + DicomDataset dataset, + int bitsStored, + DicomMaskOptions options) + { + int pixelMin = 0; + int pixelMax = bitsStored == 16 ? ushort.MaxValue : ((1 << bitsStored) - 1); + + + if (options.PaletteColorMaskIndex.HasValue) + { + int manual = options.PaletteColorMaskIndex.Value; + if (manual < pixelMin || manual > pixelMax) + { + throw new ArgumentOutOfRangeException( + nameof(options.PaletteColorMaskIndex), + $"PaletteColorMaskIndex must be in range [{pixelMin}, {pixelMax}] for BitsStored={bitsStored}."); + } + return (ushort)manual; + } + + // 2. 尝试从 Descriptor 读取 FirstMappedPixelValue + if (TryGetPaletteFirstMappedPixelValue(dataset, out int firstMapped)) + { + if (firstMapped < pixelMin || firstMapped > pixelMax) + { + throw new InvalidDataException( + $"Palette LUT FirstMappedPixelValue={firstMapped} is out of pixel range [{pixelMin}, {pixelMax}]."); + } + return (ushort)firstMapped; + } + // 3. 实在没有就退化为 0(可改成直接抛异常) + if (0 >= pixelMin && 0 <= pixelMax) + { + return 0; + } + + throw new InvalidDataException( + "Cannot resolve palette color mask index because palette LUT descriptor is missing."); + + //int autoIndex = FindDarkestPaletteIndex(dataset, maxValue); + //return (byte)autoIndex; + } + + private static bool TryGetPaletteFirstMappedPixelValue(DicomDataset dataset, out int firstMappedPixelValue) + { + firstMappedPixelValue = 0; + + if (TryGetPaletteDescriptor(dataset, DicomTag.RedPaletteColorLookupTableDescriptor, out var redDesc)) + { + firstMappedPixelValue = redDesc.FirstMappedPixelValue; + return true; + } + + if (TryGetPaletteDescriptor(dataset, DicomTag.GreenPaletteColorLookupTableDescriptor, out var greenDesc)) + { + firstMappedPixelValue = greenDesc.FirstMappedPixelValue; + return true; + } + + if (TryGetPaletteDescriptor(dataset, DicomTag.BluePaletteColorLookupTableDescriptor, out var blueDesc)) + { + firstMappedPixelValue = blueDesc.FirstMappedPixelValue; + return true; + } + + return false; + } + + private sealed class PaletteLutDescriptor + { + public int NumberOfEntries { get; init; } + public int FirstMappedPixelValue { get; init; } + public int BitsPerEntry { get; init; } + } + + private static bool TryGetPaletteDescriptor( + DicomDataset dataset, + DicomTag tag, + out PaletteLutDescriptor descriptor) + { + descriptor = null!; + + if (!dataset.Contains(tag)) + return false; + + // LUT Descriptor 可能是 US 或 SS,fo-dicom 通常可直接按 short/ushort 读 + if (dataset.TryGetValues(tag, out short[]? ssValues) && ssValues != null && ssValues.Length >= 3) + { + descriptor = new PaletteLutDescriptor + { + NumberOfEntries = ssValues[0] == 0 ? 65536 : ssValues[0], + FirstMappedPixelValue = ssValues[1], + BitsPerEntry = ssValues[2] + }; + return true; + } + + if (dataset.TryGetValues(tag, out ushort[]? usValues) && usValues != null && usValues.Length >= 3) + { + descriptor = new PaletteLutDescriptor + { + NumberOfEntries = usValues[0] == 0 ? 65536 : usValues[0], + FirstMappedPixelValue = usValues[1], + BitsPerEntry = usValues[2] + }; + return true; + } + + return false; + } + + + + + #region 尾彩 + + //private static ushort ResolvePaletteColorMaskIndex16( + //DicomDataset dataset, + //int bitsStored, + //DicomMaskOptions options) + //{ + // int maxValue = bitsStored >= 16 ? 65535 : (1 << bitsStored) - 1; + + // if (options.PaletteColorMaskIndex.HasValue) + // { + // int v = options.PaletteColorMaskIndex.Value; + // if (v < 0 || v > maxValue) + // { + // throw new ArgumentOutOfRangeException( + // nameof(options.PaletteColorMaskIndex), + // $"PaletteColorMaskIndex must be in range [0, {maxValue}] for BitsStored={bitsStored}"); + // } + // return (ushort)v; + // } + + // int autoIndex = FindDarkestPaletteIndex(dataset, maxValue); + // return (ushort)autoIndex; + //} + + //private static int FindDarkestPaletteIndex(DicomDataset dataset, int maxPixelValue) + //{ + // var redDesc = GetPaletteDescriptor(dataset, DicomTag.RedPaletteColorLookupTableDescriptor); + // var greenDesc = GetPaletteDescriptor(dataset, DicomTag.GreenPaletteColorLookupTableDescriptor); + // var blueDesc = GetPaletteDescriptor(dataset, DicomTag.BluePaletteColorLookupTableDescriptor); + + // if (redDesc.EntryCount != greenDesc.EntryCount || redDesc.EntryCount != blueDesc.EntryCount || + // redDesc.FirstMappedValue != greenDesc.FirstMappedValue || redDesc.FirstMappedValue != blueDesc.FirstMappedValue || + // redDesc.BitsPerEntry != greenDesc.BitsPerEntry || redDesc.BitsPerEntry != blueDesc.BitsPerEntry) + // { + // throw new NotSupportedException("RGB palette LUT descriptors are inconsistent."); + // } + + // ushort[] red = GetPaletteLutDataAsUShortArray(dataset, DicomTag.RedPaletteColorLookupTableData, redDesc); + // ushort[] green = GetPaletteLutDataAsUShortArray(dataset, DicomTag.GreenPaletteColorLookupTableData, greenDesc); + // ushort[] blue = GetPaletteLutDataAsUShortArray(dataset, DicomTag.BluePaletteColorLookupTableData, blueDesc); + + // if (red.Length != redDesc.EntryCount || green.Length != greenDesc.EntryCount || blue.Length != blueDesc.EntryCount) + // { + // throw new NotSupportedException("Palette LUT data length does not match LUT descriptor."); + // } + + // long bestScore = long.MaxValue; + // int bestLutIndex = 0; + + // for (int i = 0; i < redDesc.EntryCount; i++) + // { + // long score = (long)red[i] + green[i] + blue[i]; + // if (score < bestScore) + // { + // bestScore = score; + // bestLutIndex = i; + // } + // } + + // int pixelValue = redDesc.FirstMappedValue + bestLutIndex; + + // if (pixelValue < 0) + // pixelValue = 0; + // if (pixelValue > maxPixelValue) + // pixelValue = maxPixelValue; + + // return pixelValue; + //} + + //private static ushort[] GetPaletteLutDataAsUShortArray( + //DicomDataset dataset, + //DicomTag dataTag, + //PaletteLutDescriptor descriptor) + //{ + // if (!dataset.TryGetSingleValue(dataTag, out var rawBytes) || rawBytes == null || rawBytes.Length == 0) + // { + // throw new NotSupportedException($"Missing palette LUT data: {dataTag}"); + // } + + // int entryCount = descriptor.EntryCount; + // int bitsPerEntry = descriptor.BitsPerEntry; + + // if (bitsPerEntry <= 8) + // { + // if (rawBytes.Length < entryCount) + // { + // throw new NotSupportedException( + // $"Palette LUT data too short for {dataTag}. Expected at least {entryCount} bytes, actual {rawBytes.Length}."); + // } + + // var result = new ushort[entryCount]; + // for (int i = 0; i < entryCount; i++) + // { + // result[i] = rawBytes[i]; + // } + // return result; + // } + + // if (bitsPerEntry <= 16) + // { + // int requiredBytes = entryCount * 2; + // if (rawBytes.Length < requiredBytes) + // { + // throw new NotSupportedException( + // $"Palette LUT data too short for {dataTag}. Expected at least {requiredBytes} bytes, actual {rawBytes.Length}."); + // } + + // var result = new ushort[entryCount]; + // for (int i = 0; i < entryCount; i++) + // { + // int byteIndex = i * 2; + // result[i] = (ushort)(rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8)); + // } + // return result; + // } + + // throw new NotSupportedException( + // $"Unsupported palette LUT bits per entry {bitsPerEntry} for {dataTag}."); + //} + + //private readonly struct PaletteLutDescriptor + //{ + // public int EntryCount { get; } + // public int FirstMappedValue { get; } + // public int BitsPerEntry { get; } + + // public PaletteLutDescriptor(int entryCount, int firstMappedValue, int bitsPerEntry) + // { + // EntryCount = entryCount; + // FirstMappedValue = firstMappedValue; + // BitsPerEntry = bitsPerEntry; + // } + //} + + //private static PaletteLutDescriptor GetPaletteDescriptor(DicomDataset dataset, DicomTag tag) + //{ + // var values = dataset.GetValues(tag); + // if (values == null || values.Length < 3) + // throw new NotSupportedException($"Missing or invalid palette LUT descriptor: {tag}"); + + // int entryCount = values[0] == 0 ? 65536 : values[0]; + + // // 第二个值在 DICOM 标准中是 signed short + // int firstMappedValue = (short)values[1]; + + // int bitsPerEntry = values[2]; + // if (bitsPerEntry <= 0) + // throw new NotSupportedException($"Invalid palette LUT descriptor bits per entry: {bitsPerEntry}"); + + // return new PaletteLutDescriptor(entryCount, firstMappedValue, bitsPerEntry); + //} + + #endregion + private static byte ResolveGrayscaleMaskValue8(string photometric, DicomMaskOptions options) { if (!options.AutoSelectGrayscaleMaskValue) diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 0807923df..924e53f17 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -15927,6 +15927,12 @@ + + + 归档直读 + + + 测试疗效评估 @@ -16110,6 +16116,12 @@ 默认是添加/更新 + + + 将某个路径下的归档的文件 转为标准存储 + + + 坑方法,会清空之前的规则 @@ -16194,6 +16206,14 @@ 当 AutoSelectGrayscaleMaskValue=false 时,使用该值。 当为 null 且 AutoSelectGrayscaleMaskValue=false 时,默认 0。 + 灰度图像的遮挡像素值,默认 0。 + 注意:这表示写入的原始像素值,不保证视觉上一定为黑色。 + + + + + PALETTE COLOR 图像使用的遮盖索引。 + null 表示自动从调色板中选择最暗的一个索引。 diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index 861ea57fb..4539d1d72 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -152,17 +152,20 @@ namespace IRaCIS.Core.Application.Service try { + Console.WriteLine($"开始处理: {relativePath}"); + await DicomPixelMasker.MaskAsync(input, output, regions); } - catch(Exception ex) + catch (Exception ex) { // 跳过该文件 - - Console.WriteLine($"error: {ex.Message}"); + Console.WriteLine(); + Console.WriteLine($"error: {relativePath} —————— {ex.Message}"); + Console.WriteLine(); } - Console.WriteLine($"Done: {relativePath}"); + Console.WriteLine($"处理结束: {relativePath}"); } return ResponseOutput.Ok(); @@ -195,6 +198,18 @@ namespace IRaCIS.Core.Application.Service return ResponseOutput.Ok(); } + /// + /// 归档直读 + /// + /// + [AllowAnonymous] + public async Task ConvertPrefixToStandard() + { + _IOSSService.ConvertPrefixToStandard("601a0000-3e2c-0016-56de-08dae983124b/Image"); + + return ResponseOutput.Ok(); + } + [AllowAnonymous]