Określanie i zapisywanie figury o dokładnym rozmiarze w pikselach


152

Powiedzmy, że mam obraz o rozmiarze 3841 x 7195 pikseli. Chciałbym zapisać zawartość figury na dysku, w wyniku czego otrzymuję obraz o dokładnym rozmiarze, który określę w pikselach.

Bez osi, bez tytułów. Tylko obraz. Osobiście nie obchodzą mnie DPI, ponieważ chcę tylko określić rozmiar obrazu na ekranie w pikselach .

Czytałem inne wątki i wydaje się, że wszystkie one przeliczają na cale, a następnie określają wymiary figury w calach i dostosowują w jakiś sposób dpi. Chciałbym uniknąć potencjalnej utraty dokładności, która może wynikać z konwersji pikseli na cale.

Próbowałem z:

w = 7195
h = 3841
fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig(some_path, dpi=1)

bez powodzenia (Python narzeka, że ​​szerokość i wysokość muszą być mniejsze niż 32768 (?))

Ze wszystkiego, co widziałem, matplotlibwymaga określenia rozmiaru figury w inchesi dpi, ale interesują mnie tylko piksele, które figura zajmuje na dysku. W jaki sposób mogę to zrobić?

Wyjaśnienie: szukam sposobu, aby to zrobić matplotlibz innymi bibliotekami do zapisywania obrazów, a nie z nimi.


W przypadku matplotlib nie można ustawić rozmiaru figury bezpośrednio w calach.
— tiago

Odpowiedzi:


175

Matplotlib nie działa bezpośrednio z pikselami, ale raczej z fizycznymi rozmiarami i DPI. Jeśli chcesz wyświetlić figurę o określonym rozmiarze piksela, musisz znać DPI swojego monitora. Na przykład ten link wykryje to za Ciebie.

Jeśli masz obraz o wymiarach 3841x7195 pikseli, jest mało prawdopodobne, że monitor będzie tak duży, więc nie będziesz w stanie wyświetlić figury tego rozmiaru (matplotlib wymaga, aby figura zmieściła się na ekranie, jeśli poprosisz o rozmiar za duży, zmniejszy się do rozmiaru ekranu). Wyobraźmy sobie, że jako przykład potrzebujesz obrazu 800x800 pikseli. Oto jak wyświetlić obraz o rozdzielczości 800x800 pikseli na moim monitorze ( my_dpi=96):

plt.figure(figsize=(800/my_dpi, 800/my_dpi), dpi=my_dpi)

Więc po prostu dzielisz wymiary w calach przez swoje DPI.

Jeśli chcesz zapisać figurę o określonym rozmiarze, to inna sprawa. DPI ekranu nie są już tak ważne (chyba że poprosisz o figurę, która nie zmieści się na ekranie). Korzystając z tego samego przykładu piksela 800x800, możemy zapisać go w różnych rozdzielczościach, używając dpisłowa kluczowego z savefig. Aby zapisać go w tej samej rozdzielczości, co ekran, użyj tej samej rozdzielczości:

plt.savefig('my_fig.png', dpi=my_dpi)

Aby zapisać go jako obraz o rozdzielczości 8000x8000 pikseli, użyj rozdzielczości 10 razy większej:

plt.savefig('my_fig.png', dpi=my_dpi * 10)

Zwróć uwagę, że ustawienie DPI nie jest obsługiwane przez wszystkie backendy. Tutaj używany jest backend PNG, ale backend pdf i ps będą implementować rozmiar inaczej. Ponadto zmiana DPI i rozmiarów wpłynie również na takie rzeczy, jak rozmiar czcionki. Większy DPI zachowa te same względne rozmiary czcionek i elementów, ale jeśli chcesz mieć mniejsze czcionki dla większej figury, musisz zwiększyć rozmiar fizyczny zamiast DPI.

Wracając do przykładu, jeśli chcesz zapisać obraz o rozmiarze 3841 x 7195 pikseli, możesz wykonać następujące czynności:

plt.figure(figsize=(3.841, 7.195), dpi=100)
( your code ...)
plt.savefig('myfig.png', dpi=1000)

Zauważ, że użyłem wartości 100 dpi, aby zmieścić się na większości ekranów, ale zapisałem z, dpi=1000aby uzyskać wymaganą rozdzielczość. W moim systemie daje to png o rozdzielczości 3840x7190 pikseli - wydaje się, że zapisane DPI jest zawsze o 0,02 piksela / cal mniejsze od wybranej wartości, co będzie miało (mały) wpływ na duże rozmiary obrazu. Więcej dyskusji na ten temat tutaj .


5
Warto pamiętać, że rozmiary monitorów (a tym samym standardowe rozmiary okien przeglądarki i okna interfejsu użytkownika) są zwykle określane jako 96 dpi - wielokrotności 96. Nagle liczby takie jak 1440 pikseli mają znaczenie (15 cali).
— Danny Staple

Nie udało się zmusić tego do przejścia figsizedo plt.figure. Rozwiązaniem było zrobienie tego, co sugerują inne odpowiedzi i po wywołaniu go bez figsize , a następnie zadzwońfig.set_size_inches(w,h)
— Trinidad

Dokumenty dla figurei savefig.
— uchwyt

Łącze nie pokazuje prawidłowej wartości dla Apple Thunderbolt Display.
— Dmitry,

Podoba mi się to rozwiązanie, ale mam jedno ostrzeżenie. Rozmiar tekstu skaluje się odwrotnie jako dpi. (Mój system to MacBook Pro, OS X), więc w przypadku drukowania interaktywnego ustawienie dużego dpi (np. 10 * my_dpi) zmniejsza tekst do prawie niewidoczności.
— Prof Huster,

