text:
\section{Introduction}
The ability to understand and follow natural language instructions is critical for intelligent agents to interact with humans and the physical world.
One of the recently designed tasks in this direction is Vision-and-Language Navigation~(VLN)~\cite{anderson2018vision}, which requires an agent to carry out a sequence of actions in a photo-realistic simulated environment in response to a sequence of natural language instructions.
To accomplish this task, the agent should have three abilities: understanding linguistic semantics, perceiving the visual environment, and reasoning over both modalities~\cite{zhu2020babywalk,wang2019reinforced}.
While understanding vision and language are difficult problems by themselves, learning the connection between them without direct supervision makes this task even more challenging~\cite{hong2020sub}.

To address this challenge, some neural agents establish the connection using attention mechanism to relate the tokens from a given instruction to the images in a panoramic photo~\cite{anderson2018vision,fried2018speaker,ma2019self,yu2018guided}.
Surprisingly, although those models can improve the performance,~\newcite{hu2019you} found they ignore the visual information.
There is no clear evidence that the agent can correspond the components of the visual environment to the instructions~\cite{hong2020sub}.
Based on these results, recent research started to improve the agent's reasoning ability by explicitly considering the structure of language and image.
From the language side, \newcite{hong2020sub} annotated fine-grained sub-instructions and their corresponding trajectories and used the co-grounded features of a part of instruction and the image to predict the next action.
From the image side, \newcite{hu2019you} induced a high-level object-based visual representation to ground the language into the visual context.

In the same direction, we propose a neural agent, namely \textit{Spatial-Configuration-Based-Navigation (SpC-NAV)}, and consider the structure of both modalities, that is, spatial semantics of the instructions  and the objects in the images.
We use the notion of  \textit{Spatial Configuration}~\cite{dan2020spatial} to model the instructions and design a state attention  to ensure the execution order of spatial configurations.
Then, we utilize the spatial semantics elements, namely \textit{motion indicator}, \textit{spatial indicator} and \textit{landmark} in spatial configuration to establish the connection with the visual environment. Specifically, we use the similarity score between the landmark representation in the spatial configurations and the object representation in the panoramic images to control the transitions between configurations.
Also, we align object representations with the configuration representations enriched with motion indicator, spatial indicator and landmark representations to finally select the navigable image.

\begin{figure}[!ht]
\centering
\begin{subfigure}[b]{0.90\linewidth}
\includegraphics[width=\linewidth]{configuration_example.jpg}
\caption{Spatial Configuration Scheme}
\label{figure1}
\end{subfigure}
\begin{subfigure}[b]{1.00\linewidth}
\includegraphics[width=\linewidth]{annotation_exmaple.jpg}
\caption{Spatial Configuration Annotation}
\label{figure2}
\end{subfigure}
 \caption{\textbf{Spatial Configuration example}. The instruction "Move to the table with chair, and stop." can be split into two spatial configurations: "move to the table with chair" and "stop". In configuration1, "move" is motion indicator; "to" is spatial indicator; "table" is landmark. "table with chair" is a nested spatial configuration of configuration1. The role of "table" is  trajector; "with" is spatial indicator; and "chair" is landmark. In configuration2, "stop" is motion indicator.}
 \vspace{-5mm}
\label{fig:spatial Configuration Scheme Example}
\end{figure}
A spatial configuration is the smallest linguistic unit that describes the location/trans-location of an object with respect to a reference or a path that can be perceived in the environment. It contains fine-grained spatial roles, such as motion indicator, landmark, spatial indicator, trajector. Essentially, each spatial configuration forms a sub-instruction in our setting.
Figure \ref{fig:spatial Configuration Scheme Example} shows an example of splitting an instruction into its corresponding spatial configurations and the extracted spatial roles.
Previous research argues representing the semantic structure of the language could improve the reasoning capabilities of deep learning models~\cite{dan2020spatial, zheng2020srlgrn}.
There are relevant work modeling the meaning of spatial semantics in probabilistic models~\cite{kollar2010toward,tellex2011understanding} and neural models~\cite{regier1996human, ghanimifard2019goes}. However, its impact on deep learning models for navigation remains an open research problem.

\noindent The contribution of this paper is as follows:\\
1. We consider the spatial semantic structure of the instructions explicitly in terms of spatial configurations and their spatial semantic elements, i.e., spatial/motion indicators, and landmarks to enrich the configuration representations.\\
2. We introduce a state attention to guarantee that configurations are executed sequentially. Also, we utilize the grounding between the extracted spatial elements and the object representation to help control the transitions between configurations.\\
3. Our experiment results show that considering the explicit representation of semantic elements of the spatial configurations improves the strong baselines significantly in the seen environments and yields competitive results in the unseen environments.

\section{Related Work}
\label{Related Work}
Older studies on navigation before the deep learning era are mostly symbolic grounding methods, which are based on parsing the semantics of the instruction and learning probabilistic models. \newcite{macmahon2006walk} used the parser to associate the linguistic elements in free-form instruction to their corresponding action, location and object in the environment.
\newcite{tellex2011understanding} represented the spatial language as a hierarchy of Spatial Description Clauses (SDC) and proposed a discriminative probabilistic graphical model to find the most probable path with the extracted SDC and the detected visual landmark.
\newcite{mei2016listen} provided a good overview of the past classical work on navigation. However, one of the biggest limitations of those methods is that they required prior linguistic structure and manual annotations.

In recent years, given the new capabilities created by deep learning architectures, the navigation task is extended to the photo-realistic simulated environments~\cite{anderson2018vision,thomason2019vision,chen2019touchdown}.
Based on this,
a Sequence-to-Sequence (Seq2seq) baseline model was proposed by~\newcite{anderson2018vision} to encode the instructions and decode the embeddings to identify the corresponding output action sequence with the observed images. \newcite{fried2018speaker} proposed to train a speaker model to augment the instructions for the follower model. \newcite{ma2019self} introduced a visual and textual co-attention mechanism and a progress monitor loss to track the execution progress.
Although those agents achieved better performance,
the semantic structures on both language and vision sides were ignored.

We aim to exploit both symbolic grounding and neural models in the spatial domain.  \newcite{regier1996human} designed the neurons to learn the meaning of spatial prepositions.
\newcite{ghanimifard2019goes} explored the effects of spatial knowledge in a generative neural language model for the image description. We mainly work on incorporating the spatial semantics in navigation neural agent.
\newcite{hong2020sub} recently provided a method to segment the long instruction into sub-instructions. They used a shifting attention module to infer whether the current sub-instruction has been completed. Sub-instructions differ from us as
they manually aligned the instructions and viewpoints to learn the alignments, while we modeled spatial semantics to guide the alignment automatically.
Moreover, their proposed shifting attention module is hard attention, and a threshold is set to decide whether the agent should execute the next sub-instruction. However, we utilize the grounding between the landmarks and the objects to control the transitions between sub-instructions.
\
\section{Navigation Model}
\subsection{Problem Formulation}
In this task, the agent follows an instruction to navigate from a start viewpoint to a goal viewpoint in a photo-realistic environment. Formally, the agent is given a natural language instruction $S$, which is a sequence of tokens, and $\{s_{1}, s_{2}, \cdots\}$ is its corresponding token embeddings. The agent observes a 360-degree panoramic view of its surrounding scene at the current viewpoint.
Here, we follow \newcite{ma2019self} to map the $n$ navigable viewpoints to discrete images from the current panoramic view\footnote{12 headings and 3 elevations with 30 degree interval.}. We obtain $n$ images corresponding to each navigable viewpoint $I=\{I_{1},I_{2},\cdots,I_{n}\}$.
The task is to select the next viewpoint among the navigable viewpoints or the current viewpoint (indicating the stop), and finally, to generate the trajectory that takes the agent close to an intended goal location.

\begin{figure*}[!t]
    \centering
    \includegraphics[width=4.0in]{architecture.jpg}
    \caption{\textbf{Model Architecture.}
    The input to the encoder is the instruction text. The inputs to the decoder are the grounded language $C^*_t$ calculated by state attention and the aligned visual representations $I^*_t$ obtained from navigable images at each step $t$. The decoder predicts the distribution of next viewpoint $p_t$ with the updated context $h_t$. The high-level view at the top-left shows the information flow in the model aligning with the circled numbers.}
    \label{fig:model}
 \vspace{-4mm}
\end{figure*}

\subsection{Sequence-to-Sequence}
\label{Sequence-to-Sequence}
We model the agent with a LSTM-based sequence-to-sequence architecture \cite{sutskever2014sequence} to control the flow of information, as illustrated in Fig~\ref{fig:model}.
The encoder computes a contextual embedding $\bar{s}_j$ of each token embedding $s_j$ in $S$ by $\bar{s}_{j} = LSTM_{encode}(s_j)$.
At each step $t$ of navigation, the decoder receives the grounded instruction representation $C^*_{t}$ and the aligned image representation $I^*_{t}$ to update its context $h_t$ by $h_{t} = LSTM_{decode}([C^*_{t}, I^*_{t}])$. Finally, we predict the probability distribution of the next navigable viewpoint $p_t$ by $h_t$.
We introduce the method to obtain $C^*_{t}$ and $I^*_{t}$ in Section~\ref{spatial configuration grounding} and Section~\ref{visual grounding},
as well as the next viewpoint prediction in Section~\ref{navigable viewpoints prediction}.

