Example #1
0
def readStoredTable(tableFilePath):
    inpath = ut.absPath(tableFilePath)

    # open the file
    with open(inpath, 'rb') as infile:
        # verify header tags
        if stringFromFile(infile) != "SKIRT X" or intFromFile(
                infile) != 0x010203040A0BFEFF:
            raise ValueError(
                "File does not have SKIRT stored table format: {}".format(
                    inpath))

        # get the axes metadata and grids
        numAxes = intFromFile(infile)
        axisNames = [stringFromFile(infile) for i in range(numAxes)]
        axisUnits = [stringFromFile(infile) for i in range(numAxes)]
        axisScales = [stringFromFile(infile) for i in range(numAxes)]
        axisGrids = [
            arrayFromFile(infile, (intFromFile(infile), ))
            for i in range(numAxes)
        ]

        # get the quantities metadata
        numQuantities = intFromFile(infile)
        quantityNames = [stringFromFile(infile) for i in range(numQuantities)]
        quantityUnits = [stringFromFile(infile) for i in range(numQuantities)]
        quantityScales = [stringFromFile(infile) for i in range(numQuantities)]

        # get the quantity values
        shapeValues = tuple([numQuantities] +
                            [len(axisGrid) for axisGrid in axisGrids])
        values = arrayFromFile(infile, shapeValues)

        # verify the trailing tag
        if stringFromFile(infile) != "STABEND":
            raise ValueError(
                "File does not have the proper trailing tag: {}".format(
                    inpath))

    # construct the dictionary that will be returned, adding basic metadata
    d = dict(axisNames=axisNames,
             axisUnits=axisUnits,
             axisScales=axisScales,
             quantityNames=quantityNames,
             quantityUnits=quantityUnits,
             quantityScales=quantityScales)

    # add axis grids
    for i in range(numAxes):
        d[axisNames[i]] = axisGrids[i] << sm.unit(axisUnits[i])

    # add quantities information
    for i in range(numQuantities):
        d[quantityNames[i]] = values[i] << sm.unit(quantityUnits[i])

    return d
Example #2
0
def do(
    simDirPath: (str, "SKIRT simulation output directory"),
    prefix: (str, "SKIRT simulation prefix") = "",
    plot: (str, "type of plot: linmap, degmap, degavg, or cirmap") = "linmap",
    wave: (float,
           "wavelength of the frame to be plotted; 0 means all frames") = 0,
    bin: (int, "number of pixels in a bin, in both x and y directions") = 7,
    dex:
    (float,
     "number of decades to be included in the background intensity range (color bar)"
     ) = 5,
) -> "plot polarization maps for the output of one or more SKIRT simulations":

    import pts.simulation as sm
    import pts.visual as vis

    for sim in sm.createSimulations(simDirPath,
                                    prefix if len(prefix) > 0 else None):
        vis.plotPolarization(
            sim,
            plotLinMap=plot.lower().startswith("lin"),
            plotDegMap=plot.lower().startswith("degm"),
            plotDegAvg=plot.lower().startswith("dega"),
            plotCirMap=plot.lower().startswith("cir"),
            wavelength='all' if wave == 0 else wave << sm.unit("micron"),
            binSize=(bin, bin),
            decades=dex)
Example #3
0
def makeConvolvedRGBImages(simulation, contributions, name="", *, fileType="total", decades=3,
                            fmax=None, fmin=None, outDirPath=None):
    # get the (instrument, output file path) tuples to be handled
    instr_paths = sm.instrumentOutFilePaths(simulation, fileType+".fits")
    if len(instr_paths) > 0:
        name = "" if not name else "_" + name
        sbunit = sm.unit("MJy/sr")
        # construct an image object with integrated color channels for each output file
        # and keep track of the largest surface brightness value (in MJy/sr) in each of the images
        images = []
        fmaxes = []
        for instrument, filepath in instr_paths:
            logging.info("Convolving for RGB image {}{}".format(filepath.stem, name))
            # get the data cube in its intrinsic units
            cube = sm.loadFits(filepath)
            # initialize an RGB frame
            dataRGB = np.zeros( (cube.shape[0], cube.shape[1], 3) ) << sbunit
            # add color for each filter
            for band,w0,w1,w2 in contributions:
                # convolve and convert to per frequency units
                data = band.convolve(instrument.wavelengths(), cube, flavor=sbunit, numWavelengths=10)
                dataRGB[:,:,0] += w0*data
                dataRGB[:,:,1] += w1*data
                dataRGB[:,:,2] += w2*data
            # construct the image object
            images.append(vis.RGBImage(dataRGB.value))
            fmaxes.append(dataRGB.max())
        # determine the appropriate pixel range for all output images
        fmax = max(fmaxes) if fmax is None else fmax << sbunit
        fmin = fmax/10**decades if fmin is None else fmin << sbunit
        # create an RGB file for each output file
        for (instrument, filepath), image in zip(instr_paths,images):
            image.setRange(fmin.value, fmax.value)
            image.applyLog()
            image.applyCurve()
            # determine output file path
            saveFilePath = ut.savePath(filepath.with_name(filepath.stem+name+".png"), ".png", outDirPath=outDirPath)
            image.saveTo(saveFilePath)
            logging.info("Created convolved RGB image file {}".format(saveFilePath))
        # return the surface brightness range used for these images
        return fmin,fmax