16

To zadziałało dla mnie, w oparciu o twój kod, generując obraz PNG o rozmiarze 93 MB z szumem kolorów i pożądanymi wymiarami:

import matplotlib.pyplot as plt
import numpy

w = 7195
h = 3841

im_np = numpy.random.rand(h, w)

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(im_np, aspect='normal')
fig.savefig('figure.png', dpi=1)

Używam ostatnich wersji PIP bibliotek Python 2.7 w Linux Mint 13.

Mam nadzieję, że to pomoże!


4
Ustawienie bardzo niskiej rozdzielczości oznacza, że ​​czcionki będą prawie niewidoczne, chyba że wyraźnie zostaną użyte bardzo duże rozmiary czcionek.
— tiago

1
Prawdopodobnie lepiej jest ustawić wyższą rozdzielczość i podzielić rozmiar w calach (który i tak jest arbitralny) przez tę rozdzielczość. Poza tym twoja konfiguracja zapewnia dokładny piksel do reprodukcji pikseli, dzięki!
— FrenchKheldar

Próbuję tego użyć przed zapisaniem obrazów z elementami fabuły (okręgi, linie, ...). Narusza to szerokość linii, przez co elementy są ledwo widoczne.
— Gauthier

5

Na podstawie zaakceptowanej odpowiedzi tiago, oto mała ogólna funkcja, która eksportuje tablicę numpy do obrazu o tej samej rozdzielczości co tablica:

import matplotlib.pyplot as plt
import numpy as np

def export_figure_matplotlib(arr, f_name, dpi=200, resize_fact=1, plt_show=False):
    """
    Export array as figure in original resolution
    :param arr: array of image to save in original resolution
    :param f_name: name of file where to save figure
    :param resize_fact: resize facter wrt shape of arr, in (0, np.infty)
    :param dpi: dpi of your screen
    :param plt_show: show plot or not
    """
    fig = plt.figure(frameon=False)
    fig.set_size_inches(arr.shape[1]/dpi, arr.shape[0]/dpi)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(arr)
    plt.savefig(f_name, dpi=(dpi * resize_fact))
    if plt_show:
        plt.show()
    else:
        plt.close()

Jak wspomniano w poprzedniej odpowiedzi tiago, najpierw należy znaleźć DPI ekranu, co można zrobić na przykład tutaj: http://dpi.lv

Dodałem dodatkowy argument resize_factw funkcji, dzięki któremu można np. Wyeksportować obraz do 50% (0,5) oryginalnej rozdzielczości.


2

OP chce zachować dane piksela 1: 1. Jako astronom pracujący z obrazami naukowymi nie mogę pozwolić na jakąkolwiek interpolację danych obrazu, ponieważ wprowadzałaby ona nieznane i nieprzewidywalne szumy lub błędy. Na przykład, oto fragment z obrazu 480x480 zapisanego przez pyplot.savefig (): Szczegóły pikseli, które matplotlib ponownie próbkowały do ​​rozmiaru mniej więcej 2x2, ale zwróć uwagę na kolumnę 1x2 pikseli

Widać, że większość pikseli została po prostu podwojona (więc piksel 1x1 zmienia się na 2x2), ale niektóre kolumny i wiersze zmieniły się na 1x2 lub 2x1 na piksel, co oznacza, że ​​oryginalne dane naukowe zostały zmienione.

Jak zasugerował Alka, plt.imsave (), który osiągnie to, o co prosi PO. Powiedzmy, że masz dane obrazu przechowywane w image array im, wtedy można zrobić coś takiego

plt.imsave(fname='my_image.png', arr=im, cmap='gray_r', format='png')

gdzie nazwa pliku ma rozszerzenie „png” w tym przykładzie (ale i tak musisz określić format za pomocą format = 'png', o ile wiem), tablica obrazu to arr, a my wybraliśmy odwróconą skalę szarości „gray_r” jako mapa kolorów. Zwykle dodaję vmin i vmax, aby określić zakres dynamiczny, ale są one opcjonalne.

Końcowym wynikiem jest plik png o dokładnie takich samych wymiarach w pikselach jak tablica im.

Uwaga: OP nie określił żadnych osi itp., Co dokładnie robi to rozwiązanie. Jeśli ktoś chce dodać osie, znaczniki, itp., Moim preferowanym podejściem jest zrobienie tego na oddzielnym wykresie, zapisanie z przezroczystym = True (PNG lub PDF), a następnie nałożenie tego ostatniego na obraz. Gwarantuje to, że oryginalne piksele pozostały nienaruszone.


Dzięki. Minęło kilka lat, odkąd zadałem to pytanie, ale z tego, co pamiętam i widzę w Twojej odpowiedzi, działałoby to dobrze, gdy próbujesz zapisać tablicę danych jako obraz, ale co, jeśli chcesz zapisać, to rzeczywisty figura (niezależnie od jej zawartości) i nadal dokładnie kontroluje wymiary w pikselach wynikowego pliku obrazu?
— Amelio Vazquez-Reina

-1

plt.imsave pracował dla mnie. Dokumentację można znaleźć tutaj: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.imsave.html

#file_path = directory address where the image will be stored along with file name and extension
#array = variable where the image is stored. I think for the original post this variable is im_np
plt.imsave(file_path, array)

Dodaj przykładowy kod pokazujący dokładnie, który parametr ustawiasz i zalecane wartości dla przypadku użycia oryginalnego posta.
— Sarah Messer
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.