\subsection{Spatial Configurations Representation}
To obtain the configurations in a navigation instruction, we first split the instructions into sentences.
Then we design a parser with rules applied on an off-the-shelf dependency parser\footnote{https://spacy.io/} to extract all the verb phrases
and noun phrases in each sentence.
In general, each configuration contains at most one motion indicator.
Since we aim to process instructions and look for motions, we split the sentences with the extracted verb phrases as motion indicators to obtain spatial configurations.
We do not separate the nested configurations with no motion indicator and keep them attached to the dynamic configurations (i.e. the ones with motion-indicator).
As shown in Figure \ref{fig:spatial Configuration Scheme Example}, "table with chair" is the nested spatial configuration of "move to the table with chair".
Here, we only consider the prepositions that are attached to verbs, and merge the spatial indicators and motion indicators such as "move to" and use them together as the motion indicator.
After that, we insert a pseudo delimiter token after each configuration and identify their contained noun phrases as landmarks.
Each navigation instruction $S$ is split into $m$ configurations.
We re-organize the contextual embeddings of tokens  $\left[\bar{s}_1, \bar{s}_2, \cdots\right]$ generated by the encoder into the array of spatial configurations representation ${C}=\left[C_{1},C_{2} \dots C_{m}\right]$, where $m$ is the number of configurations in the instruction.
In the $i$-th configuration representation $C_{i} = \left[c^{i}_{1}, c^{i}_{2} \cdots, c^i_{\mbox{P}}\right]$, the $j$-th element $c^{i}_{j}$ is the contextual embedding of the corresponding $k$-th tokens in the instruction: $c^{i}_{j}=\bar{s}_k$. The last token of each configuration is always the pseudo delimiter indexed by $\mbox{P}$, which contains the most comprehensive context information about the preceding words.
Soft attention is widely used to merge a collection of representations $V$ into one by weighted sum based on the relevance indicated by their associated keys representations $K$ and a query $Q$, calculated by Eq.~\ref{softattention}.
\begin{equation}
\label{softattention}
\mbox{SoftAttn}(Q; K; V) = \mbox{softmax}\left( \frac{Q^{T}WK}{\sqrt{ d_k }} \right) V
\end{equation}
where $W$ is a trainable linear mapping, and $d_k$ is the dimension of each representation in $K$.
We apply a soft attention to each configuration representation with the pseudo delimiter representation $c^{i}_{\mbox{P}}$, which can be calculated by Eq.~\ref{configuration representation}.
\begin{equation}
\label{configuration representation}
\bar{C}_i = \mbox{SoftAttn}_{\mbox{config}}(Q=c^{i}_{\mbox{P}}; K=C_i;V=C_i)
\end{equation}
\noindent After obtaining configuration representations, an agent needs to identify which configuration to follow at each step. To achieve this, we incorporate the intra-configuration and inter-configuration knowledge. Concretely, intra-configuration knowledge is the motion indicator that guides the agent movement and the landmarks that could be
grounded into the objects in visual images; inter-configuration
knowledge is that configurations should be processed one after another.

As mentioned above, we identify verbs and noun chunks in configurations as motion indicator and landmarks respectively.
Each configuration can contain only one motion indicator and multiple landmarks.
Formally, for the $i$-th configuration $C_i$, the motion indicator representation is denoted as $c^i_{\mbox{M}}$ and the landmark representation is denoted as $c^i_L = \left[c^i_{\mbox{L}_1},c^i_{\mbox{L}_2},\cdots,{c^i_{\mbox{L}_p}}\right]$, where $p$ is the number of landmarks. If there is no landmark in the configuration, the value of $c^i_{\mbox{L}}$ will be set as zeros. To enhance the motion indicator and landmark information, we concatenate their word embedding with the configuration representation. In case there are multiple noun chunks in configuration, to simplify, we select the noun closest to the root of the parsing tree as the main landmark, denoted as $\hat{p}$. Then the enriched configuration representation is denoted as
$\tilde{C_i}=\left[\bar{C}_i;c^i_{\mbox{M}};c^i_{\mbox{L}_{\hat{p}}}\right]$.

\subsection{Visual Representation}
\label{visual representation}
To execute a series of configurations, the agent needs to keep track of the sequence of images observed along the navigation trajectory. We firstly transform the low-level image features from ResNet of $n$ navigable images $I=\{I_{1},I_{2},\dots,I_{n}\}$ to $I'=\left[I^{'}_1,I^{'}_2,\cdots,I^{'}_n\right]$ by a fully-connected layer $I^{'}_j=\mbox{FC}_{\mbox{img}}(I_j)$. Then, a soft attention is applied to $I^{'}$ with the previous context $h_{t-1}$, as shown in Eq.~\ref{image softattention}.
\begin{equation}
\label{image softattention}
    \bar{I} = \mbox{SoftAttn}_{\mbox{img}}(Q=h_{t-1};K=I^{'};V=I^{'})
\end{equation}

Furthermore, we equip the agent with object-based representation. Specifically, we get top-K object representations from each image with an object detection model\footnote{We employ Faster R-CNN pre-trained on Visual Genome, and use at most 36 objects that have an area greater than 10 pixels.}.
In this paper, we consider two kinds of object representation: object label representation and object visual representation.
Specifically, the label representation uses the GloVe embedding \cite{pennington2014glove}~of the type of the object, and visual representation uses the region-of-interest~(ROI) pooling of the object detection model. We will compare the two representations and a hybrid representation of them in Appendix~\ref{Representation Analysis}.
Formally, the object representations could be denoted as $O = \left[O_{1}, O_{2} \dots O_{n}\right]$, where for image $I_j$, there is $O_{j} = \left[o_{j,1}, o_{j,2}, \cdots, o_{j,K}\right]$. $o_{j,k}$ is the $k$-th object representation in $j$-th image.

\subsection{Spatial Configuration Grounding}
\label{spatial configuration grounding}
To guarantee the sequential execution, we design a state attention mechanism over the configurations.
We consider the attention weight at each step as a state that measures navigation progress and is updated by a controller.
Formally, the $i$-th configuration at step $t$ is denoted as $\alpha_{t,i}$.
At the first step, the attention weight is initialized to be focused on the first configuration $\alpha_{0}=\left[1,0,\cdots\right]$.
At each of the following steps, the attention weight is updated by a controller $\gamma_t$ with discrete convolution. $\gamma_t$ is a two dimensional probability distribution indicating to what extent the agent should execute the current configuration or move to the next. The updating process is formally defined in Eq.~\ref{state_aware attention eq}.
\begin{equation}
\label{state_aware attention eq}
\begin{aligned}
    \alpha_{t,i} &= \sum_{\imath=i-1}^{i} \alpha_{t-1,\imath}\cdot\gamma_{t,i-\imath}
\end{aligned}
\end{equation}
Using a set of rules to determine the value of the controller $\gamma$ is not practical.
For example, for the instruction "move to the table" or "move past the table", it is hard for an agent to decide whether to execute the current configuration or to move to the next one only based observing or not-observing the~"table". To address this issue, we let the agent learn the value of $\gamma$ based on three aspects of information. The first one is the previous hidden state $h_{t-1}$; the second one is the attended image representation $\bar{I_t}$ at the current step; the third one is the similarity score~$S_t$ between the landmark representations and the object representations,
Eq.~\ref{similarity score equation} shows how to get the similarity score~$S_t$, and $\alpha_{t-1}$ is the
attention weight at the previous step.

\begin{equation}
\label{similarity score equation}
\begin{aligned}
    S_t = \tilde{C}_{L}\cdot O \cdot \alpha_{t-1}
\end{aligned}
\end{equation}
Then, we use a fully connected layer to predict the distribution
$\gamma_{t} = \mbox{FC}_{\gamma}\left(\left[h_{t-1}; \bar{I_t}; S_t\right]\right)$.
Finally, we apply the state attention to $\tilde{C}$ to get the grounded instruction representation based on the configuration
$\hat{C} = \sum_{i} \alpha_{t,i} \cdot \tilde{C_i}$,
which is used as the language input to the decoder $C^*_t = \hat{C}$.

\subsection{Visual Representation Alignment}
\label{visual grounding}
The intuition to leverage the object representation is to select navigable images by aligning the object representation with the configuration representation.
We use two levels of soft attention, first over the objects in each image by configuration representation $\hat{C}$, and second over all images guided by the previous context $h_{t-1}$.
\begin{equation}
    \label{object softattention}