Example #4
0
def readStoredColumns(columnsFilePath):
    inpath = ut.absPath(columnsFilePath)

    # open the file
    with open(inpath, 'rb') as infile:
        # verify header tags
        if stringFromFile(infile) != "SKIRT X" or intFromFile(infile) != 0x010203040A0BFEFF \
                        or intFromFile(infile) != 0:
            raise ValueError(
                "File does not have SKIRT stored table format: {}".format(
                    inpath))

        # get the number of columns and rows
        numRows = intFromFile(infile)
        numColumns = intFromFile(infile)

        # get the column metadata
        columnNames = [stringFromFile(infile) for i in range(numColumns)]
        columnUnits = [stringFromFile(infile) for i in range(numColumns)]

        # get the data values
        values = arrayFromFile(infile, (numColumns, numRows))

        # verify the trailing tag
        if stringFromFile(infile) != "SCOLEND":
            raise ValueError(
                "File does not have the proper trailing tag: {}".format(
                    inpath))

    # construct the dictionary that will be returned, adding basic metadata
    d = dict(columnNames=columnNames, columnUnits=columnUnits)

    # add data values
    for i in range(numColumns):
        d[columnNames[i]] = values[i] << sm.unit(columnUnits[i])

    return d
Example #5
0
def plotPolarization(simulation,
                     *,
                     plotLinMap=True,
                     plotDegMap=False,
                     plotDegAvg=False,
                     plotCirMap=False,
                     wavelength=None,
                     binSize=(7, 7),
                     degreeScale=None,
                     decades=5,
                     outDirPath=None,
                     figSize=(8, 6),
                     interactive=None):

    # loop over all applicable instruments
    for instrument, filepath in sm.instrumentOutFilePaths(
            simulation, "stokesQ.fits"):
        # form the simulation/instrument name
        insname = "{}_{}".format(instrument.prefix(), instrument.name())

        # get the file paths for the frames/data cubes
        filepathI = instrument.outFilePaths("total.fits")[0]
        filepathQ = instrument.outFilePaths("stokesQ.fits")[0]
        filepathU = instrument.outFilePaths("stokesU.fits")[0]
        filepathV = instrument.outFilePaths("stokesV.fits")[0]

        # load datacubes with shape (nx, ny, nlambda)
        Is = sm.loadFits(filepathI)
        Qs = sm.loadFits(filepathQ)
        Us = sm.loadFits(filepathU)
        Vs = sm.loadFits(filepathV)

        # load the axes grids (assuming all files have the same axes)
        xgrid, ygrid, wavegrid = sm.getFitsAxes(filepathI)
        xmin = xgrid[0].value
        xmax = xgrid[-1].value
        ymin = ygrid[0].value
        ymax = ygrid[-1].value
        extent = (xmin, xmax, ymin, ymax)

        # determine binning configuration
        binX = binSize[0]
        orLenX = Is.shape[0]
        dropX = orLenX % binX
        startX = dropX // 2
        binY = binSize[1]
        orLenY = Is.shape[1]
        dropY = orLenY % binY
        startY = dropY // 2

        # construct arrays with central bin positions in pixel coordinates
        posX = np.arange(startX - 0.5 + binX / 2.0,
                         orLenX - dropX + startX - 0.5, binX)
        posY = np.arange(startY - 0.5 + binY / 2.0,
                         orLenY - dropY + startY - 0.5, binY)

        # determine the appropriate wavelength index or indices
        if wavelength is None:
            indices = [0]
        elif wavelength == 'all':
            indices = range(Is.shape[2])
        else:
            if not isinstance(wavelength, (list, tuple)):
                wavelength = [wavelength]
            indices = instrument.wavelengthIndices(wavelength)

        # loop over all requested wavelength indices
        for index in indices:
            wave = wavegrid[index]
            wavename = "{:09.4f}".format(wave.to_value(sm.unit("micron")))
            wavelatex = r"$\lambda={:.4g}$".format(
                wave.value) + sm.latexForUnit(wave)

            # extract the corresponding frame, and transpose to (y,x) style for compatibility with legacy code
            I = Is[:, :, index].T.value
            Q = Qs[:, :, index].T.value
            U = Us[:, :, index].T.value
            V = Vs[:, :, index].T.value

            # perform the actual binning
            binnedI = np.zeros((len(posY), len(posX)))
            binnedQ = np.zeros((len(posY), len(posX)))
            binnedU = np.zeros((len(posY), len(posX)))
            binnedV = np.zeros((len(posY), len(posX)))
            for x in range(len(posX)):
                for y in range(len(posY)):
                    binnedI[y, x] = np.sum(
                        I[startY + binY * y:startY + binY * (y + 1),
                          startX + binX * x:startX + binX * (x + 1)])
                    binnedQ[y, x] = np.sum(
                        Q[startY + binY * y:startY + binY * (y + 1),
                          startX + binX * x:startX + binX * (x + 1)])
                    binnedU[y, x] = np.sum(
                        U[startY + binY * y:startY + binY * (y + 1),
                          startX + binX * x:startX + binX * (x + 1)])
                    binnedV[y, x] = np.sum(
                        V[startY + binY * y:startY + binY * (y + 1),
                          startX + binX * x:startX + binX * (x + 1)])

            # -----------------------------------------------------------------

            # plot a linear polarization map
            if plotLinMap:
                fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize)

                # configure the axes
                ax.set_xlim(xmin, xmax)
                ax.set_ylim(ymin, ymax)
                ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large')
                ax.set_ylabel("y" + sm.latexForUnit(ygrid), fontsize='large')

                # determine intensity range for the background image, ignoring pixels with outrageously high flux
                Ib = I.copy()
                highmask = Ib > 1e6 * np.nanmedian(np.unique(Ib))
                vmax = np.nanmax(Ib[~highmask])
                Ib[highmask] = vmax
                vmin = vmax / 10**decades

                # plot the background image and the corresponding color bar
                normalizer = matplotlib.colors.LogNorm(vmin, vmax)
                cmap = plt.get_cmap('PuRd')
                cmap.set_under('w')
                backPlot = ax.imshow(Ib,
                                     norm=normalizer,
                                     cmap=cmap,
                                     extent=extent,
                                     aspect='equal',
                                     interpolation='bicubic',
                                     origin='lower')
                cbarlabel = sm.latexForSpectralFlux(Is) + sm.latexForUnit(
                    Is) + " @ " + wavelatex
                plt.colorbar(backPlot, ax=ax).set_label(cbarlabel,
                                                        fontsize='large')

                # compute the linear polarization degree
                degreeLD = np.sqrt(binnedQ**2 + binnedU**2)
                degreeLD[degreeLD > 0] /= binnedI[degreeLD > 0]

                # determine a characteristic 'high' degree of polarization in the frame
                # (this has to be done before degreeLD contains 'np.NaN')
                charDegree = np.percentile(degreeLD, 99.0)
                if not 0 < charDegree < 1:
                    charDegree = np.nanmax((np.nanmax(degreeLD), 0.0001))

                # remove pixels with minuscule polarization
                degreeLD[degreeLD < charDegree / 50] = np.NaN

                # determine the scaling so that the longest arrows do not to overlap with neighboring arrows
                if degreeScale is None:
                    degreeScale = _roundUp(charDegree)
                lengthScale = 2.2 * degreeScale * max(
                    float(len(posX)) / figSize[0],
                    float(len(posY)) / figSize[1])
                key = "{:.3g}%".format(100 * degreeScale)

                # compute the polarization angle
                angle = 0.5 * np.arctan2(
                    binnedU, binnedQ
                )  # angle from North through East while looking at the sky

                # create the polarization vector arrays
                xPolarization = -degreeLD * np.sin(
                    angle
                )  #For angle = 0: North & x=0, For angle = 90deg: West & x=-1
                yPolarization = degreeLD * np.cos(
                    angle
                )  #For angle = 0: North & y=1, For angle = 90deg: West & y=0

                # plot the vector field (scale positions to data coordinates)
                X, Y = np.meshgrid(xmin + posX * (xmax - xmin) / orLenX,
                                   ymin + posY * (ymax - ymin) / orLenY)
                quiverPlot = ax.quiver(X,
                                       Y,
                                       xPolarization,
                                       yPolarization,
                                       pivot='middle',
                                       units='inches',
                                       angles='xy',
                                       scale=lengthScale,
                                       scale_units='inches',
                                       headwidth=0,
                                       headlength=1,
                                       headaxislength=1,
                                       minlength=0.8,
                                       width=0.02)
                ax.quiverkey(quiverPlot,
                             0.85,
                             0.02,
                             degreeScale,
                             key,
                             coordinates='axes',
                             labelpos='E')

                # if not in interactive mode, save the figure; otherwise leave it open
                if not ut.interactive(interactive):
                    saveFilePath = ut.savePath(
                        filepath,
                        ".pdf",
                        outDirPath=outDirPath,
                        outFileName="{}_{}_pollinmap.pdf".format(
                            insname, wavename))
                    plt.savefig(saveFilePath,
                                bbox_inches='tight',
                                pad_inches=0.25)
                    plt.close()
                    logging.info("Created {}".format(saveFilePath))

            # -----------------------------------------------------------------

            # plot a linear polarization degree map
            if plotDegMap:
                fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize)

                # configure the axes
                ax.set_xlim(xmin, xmax)
                ax.set_ylim(ymin, ymax)
                ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large')
                ax.set_ylabel("y" + sm.latexForUnit(ygrid), fontsize='large')

                # calculate polarization degree for each pixel, in percent
                # set degree to zero for pixels with very low intensity
                cutmask = I < (np.nanmax(
                    I[I < 1e6 * np.nanmedian(np.unique(I))]) / 10**decades)
                degreeHD = np.sqrt(Q**2 + U**2)
                degreeHD[~cutmask] /= I[~cutmask]
                degreeHD[cutmask] = 0
                degreeHD *= 100

                # plot the image and the corresponding color bar
                vmax = degreeScale if degreeScale is not None else np.percentile(
                    degreeHD, 99)
                normalizer = matplotlib.colors.Normalize(vmin=0, vmax=vmax)
                backPlot = ax.imshow(degreeHD,
                                     norm=normalizer,
                                     cmap='plasma',
                                     extent=extent,
                                     aspect='equal',
                                     interpolation='bicubic',
                                     origin='lower')
                plt.colorbar(backPlot, ax=ax).set_label(
                    "Linear polarization degree (%)" + " @ " + wavelatex,
                    fontsize='large')

                # if not in interactive mode, save the figure; otherwise leave it open
                if not ut.interactive(interactive):
                    saveFilePath = ut.savePath(
                        filepath,
                        ".pdf",
                        outDirPath=outDirPath,
                        outFileName="{}_{}_poldegmap.pdf".format(
                            insname, wavename))
                    plt.savefig(saveFilePath,
                                bbox_inches='tight',
                                pad_inches=0.25)
                    plt.close()
                    logging.info("Created {}".format(saveFilePath))

            # -----------------------------------------------------------------

            # plot the y-axis averaged linear polarization degree
            if plotDegAvg:
                # construct the plot
                fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize)
                degreeHD = np.sqrt(
                    np.average(Q, axis=0)**2 + np.average(U, axis=0)**2)
                degreeHD /= np.average(I, axis=0)
                ax.plot(xgrid.value, degreeHD * 100)
                ax.set_xlim(xmin, xmax)
                ax.set_ylim(0, degreeScale)
                ax.set_title("{}   {}".format(insname, wavelatex),
                             fontsize='large')
                ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large')
                ax.set_ylabel('Average linear polarization degree (%)',
                              fontsize='large')

                # if not in interactive mode, save the figure; otherwise leave it open
                if not ut.interactive(interactive):
                    saveFilePath = ut.savePath(
                        filepathI,
                        ".pdf",
                        outDirPath=outDirPath,
                        outFileName="{}_{}_poldegavg.pdf".format(
                            insname, wavename))
                    plt.savefig(saveFilePath,
                                bbox_inches='tight',
                                pad_inches=0.25)
                    plt.close()
                    logging.info("Created {}".format(saveFilePath))

            # -----------------------------------------------------------------

            # plot a circular polarization map
            if plotCirMap:
                fig, ax = plt.subplots(ncols=1, nrows=1, figsize=figSize)

                # configure the axes
                ax.set_xlim(xmin, xmax)
                ax.set_ylim(ymin, ymax)
                ax.set_xlabel("x" + sm.latexForUnit(xgrid), fontsize='large')
                ax.set_ylabel("y" + sm.latexForUnit(ygrid), fontsize='large')

                # determine intensity range for the background image, ignoring pixels with outrageously high flux
                Ib = I.copy()
                highmask = Ib > 1e6 * np.nanmedian(np.unique(Ib))
                vmax = np.nanmax(Ib[~highmask])
                Ib[highmask] = vmax
                vmin = vmax / 10**decades

                # plot the background image and the corresponding color bar
                normalizer = matplotlib.colors.LogNorm(vmin, vmax)
                cmap = plt.get_cmap('PuRd')
                cmap.set_under('w')
                backPlot = ax.imshow(Ib,
                                     norm=normalizer,
                                     cmap=cmap,
                                     extent=extent,
                                     aspect='equal',
                                     interpolation='bicubic',
                                     origin='lower')
                cbarlabel = sm.latexForSpectralFlux(Is) + sm.latexForUnit(
                    Is) + " @ " + wavelatex
                plt.colorbar(backPlot, ax=ax).set_label(cbarlabel,
                                                        fontsize='large')

                # compute the circular polarization degree
                degreeLD = binnedV.copy()
                degreeLD[binnedI > 0] /= binnedI[binnedI > 0]

                # determine the scaling and add legend
                if degreeScale is None:
                    degreeScale = _roundUp(np.percentile(np.abs(degreeLD), 99))
                lengthScale = 0.7 / max(len(posX), len(posY))
                _circArrow(ax, 0.84 - lengthScale / 2, 0.01 + lengthScale / 2,
                           lengthScale)
                key = r'$+{} \%$'.format(100 * degreeScale)
                ax.text(0.85,
                        0.01 + lengthScale / 2,
                        key,
                        transform=ax.transAxes,
                        ha='left',
                        va='center')

                # actual plotting
                for x in range(len(posX)):
                    for y in range(len(posY)):
                        if np.isfinite(degreeLD[y, x]) and abs(
                                degreeLD[y, x]) > degreeScale / 50:
                            _circArrow(
                                ax, posX[x] / orLenX, posY[y] / orLenY,
                                degreeLD[y, x] / degreeScale * lengthScale)

                # if not in interactive mode, save the figure; otherwise leave it open
                if not ut.interactive(interactive):
                    saveFilePath = ut.savePath(
                        filepath,
                        ".pdf",
                        outDirPath=outDirPath,
                        outFileName="{}_{}_polcirmap.pdf".format(
                            insname, wavename))
                    plt.savefig(saveFilePath,
                                bbox_inches='tight',
                                pad_inches=0.25)
                    plt.close()
                    logging.info("Created {}".format(saveFilePath))
