Problem with annotation script for FIT files

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
coldt_astro
User
Beiträge: 2
Registriert: Freitag 2. Juni 2023, 12:07

Hello folks,

I have a script that doesn't output all the images. Normally it should look at the FITS header, work out RA/Dec, center it, take snaps of the galaxies in the background, annotate them and combine the two pictures into one.
After running the code it opens Figure 1 and Figure 2, as well as saving img1.png. It detected the galaxies as well but it doesn't use the snippets to create img2 and since img2 wasn't created, it can't rescale the two images into one.
Maybe some of you can help me because I am at a loss :cry:

Code: Alles auswählen

 # -*- coding: utf-8 -*-
"""M81M82_visualization.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1BmtQxdByqfsm6iVLxr8s-5eEXNfVHOgw

# Load fits image and header

## Attention: in this case only the header of the fits file is used. the image originates from a png!!!
"""

from astropy.io import fits
from astropy.coordinates import SkyCoord
from astropy import coordinates as coord
from astropy.wcs.utils import skycoord_to_pixel
from astropy.table import Table
import astropy.units as u
from astropy.wcs import WCS
from astroquery.simbad import Simbad
import matplotlib.pyplot as plt
from astropy.visualization import astropy_mpl_style
import numpy as np

plt.style.use('dark_background')

# Load the FITS image and extract header information
image_file = "C:/Users/xDDra/Desktop/Annotation/M81.fit"
raw_image_file = None#'M81.png'
hdul = fits.open(image_file)

# use better quality png in this case
if not raw_image_file:
    data = hdul[0].data
    img = np.swapaxes(np.swapaxes(data,0,2),0,1).astype(float)
    img = img/max(img.flatten())
else:
    img = plt.imread(raw_image_file)[::-1]

# get fits header
header = hdul[0].header
# get world coordinates system and drop color channel
wcs = WCS(header).dropaxis(2)
# Extract the right ascension and declination of the center of the image
center_ra = header['CRVAL1']
center_dec = header['CRVAL2']

fig = plt.figure(figsize=(10,6))
ax = plt.subplot(projection=wcs, label='overlays')
ax.imshow(img, origin='lower', cmap='gray')
ax.coords.grid(True, color='white', ls='-', alpha=.5)
ax.coords[0].set_axislabel('Right Ascension (J2000)')
ax.coords[1].set_axislabel('Declination (J2000)')

ax.scatter(center_ra, center_dec, transform=ax.get_transform('icrs'),s=10, edgecolor='red', facecolor='red')

img.shape

"""# Query Objects

### 1. define the center of the circle
### 2. define the search header using header-information
### 3. query objects
### 4. filter objects outside the FOV
### 5. fitler objects according to catalog (see definition in column TYPE)
"""

Simbad.TIMEOUT = 120

# Define the specific coordinates
center_ra = header['CRVAL1']
center_dec = header['CRVAL2']
target_coord = SkyCoord(ra=center_ra, dec=center_dec, unit=(u.deg, u.deg), frame='icrs')

# define the search radius
radius_deg = max([abs(header['CDELT1']),abs(header['CDELT2'])])*max([header['NAXIS1'],header['NAXIS2']])/1.5

# Set up the query criteria
custom_query = f"region(CIRCLE, {target_coord.ra.deg} + {target_coord.dec.deg}, {radius_deg}d)"

# Query Simbad with the criteria
result_table = Simbad.query_criteria(custom_query, otype='galaxy')

df = result_table.to_pandas()

# filter by position
df['Pixel_Position'] = [skycoord_to_pixel(SkyCoord(v[0],v[1], unit=(u.hourangle, u.deg)), wcs) for v in df[['RA','DEC']].values]
df['px'] = df['Pixel_Position'].apply(lambda x: int(x[0]))
df['py'] = df['Pixel_Position'].apply(lambda x: int(x[1]))
df = df[(df.px>0)&(df.py>0)&(df.px<img.shape[1])&(df.py<img.shape[0])]

# filter by catalog
# get catalog/TYPE of object by simple string manipulation
df['TYPE'] = df['MAIN_ID'].apply(lambda x: x.split(' ')[0].split('+')[0])#.value_counts()
filter_types = ['M', 'NGC', 'IC', 'MCG', 'UGC', '2MASX']
filtered_result = Table.from_pandas(df[df['TYPE'].isin(filter_types)])
filtered_result

df[~df['TYPE'].isin(filter_types)].TYPE.value_counts()

"""# Plot first image

## Attention: here I defined a dictionary for sorting the results accordingly
"""

img.shape

import matplotlib.patches as patches