\begin{aligned}
    \hat{O}_j &= \mbox{SoftAttn}_{\mbox{obj}}(Q=\hat{C};K=O_j;V=O_j) \\
    \hat{I} &= \mbox{SoftAttn}_{\mbox{objimg}}(Q=h_{t-1};K=\hat{O};V=I^{'})
\end{aligned}
\end{equation}
where $\hat{O} = \left[\hat{O}_1, \hat{O}_2, \cdots, \hat{O}_n\right]$.
We use the image representation $\hat{I}$, that has aligned the objects with the configurations, as the visual input to the decoder
$I^*_t=\hat{I}$.

\subsection{Navigable Viewpoint Selection}
\label{navigable viewpoints prediction}
We obtain a new decoder context $h_t$, as described in Section~\ref{Sequence-to-Sequence}, with configuration input $C^*_t$ and visual input $I^*_t$, where $t$ is the current step. The next
step is to predict the viewpoint with the image that has the highest correlation with the current context and configuration, calculated by $z_{t,j} = \left< I^{'}_j, \mbox{FC}_{\mbox{pred}}\left(\left[C^*_{t}; h_t\right]\right) \right>$,
where $\mbox{FC}_{\mbox{pred}}(\cdot)$ is a fully-connected layer.
We sum the scores of the three elevations for each navigable viewpoint $k$ as $\zeta_{t,k}=\sum_{j\in \kappa_{k}} z_{t,j}$, where $\kappa_{k}$ is the set of three elevations' image indexes.
The predicted navigable viewpoint distribution $p_{t}$ can be calculated with $p_{t}=\mbox{softmax}(\zeta_{t})$.

\subsection{Training and Inference}
\label{Training and Inference}
We train our model with two state-of-the-art training strategies in this task.~(1)~\textbf{T1}: We follow Self-Monitor~\cite{ma2019self} optimizing the model with a cross-entropy loss to maximize the
likelihood of the ground-truth navigable viewpoint given by the model,
and a mean squared error loss to minimize the normalized distance in units of length from the current viewpoint to the goal destination.
At each step, the next viewpoint is selected by sampling the predicted probability of each  navigable viewpoint.~(2)~\textbf{T2}: We follow \cite{tan2019learning} training the model with the mixture of Imitation Learning and Reinforcement Learning, where Imitation Learning minimizes the cross-entropy loss of the prediction and always samples the ground-truth navigable viewpoint at each time step, and Reinforcement Learning uses policy gradient to update the parameters of the model.

During inference, we conduct a greedy search with the highest probability of the next viewpoints to generate the trajectory.
It should be noticed that beam search with a beam size greater than one is not practical because the agent needs to move forward and backward in the physical world, resulting in a long trail trajectory before making a decision.

\section{Experimental Setup}
\begin{table*}[!t]
\small
\renewcommand{\tabcolsep}{0.5em}
    \begin{center}

    \begin{tabular}{c c c c c c c c c c c}
    \hline
      & & \multicolumn{3}{c}{Validation-Seen} & \multicolumn{3}{c}{Validation-Unseen} & \multicolumn{3}{c}{Test(Unseen)}\\
    \hline

        & Method &  NE $\downarrow$ & \textbf{SR} $\uparrow$  & \textbf{SPL} $\uparrow$& NE $\downarrow$& \textbf{SR} $\uparrow$ & \textbf{SPL} $\uparrow$& NE $\downarrow$& \textbf{SR} $\uparrow$& \textbf{SPL} $\uparrow$\\

    \hline
    1 & Random~\cite{anderson2018vision} & 9.45 & 0.16 & -&9.23 & 0.16 & - &9.77 & 0.13 & 0.12 \\
    2 & Student-forcing~\cite{anderson2018vision} & 6.01 & 0.39 & - & 7.81 & 0.22 & -& 7.85 & 0.20 & 0.18\\
    3 & Speaker-Follower \cite{fried2018speaker} & 4.36 & 0.54 &  - & 7.22 & 0.27  & - & - & - & - \\
    \hline
    4 & Speaker-Follower* & 3.66 & 0.66 & 0.58 & 6.62 & 0.36 & - &6.62 & 0.35 & 0.28 \\
    5 & Self-Monitor*~\cite{ma2019self} & \textbf{3.22} & \textbf{0.67} & 0.58 & 5.52 & 0.45 & 0.32 & \textbf{5.67} & 0.48 & 0.35 \\

    6 & Environment Dropout*~\cite{tan2019learning} & 4.19 & 0.58 & 0.55 & \textbf{5.43} & \textbf{0.48} & \textbf{0.44} & - & \textbf{0.52} & \textbf{0.47}\\
    7 & Environment Dropout + BERT* & 4.40 & 0.61 & 0.57 & 5.54 & 0.46 & 0.43 & - & - & -  \\
    \hline
    8 & SpC-NAV*& 4.09 & 0.65 & \textbf{0.61} & 5.92 & 0.45 & 0.42 & 6.22 & 0.46 & 0.44 \\
    \hline
    \end{tabular}
    \end{center}
    \vspace{-3mm}
    \caption{\textbf{Experimental Result comparing with baseline models.} * means data augmentation.}
    \label{experiment_table}
       \vspace{-2mm}
\end{table*}

\begin{table}[!t]
\scriptsize
\renewcommand{\tabcolsep}{0.2em}
    \begin{center}

    \begin{tabular}{c c c c c c c c c c}
    \hline
      & \multicolumn{3}{c}{Val-Seen} & \multicolumn{3}{c}{Val-Unseen} & \multicolumn{3}{c}{Test(Unseen)}\\
    \hline

         Method &  NE $\downarrow$ & \textbf{SR} $\uparrow$  & \textbf{SPL} $\uparrow$& NE $\downarrow$& \textbf{SR} $\uparrow$ & \textbf{SPL} $\uparrow$& NE $\downarrow$& \textbf{SR} $\uparrow$& \textbf{SPL} $\uparrow$\\

    \hline
    Self-Monitor (T1)  & 3.72 & 0.63 & 0.56 & 5.98 & 0.44 & 0.30 & - & - & - \\
    Sub-Instruction(T1) & - & - & -  & \textbf{6.16} & \textbf{0.42} & \textbf{0.32} & - & - & - \\
    SpC-NAV+T1 & 3.95 & 0.65 & 0.59 & 6.51 & 0.39  & \textbf{0.32} & 6.22 & 0.42 & 0.35\\
    \hline
    EnvDrop (T2) & 4.71 & 0.55 & 0.53 & \textbf{5.49} & \textbf{0.47} & \textbf{0.43} & - & - & -\\
    Sub-Instruction(T2) & - & - & - & 5.67 & 0.47 & \textbf{0.43} & - & - & - \\
    SpC-NAV+T2 & \textbf{4.68} & \textbf{0.59} & \textbf{0.56} & 6.68 & 0.44 & 0.39 & 6.25 & 0.45 & 0.43\\
    \hline
    \end{tabular}

    \caption{\textbf{Experimental Result with Different Training Strategies}. T1 and T2 are two training strategies.}
    \label{experiment_strategy_table}
    \end{center}
    \vspace{-5mm}
\end{table}

\noindent \textbf{Dataset}~We evaluate our model with Room-to-Room (R2R) dataset~\cite{anderson2018vision}, which is built upon the Matterport3D dataset \cite{chang2017matterport3d}. This dataset has 7,189 paths and 21,567 instructions with an average length of 29 words. The whole dataset is divided into training, seen validation, unseen validation, and (unseen) test sets. The seen validation set shares the same visual environments with the training set, while unseen validation and test sets contain different environments.

\noindent \textbf{Evaluation Metrics}~We report three evaluation metrics. (1) Navigation Error (NE): the mean of the shortest path distance between the agent's final position and the goal location.
(2) Success Rate (SR): the percentage of the cases where the predicted final position lays within 3m from the goal location.
(3) Success rate weighted by normalized inverse Path Length (SPL): SPL normalize Success Rate by trajectory length~\cite{anderson2018vision}. SPL is recommended as the primary metric because it
considers both the effectiveness and efficiency of navigation performance.

\subsection{Baseline Models}
We mainly compare Spc-NAV with the following baseline models. \textbf{Seq2Seq}~\cite{anderson2018vision} trained an encoder-decoder model with two learning strategies of random and student-forcing. \textbf{Speaker-Follower}~\cite{fried2018speaker} introduced a speaker module to synthesize new instructions to train the follower module.
\textbf{Self-Monitor}~\cite{ma2019self} co-grounded instructions and image based on soft attention mechanism.
\textbf{Environmental Dropout} \cite{tan2019learning} proposed a neural agent trained with the method of the mixture of Imitation Learning and Reinforcement Learning. \textbf{Sub-instruction}~\cite{hong2020sub} segmented the instruction into sub-instructions and designed a shifting attention module to ensure the sequential execution order between sub-instructions. The differences between Sub-instruction and our model has been discussed in Section \ref{Related Work}.

\subsection{Implementation Details}
\label{Implementation Details}
We implement SpC-NAV using PyTorch\footnote{https://pytorch.org/}\cite{paszke2017automatic}. We use 768-d BERT-base~\cite{devlin2018bert} (frozen) as the embedding of the raw instruction, and get its 512-d contextual embedding by LSTM.
We encode the representations of the motion indicator and the landmark in each configuration with 300-d GloVe embedding respectively, and concatenate them with the 512-d configuration representation to obtain the enriched configuration representation (1112-d).
We use 300-d GloVe embedding of object label representation to calculate similarity score $S$ with configuration representation. We trained an auto-encoder to map 2048-d object visual representation from Faster R-CNN to 152-d, and use it to obtain the attended object representation $\hat{O}$.
We optimize using ADAM with learning rate $1e-4$ in batches of 64.
We used a rule-based parser to obtain the spatial configuration and spatial semantic elements. This provides some noisy extractions. Appendix~\ref{split error} includes the details about the accuracy of the parser based on our manual annotations of a subset of instructions.

\section{Results and Analysis}
Table~\ref{experiment_table} shows the main performance metrics of our proposed SpC-NAV, compared with the baseline models on seen/unseen validation set and unseen testing set.
To achieve the best result, SpC-NAV is trained with the training strategy T2 (see Section~\ref{Training and Inference}) and the data augmentation proposed in \cite{tan2019learning}.
Our model improves the performance in the seen environment and obtains competitive results in the unseen environment.
Since we use BERT as the input to the encoder while the baseline models use basic word embeddings, we replace the word representations in Environment Dropout with BERT for a fair comparison.
Although the richer language representations help the performance, our model still achieves better results, especially in the seen environments. It indicates that the spatial configuration and spatial elements indeed improve the agent's reasoning ability.

Training strategies are orthogonal to our work, and our model is friendly to the strategies widely used in the literature (T1/T2) (see Section \ref{Training and Inference}). We evaluate SpC-NAV with both T1 and T2 and compare the results with their baseline models as well as Sub-Instruction. We do not apply data augmentation in this setting. As shown in Table~\ref{experiment_strategy_table}, SpC-NAV achieves consistent improvement in the seen environment compared with all the baselines.
In the unseen environment, training with T1, SpC-NAV outperforms Self-monitor (and is even comparable to it with data augmentation) and performs similarly as Sub-Instruction. However, training with T2, our model does not
outperform Environment Dropout and Sub-Instruction in unseen environments.
We analyze the errors in Section \ref{unseen environment explaination}.
\begin{figure*}[!ht]
\centering
\hfill
\begin{subfigure}[b]{0.35\textwidth}
\includegraphics[height=40pt]{motion_right_0.jpg}
\includegraphics[height=40pt]{motion_right_1.jpg}
\includegraphics[height=40pt]{motion_right_2.jpg}
\\
\includegraphics[height=40pt]{motion_wrong_0.jpg}
\includegraphics[height=40pt]{motion_wrong_1.jpg}
\includegraphics[height=40pt]{original.jpg}
\caption{$\pm$Motion Indicator
\newline
\scriptsize{Walk up the stairs.\quad\quad\quad\quad\quad}}
\end{subfigure}
\hfill
\begin{subfigure}[b]{0.25\textwidth}
\includegraphics[height=40pt]{landmark_normal_right.jpg}
\includegraphics[height=40pt]{landmark_right.jpg}
\\
\includegraphics[height=40pt]{landmark_normal_wrong.jpg}
\includegraphics[height=40pt]{landmark_wrong.jpg}
\caption{$\pm$Landmark
\newline
\scriptsize{Walk past the dinning room table.}}
\end{subfigure}
\hfill
\begin{subfigure}[b]{0.35\textwidth}
\includegraphics[height=40pt]{original_landmark_right.jpg}
\includegraphics[height=40pt]{similarity_start_point_right.jpg}
\includegraphics[height=40pt]{similarity_right.jpg}
\\
\includegraphics[height=40pt]{original_landmark_wrong.jpg}
\includegraphics[height=40pt]{similarity_start_point_wrong.jpg}
\includegraphics[height=40pt]{similarity_wrong.jpg}
\caption{$\pm$Similarity Score
\newline
\scriptsize{
Turn right, and walk past the couch.}}
\end{subfigure}
\vspace{-3mm}
\hfill
\caption{\textbf{Analysis of Seen examples.} In these three scenarios, the corresponding spatial configurations are provided. Green arrows in the above figures show the correct trajectory was selected after the additional spatial semantics; red arrows show without that information the agent went wrong.}
\label{fig: After Adding Motion Indicator}
\vspace{-3mm}
\end{figure*}

\subsection{Ablation Analysis}
Table~\ref{Ablation Study} shows how various spatial semantic elements influence the performance of the model. The model is trained with the training strategy T1. Row\#1 is our model without considering spatial elements.
From row\#2 to row\#3, we incorporate the representations of the motion indicator and the landmark into spatial configuration representation incrementally. In row\#4, we use the similarity score between the landmark representations in the configuration and the object label representations in the image to control the transitions between spatial configurations. All motion indicator, landmark and similarity score improve the performance. After applying the similarity score, the large gain indicates that the connection between landmarks and objects is important in language grounding.
\begin{table}[!ht]
\small
\centering
\renewcommand{\tabcolsep}{0.2em}
\begin{subtable}[t]{0.44\textwidth}
     \begin{tabular}{l l c c c c c c}
    \hline
    & & \multicolumn{3}{c}{Validation-Seen} & \multicolumn{3}{c}{Validation-Unseen} \\
    \hline
     & Model & NE$\downarrow$ & \textbf{SR}$\uparrow$  & \textbf{SPL}$\uparrow$ & NE$\downarrow$ & \textbf{SR}$\uparrow$ & \textbf{SPL}$\uparrow$\\
    \hline

    1 &SpC-NAV & 4.11 & 0.62 & 0.53 & 6.49 & 0.39 & 0.29 \\
    2 &SpC-NAV\textsubscript{\small{M}} & \textbf{3.88} & 0.62 & 0.53 & \textbf{6.21} & \textbf{0.40} & 0.28 \\
    3 &SpC-NAV\textsubscript{\small{M+L}} & 4.01 & 0.62 & 0.54 & 6.27 & 0.39 & 0.29 \\
    4 &SpC-NAV\textsubscript{\small{M+L+S}} & 3.95 & \textbf{0.65} & \textbf{0.59} & 6.51 & 0.39 & \textbf{0.32}\\
    \hline
    \end{tabular}
\end{subtable}
\caption{\textbf{Ablation study with different spatial semantics.} The subscription letters mean the model took those information into account; \textit{M: motion indicator}; \textit{L: landmark}; \textit{S: similarity score}.}
\label{Ablation Study}
\vspace{-6mm}
\end{table}

\subsection{Qualitative Analysis}
\subsubsection*{Seen Environment}
\label{Seen Environment Example}
We analyze some  qualitative examples to find out how the spatial semantics improve the model.
For the semantics of motion, we find that our model can improve the cases that motions contain "up" and "down" after adding the representation of motion indicator.
Figure~\ref{fig: After Adding Motion Indicator}~(a) shows an example of such a scenario. The spatial configuration is "walk up the stairs", and the agent could find the right viewpoints after we incorporated the representation of the motion indicator "walk up".
However, the model makes more mistakes in the cases that the motion indicators are highly related to the objects, such as "walk through", "walk past", and "walk towards", which need the landmark information.
In these latter cases, the model should consider both motions and landmarks together.
In another experiment, we added the landmark representation.
Figure \ref{fig: After Adding Motion Indicator}~(b) shows an example that the spatial configurations is "walk past the dinning room table".  The agent can select the correct viewpoints when we incorporate the representation of landmark "dinning room table".
We also analyze the influence of the similarity score, and found that when the information in the current configuration is not sufficient to make a decision, the similarity score will assist in choosing the next configuration. For example, in Figure~\ref{fig: After Adding Motion Indicator}~(c), the spatial configurations are "turn right" and "walk past the couch". Without using the similarity score in controlling the transitions between configurations, the agent tends to select a viewpoint in the "right" direction. But with similarity score, the agent will consider both "turn right" and "walk past the couch", and selects the correct viewpoint that the "couch" can be seen.

\subsubsection*{Unseen Environment}
\label{unseen environment explaination}
Table~\ref{experiment_table} and Table~\ref{experiment_strategy_table} show that our model does not outperform Environment Dropout in the unseen environments. We noticed that the main error is that some objects can not be detected in the image by the object detection model. This is more problematic for our model because we explicitly align the landmark phrases with the detected objects.
For example,
in Fig~\ref{unseen environment}~(a), the agent selects the correct viewpoint when the configuration is "Walk to the glass door" because the connection between the landmark "glass door" and the object "door" has been learned in training set. In Fig (b), the agent is wrong when the configuration is "Walk to the pottery." because the "pottery" is not detected at the initial perspective and the word "pottery" never appears in the training set. However, the agent selects a viewpoint that a bounding box contains a pottery.
The gap between seen and unseen become larger after data augmentation since our model is able to capture the structure of the language by observing more examples. It can deal with the variations in the instructions and improve the performance in the seen environment, but it fails to deal with the novel objects and visual variations in the unseen environments. This is an orthogonal issue addressed in zero-shot learning~\cite{blukis2020few}.
\begin{figure}[!t]
\centering
\begin{minipage}[c]{0.28\columnwidth}
\includegraphics[height=45pt]{unseen_example1_1.jpg}
\end{minipage}
\begin{minipage}[c]{0.28\columnwidth}
\includegraphics[height=45pt]{unseen_example2.jpg}
\end{minipage}
\begin{minipage}[c]{0.35\columnwidth}
\scriptsize{(a) Walk to the \textbf{glass door}.}
\end{minipage}
\\
\begin{minipage}[c]{0.28\columnwidth}
\includegraphics[height=45pt]{unseen_example1_1.jpg}
\end{minipage}
\begin{minipage}[c]{0.28\columnwidth}
\includegraphics[height=45pt]{unseen_example4.jpg}
\end{minipage}
\begin{minipage}[c]{0.35\columnwidth}
\scriptsize{(b) Go to the \textbf{pottery}.}
\end{minipage}
\caption{\textbf{Analysis of an Unseen example}}
\label{unseen environment}
\vspace{-6mm}
\end{figure}
\subsection{State Attention Visualization}
We visualize the state attention and the soft attention weights over configurations.
As shown in Fig~\ref{fig:state-seen} and Fig~\ref{fig:state-unseen}, our designed state attention demonstrates that the grounded configuration shifts gradually from the first configuration to the last in both seen and unseen environments.
We apply the soft attention used in Self-Monitor on spatial configurations, as shown in Fig~\ref{fig:soft-seen} and Fig~\ref{fig:soft-unseen}, it can not preserve the sequential execution order.
We also show the soft attention weights of the grounded instruction in the Self-Monitor by splitting the instructions with the boundaries of our configurations.
As shown in Fig~\ref{fig:80-seen} and Fig~\ref{fig:80-unseen},
although their attention weights show the gradual shift,
many configurations are skipped.

\begin{figure}[!ht]
    {
    \subcaptionbox{\tiny{State Seen}\label{fig:state-seen}}
    {\includegraphics[height=.55in]{matrix_seen.jpg}}
    \subcaptionbox{\tiny{Soft Seen}\label{fig:soft-seen}}
    {\includegraphics[height=.55in]{matrix_soft_seen.jpg}}
    \subcaptionbox{\tiny{State Unseen}\label{fig:state-unseen}}
    {\includegraphics[height=.55in]{matrix_unseen.jpg}}
    \subcaptionbox{\tiny{Soft Unseen}\label{fig:soft-unseen}}
    {\includegraphics[height=.55in]{matrix_soft_unseen.jpg}}
    \\
    \subcaptionbox{\tiny{Soft seen of Self-Monitor}\label{fig:80-seen}}
    {\includegraphics[height=.55in]{matrix_80_reverse_seen.jpg}}
    \\
    \subcaptionbox{\tiny{Soft unseen of Self-Monitor}\label{fig:80-unseen}}
    {\includegraphics[height=.55in]{matrix_80_reverse_unseen.jpg}}
    \\
    }
    \vspace{-3mm}
    \caption{\textbf{Attention weights of various attention strategies with seen and unseen examples.} The horizontal axis is the configuration order, and the vertical axis is  the temporal order of the steps taken by the agent.
    Each row in sub-figures show the attention distribution over the configurations (or tokens) in an instruction at each time step.
    The green vertical lines in Figure (\subref{fig:80-seen}) and Figure (\subref{fig:80-unseen}) indicate the split points of the configurations in the instruction.
    \label{fig:visualization}}
\end{figure}
\section{Conclusion}

We propose a neural agent that incorporates the semantic elements of spatial language for vision-and-language navigation.
We use the notion of spatial configurations as the main linguistic unit of the instructions and enhance the spatial configuration representation with the representations of motion indicator and landmark. We design a state attention to guarantee the sequential execution order of configurations and use the similarity score between the representations of landmarks and objects to control the transitions between configurations.
Based on our results, incorporating the spatial semantics improves reasoning ability over navigation. Future work could investigate more fine-grained spatial semantics and the geometry of spatial relations.
Also, we will deal with novel objects in a zero-shot setting to improve the unseen environments results.

\bibliographystyle{acl_natbib}

redpajama_set_name: RedPajamaArXiv


text:
PN blasts 'failure' of traffic roadmap as air pollution increases
Marthese Portelli warns that CO2 emissions in Malta increased by 2.5% last year, compared with an average 5% decline across the EU
16 June 2015, 10:57am
The Nationalist Party has hit out at the government for "failing" to stick to its roadmap to reduce traffic on the roads.
"Traffic has now become the public's biggest headache," shadow transport minister Marthese Portelli said in a statement. "The government should implement its famous roadmap to reduce traffic, or at least to stop it increasing."
Recent Eurostat statistics show that carbon dioxide emissions increased by 2.5% in Malta last year when compared with 2013, behind only Bulgaria (+7.1%) and Cyprus (+3.5%). On average, carbon dioxide emissions decreased by 5% across the EU.
Portelli linked this emission increase to the increase in traffic on the roads.
"This is a consequence of the increase in traffic," Portelli said.
More from Staff Reporter

redpajama_set_name: RedPajamaCommonCrawl


Early 90's Kawasaki 750 Help!
Thread: Early 90's Kawasaki 750 Help!
well I went to get the ski today and its a 93 st 750 motor is supposed to have good compression just the electrical is not in the best shape....prevous owner said it just needs a cdi but imo it needs everything in the box. Anyone interested in a motor pump cables seat hood ect?
Kawasaki zxi 750 HELP! PTO!


Guru Gautam Steels are Manufacturers, Supplier and Exporters of Inconel 625 Wire that is Fabricated From High Quality Raw Materials. This range of wire is precisely designed by making use of optimum quality nickel chromium molybdenum alloy that leaves it an excellent corrosion and oxidation resistant. Inconel 625 Wire are Available in Following Specification, Grade and Sizes.
ASTM B466 Inconel 625 Wire Bobbin, DIN 2.4856 Wire, Inconel 625 Filler Wire, Inconel 625 High Galvanized Wire Manufacturer in india.
Our products like Inconel 625 Wire are packaged to make sure that there's no damage throughout transit. Just in case of exports, customary export packaging is completed in wooden boxes or cases. All Inconel 625 Wire are marked with Trademark (Company's LOGO), Grade, Size and Lot No. On requests we can do custom marking on our products.


text:
Man receives a genetically-modified pig heart in the world's first transplant of its kind
Nike buys NFT fashion and collectibles startup RTFKT
Launched in 2020, RTFKT is best known for making sneakers and other accessories for the metaverse...
Forget the self-driving car, meet the self-driving suitcase
Some of the technologies used in autonomous cars have been adapted in products unveiled at the 2018 Consumer Electronics Show in Las Vegas, to help travellers with the weighty problem of dealing with baggage.
Ikea buys on-demand help startup TaskRabbit
Ikea has announced a deal to acquire on-demand help startup TaskRabbit as the world's largest furniture retailer grabbed a seat in the online sharing economy...
New CNN Travel multiplatform, multimedia digital destination launched
CNN has launched an all-new CNN Travel multimedia digital destination. CNN Travel has a new design as a globally-minded, mobile-first, social product with an emphasis on visual storytelling...
First global index of makers and shakers in EdTech revealed
In association with the World Innovation Summit for Education (WISE), EdTechXGlobal has launched the Makers and Shakers of Education Technology Index - the first comprehensive global listing...
Cannes Lions Start-Up Academy talent announced
Following a global search, Cannes Lions and R/GA have announced the 10 companies participating in this year's Start-Up Academy which will take place during Lions Innovation...
GM buys self-driving technology startup Cruise
General Motors said it is buying automated driving technology startup Cruise Automation to boost its efforts to develop self-driving cars. Cruise, founded in San Francisco by a group of robotics experts...
Four top tips to avoid startup failure
Business startups are a spin of the wheel, a bigger gamble than betting on a racehorse that's primed for the glue factory...
By Endri Hasanaj 28 Dec 2015
{menuContent}


text:
import os
from collections import OrderedDict
from contextlib import suppress
from datetime import datetime
from multiprocessing import Process
from pathlib import Path
from typing import Dict, Optional

from astropy import units as u
from astropy.coordinates import get_moon
from astropy.coordinates import get_sun
from astropy.io.fits import setval
from panoptes.utils import error
from panoptes.utils.time import current_time, CountdownTimer, flatten_time

import panoptes.pocs.camera.fli
from panoptes.pocs.base import PanBase
from panoptes.pocs.camera import AbstractCamera
from panoptes.pocs.dome import AbstractDome
from panoptes.pocs.images import Image
from panoptes.pocs.mount.mount import AbstractMount
from panoptes.pocs.scheduler import BaseScheduler
from panoptes.pocs.scheduler.field import Field
from panoptes.pocs.scheduler.observation.base import Observation
from panoptes.pocs.scheduler.observation.compound import Observation as CompoundObservation
from panoptes.utils import images as img_utils
from panoptes.utils.images import fits as fits_utils
from panoptes.pocs.utils.cli.image import upload_image
from panoptes.pocs.utils.location import create_location_from_config

class Observatory(PanBase):

    def __init__(self, cameras=None, scheduler=None, dome=None, mount=None, *args, **kwargs):
        """Main Observatory class

        Starts up the observatory. Reads config file, sets up location,
        dates and weather station. Adds cameras, scheduler, dome and mount.
        """
        super().__init__(*args, **kwargs)
        self.scheduler = None
        self.dome = None
        self.mount = None
        self.logger.info('Initializing observatory')

        # Setup information about site location
        self.logger.info('Setting up location')
        site_details = create_location_from_config()
        self.location = site_details.location
        self.earth_location = site_details.earth_location
        self.observer = site_details.observer

        # Do some one-time calculations
        now = current_time()
        self._local_sun_pos = self.observer.altaz(now, target=get_sun(now)).alt  # Re-calculated
        self._local_sunrise = self.observer.sun_rise_time(now)
        self._local_sunset = self.observer.sun_set_time(now)
        self._evening_astro_time = self.observer.twilight_evening_astronomical(now, which='next')
        self._morning_astro_time = self.observer.twilight_morning_astronomical(now, which='next')

        # Set up some of the hardware.
        self.set_mount(mount)
        self.cameras: Dict[str, AbstractCamera] = OrderedDict()
        self._primary_camera: Optional[AbstractCamera] = None

        if cameras:
            self.logger.info(f'Adding cameras to the observatory: {cameras}')
            for cam_name, camera in cameras.items():
                self.add_camera(cam_name, camera)

        # TODO(jamessynge): Figure out serial port validation behavior here compared to that for
        #  the mount.
        self.set_dome(dome)

        self.set_scheduler(scheduler)
        self.current_offset_info = None

        self._image_dir = self.get_config('directories.images')

        self.logger.success('Observatory initialized')

    ##########################################################################
    # Helper methods
    ##########################################################################

    def is_dark(self, horizon='observe', default_dark=-18 * u.degree, at_time=None):
        """If sun is below horizon.

        Args:
            horizon (str, optional): Which horizon to use, 'flat', 'focus', or
                'observe' (default).
            default_dark (`astropy.unit.Quantity`, optional): The default horizon
                for when it is considered "dark". Default is astronomical twilight,
                -18 degrees.
            at_time (None or `astropy.time.Time`, optional): Time at which to
                check if dark, defaults to now.

        Returns:
            bool: If it is dark or not.
        """
        if at_time is None:
            at_time = current_time()

        horizon_deg = self.get_config(f'location.{horizon}_horizon', default=default_dark)
        is_dark = self.observer.is_night(at_time, horizon=horizon_deg)

        self._local_sun_pos = self.observer.altaz(at_time, target=get_sun(at_time)).alt
        self.logger.debug(f"Sun {self._local_sun_pos:.02f} > {horizon_deg} [{horizon}]")

        return is_dark

    ##########################################################################
    # Properties
    ##########################################################################

    @property
    def sidereal_time(self):
        return self.observer.local_sidereal_time(current_time())

    @property
    def has_cameras(self):
        return len(self.cameras) > 0

    @property
    def primary_camera(self) -> panoptes.pocs.camera.camera.AbstractCamera:
        """Return primary camera.

        Note:
            If no camera has been marked as primary this will return the first
            camera in the OrderedDict as primary.

        Returns:
            `pocs.camera.Camera`: The primary camera.
        """
        if not self._primary_camera and self.has_cameras:
            return self.cameras[list(self.cameras.keys())[0]]
        else:
            return self._primary_camera

    @primary_camera.setter
    def primary_camera(self, cam):
        cam.is_primary = True
        self._primary_camera = cam

    @property
    def current_observation(self) -> Optional[Observation]:
        if self.scheduler is None:
            self.logger.info(f'Scheduler not present, cannot get current observation.')
            return None
        return self.scheduler.current_observation

    @current_observation.setter
    def current_observation(self, new_observation: Observation):
        if self.scheduler is None:
            self.logger.info(f'Scheduler not present, cannot set current observation.')
        else:
            self.scheduler.current_observation = new_observation

    @property
    def has_dome(self):
        return self.dome is not None

    @property
    def can_observe(self):
        """A dynamic property indicating whether or not observations are possible.

        This property will check to make sure that the following are present:
          * Scheduler
          * Cameras
          * Mount

        If any of the above are not present then a log message is generated and
        the property returns False.

        Returns:
            bool: True if observations are possible, False otherwise.
        """
        checks = {
            'scheduler': self.scheduler is not None,
            'cameras': self.has_cameras is True,
            'mount': self.mount is not None,
        }

        can_observe = all(checks.values())

        if can_observe is False:
            for check_name, is_true in checks.items():
                if not is_true:
                    self.logger.warning(f'{check_name.title()} not present')

        return can_observe

    ##########################################################################
    # Device Getters/Setters
    ##########################################################################

    def add_camera(self, cam_name, camera):
        """Add camera to list of cameras as cam_name.

        Args:
            cam_name (str): The name to use for the camera, e.g. `Cam00`.
            camera (`pocs.camera.camera.Camera`): An instance of the `~Camera` class.
        """
        assert isinstance(camera, AbstractCamera)
        self.logger.debug(f'Adding {cam_name}: {camera}')
        if cam_name in self.cameras:
            self.logger.debug(
                f'{cam_name} already exists, replacing existing camera under that name.')

        self.cameras[cam_name] = camera
        if camera.is_primary:
            self.primary_camera = camera

    def remove_camera(self, cam_name):
        """Remove cam_name from list of attached cameras.

        Note:
            If you remove and then add a camera you will change the index order
            of the camera. If you prefer to keep the same order then use `add_camera`
            with the same name as an existing camera to to update the list and preserve
            the order.

        Args:
            cam_name (str): Name of camera to remove.
        """
        self.logger.debug(f'Removing {cam_name}')
        del self.cameras[cam_name]

    def set_scheduler(self, scheduler):
        """Sets the scheduler for the `Observatory`.
        Args:
            scheduler (`pocs.scheduler.BaseScheduler`): An instance of the `~BaseScheduler` class.
        """
        self._set_hardware(scheduler, 'scheduler', BaseScheduler)

    def set_dome(self, dome):
        """Set's dome or remove the dome for the `Observatory`.
        Args:
            dome (`pocs.dome.AbstractDome`): An instance of the `~AbstractDome` class.
        """
        self._set_hardware(dome, 'dome', AbstractDome)

    def set_mount(self, mount):
        """Sets the mount for the `Observatory`.
        Args:
            mount (`pocs.mount.AbstractMount`): An instance of the `~AbstractMount` class.
        """
        self._set_hardware(mount, 'mount', AbstractMount)

    def _set_hardware(self, new_hardware, hw_type, hw_class):
        # Lookup the set method for the hardware type.
        hw_attr = getattr(self, hw_type)

        if isinstance(new_hardware, hw_class):
            self.logger.success(f'Adding {new_hardware}')
            setattr(self, hw_type, new_hardware)
        elif new_hardware is None:
            if hw_attr is not None:
                self.logger.success(f'Removing hw_attr={hw_attr!r}')
            setattr(self, hw_type, None)
        else:
            raise TypeError(f"{hw_type.title()} is not an instance of {str(hw_class)} class")

    ##########################################################################
    # Methods
    ##########################################################################

    def initialize(self):
        """Initialize the observatory and connected hardware """
        self.logger.debug("Initializing mount")
        self.mount.initialize()
        if self.dome:
            self.dome.connect()

    def power_down(self):
        """Power down the observatory. Currently just disconnects hardware.
        """
        self.logger.debug("Shutting down observatory")
        if self.mount:
            self.mount.disconnect()
        if self.dome:
            self.dome.disconnect()

    @property
    def status(self):
        """Get status information for various parts of the observatory."""
        status = {'can_observe': self.can_observe}

        now = current_time()

        try:
            if self.mount and self.mount.is_initialized:
                status['mount'] = self.mount.status
                current_coords = self.mount.get_current_coordinates()
                status['mount']['current_ha'] = self.observer.target_hour_angle(now, current_coords)
                if self.mount.has_target:
                    target_coords = self.mount.get_target_coordinates()
                    target_ha = self.observer.target_hour_angle(now, target_coords)
                    status['mount']['mount_target_ha'] = target_ha
        except Exception as e:  # pragma: no cover
            self.logger.warning(f"Can't get mount status: {e!r}")

        try:
            if self.dome:
                status['dome'] = self.dome.status
        except Exception as e:  # pragma: no cover
            self.logger.warning(f"Can't get dome status: {e!r}")

        try:
            if self.current_observation:
                status['observation'] = self.current_observation.status
                field = self.current_observation.field
                status['observation']['field_ha'] = self.observer.target_hour_angle(now, field)
        except Exception as e:  # pragma: no cover
            self.logger.warning(f"Can't get observation status: {e!r}")

        try:
            status['observer'] = {
                'siderealtime': str(self.sidereal_time),
                'utctime': now,
                'localtime': datetime.now(),
                'local_evening_astro_time': self._evening_astro_time,
                'local_morning_astro_time': self._morning_astro_time,
                'local_sun_set_time': self._local_sunset,
                'local_sun_rise_time': self._local_sunrise,
                'local_sun_position': self._local_sun_pos,
                'local_moon_alt': self.observer.moon_altaz(now).alt,
                'local_moon_illumination': self.observer.moon_illumination(now),
                'local_moon_phase': self.observer.moon_phase(now),
            }

        except Exception as e:  # pragma: no cover
            self.logger.warning(f"Can't get time status: {e!r}")

        return status

    def get_observation(self, *args, **kwargs):
        """Gets the next observation from the scheduler

        Returns:
            observation (pocs.scheduler.observation.Observation or None): An
                object that represents the observation to be made.

        Raises:
            error.NoObservation: If no valid observation is found
        """

        self.logger.debug("Getting observation for observatory")

        if not self.scheduler:
            self.logger.info(f'Scheduler not present, cannot get the next observation.')
            return None

        # If observation list is empty or a reread is requested
        reread_file = (
                self.scheduler.has_valid_observations is False or
                kwargs.get('read_file', False) or
                self.get_config('scheduler.check_file', default=False)
        )

        # This will set the `current_observation`.
        self.scheduler.get_observation(read_file=reread_file, *args, **kwargs)

        if self.current_observation is None:
            self.scheduler.clear_available_observations()
            raise error.NoObservation("No valid observations found")

        return self.current_observation

    def take_observation(self, blocking: bool = True):
        """Take individual images for the current observation.

        This method gets the current observation and takes the next
        corresponding exposure.

        Args:
            blocking (bool): If True (the default), wait for cameras to finish
                exposing before returning, otherwise return immediately.

        """
        if len(self.cameras) == 0:
            raise error.CameraNotFound("No cameras available, unable to take observation")

        # Get observatory metadata
        headers = self.get_standard_headers()

        # All cameras share a similar start time
        headers['start_time'] = current_time(flatten=True)

        # Take exposure with each camera.
        for cam_name, camera in self.cameras.items():
            self.logger.debug(f"Exposing for camera: {cam_name}")
            camera.take_observation(self.current_observation, headers=headers)

        if blocking:
            cam = self.primary_camera
            exptime = self.current_observation.exptime.value
            readout_time = cam.readout_time
            timeout = exptime + readout_time + cam.timeout

            timer = CountdownTimer(timeout, name='Observe')
            # Sleep for the exposure time to start.
            timer.sleep(max_sleep=exptime + readout_time)
            # Then start checking for complete exposures.
            while timer.expired() is False:
                done_observing = [cam.is_observing is False for cam in self.cameras.values()]
                if all(done_observing):
                    self.logger.info('Finished observing for all cameras')
                    break

                timer.sleep(max_sleep=readout_time)

            # If timer expired check cameras and remove if stuck.
            if timer.expired():
                self.logger.warning(f'Timer expired waiting for cameras to finish observing')
                not_done = [cam_id for cam_id, cam in self.cameras.items() if cam.is_observing]
                for cam_id in not_done:
                    self.logger.warning(f'Removing {cam_id} from observatory')
                    with suppress(KeyError):
                        del self.cameras[cam_id]

    def process_observation(self,
                            compress_fits: Optional[bool] = None,
                            record_observations: Optional[bool] = None,
                            make_pretty_images: Optional[bool] = None,
                            plate_solve: Optional[bool] = None,
                            upload_image_immediately: Optional[bool] = None,
                            ):
        """Process an individual observation.

        Args:
            compress_fits (bool or None): If FITS files should be fpacked into .fits.fz.
                If None (default), checks the `observations.compress_fits` config-server key.
            record_observations (bool or None): If observation metadata should be saved.
                If None (default), checks the `observations.record_observations`
                config-server key.
            make_pretty_images (bool or None): Make a jpg from raw image.
                If None (default), checks the `observations.make_pretty_images`
                config-server key.
            plate_solve (bool or None): If images should be plate solved, default None for config.
            upload_image_immediately (bool or None): If images should be uploaded (in a separate
                process).
        """
        for cam_name in self.cameras.keys():
            try:
                exposure = self.current_observation.exposure_list[cam_name][-1]
            except IndexError:
                self.logger.warning(f'Unable to get exposure for {cam_name}')
                continue

            try:
                self.logger.debug(f'Processing observation with {exposure=!r}')
                metadata = exposure.metadata
                image_id = metadata['image_id']
                seq_id = metadata['sequence_id']
                file_path = metadata['filepath']
                exptime = metadata['exptime']
            except KeyError as e:
                self.logger.warning(f'No information in image metadata, unable to process:  {e!r}')
                continue

            field_name = metadata.get('field_name', '')

            if metadata.get('status') == 'complete':
                self.logger.debug(f'{image_id} has already been processed, skipping')
                return

            if plate_solve or self.get_config('observations.plate_solve', default=False):
                self.logger.debug(f'Plate solving {file_path=}')
                try:
                    metadata = fits_utils.get_solve_field(file_path)
                    file_path = metadata['solved_fits_file']
                    self.logger.debug(f'Solved {file_path}, replacing metadata.')
                except Exception as e:
                    self.logger.warning(f'Problem solving {file_path=}: {e!r}')

            if compress_fits or self.get_config('observations.compress_fits', default=False):
                self.logger.debug(f'Compressing {file_path=!r}')
                compressed_file_path = fits_utils.fpack(str(file_path))
                exposure.path = Path(compressed_file_path)
                metadata['filepath'] = compressed_file_path
                self.logger.debug(f'Compressed {compressed_file_path}')

            if record_observations or self.get_config('observations.record_observations',
                                                      default=False):
                self.logger.debug(f"Adding current observation to db: {image_id}")
                metadata['status'] = 'complete'
                self.db.insert_current('observations', metadata)

            if make_pretty_images or self.get_config('observations.make_pretty_images',
                                                     default=False):
                try:
                    image_title = f'{field_name} [{exptime}s] {seq_id}'

                    self.logger.debug(f"Making pretty image for {file_path=!r}")
                    link_path = None
                    if metadata['is_primary']:
                        # TODO This should be in the config somewhere.
                        link_path = Path(self.get_config('directories.images')) / 'latest.jpg'

                    pretty_process = Process(name=f'PrettyImageProcess-{image_id}',
                                             target=img_utils.make_pretty_image,
                                             args=(file_path,),
                                             kwargs=dict(title=image_title,
                                                         link_path=str(link_path)))
                    pretty_process.start()
                except Exception as e:  # pragma: no cover
                    self.logger.warning(f'Problem with extracting pretty image: {e!r}')

            if upload_image_immediately or self.get_config('observations.upload_image_immediately',
                                                           default=False):
                self.logger.debug(f"Uploading current observation: {image_id}")
                try:
                    self.upload_exposure(exposure_info=exposure)
                except Exception as e:
                    self.logger.warning(f'Problem uploading exposure: {e!r}')

    def analyze_recent(self):
        """Analyze the most recent exposure

        Compares the most recent exposure to the reference exposure and determines
        the offset between the two.

        Returns:
            dict: Offset information
        """
        # Clear the offset info
        self.current_offset_info = None

        pointing_image_id, pointing_image = self.current_observation.pointing_image
        self.logger.debug(f"Analyzing recent image using pointing image: '{pointing_image}'")

        try:
            # Get the image to compare
            image_id, image_path = self.current_observation.last_exposure

            current_image = Image(image_path, location=self.earth_location)

            solve_info = current_image.solve_field(skip_solved=False)

            self.logger.debug(f"Solve Info: {solve_info}")

            # Get the offset between the two
            self.current_offset_info = current_image.compute_offset(pointing_image)
            self.logger.debug(f'Offset Info: {self.current_offset_info}')

            # Store the offset information
            self.db.insert_current('offset_info', {
                'image_id': image_id,
                'd_ra': self.current_offset_info.delta_ra.value,
                'd_dec': self.current_offset_info.delta_dec.value,
                'magnitude': self.current_offset_info.magnitude.value,
                'unit': 'arcsec',
            })

        except error.SolveError:
            self.logger.warning("Can't solve field, skipping")
        except Exception as e:
            self.logger.warning(f"Problem in analyzing: {e!r}")

        return self.current_offset_info

    def upload_exposure(self, exposure_info, bucket_name=None):
        """Uploads the most recent image from the current observation."""
        bucket_name = bucket_name or self.get_config('panoptes_network.buckets.upload')

        image_path = exposure_info.path
        if not image_path.exists():
            raise FileNotFoundError(f'File does not exist: {str(image_path)}')

        self.logger.debug(f'Preparing {image_path} for upload')

        # Remove the local images directory for the upload name and replace with PAN_ID.
        bucket_path = str(image_path.absolute()).replace(self.get_config('directories.images'),
                                                         self.get_config('pan_id'))

        # Create a separate process for the upload.
        upload_process = Process(name=f'ImageUploaderProcess-{exposure_info.image_id}',
                                 target=upload_image,
                                 kwargs=dict(file_path=image_path,
                                             bucket_path=bucket_path,
                                             bucket_name=bucket_name))

        self.logger.info(f'Uploading {str(image_path)} to {bucket_path} on {bucket_name}')
        upload_process.start()

    def update_tracking(self, **kwargs):
        """Update tracking with rate adjustment.

        The `current_offset_info` contains information about how far off
        the center of the current image is from the pointing image taken
        at the start of an observation. This offset info is given in arcseconds
        for the RA and Dec.

        A mount will accept guiding adjustments in number of milliseconds
        to move in a specified direction, where the direction is either `east/west`
        for the RA axis and `north/south` for the Dec.

        Here we take the number of arcseconds that the mount is offset and,
        via the `mount.get_ms_offset`, find the number of milliseconds we
        should adjust in a given direction, one for each axis.

        The minimum and maximum tracking corrections can be passed as keyword
        arguments (`min_tracking_threshold=100` and `max_tracking_threshold=99999`)
        or can be specified in the mount config settings.

        Args:
            **kwargs: Keyword arguments that are passed to `get_tracking_correction`
                and `correct_tracking`.
        """
        if self.current_offset_info is not None:
            self.logger.debug("Updating the tracking")

            # Get the pier side of pointing image
            _, pointing_image = self.current_observation.pointing_image
            pointing_ha = pointing_image.header_ha

            try:
                pointing_ha = pointing_ha.value
            except AttributeError:
                pass

            self.logger.debug("Pointing HA: {:.02f}".format(pointing_ha))
            correction_info = self.mount.get_tracking_correction(
                self.current_offset_info,
                pointing_ha,
                **kwargs
            )

            try:
                self.mount.correct_tracking(correction_info, **kwargs)
            except error.Timeout:
                self.logger.warning("Timeout while correcting tracking")

    def get_standard_headers(self, observation=None):
        """Get a set of standard headers

        Args:
            observation (`~pocs.scheduler.observation.Observation`, optional): The
                observation to use for header values. If None is given, use
                the `current_observation`.

        Returns:
            dict: The standard headers
        """

        if observation is None:
            observation = self.current_observation

        assert observation is not None, self.logger.warning("No observation, can't get headers")

        field = observation.field

        self.logger.debug(f'Getting headers for : {observation}')

        t0 = current_time()
        moon = get_moon(t0, self.observer.location)

        headers = {
            'airmass': self.observer.altaz(t0, field).secz.value,
            'creator': "POCSv{}".format(self.__version__),
            'elevation': self.location.get('elevation').value,
            'ha_mnt': self.observer.target_hour_angle(t0, field).value,
            'latitude': self.location.get('latitude').value,
            'longitude': self.location.get('longitude').value,
            'moon_fraction': self.observer.moon_illumination(t0),
            'moon_separation': field.coord.separation(moon).value,
            'observer': self.get_config('name', default=''),
            'origin': 'Project PANOPTES',
            'tracking_rate_ra': self.mount.tracking_rate,
        }

        # Add observation metadata
        headers.update(observation.status)

        # Explicitly convert EQUINOX to float
        try:
            equinox = float(headers['equinox'].replace('J', ''))
        except Exception:
            equinox = 2000.  # We assume J2000

        headers['equinox'] = equinox

        return headers

    def autofocus_cameras(self, camera_list=None, **kwargs):
        """
        Perform autofocus on all cameras with focus capability, or a named subset
        of these. Optionally will perform a coarse autofocus first, otherwise will
        just fine tune focus.

        Args:
            camera_list (list, optional): list containing names of cameras to autofocus.
            **kwargs: Options passed to the underlying `Focuser.autofocus` method.

        Returns:
            dict of str:threading_Event key:value pairs, containing camera names and
                corresponding Events which will be set when the camera completes autofocus.
        """
        if camera_list:
            # Have been passed a list of camera names, extract dictionary
            # containing only cameras named in the list
            cameras = {cam_name: self.cameras[
                cam_name] for cam_name in camera_list if cam_name in self.cameras.keys()}
            if cameras == {}:
                self.logger.warning(f"No matching camera names in ({camera_list})")
        else:
            # No cameras specified, will try to autofocus all cameras from self.cameras
            cameras = self.cameras

        autofocus_events = dict()

        # Start autofocus with each camera
        for cam_name, camera in cameras.items():
            self.logger.debug(f"Autofocusing camera: {cam_name}")

            try:
                assert camera.focuser.is_connected
            except AttributeError:
                self.logger.debug(f'Camera {cam_name} has no focuser, skipping autofocus')
            except AssertionError:
                self.logger.debug(f'Camera {cam_name} focuser not connected, skipping autofocus')
            else:
                try:
                    # Start the autofocus
                    autofocus_event = camera.autofocus(**kwargs)
                except Exception as e:
                    self.logger.error(f"Problem running autofocus: {e!r}")
                else:
                    autofocus_events[cam_name] = autofocus_event

        return autofocus_events

    def open_dome(self):
        """Open the dome, if there is one.

        Returns: False if there is a problem opening the dome,
                 else True if open (or if not exists).
        """
        if not self.dome:
            return True
        if not self.dome.connect():
            return False
        if not self.dome.is_open:
            self.logger.info('Opening dome')
        return self.dome.open()

    def close_dome(self):
        """Close the dome, if there is one.

        Returns: False if there is a problem closing the dome,
                 else True if closed (or if not exists).
        """
        if not self.dome:
            return True
        if not self.dome.connect():
            return False
        if not self.dome.is_closed:
            self.logger.info('Closed dome')
        return self.dome.close()

    def take_flat_fields(self,
                         which='evening',
                         alt=None,
                         az=None,
                         min_counts=1000,
                         max_counts=12000,
                         target_adu_percentage=0.5,
                         initial_exptime=3.,
                         min_exptime=0.,
                         max_exptime=60.,
                         readout=5.,
                         camera_list=None,
                         bias=2048,
                         max_num_exposures=10,
                         no_tracking=True
                         ):  # pragma: no cover
        """Take flat fields.
        This method will slew the mount to the given AltAz coordinates(which
        should be roughly opposite of the setting sun) and then begin the flat-field
        procedure. The first image starts with a simple 1 second exposure and
        after each image is taken the average counts are analyzed and the exposure
        time is adjusted to try to keep the counts close to `target_adu_percentage`
        of the `(max_counts + min_counts) - bias`.
        The next exposure time is calculated as:
        .. code-block:: python
            # Get the sun direction multiplier used to determine if exposure
            # times are increasing or decreasing.
            if which == 'evening':
                sun_direction = 1
            else:
                sun_direction = -1
            exptime = previous_exptime * (target_adu / counts) *
                          (2.0 ** (sun_direction * (elapsed_time / 180.0))) + 0.5
        Under - and over-exposed images are rejected. If image is saturated with
        a short exposure the method will wait 60 seconds before beginning next
        exposure.
        Optionally, the method can also take dark exposures of equal exposure
        time to each flat-field image.
        Args:
            which (str, optional): Specify either 'evening' or 'morning' to lookup coordinates
                in config, default 'evening'.
            alt (float, optional): Altitude for flats, default None.
            az (float, optional): Azimuth for flats, default None.
            min_counts (int, optional): Minimum ADU count.
            max_counts (int, optional): Maximum ADU count.
            target_adu_percentage (float, optional): Exposure time will be adjust so
                that counts are close to: target * (`min_counts` + `max_counts`). Defaults
                to 0.5.
            initial_exptime (float, optional): Start the flat fields with this exposure
                time, default 3 seconds.
            max_exptime (float, optional): Maximum exposure time before stopping.
            camera_list (list, optional): List of cameras to use for flat-fielding.
            bias (int, optional): Default bias for the cameras.
            max_num_exposures (int, optional): Maximum number of flats to take.
            no_tracking (bool, optional): If tracking should be stopped for drift flats,
                default True.
        """
        if camera_list is None:
            camera_list = list(self.cameras.keys())

        target_adu = target_adu_percentage * (min_counts + max_counts)

        # Get the sun direction multiplier used to determine if exposure
        # times are increasing or decreasing.
        if which == 'evening':
            sun_direction = 1
        else:
            sun_direction = -1

        # Setup initial exposure times.
        exptimes = {cam_name: [initial_exptime * u.second] for cam_name in camera_list}

        # Create the observation.
        try:
            flat_obs = self._create_flat_field_observation(
                alt=alt, az=az, initial_exptime=initial_exptime,
                field_name=f'{which.title()}Flat'
            )
        except Exception as e:
            self.logger.warning(f'Problem making flat field: {e}')
            return

        # Slew to position
        self.logger.debug(f"Slewing to flat-field coords: {flat_obs.field}")
        self.mount.set_target_coordinates(flat_obs.field)
        self.mount.slew_to_target(blocking=True)
        if no_tracking:
            self.logger.info(f'Stopping the mount tracking')
            self.mount.query('stop_tracking')
        self.logger.info(f'At {flat_obs.field=} with tracking stopped, starting flats.')

        while len(camera_list) > 0:

            start_time = current_time()
            fits_headers = self.get_standard_headers(observation=flat_obs)
            fits_headers['start_time'] = flatten_time(start_time)

            # Report the sun level
            sun_pos = self.observer.altaz(start_time, target=get_sun(start_time)).alt
            self.logger.debug(f"Sun {sun_pos:.02f}°")

            # Take the observations.
            exptime = min_exptime
            camera_filename = dict()
            for cam_name in camera_list:
                # Get latest exposure time.
                exptime = max(exptimes[cam_name][-1].value, min_exptime)
                fits_headers['exptime'] = exptime

                # Take picture and get filename.
                self.logger.info(f'Flat #{flat_obs.current_exp_num} on {cam_name=} with {exptime=}')
                camera = self.cameras[cam_name]
                metadata = camera.take_observation(flat_obs, headers=fits_headers, exptime=exptime)
                camera_filename[cam_name] = metadata['filepath']

            # Block until done exposing on all cameras.
            flat_field_timer = CountdownTimer(exptime + readout, name='Flat Field Images')
            while any([cam.is_observing for cam_name, cam in self.cameras.items()
                       if cam_name in camera_list]):
                if flat_field_timer.expired():
                    self.logger.warning(f'{flat_field_timer} expired while waiting for flat fields')
                    return

                self.logger.trace('Waiting for flat-field image')
                flat_field_timer.sleep(1)

            # Check the counts for each image.
            is_saturated = False
            too_bright = False
            for cam_name, filename in camera_filename.items():

                # Make sure we can find the file.
                img_file = filename.replace('.cr2', '.fits')
                if not os.path.exists(img_file):
                    img_file = img_file.replace('.fits', '.fits.fz')
                    if not os.path.exists(img_file):  # pragma: no cover
                        self.logger.warning(f"No flat file {img_file} found, skipping")
                        continue

                self.logger.debug(f"Checking counts for {img_file}")

                # Get the bias subtracted data.
                data = fits_utils.getdata(img_file) - bias

                # Simple mean works just as well as sigma_clipping and is quicker for RGB.
                # TODO(wtgee) verify this.
                counts = data.mean()
                self.logger.info(f"Counts: {counts:.02f} Desired: {target_adu:.02f}")

                # Check we are above minimum counts.
                if counts < min_counts:
                    self.logger.info("Counts are too low, flat should be discarded")
                    setval(img_file, 'QUALITY', value='BAD', ext=int(img_file.endswith('.fz')))

                # Check we are below maximum counts.
                if counts >= max_counts:
                    self.logger.info("Image is saturated")
                    is_saturated = True
                    setval(img_file, 'QUALITY', value='BAD', ext=int(img_file.endswith('fz')))

                # Get suggested exposure time.
                elapsed_time = (current_time() - start_time).sec
                self.logger.debug(f"Elapsed time: {elapsed_time:.02f}")
                previous_exptime = exptimes[cam_name][-1].value

                # TODO(wtgee) Document this better.
                suggested_exptime = int(previous_exptime * (target_adu / counts) *
                                        (2.0 ** (sun_direction * (elapsed_time / 180.0))) + 0.5)

                self.logger.info(f"Suggested exptime for {cam_name}: {suggested_exptime:.02f}")

                # Stop flats if we are going on too long.
                self.logger.debug(f"Checking for too many exposures on {cam_name}")
                if len(exptimes) == max_num_exposures:
                    self.logger.info(f"Have ({max_num_exposures=}), stopping {cam_name}.")
                    camera_list.remove(cam_name)

                # Stop flats if any time is greater than max.
                self.logger.debug(f"Checking for long exposures on {cam_name}")
                if suggested_exptime >= max_exptime:
                    self.logger.info(f"Suggested exposure time greater than max, "
                                     f"stopping flat fields for {cam_name}")
                    camera_list.remove(cam_name)

                self.logger.debug(f"Checking for saturation on short exposure on {cam_name}")
                short_exptime = 2
                if is_saturated and previous_exptime <= short_exptime:
                    too_bright = True

                # Add next exptime to list.
                exptimes[cam_name].append(suggested_exptime * u.second)

            if too_bright:
                if which == 'evening':
                    self.logger.info("Saturated short exposure, waiting 60 seconds for more dark")
                    CountdownTimer(60, name='WaitingForTheDarkness').sleep()
                else:
                    self.logger.info('Saturated short exposure, too bright to continue')
                    return

    def _create_flat_field_observation(self,
                                       alt=70,  # degrees
                                       az=None,
                                       field_name='FlatField',
                                       flat_time=None,
                                       initial_exptime=5):
        """Small convenience wrapper to create a flat-field Observation.
        Flat-fields are specified by AltAz coordinates so this method is just a helper
        to look up the current RA-Dec coordinates based on the unit's location and
        the current time (or `flat_time` if provided).
        If no azimuth is provided this will figure out the azimuth of the sun at
        `flat_time` and use that position minus 180 degrees.
        Args:
            alt (float, optional): Altitude desired, default 70 degrees.
            az (float, optional): Azimuth desired in degrees, defaults to a position
                -180 degrees opposite the sun at `flat_time`.
            field_name (str, optional): Name of the field, which will also be directory
                name. Note that it is probably best to pass the camera.uid as name.
            flat_time (`astropy.time.Time`, optional): The time at which the flats
                will be taken, default `now`.
            initial_exptime (int, optional): Initial exptime in seconds, default 5.
        Returns:
            `pocs.scheduler.Observation`: Information about the flat-field.
        """
        self.logger.debug("Creating flat-field observation")

        if flat_time is None:
            flat_time = current_time()

        # Get an azimuth that is roughly opposite the sun.
        if az is None:
            sun_pos = self.observer.altaz(flat_time, target=get_sun(flat_time))
            az = sun_pos.az.value - 180.  # Opposite the sun

        self.logger.debug(f'Flat-field coords: {alt=:.02f} {az=:.02f}')

        field = Field.from_altaz(field_name, alt, az, self.earth_location, time=flat_time)
        flat_obs = CompoundObservation(field, exptime=initial_exptime)

        # Note different 'flat' concepts.
        flat_obs.seq_time = flatten_time(flat_time)

        self.logger.debug(f'Flat-field observation: {flat_obs}')
        return flat_obs

redpajama_set_name: RedPajamaGithub


Actually, yes promise all new slot games manufacturer Net Entertainment a lot of fun, the Reel Rush Slot but definitely stands out with its innovative design and features. The game is played on 5 reels, which is always started with a screen in the form of a lozenge. This means that on roll 1 and 5 turns at the beginning only the central panel, on reel 2 and 4 only the middle three panels and only on reel 3 every five fields. With each successful round at a profit then new fields are free and increase thus the chances of winning.
The maximum win at a Reel Rush Slot game round is the way in enormous 480,000 coins and you'll also supported by a Wild symbol that can appear on reels 2, 3, 4 and 5, both in the main game as well as part of the free games. As already mentioned, a winning combination in the main game automatically triggers a further rotation, this being limited to five new spins.
The winning ways of Reel Rush Slot increase as follows: 1. new spin = 153 ways to win, 2 = 405 ways to win, 3 = 675 ways to win, 4 = 1,125 ways to win and 5 = 1,875 ways to win. After the fifth new spin with profit but is not the end, because then the 8 free spins are activated that are played with 3,125 Ways to Win.
The maximum win at a game round is the way in enormous 480,000 coins and you'll also supported by a Wild symbol that can appear on reels 2, 3, 4 and 5, both in the main game as well as part of the free games. As already mentioned, a winning combination in the main game automatically triggers a further rotation, this being limited to five Neudrehungen. The winning ways increase as follows: 1. Neudrehung = 153 ways to win, 2 = 405 ways to win, 3 = 675 ways to win, 4 = 1,125 ways to win and 5 = 1,875 ways to win. After the fifth Neudrehung with profit but is not the end, because then the 8 free spins are activated that are played with 3,125 Ways to Win.


text:
Home Sports Bwalya questions 'harsh' Perez red card against West Ham
Bwalya questions 'harsh' Perez red card against West Ham
The African football great feels the Spaniard's challenge wasn't as bad to have warranted dismissal against David Moyes' men
Zambia football legend Kalusha Bwalya is adamant Ayoze Perez should not have been shown a red card for his challenge on Pablo Fornals during Leicester City's 4-1 defeat by West Ham United on Monday.
Seven minutes before the half-time break, the former Spain youth international launched a late challenge on his compatriot, and as a result, he was given the marching orders by referee Michael Oliver after a VAR review.
With the Foxes reduced to 10 men, they struggled to impress and returned home with heads bowed low.
Unhappy with Oliver's decision, the 1988 African Player of the Year went on social media to question former Fifa referee Jerome Damon on the validation for sending off the 28-year-old.
"No, how can that be a red card? Jerome Damon, he didn't even know where to put his feet. Very harsh," the former Cercle Brugge and PSV Eindhoven star queried on Twitter.
In his response, Damon educated the Zambian great on what Fifa's Laws of the Game demands as it concerns Perez's dismissal.
"Just saw it fleetingly now…full weight on to the ankle of an opponent. Ankle goes over, with no attempt to lessen the contact. Red card justified. Law 12: endangers of an opponent," the South African replied.
On his 274th appearance in English football, Perez has now been shown a red card for the first time since he began his professional career in 2011 at Spanish outfit Tenerife.
Benefitting from the visitors' numerical disadvantage, Moyes' side doubled their advantage in the 56th courtesy of Algeria international Said Benrahma who was teed up by Michail Antonio.
Caglar Soyuncu misjudged Kasper Schmeichel's positioning for a pass back and hands the ball straight to Antonio.
The angle was too tight for the Englishman to shoot, instead, he found the African star who rolled the ball into an empty net.
Not willing to go down without a fight, the reigning English FA Cup kings pulled one goal back through Youri Tielemans in the 69th minute.
Lifted by that, West Ham sealed victory courtesy of goals a late brace by Antonio.
Leicester would be hoping to bounce back from this defeat when they travel to Norwich City on August 28.
Previous articleApple and Google's Fight in Seoul Tests Biden in Washington
Next articleWorkers Can't Wait to Return, but Delta Variant Upends Plans
Demaryius Thomas, Ex-Denver Broncos Wide Receiver, Is Found Dead
Demaryius Thomas, a former star wide receiver for the Denver Broncos who played for the National Football League for 10 seasons before retiring...
What We Learned From Week 16 in the N.F.L.
Rams 30, Vikings 23: A 61-yard punt return for a touchdown by the Rams in the third quarter was essentially the difference in...
The Reason Every N.F.L. Playoff Team Will Lose
There are no nigh-unbeatable 1972 Miami Dolphins or 2007 New England Patriots in this year's N.F.L. playoff field. Each team has at least...


text:
Vuelta a PEZ: Start Minus One!
By Alastair Hamilton	 On Aug 19, 2011
Roadside Preview: Day 2 and it's all go at La Vuelta with press conferences and team presentations to attend. Lots to see but not much to eat…this is not normal for La Vuelta! Could be the crisis I guess? Anyway, pizza on the way home will have to do today; here is what I got up to on Vuelta start minus one!
An early start was on the cards today as there were two press conferences to attend before the team's presentation tonight. I say early…2pm for La Vuelta is a bit off! Too late for "El Almuerzo" (mid-morning snack) and too early for "La Comida" (lunch). Anyway, Geox-TMC was first up, Carlos Sastre and Denis Menchov met the press at the Vuelta Sala de Prensa and then meet Omega Pharma-Lotto in the rather nice Albir Playa hotel.
Yesterday, I spotted Euskaltel-Euskadi on my way out, today it was Sky, but I was not fast enough with the camera for a photo, I guess there is going to be enough chances for photographing cyclists! At the press office things were much busier than yesterday.
Most journo's were there to listen to Carlos Sastre and Denis Menchov of Geox-TMC and catch-up on their e-mails with the free internet. Geox manager, "Matxin" Fernandez was first there to set the scene with Sastre close behind and being asked questions before the conference had started. Menchov seems a little more reserved, not shy, but holds back a little.
The usual questions were asked…you've heard them all before. Sastre thinks "there are a lot of opportunities!" Carlos always comes across as a happy guy and added; the individual time trial is important, but so are all the mountain stages." He feels the team is ready for La Vuelta; "the sensations in the team are good!"
Menchov on the other hand was philosophical about his main rivals, "there's not just Nibali (last year's winner), there are 4, 5 or 6 others, the Katusha team is very strong" and "the individual time trial will be very important."
When Sastre was asked about him and Menchov in the same team, he answered: "We were rivals, now we are team-mates." Matxin had the final word "I wish all the teams had more than one leader!"
It only left me to receive a very nice bottle of Villa Sandi wine from the team, as the owner of Geox also owns the vineyard. That'll go down well one night!
In the Albir Playa hotel it was slightly different, two Flemish/Belgian journalists', the Belgian TV station Sporza and…Me!
Jurgen Van Den Broeck, DS; Jean-Pierre Heynderickx and Team Manager; Marc Sergeant were to take the questions, which were all in Flemish. But I got my chance to quiz VDB.
PEZ: Which are the most important days?
VDB: The last nine days, they are all hard! Also Saturday's TTT is important.
PEZ: What about the individual time trial?
VDB: I'll have to wait and see, I'm not targeting the GC.
PEZ: What about the L'Angliru?
VDB I've never seen it, but everyone has told me about it…we will see!
PEZ: Who will you be watching?
VDB Like I said I'm not going for the GC, but I think Rodriguez and Nibali and maybe Wiggins, but we don't know how he will be after his accident, like me!
So that was the day over, next the Team's Presentation in the evening.
Previous years have been a little fraught for the organisation and difficult for the TV and the teams with a lot of hanging around, but usually a good selection of food to keep you interested. This year was more slick, but only beer and soft drinks and only water melon to eat! That said it was a nice evening watching the stars, meeting old friends and taking in the sights down at the beach.
Tom Boonen and Sylvain Chavanel look fairly happy with things and it was good to meet up with their PR man Alessandro Tegner who I last saw in Calpe in February.
Could that be the Tashkent Terror; Djamolidine Abdoujaparov talking to Katusha's Andrei Tchmil? And yes it is, he's working for Astana now.
Next up were the Irish cousins for a family portrait; Dan Martin and Nico Roche. Both were not overly impressed with the heat for training.
Alessandro Petacchi always looks fit, but doesn't win as much as he used too, maybe a stage win in La Vuelta?
Marzio Bruseghin must be lean enough to have a beer the day before the start of a Grand Tour!
It was great to see BMC Young Gun; Taylor Phinney and the team's PR Sean Weide.
The soon to be parted friends; Stuart O'Grady and Fabian Cancellara.
Jurgen Van Den Broeck obviously wasn't fazed by my questioning; I think he likes a laugh!
Wiggins was not happy to have to wait and commented that they had been treated like dogs for the last hour! Yes dog's in a big bus with A/C! Cav was cool and taking it all in his stride.
OK time for a beer, who is this? Oscar Freire, fit after his nose operation, but not drinking beer!
And of course last year's winner, Vincenzo Nibali.
Well there were many other people I saw and met, but far too many to fit in here. Roll on the team time trial tomorrow and a very open and hopefully exciting Vuelta a Espaсa.
alastair hamiltonlatest newsvuelta11
Alastair Hamilton
Alastair Hamilton has been a pro team mechanic on the road, track and mountain bike and worked for the Great Britain team at the World championships in all disciplines. Since moving to Spain and finding out how to use a computer, he has gone from contributor of Daily Distractions at the 2002 Vuelta a España to editor at PezCyclingNews.
La Vuelta '11: The Contenders!
Vuelta'11 St.1: Sky Falls In, Fuglsang Leads!
Vuelta11
Vuelta'11 St.21: Cobo Clinches It!
Vuelta'11 St.20: Bennati Victor In Vitoria!
Vuelta'11 St.19: Basque Triumph At Home In Bilbao
Vuelta'11 St.18: A Long Break Day!