dme-compunet/YoloV8

Error Sequence contains no elements on image plotting

H-pun opened this issue · 3 comments

Hello, I'm currently working on my API project using this library.

I use this standard code and it works perfectly fine on my computer.

using Compunet.YoloV8;
using Compunet.YoloV8.Plotting;
using SixLabors.ImageSharp;

var imagePath = "path/to/image";

using var predictor = YoloV8Predictor.Create("path/to/model");

var result = await predictor.DetectAsync(imagePath);

using var image = Image.Load(imagePath);
using var ploted = await result.PlotImageAsync(image);

ploted.Save("./pose_demo.jpg")

but when I deploy it on Cloud Run (Google Cloud Platform) it throws an error:

 System.TypeInitializationException: The type initializer for 'Compunet.YoloV8.Plotting.DetectionPlottingOptions' threw an exception.
 ---> System.InvalidOperationException: Sequence contains no elements
 at System.Linq.ThrowHelper.ThrowNoElementsException()
 at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
 at Compunet.YoloV8.Plotting.PlottingOptions.GetDefaultFontFamily()
 at Compunet.YoloV8.Plotting.PlottingOptions..ctor()
 at Compunet.YoloV8.Plotting.DetectionPlottingOptions..ctor()
 at Compunet.YoloV8.Plotting.DetectionPlottingOptions..cctor()
 --- End of inner exception stack trace ---
 at Compunet.YoloV8.Plotting.DetectionPlottingOptions.get_Default()
 at Compunet.YoloV8.Plotting.PlottingExtensions.PlotImage(DetectionResult result, ImageSelector`1 originImage)
 at Compunet.YoloV8.Plotting.PlottingAsyncOperationExtensions.<>c__DisplayClass2_0.<PlotImageAsync>b__0()
 at System.Threading.Tasks.Task`1.InnerInvoke()
 at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
 --- End of stack trace from previous location ---
 at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
 at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
 --- End of stack trace from previous location ---
 at Compunet.YoloV8.Plotting.PlottingAsyncOperationExtensions.PlotImageAsync(DetectionResult result, Image originImage)
 at Dietary.Controllers.FoodController.Predict(IFormFile imgFile) in /src/Controllers/FoodController.cs:line 65

I know it has something to do with image plotting because without the plot, the predict works perfectly fine.
Or does it have something to do with my docker?

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

ENV ASPNETCORE_URLS=http://+:8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["Dietary.csproj", "./"]
RUN dotnet restore "Dietary.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "Dietary.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Dietary.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Dietary.dll"]

You were right! it was because there were no fonts available in the container.

So i tried your solution by adding default font to my project and load it like this:
using var ploted = await result.PlotImageAsync(image, new() { FontFamily = new FontCollection().Add(fontPath) });

However, upon initialization, the PlottingOptions constructor will always invokes GetDefaultFontFamily(), which is the main problem since there are no system fonts in the first place.

public PlottingOptions()
{
    FontFamily = GetDefaultFontFamily();
    FontSize = 12f;
} 

So currently my solution is by installing ttf-mscorefonts-installer for default SystemFonts on my Docker container. However, this solution is suboptimal as it could potentially degrade server performance.

RUN echo "deb http://deb.debian.org/debian/ bookworm main contrib" > /etc/apt/sources.list && \
    echo "deb-src http://deb.debian.org/debian/ bookworm main contrib" >> /etc/apt/sources.list && \
    echo "deb http://security.debian.org/ bookworm-security main contrib" >> /etc/apt/sources.list && \
    echo "deb-src http://security.debian.org/ bookworm-security main contrib" >> /etc/apt/sources.list
RUN sed -i'.bak' 's/$/ contrib/' /etc/apt/sources.list
RUN apt-get update; apt-get install -y ttf-mscorefonts-installer fontconfig

Well I think by using FirstOrDefault() in GetDefaultFontFamily() is better than First() since it wont throw Sequence contains no elements and FontFamily can be reassigned if there are no fonts available.

I'll fix it soon, thanks for reporting it.