sub=1
dpi=150
dfi = filtered_result.to_pandas()
rep_dic ={
    'M':1,'NGC':2,'IC':3, 'UGC':4#, 'MCG', '2MASS', '2MASX', 'LEDA'
}
dfi['sorting'] = dfi.TYPE.replace(rep_dic)
dfi.MAIN_ID = dfi.MAIN_ID.apply(lambda x: x.replace(' ',''))
dfi = dfi.sort_values(['sorting', 'MAIN_ID'], ascending=True).reset_index()

scale=20
fig = plt.figure(figsize=(scale,(img.shape[0]/img.shape[1])*scale))
ax1 = plt.subplot(projection=wcs, label='overlays')
ax1.imshow(img[::sub,::sub])
ax1.coords.grid(True, color='white', ls='-', alpha=.5)
ax1.coords[0].set_axislabel('Right Ascension (J2000)')
ax1.coords[1].set_axislabel('Declination (J2000)',minpad=-1)

all_patches = []
cmap = plt.get_cmap("tab10")
filter_idxs = []
j=0
for i,row in dfi.iterrows():
    if row.MAIN_ID == 'M81':
        patch_size=1024
        color = cmap(0)
        lw=4
        alpha=1
    elif row.TYPE == 'M':
        patch_size=512
        color = cmap(0)
        lw=4
        alpha=1
    elif row.TYPE == 'NGC':
        patch_size=256
        color = cmap(2)
        lw=4
        alpha=1
    elif row.TYPE == 'IC':
        patch_size=128
        color = cmap(1)
        lw=4
        alpha=1
    elif row.TYPE == 'UGC':
        patch_size=128
        color = cmap(1)
        lw=4
        alpha=1
    else:
        patch_size=32
        color = 'white'
        lw=2
        alpha=.5
    lw=1
    alpha=.5
    color = 'white'
    if (row.px-patch_size//2 > 0)&(row.px+patch_size//2 < img.shape[1])&(row.py-patch_size//2 > 0)&(row.py+patch_size//2 < img.shape[0]):
        rect = patches.Rectangle((row.px-patch_size//2, row.py-patch_size//2), patch_size, patch_size, alpha=alpha, linewidth=lw, edgecolor=color, facecolor='none')
        ax1.add_patch(rect)
        ax1.text(row.px-patch_size//2,row.py-patch_size//2-10,str(j+1),#+' ['+row.MAIN_ID+']',
                 ha='left',va='top',color=color,fontsize=14)
        j+=1
        patch = img[row.py-patch_size//2:row.py+patch_size//2,row.px-patch_size//2:row.px+patch_size//2]
        all_patches.append(patch)
        filter_idxs.append(i)
#ax1.set_title("M81 Bodes Galaxy & M81 Cigar Galaxy", fontsize=18)
#points = dfi[dfi.MAIN_ID.isin(['M84','M86','NGC4477','NGC4473','NGC4461','NGC4458','NGC4438','NGC4435'])][['px','py']].values
#ax1.plot(points[:, 0], points[:, 1], 'w',lw=3, ls='--',alpha=.5)
plt.tight_layout()
plt.savefig('C:/Users/xDDra/Desktop/Annotation/img1.png', bbox_inches='tight', pad_inches=.1, dpi=dpi)
plt.show()

img1 = plt.imread('C:/Users/xDDra/Desktop/Annotation/img1.png')

all_patches = np.array(all_patches)
all_patches.shape#, img1.shape

"""# darkmatters Logo"""

logo = plt.imread('C:/Users/xDDra/Desktop/Annotation/darkmatters_logo.png')
plt.imshow(logo)


"""# Second Image"""

m=6#int(np.ceil(np.sqrt(len(all_patches))))
n=10#10#int(np.ceil(len(all_patches)/m))
print(len(all_patches), m, n, m*n)

dft = dfi.iloc[filter_idxs].reset_index()
scale=2
fig, axarr = plt.subplots(m,n,figsize=(n*scale,m*scale))
for i, row in dft.iterrows():
    ax = axarr[i//n,i%n]
    ax.imshow(all_patches[i][::-1])
    ax.set_title(row.MAIN_ID, fontsize=10)
    ax.set_xticks([])
    ax.set_yticks([])
    ax.text(2,4,str(i+1),ha='left',va='top',fontsize=12)
for i in np.arange(len(all_patches),m*n):
    ax = axarr[i//n,i%n]
    ax.axis('off')
    
print()
#axarr[m-1,n-1].imshow(logo1, cmap='Greys')
#axarr[m-1,n-2].imshow(logo2, cmap='gray')
axarr[m-1,n-1].imshow(logo3)
plt.tight_layout()
plt.savefig('C:/Users/xDDra/Desktop/Annotation/img2.png', bbox_inches='tight', pad_inches=.1, dpi=dpi)
plt.show()

img2 = plt.imread('C:/Users/xDDra/Desktop/Annotation/img2.png')
img2.shape

"""# Image rescaling s.t. vstack works

## ugly, I know, but it does the job.
"""

from skimage.transform import resize
from PIL import Image

img22 = (resize(img2, (img2.shape[0]*(img1.shape[1]/img2.shape[1]), img1.shape[1]))*255).astype(np.uint8)
im = Image.fromarray(np.vstack([(img1*255).astype(np.uint8),img22])[:,:,:3])
im.save("C:/Users/xDDra/Desktop/Annotation/Annotated", quality=95, subsampling=0)
 
Benutzeravatar
__blackjack__
User
Beiträge: 13064
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@coldt_astro: If this runs til line 217 you'll get a very obvious `NameError` there. So is this really the code you are actually running?

Imports belong at the top of the module, after the module docstring, so it's easy to see the dependencies of the module.

The imported `coords` and `astropy_mpl_style` are not used anywhere.

``as`` in imports is used to rename things, but `patches` is ”renamed” to `patches`.

On module level there should be only code that defines modules, functions, and classes. The main program usually resides in a function called `main()`.

Names like `file`, `image_file` and so on should be used for *files*, not for file*names*. With `file` the reader expects an object with `read()` or `write()` and `close()` methods, not a string.

Neither data nor code should be repeated. Things like a common path should be defined once as constant and not be repeated throughout the program.

`center_ra` and `center_dec` are defined twice. The same way.

There are some statements that have no effect in a normal program. And literals strings are not meant to be used as comments. Only the # character is for comments.

`max()` takes multiple arguments. There's no need to put the values into a list.

Comments are there to explain something that can't be inferred from the code (and documentation of language and libraries). They should not simply repeat the code. Rule of thumb: Don't comment *what* the code does, that's already what the code itself tells the reader, but *why*, if that isn't obvious.

There's no need for the "Pixel_Position" column in the data frame. It's not really used and usually it's not a good idea to store sequences as values into a column.

The filtering by coordinates can be expressed with just two conditions with the `between()` method.

If only the first element of a `str.split()` result ist needed it's better to call the method with the maximum count of splits as second argument.

The splits of "MAIN_ID" values are a bit strange. Here a comment *why* this is done this way may be in order. Effectively it returns the part before the first space *or* "+", whatever comes first. This would be expressed much clearer as a regular expression IMHO. Also `Series` objects have string manipulation methods:

Code: Alles auswählen

In [115]: s
Out[115]: 
0    xyz abc+def hij
1    xyz+abc def hij
dtype: object

In [116]: s.apply(lambda text: text.split(" ", 1)[0].split("+", 1)[0])
Out[116]: 
0    xyz
1    xyz
dtype: object

In [117]: s.str.extract("(.*?)[ +]")
Out[117]: 
     0
0  xyz
1  xyz
Having the names `df`, `dft`, and `dfi` and `dpi` in the same name space (with lots of names) is not a good idea. Also there is not really a need for `dfi` as `df` isn't used after `dfi` is defined. The same is true for `dfi` and `dft`.

The "sorting" column is a bit strange as its type depends on whether all values could be replaced (→ integer) or not (→ string).

From the ``if``/``elif``/``else`` construct that determines patch size, line witdh, and color, and opacity, only the patch size is actually used. Also this can be simplified by a dictionary mapping "TYPE" values to patch sizes.

`j` is redundant because there is the list with the patches and `j` is always the length of that list + 1.

``&`` isn't for normal boolean operations. That's ``and``.

`iloc` shouldn't be used for labels even when you assume row labels and indices are the same. A change in the program and this assumption might be false.

When saving the last image I guess there's an exception too because the image file type/format isn't specified. I guess there should be a .jpg extension in the filename.

Untested:

Code: Alles auswählen

#!/usr/bin/env python3
"""M81M82_visualization.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1BmtQxdByqfsm6iVLxr8s-5eEXNfVHOgw

# Load fits image and header

## Attention: in this case only the header of the fits file is used. the image
originates from a png!!!
"""
from pathlib import Path

import astropy.units as u
import matplotlib.pyplot as plt
import numpy as np
from astropy.coordinates import SkyCoord
from astropy.io import fits
from astropy.wcs import WCS
from astropy.wcs.utils import skycoord_to_pixel
from astroquery.simbad import Simbad
from matplotlib.patches import Rectangle
from PIL import Image
from skimage.transform import resize

ANNOTATION_PATH = Path("C:/Users/xDDra/Desktop/Annotation")
IMAGE_FILE_PATH = ANNOTATION_PATH / "M81.fit"
RAW_IMAGE_FILE_PATH = None  # "M81.png"
DPI = 150


def main():
    plt.style.use("dark_background")
    #
    # Load the FITS image and extract header information.
    #
    fits_image = fits.open(IMAGE_FILE_PATH)
    #
    # Use better quality png in this case.
    #
    if RAW_IMAGE_FILE_PATH:
        image = plt.imread(RAW_IMAGE_FILE_PATH)[::-1]
    else:
        #
        # TODO `astype()` may be unnecessary.
        #
        image = np.swapaxes(
            np.swapaxes(fits_image[0].data, 0, 2), 0, 1
        ).astype(float)
        image = image / image.max()

    header = fits_image[0].header
    #
    # Get world coordinates system and drop color channel.
    #
    wcs = WCS(header).dropaxis(2)

    right_ascension_of_center = header["CRVAL1"]
    declination_of_center = header["CRVAL2"]
    #
    # TODO Don't use the global state API but the object oriented one.
    #
    _ = plt.figure(figsize=(10, 6))
    ax = plt.subplot(projection=wcs, label="overlays")
    ax.imshow(image, origin="lower", cmap="gray")
    ax.coords.grid(True, color="white", ls="-", alpha=0.5)
    ax.coords[0].set_axislabel("Right Ascension (J2000)")
    ax.coords[1].set_axislabel("Declination (J2000)")
    ax.scatter(
        right_ascension_of_center,
        declination_of_center,
        transform=ax.get_transform("icrs"),
        s=10,
        edgecolor="red",
        facecolor="red",
    )
    #
    # Query Objects
    #
    # 1. define the center of the circle
    # 2. define the search header using header-information
    # 3. query objects
    # 4. filter objects outside the FOV
    # 5. fitler objects according to catalog (see definition in column TYPE)
    #
    Simbad.TIMEOUT = 120

    # Define the specific coordinates
    target_coord = SkyCoord(
        ra=right_ascension_of_center,
        dec=declination_of_center,
        unit=(u.deg, u.deg),
        frame="icrs",
    )
    search_radius = (
        max(abs(header["CDELT1"]), abs(header["CDELT2"]))
        * max(header["NAXIS1"], header["NAXIS2"])
        / 1.5
    )
    df = Simbad.query_criteria(
        f"region(CIRCLE, {target_coord.ra.deg} + {target_coord.dec.deg},"
        f" {search_radius}d)",
        otype="galaxy",
    ).to_pandas()
    #
    # Filter by position.
    #
    coordinates = [
        skycoord_to_pixel(
            SkyCoord(right_ascension, declination, unit=(u.hourangle, u.deg)),
            wcs,
        )
        for right_ascension, declination in df[["RA", "DEC"]].values
    ]
    df["px"] = [int(x) for x, _ in coordinates]
    df["py"] = [int(y) for _, y in coordinates]
    df = df[
        df["px"].between(0, image.shape[1], "neither")
        & df["py"].between(0, image.shape[0], "neither")
    ]
    #
    # Filter by catalog.
    #
    # Get catalog/TYPE of object by simple string manipulation.
    #
    df["TYPE"] = df["MAIN_ID"].str.extract("(.*?)[ +]")
    df = df[df["TYPE"].isin(["M", "NGC", "IC", "MCG", "UGC", "2MASX"])]
    #
    # Plot first image
    #
    # Attention: here I defined a dictionary for sorting the results
    # accordingly.
    #
    df["sorting"] = df["TYPE"].replace(
        {"M": "1", "NGC": "2", "IC": "3", "UGC": "4"}
    )
    df["MAIN_ID"] = df["MAIN_ID"].str.replace(" ", "")
    df = df.sort_values(["sorting", "MAIN_ID"], ascending=True).reset_index()

    scale = 20
    _ = plt.figure(figsize=(scale, (image.shape[0] / image.shape[1]) * scale))
    ax = plt.subplot(projection=wcs, label="overlays")
    ax.imshow(image[::1, ::1])
    ax.coords.grid(True, color="white", ls="-", alpha=0.5)
    ax.coords[0].set_axislabel("Right Ascension (J2000)")
    ax.coords[1].set_axislabel("Declination (J2000)", minpad=-1)

    type_to_patch_size = {"M": 512, "NGC": 256, "IC": 128, "UGC": 128}
    color = "white"
    patches = []
    patch_row_labels = []
    for row_label, row in df.iterrows():
        patch_size = (
            1024
            if row["MAIN_ID"] == "M81"
            else type_to_patch_size.get(row["TYPE"], 32)
        )
        x_1 = row["px"] - patch_size // 2
        y_1 = row["py"] - patch_size // 2
        x_2 = row["px"] + patch_size // 2
        y_2 = row["py"] + patch_size // 2
        if (
            x_1 > 0
            and x_2 < image.shape[1]
            and y_1 > 0
            and y_2 < image.shape[0]
        ):
            ax.add_patch(
                Rectangle(
                    (x_1, y_1),
                    patch_size,
                    patch_size,
                    alpha=0.5,
                    linewidth=1,
                    edgecolor=color,
                    facecolor="none",
                )
            )
            ax.text(
                x_1,
                y_1 - 10,
                str(len(patches) + 1),
                ha="left",
                va="top",
                color=color,
                fontsize=14,
            )
            patches.append(image[y_1:y_2, x_1:x_2])
            patch_row_labels.append(row_label)

    # ax.set_title("M81 Bodes Galaxy & M81 Cigar Galaxy", fontsize=18)
    # points = df[
    #     df["MAIN_ID"].isin(
    #         [
    #             "M84",
    #             "M86",
    #             "NGC4477",
    #             "NGC4473",
    #             "NGC4461",
    #             "NGC4458",
    #             "NGC4438",
    #             "NGC4435",
    #         ]
    #     )
    # ][["px", "py"]].values
    # ax.plot(points[:, 0], points[:, 1], "w", lw=3, ls="--", alpha=0.5)

    image1_path = ANNOTATION_PATH / "image1.png"

    plt.tight_layout()
    plt.savefig(image1_path, bbox_inches="tight", pad_inches=0.1, dpi=DPI)
    plt.show()

    patches = np.array(patches)
    plt.imshow(plt.imread(ANNOTATION_PATH / "darkmatters_logo.png"))
    #
    # Second Image.
    #
    m = 6
    n = 10
    print(len(patches), m, n, m * n)

    df = df.loc[patch_row_labels].reset_index()
    scale = 2
    _, axs = plt.subplots(m, n, figsize=(n * scale, m * scale))
    for i, row in df.iterrows():
        ax = axs[i // n, i % n]
        ax.imshow(patches[i][::-1])
        ax.set_title(row["MAIN_ID"], fontsize=10)
        ax.set_xticks([])
        ax.set_yticks([])
        ax.text(2, 4, str(i + 1), ha="left", va="top", fontsize=12)

    for i in np.arange(len(patches), m * n):
        axs[i // n, i % n].axis("off")

    # axs[m-1,n-1].imshow(logo1, cmap="Greys")
    # axs[m-1,n-2].imshow(logo2, cmap="gray")
    # axs[m-1,n-1].imshow(logo3)

    image2_path = ANNOTATION_PATH / "image2.png"
    plt.tight_layout()
    plt.savefig(image2_path, bbox_inches="tight", pad_inches=0.1, dpi=DPI)
    plt.show()
    #
    # Image rescaling s.t. vstack works.
    #
    # Ugly, I know, but it does the job.
    #
    image1 = plt.imread(image1_path)
    image2 = plt.imread(image2_path)
    Image.fromarray(
        np.vstack(
            [
                (image1 * 255).astype(np.uint8),
                (
                    resize(
                        image2,
                        (
                            image2.shape[0]
                            * (image1.shape[1] / image2.shape[1]),
                            image1.shape[1],
                        ),
                    )
                    * 255
                ).astype(np.uint8),
            ]
        )[:, :, :3]
    ).save(ANNOTATION_PATH / "Annotated.jpg", quality=95, subsampling=0)


if __name__ == "__main__":
    main()
There too much code for just one main function here. This should be refactored into smaller functions.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
coldt_astro
User
Beiträge: 2
Registriert: Freitag 2. Juni 2023, 12:07

Hey, it is split into different cells. Main problem seems to be that the program for some reason, cannot read something in the FITS Header.

Code: Alles auswählen

WARNING: FITSFixedWarning: 'cdfix' made the change 'Success'. [astropy.wcs.wcs]
WARNING:astroquery:FITSFixedWarning: 'cdfix' made the change 'Success'.
<matplotlib.collections.PathCollection at 0x7f7606375510>
It is plate solved via the plate solver in PixInsight. ICRS being used.
https://i.imgur.com/MqwE869.png
Antworten