Example #6
0
def do(
    simDirPath: (str, "SKIRT simulation output directory"),
    prefix: (str, "SKIRT simulation prefix") = "",
    type: (str,
           "type of SKIRT instrument output files to be handled") = "total",
    name: (str,
           "name segment that will be added to the image file names") = "",
    colors:
    (str,
     "three comma-separated wavelength values or broadband names defining the R,G,B colors"
     ) = "",
) -> "create RGB images for surface brightness maps generated by SKIRT instruments":

    import pts.band as bnd
    import pts.simulation as sm
    import pts.utils as ut
    import pts.visual as vis

    # get the simulations to be handled
    sims = sm.createSimulations(simDirPath,
                                prefix if len(prefix) > 0 else None)

    # parse the colors and handle accordingly

    # no colors given
    if len(colors) == 0:
        if len(name) > 0:
            raise ut.UserError(
                "name argument is not supported when colors are not specified")
        for sim in sims:
            vis.makeRGBImages(sim, fileType=type)
        return

    # get segments
    segments = colors.split(',')
    if len(segments) != 3:
        raise ut.UserError(
            "colors argument must have three comma-separated segments")

    # try wavelengths
    try:
        wavelengths = [float(segment) for segment in segments]
    except ValueError:
        wavelengths = None
    if wavelengths is not None:
        tuples = {name: wavelengths << sm.unit("micron")}
        for sim in sims:
            vis.makeRGBImages(sim, wavelengthTuples=tuples, fileType=type)
        return

    # try bands
    try:
        bands = [bnd.BroadBand(segment) for segment in segments]
    except ValueError:
        bands = None
    if bands is not None:
        contributions = [(bands[0], 1, 0, 0), (bands[1], 0, 1, 0),
                         (bands[1], 0, 0, 1)]
        for sim in sims:
            vis.makeConvolvedRGBImages(sim,
                                       contributions=contributions,
                                       fileType=type,
                                       name=name)
        return

    raise ut.UserError(
        "colors argument must specify three wavelengths in micron or three broadband names"
    )